admin 管理员组

文章数量: 887021

Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX 的多用户、多任务、支持多线程和多CPU的操作系统。

Linux的版本分为两种:内核版本和发行版本。

内核版本是指在Linus领导下的内核小组开发维护的系统内核的版本号。

发行版本是一些组织和公司根据自己发行版的不同而自定的,简单点理解就是将Linux内核与应用软件做一个打包。

目前市面上较知名的发行版有:Ubuntu、RedHat、CentOS、Debian、Fedora、SuSE、OpenSUSE、Arch Linux、SolusOS等。

Linux在服务器系统、嵌入式系统、桌面应用系统和移动手持系统等方面都有很广泛的应用。

虚拟机:一台用软件虚拟出来的电脑(virtualBox免费、VMware收费...)。

在VMware中创建CentOS7操作系统,通过SecureCRT远程工具连接到Linux。

在Linux文件系统中有两个特殊的目录,一个是用户所在的工作目录,也叫当前目录,可以使用一个点或 ./ 来表示;另一个是当前目录的上一级目录,也叫父目录,可以使用两个点或 ../ 来表示。

如果一个目录/文件以一个点开始,则表示这个目录/文件是隐藏的(如:.bashrc)以默认方式查找时,不显示该目录/文件。

CRT工具中查看目录结构:

bin       存放二进制可执行文件

sbin     存放二进制可执行文件,只有root才能访问

etc       存放系统配置文件

usr       存放共享的系统资源

home   存放用户文件的根目录

root      超级用户目录

dev      存放设备文件

lib        存放跟文件系统中的程序运行所需要的共享库及内核模块

mnt     系统管理员安装临时文件系统的安装点

boot    存放系统引导时使用的各种文件

tmp     存放各种临时文件

var      存放运行时需要改变数据的文件

#语法

查看用户信息:cat /etc/passwd

root用户名:x密码:0账号id:0组号id

创建一个用户:useradd 选项 用户名

#选项:

-c  comment  指定一段注释性描述。

-d  目录  指定用户主目录,如果此目录不存在,则同时使用-m选项,可以创建主目录。

-g  用户组  指定用户所属的用户组。

-G 用户组,用户组  指定用户所属的附加组。

-s  Shell文件  指定用户的登录Shell。

-u  用户号  指定用户的用户号,如果同时有-o选项,则可以重复使用其他用户的标识号。

删除一个用户:userdel 选项 用户名

其中常用选项:-r 表示将用户的主目录一起删除

用户口令管理:passwd 选项 用户名

#选项:

-l  锁定口令,即禁用账号。

-u 口令解锁。

-d 使账号无口令。

-f  强迫用户下次登录时修改口令。

PS:Linux操作系统中密码部分不予显示,所以看起来是空白的,实际已经输入了密码。

Linux的目录结构为树状结构,最顶级的目录为根目录, 其他的目录可通过挂载的方式将它们添加到树中,通过解除挂载可以移除它们。

绝对路径:由根目录 / 写起,例如:/usr/share/doc这个目录。

相对路径:不是由 / 写起,例如:从/usr/share/doc切换到/usr/share/man 底下时,可以写成:cd ../man。

查看目录:

ls:查看当前路径下的文件名称

ls -a:查看全部的文件,连同隐藏文件(以点为开头的文件)一起列出来(常用)

ls -d:仅列出目录本身,而不是列出目录内的文件数据(常用)

ls -l(等价于ll):长数据串列出,包含文件的属性与权限等数据(常用)

ls -al:将目录下所有的文件列出来(含属性和隐藏档)

切换目录:

cd [相对路径或绝对路径]

#使用绝对路径切换到local目录  cd /usr/local

#表示回到自己的家目录,即/root目录  cd ~

#表示去到当前的上一级目录,即/root上一级目录的意思  cd ..

显示当前目录:pwd

创建目录:

mkdir [-mp] 目录名称

-m:配置文件的权限直接配置,不需要看默认权限

-p:直接将所需要的目录(包含上一级目录)通过递归的方式创建

例如:mkdir -p test1/test2/test3/test4

查看帮助:mkdir --help

删除目录:

rmdir [-p] 目录名称

-p:连同上一级空的目录也一起删除

一页一页的显示文件内容

语法:more 文件名称

例如:more /etc/man_db.config

....(中间省略)....

--More--(28%) <== 重点在这一行,光标会在这等待你输入命令

在 more 这个程序运行的过程中,空格键代表向下翻一页,回车键代表向下翻一行,:f 代表立刻显示出文档名和当前显示的行数,q 代表立刻离开more,不再显示该文件内容。

语法:less 文件名称

例如:less /etc/man.config

....(中间省略)....

: <== 光标会在这等待你输入命令

在 less 这个程序运行的过程中,空格键代表向下翻动一页,pagedown代表向下翻动一页,pageup代表向上翻动一页,q 代表离开less这个程序。

创建文件:

touch 创建一个空白的普通文件

写入内容:

echo '内容'>文件名称 覆盖模式  echo '内容'>>文件名称 追加模式

echo 把内容重定向到指定的文件中,有则打开,无则创建。

复制文件:

cp [-adfilprsu] 来源档(source) 目标档(destination)

-a:相当于 -pdr 的意思,至于 -pdr 请参考下列说明(常用)

-d:若来源档为连结档的属性(link file),则复制连结档属性而非文件本身

-f:为强制(force)的意思,若目标文件已存在且无法开启,则移除后再次尝试

-i:若clear问动作的进行(常用)

-l:进行硬式连结(hard link)的连结档创建,而非复制文件本身

-p:连同文件的属性一起复制过去,而非使用默认属性(备份常用)

-r:递归持续复制,用于目录的复制行为(常用)

-s:复制成为符号连结档(symbolic link),即捷径文件

-u:若目标档比来源档旧,才去升级目标档

移动文件:

mv [-fiu] 来源档 目标档

-f:force 强制的意思,若目标档已经存在,则不会询问,直接覆盖

-i:若目标档已经存在,则会询问是否覆盖

-u:若目标档已经存在,且来源档比较新时,才会去升级

删除文件:

rm [-fir] 文件或目录

-f:就是force的意思,忽略不存在的文件,不会出现警告信息

-i:互动模式,在删除前会询问使用者是否动作

-r:递归删除,常用在目录的删除,这是非常危险的选项

一般Linux上常用的压缩方式是选用tar将许多文件打包成一个,再通过gzip压缩命令压缩成xxx.tar.gz或xxx.tgz文件。

常用参数:

-c:创建一个新tar文件

-v:显示运行过程的信息

-f:指定文件名

-z:调用gzip 压缩命令进行压缩

-t:查看压缩文件的内容

-x:解开tar文件

打包:tar -cvf xxx.tar 要打包的文件或目录的列表,用空格隔开

打包并压缩:tar -zcvf xxx.tar.gz 要打包的文件或目录的列表,用空格隔开

解压:tar -xvf xx.tar

解压并拆包:tar -zxvf xxx.tar.gz -C /usr/kkb 注意-C要大写,防止拆包解压的路径找不到

管道| 是Linux中比较重要且常用的一个内容,其作用是将一个命令的输出作为另一个命令的输入组合使用。

ls --help | more  #分页查看帮助信息,按q退出

查看进程快照:

ps:- axu  显示当前进程的快照

查看java/mysql进程:ps - axu | grep java/mysql

VIM编辑器的三种模式分别是:命令模式(Command mode)、输入模式(Insert mode)和底线命令模式(Last line mode)。

输入vim可进入命令模式,以下是常用的几个命令:

i 切换到输入模式,编辑文本

x 删除当前光标所在处的字符

:切换到底线命令模式,可在最底一行输入命令

命令模式只有一些最基本的命令,因此仍要依靠底线命令模式输入更多命令。

在输入模式中,可以使用以下按键:

字符按键和Shift组合,输入字符

ENTER,回车键,换行

BACK SPACE,退格键,删除光标前一个字符

DEL,删除键,删除光标后一个字符

↑↓←→,方向键,在文本中移动光标

HOME/END,移动光标到行首/行尾

Page Up/Down,上/下翻页

Insert,切换光标为输入/替换模式,光标将变成竖线/下划线

ESC,退出输入模式,切换到命令模式

底线命令模式可以输入单个或多个字符的命令

在底线命令模式中,基本的命令有:q 退出程序  w 保存文件

按ESC键可随时退出底线命令模式

指令

说明

:w

将编辑的数据写入硬盘档案中(常用)

:w!

若文件属性为只读时,强制写入该档案(与权限有关)

:q

离开vim(常用)

:q!

使用!为强制离开不储存档案

:wq

储存后离开,若为:wq!则强制储存后离开(常用)

:set number

显示行号

:set nonumber

取消行号显示

Linux的文件权限共有10个字符

我们将它分为4大部分来理解:- --- --- ---

第1部分:表示文件的类型

-  表示是一个文件

d 表示是一个目录

l  表示是一个连接(理解为快捷方式)

第2部分:当前用户具有的对该文件的权限(Owner所有者,缩写u)

第3部分:当前组内其他用户具有的对该文件的权限(Group用户组,缩写g)

第4部分:其他组的用户具有的对该文件的权限(Other其他人,缩写o)

r(4):read 读  w(2):write 写  x(1):execute 执行

针对目录加执行权限,而文件不加执行权限(因文件具备执行权限存在安全隐患)

对于文件和目录来说,rwx有不同的作用和含义:

针对文件:

r:读取文件内容

w:修改文件内容

x:执行权限对除二进制程序以外的文件没什么意义

针对目录:

目录本质可看做是存放文件列表、节点号等内容的文件

r:查看目录下的文件列表

w:删除和创建目录下的文件

x:可以cd进入目录,可以查看目录中文件的详细属性,可以访问目录下文件内容(基础权限)

PS:root账户不受文件权限的读写限制,而执行权限受限制。

用户获取文件权限的顺序:先看是否为所有者,如果是,则后面权限不看;再看是否为所属组,如果是,则后面权限不看。

chown(即change owner)主要作用是改变文件或目录的所有者,使用权限:root

chmod修改文件和文件夹的读写执行属性,使用权限:所有使用者

mode方式语法:chmod who opt per file

who:u g o a(all所有用户,默认)

opt:+添加某个权限 –取消某个权限 =赋予某个权限

per:r w x X

数字方式语法:chmod XXX file

rwx rw- r--

421 420 400

7     6     4

0无权限,1可执行(x),2写入权限(w),4可读权限(r)

例如:-rwxr--r--. 1 root root 10 Oct 16 02:55 yhp.log

所有者权限=rwx=4+2+1=7

用户组权限=r--=4+0+0=4

其他人权限=r--=4+0+0=4  组合:744

修改权限:

给用户组加入写入权限:+2

给其他人加入执行权限:+1

chmod 765 yhp.log

uname -a:显示完整的系统信息

hostname:显示主机名

hostname XXX:修改主机名(不推荐,临时生效)

若想修改主机名使其永久生效,需要修改/etc/sysconfig/network文件

查看IP地址:ip addr

修改IP地址:修改/etc/sysconfig/network-scripts/

重启网络服务:service network restart

域名映射:修改/etc/hosts文件

查看网络服务状态:systemctl status network

启动网络服务:systemctl start network

停止网络服务:systemctl stop network

重启网络服务:systemctl restart network

设置开机启动:systemctl enable network

查看防火墙状态:systemctl status firewalld

启动防火墙:systemctl start firewalld

关闭防火墙:systemctl stop firewalld

查询防火墙服务是否开机启动:systemctl is-enabled firewalld

开机时启用防火墙服务:systemctl enable firewalld

开机时禁用防火墙服务:systemctl disable firewalld

查询已经启动的服务列表:systemctl list-unit-files|grep enabled

查询启动失败的服务列表:systemctl --failed

Maven翻译为“专家”、“内行”。

Maven是Apache下的一个纯java开发的开源项目。

Maven是一个项目管理工具,可以对java进行项目构建和依赖管理,也可以被用于构建和管理各种项目,例如C#,Ruby,Scala和其他语言编写的项目。

Maven的两大功能:项目构建和依赖管理。

项目构建的过程:源代码、编译、测试、打包、部署、运行。

Maven将项目构建的过程进行标准化,每个阶段使用一个命令完成,以下是构建过程的一些阶段:

清理:删除以前的编译结果,为重新编译做好准备。

编译:将java源程序编译为字节码文件。

测试:对项目的关键点进行测试,确保项目在迭代开发过程中关键点的正确性。

报告:在每一次测试后以标准的格式记录和展示测试结果。

打包:将一个包含诸多文件的工程封装为一个压缩文件,用于安装或部署。

(java工程对应jar包,Web工程对应war包)

安装:在Maven环境下特指将打包的结果jar包或war包安装到本地仓库中。

部署:将打包的结果部署到远程仓库或将war包部署到服务器上运行。

问:什么是依赖?

答:一个java项目可能要使用一些第三方的jar包才可以运行,那么我们说这个java项目依赖了这些第三方的jar包。

使用Maven依赖管理添加jar包的好处:

1.通过pom.xml文件对jar包的版本进行统一管理,可避免版本冲突。

2.Maven团队维护了一个非常全的Maven仓库,里面包含了当前使用的jar包,Maven工程可以自动从Mave仓库下载jar包,非常方便。

在Maven中可以将仓库理解为一个位置,一个专门存放项目中依赖的第三方库的位置;Maven的仓库可以分为本地仓库和远程仓库。

本地仓库相当于一个缓存,在电脑上是一个文件夹,我们可以设置这个文件夹的路径,当工程第一次需要某种jar包时,会从远程仓库(互联网)下载并保存到本地仓库中(程序员的电脑上);当第二次使用时,不需要去远程仓库下载,会先去本地仓库中找,如果找不到才会去远程仓库上下载。

默认情况下,每个用户在自己的用户目录下都有一个路径为 .m2/respository/ 的仓库目录。

远程仓库中分为中央仓库和私服两类。

中央仓库中的jar包由专业团队维护,中央仓库中存放了全世界大多数流行的开源软件的jar包,是Maven默认的远程仓库。要浏览中央仓库的内容,Maven 社区提供了一个URL:http://search.Maven/#browse 使用这个仓库,开发人员可以搜索所有可以获取的代码库。

私服是另一种特殊的远程仓库,为了节省带宽和时间,应该在局域网内架设一个私有的仓库服务器,用其代理所有外部的远程仓库,内部的项目还能部署到私服上供其他项目使用。

POM(Project Object Model,项目对象模型)是Maven工程的基本工作单元,是一个XML文件,包含了项目的基本信息,用于描述项目如何构建、声明项目依赖等等。

执行任务或目标时,Maven会在当前目录中查找POM,它读取POM获取所需的配置信息,然后执行目标。

Maven坐标是为了定位一个唯一确定的jar包。

groupId:定义当前Maven项目组织名称。

artifactId:定义实际项目名称。

version:定义当前项目的当前版本或者是所依赖的jar包的版本。

Maven拥有三套相互独立的生命周期,分别是clean、default和site。

clean生命周期包含3个阶段:

1.pre-clean     执行一些清理前需要完成的工作

2.clean           清理上一次构建过程中生成的文件,比如编译后的class文件等

3.post-clean    执行一些清理后需要完成的工作

default生命周期-构建项目

所有生命周期中最核心的部分,绝大部分的工作都发生在这个生命周期

generate-resources   产生主代码中的资源在classpath的包中

process-resource      复制并处理资源文件至目标目录,准备进行打包

compile                     编译项目的主源码,一般来说编译src/main/java目录下的java文件至项目输出的主classpath目录中

test-compile              编译项目的测试代码,编译src/test/java目录下的java文件至项目输出的测试classpath目录中

test                            使用单元测试框架运行测试,测试代码不会被打包或部署

package                    打包成可发布的格式

install                        将包安装到Maven本地仓库,供本地其他Maven项目使用

deploy                       将最终的包复制到远程仓库,供其他开发人员和Maven项目使用

PS:运行任何一个阶段时,它前面的所有阶段都会被运行,这也就是为什么我们运行mvn install的时候,代码会被编译、测试、打包,此外,Maven的插件机制完全依赖于Maven的生命周期,因此理解生命周期至关重要。

参考资料地址:http://Maven.apache/guides/introduction/introduction-to-the-lifecycle.html

site生命周期   

目的:建立和发布项目站点

pre-site        执行一些在生成项目站点之前需要完成的工作

site               生成项目站点文档

post-site       执行一些在生成项目站点之后需要完成的工作

site-deploy   将生成的项目站点发布到服务器上

Maven的常用指令

mvn compile

执行完毕后,会生成target目录,该目录中存放了编译后的字节码文件

mvn clean

执行完毕后,会将target目录删除

mvn test

执行完毕后,会在target目录中生成三个文件夹:

surefire、surefire-reports(测试报告)、test-classes(测试的字节码文件)

mvn package

执行完毕后,会在target目录中生成一个文件,该文件可能是jar、war

mvn install

执行完毕后,会在本地仓库中出现安装后的jar包,方便其他工程引用

cmd中录入组合指令

mvn clean compile

先执行clean,再执行compile,通常应用于上线前执行,清除测试类

mvn clean test

先执行clean,再执行test,通常应用于测试环节

mvn clean package

先执行clean,再执行package,将项目打包,通常应用于发布前

执行过程:

        清理————清空环境

        编译————编译源码

        测试————测试源码

        打包————将编译的非测试类打包

mvn clean install

先执行clean,再执行install,将项目打包,通常应用于发布前

执行过程:

        清理————清空环境

        编译————编译源码

        测试————测试源码

        打包————将编译的非测试类打包

        部署————将打好的包发布到资源仓库中

Maven的传递性和依赖性

依赖管理

<dependencies>

      <dependency>

            <groupId>junit</groupId>

            <artifactId>junit</artifactId>

            <version>4.10</version>

            <scope>test</scope>

      </dependency>

</dependencies>

依赖范围(scope标签的取值)

依赖范围

scope

对于主代码classpath有效

对于测试代码classpath有效

被打包,对于运行时classpath有效

例子

compile

Y

Y

Y

log4j

test

-

Y

-

junit

provided

Y

Y

-

servlet-api

runtime

-

-

Y

JDBC Driver

Implementation

compile-默认值,在工程环境的classpath(编译环境)和打包(如果是war包,会包含在war包中)时都有效。

provided容器或JDK已提供范围,表示该依赖包已经由目标容器(如tomcat)和JDK提供,只在编译的classpath中加载和使用,打包时不会包含在目标包中。

runtime一般是运行和测试环境使用,编译时不用加入classpath,在打包时会打包到目标包中。

test一般是单元测试场景使用,在编译环境加入classpath,但打包时不会加入(如junit)等。

system(一般不用,不同机器可能不兼容)系统范围,与provided类似,只是标记为该scope的依赖包需要明确指定基于文件系统的jar包路径。

直接依赖和间接依赖

如果C中使用B,B中使用A,则称B是C的直接依赖,而称A是C的间接依赖。

依赖范围对传递依赖的影响

compile

test

provided

runtime

compile

compile

-

-

runtime

test

test

-

-

test

provided

provided

-

provided

provided

runtime

runtime

-

-

runtime

左边第一列表示第一直接依赖范围

上面第一行表示第二直接依赖范围

中间交叉的单元格表示传递性的依赖范围

依赖冲突

1.如果直接依赖与间接依赖中,包含有同一个坐标不同版本的资源依赖,以直接依赖的版本为准(就近原则)

2.如果直接依赖中,包含有同一个坐标不同版本的资源依赖,以配置顺序下方的版本为准(就近原则)

可选依赖

true/false用于设置是否可选,也可以理解为jar包是否向下传递。

在依赖中添加optional选项决定此依赖是否向下传递,如果是true则不传递,如果是false则传递,默认为false。

排除依赖

Maven的传递依赖能够自动将间接依赖引入项目中来,这样极大地简化了项目中的依赖管理,但是有的时候间接依赖的关联包因为版本或其他原因,并不是我们想要的版本,因此需要排除依赖。

在直接依赖的配置里面添加exclusions→exclusion元素,指定要排除依赖的groupId和artifactId就可以了。

<dependency>

           <groupId>junit</groupId>

           <groupId>junit</artifactId>

           <version>4.11</version>

           <scope>test</scope>

<exclusions>

            <exclusion>

                <groupId>>org.hamcrest</groupId>

                <groupId>>hamcrest-core</groupId>

            </exclusion>

       </exclusions>

</dependency>

PS:排除依赖包中所包含的依赖关系,不需要添加版本号。

若运行java maven项目报错:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile failure

可在pom.xml中尝试加入Maven编译插件:

<build>

    <plugins>

        <plugin>

            <groupId>org.apache.maven.plugins</groupId>

           <artifactId>maven-compiler-plugin</artifactId>

           <version>3.1</version>

           <configuration>

                <source>1.8</source>

                <target>1.8</target>

                <encoding>UTF-8</encoding>

            </configuration>

        </plugin>

    </plugins>

</build>

我们之前创建的web项目都需要额外配置tomcat以后才能运行项目,现在Maven提供了tomcat插件,这样我们就无需再添加额外的tomcat了。

步骤1:创建Maven类型的web工程

步骤2: 在pom.xml文件中添加插件的信息

<plugins>

       <plugin>

            <!-- 配置插件 -->

            <groupId>org.apache.tomcat.maven</groupId>

            <artifactId>tomcat7-maven-plugin</artifactId>

            <version>2.2</version>

            <configuration>

                <port>8080</port> <!-- tomcat的端口 -->

                <path>/</path> <!-- 项目的访问路径 -->

            </configuration>

       </plugin>

</plugins>

点击add configuration,点击“+”添加Maven

(Name:自定义的名字,Command line:tomcat7:run/tomcat运行的指令)

nosql指非关系型的数据库。

优势:易扩展、大数据量,高性能、灵活的数据模型和高可用。

Redis(远程字典服务器)

与其他key-value缓存产品相比,有以下三个特点:

1.Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用

2.Redis不仅仅支持简单的key-value类型的数据,同时还提供了list、set、zset、hash等数据结构的存储

3.Redis支持数据的备份,即master-slave(主从)模式的数据备份

Redis下载(百度教程)

Http://redis.io/ 英文地址

Http://www.redis/ 中文地址

Redis是一种基于内存的数据库,并且提供了一定的持久化功能,它是一种键值key-value数据库,使用key作为索引找到当前缓存的数据,并且返回给程序调用者。

当前的Redis支持6种数据类型,它们分别是字符串String、列表List、集合set、哈希结构hash、有序集合zset和基数HyperLogLog。

Redis常用指令

网站:Redis 命令参考 — Redis 命令参考

Redis事务可以一次执行多个命令,并且带有以下两个重要的保证:

1.事务是一个单独的隔离操作

事务中的所有命令都会序列化、按顺序地执行;事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

2.事务是一个原子操作

事务中的命令要么全部被执行,要么全部都不执行。

一个事务从开始到执行会经历以下三个阶段:开始事务、命令入队、执行事务。

以下是一个事务的例子,它先以MULTI开始一个事务,然后将多个命令入队到事务中,最后由EXEC命令触发事务,一并执行事务中的所有命令:

192.168.227.128:6379> multi

OK

192.168.227.128:6379> set bookname java

QUEUED

192.168.227.128:6379> set bookname c++

QUEUED

192.168.227.128:6379> set bookname html

QUEUED

192.168.227.128:6379> exec

1)OK

2)OK

3)OK

Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。

#创建订阅的频道名:redisMessage

192.168.227.128:6379> subscribe redisMessage

Reading messages...(press Ctrl-C to quit)

1) "subscribe"

2) "redisMessage"

3) (integer) 1

#通过PUBLISH命令发送消息给频道redisMessage

192.168.227.128:6379> publish redisMessage "demo1 test"

(integer) 1

192.168.227.128:6379> publish redisMessage "demo2 test"

(integer) 1

#订阅者的客户端会显示以下消息

192.168.227.128:6379> subscribe redisMessage

Reading messages...

1) "subscribe"

2) "redisMessage"

3) (integer) 1

1) "message"

2) "redisMessage"

3) "demo1 test"

1) "message"

2) "redisMessage"

3) "demo2 test"

Redis持久化:由于Redis的值放在内存中,为了防止突然断电等特殊情况的发生,需要对数据进行持久化备份,即将内存数据保存到硬盘。

Redis持久化的存储方式:RDB和AOF。

RDB持久化

RDB是在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。

优点:使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证了redis的高性能。

缺点:RDB是间隔一段时间进行持久化,如果持久化之间redis发生故障,会发生数据丢失,所以这种方式更适合数据要求不严谨的时候。

AOF持久化

Append-only file,将“操作+数据”以格式化指令的方式追加到操作日志文件的尾部,在append操作返回后(已经写入到文件或者即将写入),才进行实际的数据变更,“日志文件”保存了历史所有的操作过程;当server需要数据恢复时,可以直接replay此日志文件,即可还原所有的操作过程。

AOF相对可靠,它和mysql中bin.log、apache.log、zookeeper中txn-log简直异曲同工。AOF文件的内容是字符串,非常容易阅读和解析。

优点:可以保持更高的数据完整性,如果设置追加file的时间是1s,当redis发生故障,最多会丢失1s的数据;如果日志写入不完整支持redis-check-aof来进行日志修复,AOF文件没被rewrite之前(文件过大时会对命令进行合并重写)可以删除其中的某些命令(比如误操作的flushall)。

缺点:AOF文件比RDB文件大,且恢复速度慢。

Redis主从复制

持久化保证了即使redis服务器重启也不会丢失数据,但是当redis服务器的硬盘损坏了可能会导致数据丢失,通过redis的主从复制机制就可以避免这种单点故障(单台服务器的故障)。

主redis中的数据和从redis上的数据保持实时同步,当主redis写入数据时通过主从复制机制复制到两个从服务上。

主从复制不会阻塞master,在同步数据时master可以继续处理client请求。

工作中一般选用:一主两从或一主一从

主从搭建步骤:

主机:不用配置,仅仅只需要配置从机。从机slave配置:(这里是伪集群)

第一步:复制出一个从机,注意使用root用户

[root@localhost myapps]# cp redis/ redis2 -r

第二步:修改从机的redis.conf

语法:replicaof 主机ip 主机端口号

提示:检索文件:输入/replicaof当前页没有时,输入n,查找下一页

第三步:修改从机的port地址为6380

第四步:清除从机中的持久化文件

[root@localhost redis2]# rm -rf dump.rdb

第五步:启动从机

[root@localhost redis2]# ./bin/redis-server ./redis.conf

第六步:启动6380的客户端

[root@localhost redis2]# ./bin/redis-cli -h 192.168.227.128 -p 6380

192.168.227.128:6380> keys *

停止客户端:./bin/redis-cli -p 6380 shutdown

注意:主机一旦发生增删改操作,数据会自动同步到从机中;从机不能执行写操作,只能读。

复制的过程原理:

当从库和主库建立MS(master slaver)关系后,会向主数据库发送SYNC命令;主库接收到SYNC命令后会开始在后台保存快照(RDB持久化过程),并将期间接收到的写命令缓存起来;快照完成后,主Redis会将快照文件和所有缓存的写命令发送给从Redis;从Redis接收到后,会载入快照文件并且执行收到的缓存命令;主Redis每当接收到写命令时就会将命令发送从Redis,保证数据的一致。

复制架构中出现宕机情况?

从Redis宕机:重启就好。

主Redis宕机:从数据库(从机)中执行SLAVEOF NO ONE命令,断开主从关系并且提升为主库继续服务[把一个从机当作主机,这个时候新主机(之前的从机)就具备写入的能力];主服务器修好后,重新启动后,执行SLAVEOF命令,将其设置为从机[老主机设置为从机]。

Redis哨兵模式

哨兵的作用就是对Redis系统的运行情况监控,它是一个独立进程。

功能:1、监控主数据库和从数据库是否运行正常

           2、主数据库出现故障后,自动将从数据库转化为主数据库;如果主机宕机,则开启选举工作,选择一个从机作为主机

环境准备:一主两从,启动任一从机时,启动哨兵模式

虽然哨兵(sentinel)释出为一个单独的可执行文件redis-sentinel,但实际上它只是一个运行在特殊模式下的Redis服务器,你可以在启动一个普通Redis服务器时通过给定 --sentinel 选项来启动哨兵(sentinel)。

第一步:配置哨兵

哨兵主要是用来监听主服务器的,所以一般把哨兵部署在从服务器上监听。

启动哨兵进程,首先需要创建哨兵配置文件vi sentinel.conf(可直接自定义该文件到bin目录下,也可以从源码配置redis-5.0.5/sentinel.conf中复制内容)在配置中输入sentinel monitor mastername 127.0.0.1 6379 1

说明:mastername:监控主数据的名称自定义  

127.0.0.1:监控主数据库的IP  6379:端口号  1:最低通过票数

第二步:启动哨兵

哨兵是一个单独的进程,启动之前确保主从服务是正常的;先启动主服务,后启动从服务。

把日志写入指定的文件:

[root@localhost bin]# ./redis-sentinel ./sentinel.conf >sent.log &

启动redis服务后,程序会自动配置文件sentinel.conf并生成内容。

注意:若再次启动需要删除生成的内容。

哨兵启动方式:

[root@localhost bin]# ./redis-server sentinel.conf --sentinel

哨兵进程控制台为master数据库添加了一个监控,与此同时多了哨兵进程。

查询配置文件sentinel.conf中生成的内容:启动哨兵的时候,修改了哨兵的配置文件。如果需要再次启动哨兵,需要删除myid唯一标示(保险的做法就是启动的一次,新配置一次)。

第三步:主机宕机(机房意外断电、硬盘故障)

杀死主机:kill -9 pid(进程号)

从库自动提升为主库,哨兵替代运维,自动监控完成。

同时也会自动修改redis.conf的主从配置文件指向了新主机,再次启动时原有的主机会变为从机。

总结

主从集群:主机有写入权限;从机没有(只能读)。

意外宕机方案

手动恢复:人为重启服务器,主机宕机,把从机设置为主机。

自动恢复:使用哨兵模式监控,自动切换主从。

Redis集群方案

架构细节:

1、所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。

2、节点的fail是通过集群中超过半数的节点检测有效时整个集群才生效。

3、客户端与redis节点直连,不需要中间proxy层,客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。

4、redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster负责维护node<->slot<->value

Redis集群中内置了16384个哈希槽,当需要在Redis集群中放置一个key-value 时,redis先对key使用crc16算法算出一个结果,然后把结果对16384求余数,这样每个key都会对应一个编号在0-16383之间的哈希槽,redis会根据节点数量大致均等的将哈希槽映射到不同的节点。

心跳机制

(1)集群中所有master参与投票,如果半数以上master节点与其中一个master节点通信超过(cluster-node-timeout),认为该master节点挂掉。

(2)什么时候整个集群不可用(cluster_state:fail)?

1.如果集群任意master挂掉,且当前master没有slave,则集群进入fail状态。也可以理解成集群的[0-16383]slot映射不完全时进入fail状态。

2.如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态。

第一步:创建集群目录

[root@localhost redis]# mkdir redis-cluster

第二步:搭建集群最少也得需要3台主机,如果每台主机再配置一台从机的话,则最少需要6台机器。

设计端口如下:创建6个redis实例,需要端口号7001~7006

[root@localhost myapps]# cp redis/ redis-cluster/7001 -r

第三步:如果存在持久化文件,则删除

第四步:修改redis.conf配置文件,打开Cluster-enable yes(是否支持集群)

第五步:修改端口

第六步:复制出7002-7006机器

[root@localhost redis-cluster]# cp 7001/ 7002 -r

[root@localhost redis-cluster]# cp 7001/ 7003 -r

[root@localhost redis-cluster]# cp 7001/ 7004 -r

[root@localhost redis-cluster]# cp 7001/ 7005 -r

[root@localhost redis-cluster]# cp 7001/ 7006 -r

第七步:修改7002-7006机器的端口

第八步:启动7001-7006这六台机器,写一个启动脚本

[root@localhost redis-cluster]# vi startall.sh

内容:

cd 7001

./bin/redis-server ./redis.conf

cd ..

cd 7002

./bin/redis-server ./redis.conf

cd ..

cd 7003

./bin/redis-server ./redis.conf

cd ..

cd 7004

./bin/redis-server ./redis.conf

cd ..

cd 7005

./bin/redis-server ./redis.conf

cd ..

cd 7006

./bin/redis-server ./redis.conf

cd ..

第九步:修改start-all.sh文件的权限

[root@localhost redis-cluster]# chmod u+x startall.sh

第十步:启动所有的实例

[root@localhost redis-cluster]# ./startall.sh

第十一步:创建集群(关闭防火墙)

[root@localhost redis_cluster]# cd /home/admin/myapps/redis-cluster/7001/bin

[root@localhost bin]# ./redis-cli --cluster create 192.168.227.128:7001 192.168.227.128:7002 192.168.227.128:7003 192.168.227.128:7004 192.168.227.128:7005 192.168.227.128:7006 --cluster-replicas 1

连接集群:

[root@localhost 7001]# ./bin/redis-cli -h 192.168.227.128 -p 7001 -c

查看集群信息:

192.168.227.128:7001> cluster info

查看集群中节点信息:

192.168.227.128:7001> cluster nodes

关闭防火墙:service iptables stop

查看防火墙:service iptables status

Jedis连接集群(关闭防火墙)

注意:如果redis重启,需要将redis中生成的dump.rdb和nodes.conf文件删除,然后再重启。

<dependency>

           <groupId>.clients</groupId>

           <groupId>jedis</artifactId>

           <version>2.9.0</version>

</dependency>

注意Jedis的版本,其他版本有可能报错:java.lang.NumberFormatException: For input string: "7002@17002"。

代码实现:

public static void main(String[] args) throws IOException {

      //创建一连接,JedisCluster对象,在系统中是单例存在

      Set nodes = new HashSet<HostAndPort>();

      nodes.add(new HostAndPort("192.168.227.128", 7001));

      nodes.add(new HostAndPort("192.168.227.128", 7002));

      nodes.add(new HostAndPort("192.168.227.128", 7003));

      nodes.add(new HostAndPort("192.168.227.128", 7004));

      nodes.add(new HostAndPort("192.168.227.128", 7005));

      nodes.add(new HostAndPort("192.168.227.128", 7006));

      JedisCluster cluster = new JedisCluster(nodes);

      //执行JedisCluster对象中的方法,方法和redis指令一一对应 cluster.set("test1", "test111");

      String result = cluster.get("test1");

      System.out.println(result);

      //存储List数据到列表中

      cluster.lpush("site-list", "java");

      cluster.lpush("site-list", "c");

      cluster.lpush("site-list", "mysql");

      // 获取存储的数据并输出

      List list = cluster.lrange("site-list", 0 ,2);

      for(int i=0; i<list.size(); i++) {

            System.out.println("列表项为: "+list.get(i));

      }

      //程序结束时需要关闭JedisCluster对象

      cluster.close();

      System.out.println("集群测试成功!");

}

Redis高端面试-缓存穿透、缓存击穿、缓存雪崩问题

广义的缓存就是在第一次加载某些可能会复用数据的时候,在加载数据的同时,将数据放到一个指定的地点做保存;在下次加载的时候,从这个指定地点去取数据。这里加缓存是有一个前提的,就是从这个地方取数据,比从数据源取数据要快的多。

java狭义一些的缓存主要是指三大类:

1、虚拟机缓存(ehcache,JBoss Cache)

2、分布式缓存(redis,memcache)

3、数据库缓存

正常来说,速度由上到下依次减慢

缓存雪崩通俗简单的理解就是:由于原有缓存失效(或者数据未加载到缓存中),新缓存未到期间(缓存正常从 Redis中获取)所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机,造成系统的崩溃。

解决方案:

1、在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。

比如对某个key只允许一个线程查询数据和写缓存,其他线程等待,虽然能够在一定的程度上缓解了数据库的压力,但是与此同时又降低了系统的吞吐量。

public Users getByUsers(Long id) {

      //先查询

      redis String key = this.getClass().getName() + "-" + Thread.currentThread().getStackTrace() [1].getMethodName() + "-id:" + id;

      String userJson = redisService.getString(key);

      if (!StringUtils.isEmpty(userJson)) {

            Users users = JSONObject.parseObject(userJson, Users.class);

            return users;

      }

      Users user = null;

      try { lock.lock();

            //查询db

            user = userMapper.getUser(id);

            redisService.setSet(key, JSONObject.toJSONString(user));

      } catch (Exception e) {

      } finally {

             lock.unlock(); // 释放锁

      }

      return user;

}

注意:加锁排队只是为了减轻数据库的压力,并没有提高系统的吞吐量。假设在高并发下,缓存重建期间key是锁着的,这时过来的1000个请求999个都在阻塞,同样会导致用户等待超时,这是个治标不治本的方法。

2、分析用户的行为,不同的key设置不同的过期时间,让缓存失效的时间点尽量均匀。

缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。

解决方案:

1、如果查询数据库也为空,直接设置一个默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴。

2、把空结果也给缓存起来,这样下次同样的请求就可以直接返回空了,既可以避免当查询的值为空时引起的缓存穿透,同时也可以单独设置个缓存区域存储空值,对要查询的key进行预先校验,然后再放行给后面的正常缓存处理逻辑。

public String getByUsers2(Long id) {

      //先查询

      redis String key = this.getClass().getName() + "-" + Thread.currentThread().getStackTrace() [1].getMethodName()+ "-id:" + id;

      String userName = redisService.getString(key);

      if (!StringUtils.isEmpty(userName)) {

            return userName;

      }

      System.out.println("######开始发送数据库DB请求########");

      Users user = userMapper.getUser(id);

      String value = null;

      if (user == null) {

            //标识为null

            value = "";

      } else {

            value = user.getName();

      }

      redisService.setString(key, value);

      return value;

}

注意:再给对应的ip存放真值的时候,需要先清除之前对应的空缓存。

缓存击穿

对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候需要考虑缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。

热点key:某个key访问非常频繁,当key失效的时候有大量线程来构建缓存,导致负载增加,系统崩溃。

解决办法:

①使用锁,单机用synchronized、lock等,分布式用分布式锁。

②缓存过期时间不设置,而是在key对应的value里设置;如果检测到存的时间超过过期时间,则异步更新缓存。

Redis高端面试-分布式锁

使用分布式锁要满足的几个条件:

1、系统是一个分布式系统(关键是分布式,单机的可以使用ReentrantLock或者synchronized代码块来实现)

2、共享资源(各个系统访问同一个资源,资源的载体可能是传统关系型数据库或者nosql)

3、同步访问(即有很多个进程同时访问同一个共享资源) 

什么是分布式锁?

线程锁:主要用来给方法、代码块加锁。当某个方法或某块代码使用了锁,在同一时刻仅有一个线程执行该方法或该代码块。线程锁只在同一JVM中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如synchronized是共享对象头、显示锁Lock是共享某个变量(state)。

进程锁:为了控制同一操作系统中多个进程访问某个共享资源,因为进程具有独立性,各个进程无法访问其他进程的资源,因此无法通过synchronized等线程锁实现进程锁。

分布式锁:当多个进程不在同一个系统中,可用分布式锁控制多个进程对资源进行访问。

线程间和进程间的并发问题都是可以通过分布式锁解决的,但是强烈不建议这样做,因为采用分布式锁解决这些小问题是非常消耗资源的,分布式锁应该用来解决分布式情况下的多进程并发问题才是最合适的。

Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。redis的SETNX命令可以方便的实现分布式锁。

语法:SETNX key value

SETNX是[SET if Not exists](如果不存在,则SET)的简写。

返回值:设置成功,返回1;设置失败,返回0。

我们使用执行下面的命令SETNX可以用作加锁原语(locking primitive)。

比如对关键字(key) foo加锁,客户端通过SETNX lock.foo的方式,如果SETNX返回1,则说明客户端已经获得了锁,SETNX将键lock.foo的值设置为锁的超时时间(当前时间 + 锁的有效时间);之后客户端可以通过DEL lock.foo来释放锁,如果SETNX返回0,则说明key已经被其他客户端上锁了;如果锁是非阻塞的(non blocking lock),我们可以选择返回调用或者进入一个重试循环,直到成功获得锁或重试超时(timeout)。

语法:GETSET key value

先获取key对应的value值。若不存在则返回nil,然后将旧的value更新为新的value。

注意(回答面试的核心点):

1、同一时刻只能有一个进程获取到锁。setnx

2、释放锁:锁信息必须是会过期超时的,不能让一个线程长期占有一个锁而导致死锁(最简单的方式就是del,如果在删除之前死锁了)

死锁情况是在判断超时后直接操作业务,设置过期时间,执行业务,然后删除释放锁,其他进程再次通过setnx来抢锁。

解决死锁:

public static boolean lock(String lockName) {

      Jedis jedis = RedisPool.getJedis();

      //lockName可以为共享变量名,也可以为方法名,主要用于模拟锁信息

      System.out.println(Thread.currentThread() + "开始尝试加锁!");

      Long result = jedis.setnx(lockName, String.valueOf(System.currentTimeMillis() + 5000));

      if (result != null && result.intValue() == 1){

            System.out.println(Thread.currentThread() + "加锁成功!");

            jedis.expire(lockName, 5);

            System.out.println(Thread.currentThread() + "执行业务逻辑!");

            jedis.del(lockName);

            return true;

      } else {

           //判断是否死锁

           String lockValueA = jedis.get(lockName);

           //得到锁的过期时间,判断小于当前时间,说明已超时但是没释放锁,通过下面的操作来尝试获得锁。

            if (lockValueA != null && Long.parseLong(lockValueA) < System.currentTimeMillis()){

                  String lockValueB = jedis.getSet(lockName, String.valueOf(System.currentTimeMillis() + 5000));

                 //这里返回的值是旧值,如果有的话。之前没有值就返回null,设置的是新超时。

                 if (lockValueB == null || lockValueB.equals(lockValueA)){

                        System.out.println(Thread.currentThread() + "加锁成功!");

                        jedis.expire(lockName, 5);

                        System.out.println(Thread.currentThread() + "执行业务逻辑!");

                         jedis.del(lockName);

                        return true;

                  } else {

                        return false;

                 }

            } else {

                 return false;

           }

      }

}

本文标签: 第九章 笔记