第二十周作业
1.总结tomcat优化方法
1.jvm 组成
由于tomcat的运行依赖于JVM ,从虚拟机的角度把tomcat的调整分为外部环境调优JVM和tomcat自身调优两部分
jvm的优化是在硬件资源有限的情况下主要是优化运行时数据区的堆(Heap)区域,如果硬件资源(内存)足够的也就谈不到优化了
jvm 组成:
类加载子系统:使用java 语言编写.java source code 文件,通过javac编译成.class Byte code, class loader 类加载器将所需所有的类加载到内存,必要时将类实例化成实例
运行时数据区:最消耗内存的空间,需要优化
执行引擎:包括jit(justh timecompiler)即时编译器,gc垃圾回收器
本地方法接口:将本地方法栈通过JNI(java native interface)调用native method libraries 比如:c,c++库等,扩展java 功能,融合不同的编程语言为java 所用
jvm 运行时数据区域有以下部分构成:
Method Area (线程共享):方法区时所有线程共享的内存空间,存放已经加载的类信息(构造方法,接口定义 )常量 静态变量 运行时常量池等,但实例变量存放在堆内存中,从JDK8开始此空间永久代改名为元空间
heap (线程共享):堆在虚拟机启动时创建,存放创建的所有对象信息。如果对象无法申请到可用内存将抛出OOM异常.堆是靠GC垃圾回收器管理的,通过-Xmx -Xms 指定最大堆和最小堆空间大小
Java stack (线程私有):Java栈是每个线程会分配一个栈,存放java中8大基本数据类型,对象引用,实例的本地变量,方法参数和返回值等,基于FILO()(First In Last Out),每个方法为一个栈帧
Program Counter Register (线程私有):PC寄存器就是一个指针,指向方法区中的方法字节码,每一个线程用于记录当前线程正在执行的字节码指令地址。由执行引擎读取下一条指令.因为线程需要切换,当一个线程被切换回来需要执行的时候,知道执行到哪里了
Native Method stack (线程私有):本地方法栈为本地方法执行构建的内存空间,存放本地方法执行时的局部变量、操作数等。
所谓本地方法,使用native 关健字修饰的方法,比如:Thread.sleep方法. 简单的说是非Java实现的方法,例如操作系统的C编写的库提供的本地方法,Java调用这些本地方法接口执行。但是要注意,本地方法应该避免直接编程使用,因为Java可能跨平台使用,如果用了Windows API,换到了Linux平台部署就有了问题
2.GC (Garbage Collection)垃圾收集器
GC (Garbage Collection) 垃圾收集器
在堆内存中如果创建的对象不再使用,仍然占用着内存,此时即为垃圾,需要及时进行垃圾回收,从而释放内存空间给其他对象使用
JAVA和Python中不需要程序员进行人为的回收垃圾,而由JVM或相关程序自动回收垃圾,减轻程序员的开发难度,但可能会造成执行效率低下
堆内存里面经常创建 销毁对象,内存也是被使用,被释放,如果不妥善处理,一个使用频繁的进程,可能会出现虽然有足够的内存容量,但是无法分配出可用内存空间,因为没有连续成片的内存了,内存全是碎片化的空间.所以需要有合适的垃圾回收机制,确保正常释放不再使用的内存空间,需要保证内存空间尽可能的保持一定的连续性
对于垃圾回收,需要解决三个问题:
哪些是垃圾要回收
怎么回收垃圾
什么时候回收垃圾
垃圾确定方法
引用计数: 每一个堆内对象上都与一个私有引用计数器,记录着被引用的次数,引用计数清零,该对象所占用堆内存就可以被回收。循环引用的对象都无法将引用计数归零,就无法清除。
垃圾回收基本算法:
标记-清除 mark-sweep
分垃圾标记阶段和内存释放阶段.
标记阶段,找到所有可访问对象打个标记.
清理阶段,遍历整个堆,对未标记对象(即不再使用的对象)逐一进行清理
标记-清除最大的问题会造成内存碎片,但是不浪费空间,效率较高(如果对象较多,逐一删除效率也会影响)
标记-压缩Mark-Compact
分垃圾标记阶段和内存整理阶段.
标记阶段,找到所有可访问对象打个标记.
内存清理阶段时,整理时将对象向内存一端移动,整理后存活对象连续的集中在内存一端
标记-压缩算法好处是整理后内存空间连续分配,有大段的连续内存可分配,没有内存碎片,缺点是内存整理过程有消耗,效率相对低下
复制Copying
先将可用内存分为大小相同两块区域A和B ,每次只用其中一块,比如A,当A用完之后,则将A中存活的对象复制到B,复制到B的时候连续的使用内存,最后将A一次性清除干净.
缺点是比较浪费内存,只能使用原来一半内存,以为内存对半划分,复制过程有代价.
优点是没有碎片,复制过程中保证对象使用连续空间,且一次性清除所有垃圾,所以效率很高.
多种算法总结
没有最好的算法,在不同场景选择合适的算法
效率:复制算法>标记清除算法>标记压缩算法
内存整齐度:复制算法=标记压缩算法>标记清除算法
内存利用率:标记压缩算法=标记清除算法>复制算法
stw 对于大多数垃圾回收算法而言,GC线程工作时候,停止所有工作线程,成为 stop the world . GC完成时候,恢复其他线程工作线程运行,这也是JVM 运行中最头疼的问题.
分代堆内存GC策略
上述垃圾回收算法都有优缺点,能不能对不同数据进行区分管理,不同分区对数据实施不同回收策略分而治之.
内存分代
将堆(heap)内存空间分为三个不同的类别:年轻代 老年代 持久代
Heap堆内存分为:
年轻代Young: Young Generation
伊甸园区eden:只有一个,刚刚创建的对象
幸存(存活)区servivor space 有两个幸存区,一个是from区域,一个是to区域,大小相等,地位也相同,可互换
from 指的是本次复制数据的源区
to 指的是本次复制数据额目标区
老年代 Tenured: Old Generation 长时间存活的对象
默认空间大小比例:
永久代:jdk1.7之前使用,即Method Area 方法区,保存JVM自身的类和方法,存储JAVA运行时的环境信息,JDK1.8后,改名为 MetaSpace,此空间不存在垃圾回收,关闭JVM会释放此区域内存,此空间物理上不属于Heap内存,但逻辑上存在于heap内存
永久代必须指定大小限制,字符串常量jdk1.7存在永久代,1.8后存放在heap中
MetaSpace可以设置,也可以不设置,无上限
一般情况下99%的对象都是临时对象
年轻代+老年代占用了所有Heap空间,Metaspace 实际不占用heap空间,逻辑上存在于heap
默认JVM试图分配最大内存的总内存的1/4,初始化默认内存为总内存的1/64
在tomcat 状态页可以看到以下的内存分代
年轻代回收 Minor GC
1.起始时,所有创建新对象(特大对象直接进入老年代)都出生在eden上,eden满了,启动GC,这个称为 Young GC 或者 Minor GC
2.先标记eden存活对象,然后将存活对象复制到 s0 , eden 剩余所有空间都清空,GC 完成
3.继续创建对象,当eden再次满了,启动GC
4.先同时标记eden和s0中存活对象,然后将存活对象复制到S1,将eden和s0清空,此次GC完成
5.继续新建对象,当eden满了,启动GC
6.先标记eden 和s1 中存活对象,然后将存活对象复制到s0 ,将eden和s1清空,此次GC完成
以后就重复上面6个步骤
通常大多数对象都不会存活很久,而且创建活动非常多,新生代就需要频繁垃圾回收
但是一个对象一直存活,它最后就在from to 来回复制,如果 from 区域中对象复制次数达到阈值(默认15次 CMS 为6次,可通过java的选项 -XX:MaxTenuringThreshold=N 指定),就直接复制到老年代
老年代回收 Major GC
进入老年代的数据较少,所以老年代区域被占满的速度较慢,所以垃圾回收不频繁
如果老年代也满了,会触发老年代GC,成为 old GC 或者 Major GC
由于老年代对象一般来说存活次数较长,所以较常采用标记-压缩算法
当老年代满时,会触发full GC ,即都所有代的内存进行垃圾回收
Minor GC 比较频繁, Major GC 较少,但一般 MajorGC时候,由于老年代对象也可以引用新生代对象,所以先进行一次minor GC ,然后在 Major GC 会提高效率,可以认为回收老年代的时候完成了一次full GC,所以可以认为 Major GC =FullGC
GC 触发条件
Minor GC 触发条件: 当eden 区域满了触发
Full GC 触发条件:
老年代满了
System.gc()手动调用,不推荐
年轻代:
存活时长低
适合复制算法
老年代:
区域大,存活时长高
适合标记压缩算法
3.java 内存调整相关参数
java 命令行参考文档:
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
帮助 [root@ansible app]# man java
选项分类
-选项名称 此为标准选项,所有HotSpot 都支持
-X选项名称 此为稳定的非标准选项
-XX:选项名称 非标准的不稳定选项,下一个版本可能会取消
参数 | 说明 | 举例 |
---|---|---|
-Xms | 设置应用程序初始使用的堆内存大小(年轻代+老年代) | -Xms26g |
-Xmx | 设置应用程序能获得的最大堆内存,早期JVM不建议超过32G,内存管理效率下降 | -Xmx26g |
-XX:NewSize | 设置初始新生代大小(伊甸园区) | -XX:NewSize=256m |
-XX:MaxNewSize | 设置最大新生代内存空间(伊甸园区) | -XX:MaxNewSize=256m |
-Xmnsize | 同时设置-XX:NewSize和-XX:MaxNewSize 代替两者(伊甸园区) | -Xmn1g |
-XX:NewRatio | 设置年轻代和年老代大小之间的比率。 默认情况下,此选项设置为 2 | -XX:NewRatio=2 |
-XX:SurvivorRatio | 以比例方式设置eden和survivor(s0和s1) ;置伊甸园空间大小和幸存者空间大小之间的比率。 默认情况下,此选项设置为 8 | -XX:SurvivorRatio=6 eden/survivor=6/1 new/survivor=8/1 |
-Xss | 设置每个线程私有的栈空间大小,依据具体线程大小和数量;设置线程堆栈大小(以字节为单位),附加字母k或K表示KB,m或M表示MB,g或G表示GB | -Xss256k 默认值取决于平台: Linux/x64(64 位):1024 KB |
4.Tomcat的JVM参数设置
默认不指定,-Xmx 大约使用了1/4的内存
[root@ansible ~]# vim /usr/local/tomcat/bin/catalina.sh
# OS specific support. $var _must_ be set to either true or false.
#在此行下面增加一行
#JAVA_OPTS="-server -Xms128m -Xmx512m -XX:NewSize=48m -XX:MaxNewSize=200m
JAVA_OPTS="-server -Xms4096m -Xmx4096m -Xmn1g -Xss512m"
#-server:VM运行在server模式,为在服务器端最大化程序运行速度而优化
#-client:VM运行在Client模式,为客户端环境减少启动时间而优化
重启tomcat 观察
[root@ansible ~]# systemctl restart tomcat.service
[root@ansible bin]# ps -auxf|grep java |grep -v "grep"
tomcat 51740 13.1 1.1 13037168 191360 ? Sl 19:42 0:04 /usr/local/jdk/bin/java -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -server -Xms4096m -Xmx4096m -Xmn1g -Xss512m -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -Dignore.endorsed.dirs= -classpath /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar -Dcatalina.base=/usr/local/tomcat -Dcatalina.home=/usr/local/tomcat -Djava.io.tmpdir=/usr/local/tomcat/temp org.apache.catalina.startup.Bootstrap start
5.Tomcat Executor(线程池)优化
Executor (执行器)表示可以在Tomcat中的组件之间共享的线程池。每个连接器都会创建一个线程池,但是当配置为支持执行器时,您可以在(主要)连接器之间以及其他组件之间共享线程池
Executor 是Service元素的嵌套元素。为了使它能够被连接器拾取,Executor元素必须出现在server.xml中的Connector元素之前
executor组件为每个Service组件提供线程池,使得各个connector和Engine可以从线程池中获取线程处理请求,从而实现tomcat的并发处理能力。
一定要注意,Executor的线程池大小是为Engine组件设置,而不是为Connector设置的,Connector的线程数量由Connector组件的acceptorThreadCount属性来设置。如果要在配置文件中设置该组件,则必须设置在Connector组件的前面,以便在Connector组件中使用executor`属性来引用配置好的Executor组件。如果不显式设置,则采用Connector组件上的默认配置
<!-- 默认配置: -->
<!--The connectors can use a shared executor, you can define one or more named thread pools-->
<!--
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"/>
-->
<!-- 去掉注释 修改配置: -->
<Executor name="tomcatThreadPool"
namePrefix="catalina-exec-"
maxThreads="1500"
minSpareThreads="50"
prestartminSpareThreads="true"
maxIdleTime="60000"
maxQueueSize = "100" />
官方文档: https://tomcat.apache.org/tomcat-8.5-doc/config/executor.html https://tomcat.apache.org/tomcat-9.0-doc/config/executor.html
Executor的所有实现都支持以下属性:通用属性
属性 | 描述 |
---|---|
className |
实现的类。实现必须实现接口。此接口确保可以通过对象的属性引用该对象并实现生命周期,以便可以使用容器启动和停止该对象。类名的缺省值为org.apache.catalina.Executor``name``org.apache.catalina.core.StandardThreadExecutor |
name |
用于在服务器.xml中的其他位置引用此池的名称。该名称是必需的,并且必须是唯一的。 |
默认实现支持以下属性: 标准实施
属性 | 描述 |
---|---|
threadPriority |
(int)执行器中线程的线程优先级,默认值为(常量的值)5``Thread.NORM_PRIORITY |
daemon |
(布尔值)无论线程是否应为守护程序线程,默认值为true |
namePrefix |
(字符串)执行程序创建的每个线程的名称前缀。单个线程的线程名称将为namePrefix+threadNumber |
maxThreads |
(int)此池中活动线程的最大数目缺省值为200 |
minSpareThreads |
(int)最小线程数(空闲和活动)始终保持活动状态,默认值为25 |
maxIdleTime |
(int)空闲线程关闭之前的毫秒数,除非活动线程数小于或等于 minSpareThreads。默认值为(1 分钟)60000 |
maxQueueSize |
(int)在我们拒绝可运行任务之前可以排队等待执行的最大数量。默认值为Integer.MAX_VALUE |
prestartminSpareThreads |
(布尔值)无论 minSpareThreads 是否应在启动执行器时启动,默认值为false |
threadRenewalDelay |
(long)如果配置了ThreadLocalLeakPreventionListener,它将通知此执行程序有关已停止的上下文。停止上下文后,将续订池中的线程。为避免同时续订所有线程,此选项会在续订任意 2 个线程之间设置延迟。该值以毫秒为单位,默认值为毫秒。如果值为负数,则不会续订线程。1000 |
6.Tomcat Connector(连接器)优化
官方文档:
https://tomcat.apache.org/tomcat-9.0-doc/config/http.html
在Connector中设置executor属性引用Executor(线程池)
<!-- 默认配置
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
-->
<!-- 修改为下面 -->
<Connector executor="tomcatThreadPool"
port="8080"
protocol="HTTP/1.1"
connectionTimeout="60000"
maxConnections="10000"
enableLookups="false"
acceptCount="100"
maxPostSize="10485760"
maxHttpHeaderSize="8192"
compression="on"
disableUploadTimeout="true"
compressionMinSize="2048"
acceptorThreadCount="2"
URIEncoding="utf-8"
processorCache="20000"
tcpNoDelay="true"
connectionLinger="-1"
server="Server Version 11.0"
redirectPort="8443"/>
官方参考 https://tomcat.apache.org/tomcat-9.0-doc/config/http.html
连接器的所有实现都支持以下属性: 通用属性
属性 | 描述 |
---|---|
allowTrace |
可用于启用或禁用 TRACE HTTP 方法的布尔值。如果未指定,则此属性设置为 false。 |
asyncTimeout |
异步请求的默认超时(以毫秒为单位)。如果未指定,则此属性将设置为 Servlet 规范默认值 30000(30 秒)。 |
continueResponseTiming |
何时使用中间响应代码响应包含标头的请求。可以使用以下值:100``Expect: 100-continue``immediately - 一个中间的100状态响应将在可行的情况下尽快返回onRead - 仅当 Servlet 读取请求正文时,才会返回中间的 100 状态响应,从而允许 Servlet 检查标头,并可能在用户代理发送可能较大的请求正文之前做出响应。 |
defaultSSLHostConfigName |
如果客户端连接不提供 SNI,或者如果提供了 SNI 但与任何已配置的SSLHostConfig不匹配,则将用于安全连接的默认SSLHostConfig的名称(如果此连接器配置为安全连接)。如果未指定,将使用 默认值 。提供的值始终转换为小写。_default_ |
discardFacades |
一个布尔值,可用于启用或禁用隔离容器内部请求处理对象的外观对象的回收。如果设置为外观,则在每次请求后都将设置为垃圾回收,否则它们将被重用。启用安全管理器时,此设置不起作用。如果未指定,则此特性设置为系统属性的值,如果未设置。true``org.apache.catalina.connector.RECYCLE_FACADES``false |
enableLookups |
如果希望调用来执行 DNS 查找以返回远程客户端的实际主机名,请设置为。设置为跳过 DNS 查找,改为以字符串形式返回 IP 地址(从而提高性能)。默认情况下,DNS 查找处于禁用状态。true``request.getRemoteHost()``false |
encodedSolidusHandling |
当设置为包含序列的请求路径时,将被拒绝并显示 400 响应。当设置为请求包含序列的路径时,该序列将被解码为同时解码其他序列。当设置为请求包含序列的路径时,将以未更改的顺序进行处理。如果未指定,则缺省值为 。如果设置了已弃用的系统属性,则可以修改此默认值。reject``%2f``decode``%2f``/``%nn``passthrough``%2f``%2f``reject``org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH |
maxHeaderCount |
容器允许的请求中标头的最大数量。包含的标头数超过指定限制的请求将被拒绝。小于 0 的值表示没有限制。如果未指定,则使用默认值 100。 |
maxParameterCount |
容器将自动解析的参数和值对的最大数量(GET 加 POST)。超出此限制的参数和值对将被忽略。小于 0 的值表示没有限制。如果未指定,则使用默认值 10000。请注意,筛选器可用于拒绝达到限制的请求。FailedRequestFilter |
maxPostSize |
将由容器 FORM URL 参数解析处理的 POST 的最大大小(以字节为单位)。可以通过将此属性设置为小于零的值来禁用该限制。如果未指定,则此属性设置为 2097152(2 兆字节)。请注意,失败的请求筛选器 可用于拒绝超过此限制的请求。 |
maxSavePostSize |
在 FORM 或 CLIENT-CERT 身份验证期间将由容器保存/缓冲的 POST 的最大大小(以字节为单位)。对于这两种类型的身份验证,在对用户进行身份验证之前,将保存/缓冲 POST。对于 CLIENT-CERT 身份验证,在 SSL 握手期间对 POST 进行缓冲,并在处理请求时清空缓冲区。对于 FORM 身份验证,在将用户重定向到登录表单时,将保存 POST,并一直保留到用户成功进行身份验证或与身份验证请求关联的会话过期为止。可以通过将此属性设置为 -1 来禁用该限制。将该属性设置为零将禁用在身份验证期间保存 POST 数据。如果未指定,则此属性设置为 4096(4 KB)。 |
parseBodyMethods |
以逗号分隔的 HTTP 方法列表,将解析请求主体所使用的请求参数,其请求参数与 POST 相同。这在希望支持 PUT 请求的 POST 样式语义的 RESTful 应用程序中非常有用。请注意,除其他任何设置外,其他任何设置都会导致 Tomcat 的行为方式与 servlet 规范的意图背道而驰。根据 HTTP 规范,此处明确禁止使用 HTTP 方法 TRACE。默认值为application/x-www-form-urlencoded``POST``POST |
port |
此连接器将在其上创建服务器套接字并等待传入连接的 TCP 端口号。您的操作系统将只允许一个服务器应用程序侦听特定 IP 地址上的特定端口号。如果使用特殊值 0(零),则 Tomcat 将随机选择一个空闲端口以用于此连接器。这通常仅在嵌入式和测试应用中有用。 |
protocol |
设置协议以处理传入流量。缺省值是使用自动切换机制选择基于 Java NIO 的连接器或基于 APR/本机的连接器。如果 (Windows) 或(在大多数 unix 系统上)环境变量包含 Tomcat 本机库,并且用于初始化 APR 的属性设置为 ,则将使用 APR/本机连接器。如果找不到本机库或未配置该属性,则将使用基于 Java NIO 的连接器。请注意,APR/本机连接器的 HTTPS 设置与 Java 连接器不同。 要使用显式协议而不是依赖于上述自动切换机制,可以使用以下值:- 非阻塞 Java NIO 连接器 - 非阻塞 Java NIO2 连接器 - APR/本机连接器。 也可以使用自定义实现。 查看我们的连接器比较图表。对于 http 和 https,两个 Java 连接器的配置是相同的。 有关 APR 连接器和特定于 APR 的 SSL 设置的详细信息,请访问APR 文档HTTP/1.1``PATH``LD_LIBRARY_PATH``AprLifecycleListener``useAprConnector``true``org.apache.coyote.http11.Http11NioProtocol``org.apache.coyote.http11.Http11Nio2Protocol``org.apache.coyote.http11.Http11AprProtocol |
proxyName |
如果在代理配置中使用此连接器,请配置此属性以指定要为调用 返回的服务器名称。有关详细信息,请参阅代理支持。request.getServerName() |
proxyPort |
如果在代理配置中使用此连接器,请配置此属性以指定要为调用 返回的服务器端口。有关详细信息,请参阅代理支持。request.getServerPort() |
redirectPort |
如果此连接器支持非 SSL 请求,并且收到需要 SSL 传输的匹配请求,则 Catalina 会自动将请求重定向到此处指定的端口号。<security-constraint> |
scheme |
将此属性设置为您希望通过调用 返回的协议的名称。例如,对于 SSL 连接器,应将此属性设置为 ""。默认值为""。request.getScheme()``https``http |
secure |
如果希望对此连接器收到的请求进行调用以返回,请将此属性设置为。您可能希望在从 SSL 加速器(如加密卡、SSL 设备甚至 Web 服务器)接收数据的 SSL 连接器或非 SSL 连接器上执行此操作。缺省值为 。true``request.isSecure()``true``false |
URIEncoding |
这将指定用于在对 URL 进行 %xx 解码后对 URI 字节进行解码的字符编码。缺省值为 。UTF-8 |
useBodyEncodingForURI |
这将指定是否应将 contentType 中指定的编码用于 URI 查询参数,而不是使用 URI 编码。此设置是为了与 Tomcat 4.1.x 兼容,其中 contentType 中指定的编码或使用 Request.setCharacterEncoding 方法显式设置的编码也用于 URL 中的参数。缺省值为 。false **注意:1)**此设置仅适用于请求的查询字符串。与它不同,它不会影响请求 URI 的路径部分。2) 如果请求字符编码未知(不是由浏览器提供的,也不是由使用 Request.setCharacterEncoding 方法设置或类似的过滤器设置的),则默认编码始终为"ISO-8859-1"。该设置对此默认值没有影响。URIEncoding``SetCharacterEncodingFilter``URIEncoding |
useIPVHosts |
将此属性设置为 使 Tomcat 使用接收请求的 IP 地址来确定要向其发送请求的主机。缺省值为 。true``false |
xpoweredBy |
将此属性设置为 使 Tomcat 使用规范中建议的标头来宣传对 Servlet 规范的支持。缺省值为 。true``false |
标准 HTTP 连接器(NIO、NIO2 和 APR/本机)除了上面列出的常见连接器属性外,还支持以下属性 标准实施
属性 | 描述 |
---|---|
acceptCount |
当达到传入连接请求时,操作系统的最大长度为传入的连接请求提供了队列。操作系统可能会忽略此设置,并对队列使用不同的大小。当此队列已满时,操作系统可能会主动拒绝其他连接,或者这些连接可能会超时。默认值为 100。maxConnections |
acceptorThreadPriority |
接受器线程的优先级。用于接受新连接的线程。默认值为(常量的值)。有关此优先级含义的更多详细信息,请参阅该类的 JavaDoc。5``java.lang.Thread.NORM_PRIORITY``java.lang.Thread |
address |
对于具有多个 IP 地址的服务器,此属性指定将用于侦听指定端口的地址。默认情况下,连接器将侦听所有本地地址。除非使用系统属性以其他方式配置 JVM,否则当配置了 或 时,基于 Java 的连接器(NIO、NIO2)将同时侦听 IPv4 和 IPv6 地址。如果配置了 IPv4 地址,APR/本机连接器将仅侦听 IPv4 地址,如果配置了 IPv6 地址,则将侦听 IPv6 地址(以及可选的 IPv4 地址,具体取决于ipv6v6only的设置)。0.0.0.0``::``0.0.0.0``:: |
allowHostHeaderMismatch |
默认情况下,Tomcat 将拒绝在请求行中指定主机但在主机标头中指定其他主机的请求。可以通过将此属性设置为 来禁用此检查。如果未指定,则缺省值为 。true``false |
allowedTrailerHeaders |
默认情况下,Tomcat 在处理分块输入时将忽略所有尾部标头。对于要处理的标头,必须将其添加到此以逗号分隔的标头名称列表中。 |
bindOnInit |
控制何时绑定连接器使用的套接字。默认情况下,它在连接器启动时绑定,在连接器被销毁时解除绑定。如果设置为 ,则套接字将在连接器启动时绑定,并在连接器停止时解除绑定。false |
clientCertProvider |
当客户端证书信息以实例以外的形式呈现时,需要先对其进行转换,然后才能使用它,并且此属性控制使用哪个 JSSE 提供程序来执行转换。例如,它与AJP连接器,HTTP APR连接器和org.apache.catalina.valves.SSLValve一起使用。如果未指定,则将使用默认提供程序。java.security.cert.X509Certificate |
compressibleMimeType |
该值是可以使用 HTTP 压缩的 MIME 类型的逗号分隔列表。缺省值为 。如果显式指定类型,则将覆盖默认值。text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json,application/xml |
compression |
连接器可能会使用 HTTP/1.1 GZIP 压缩来尝试节省服务器带宽。参数的可接受值为"off"(禁用压缩)、"on"(允许压缩,这会导致压缩文本数据)、"force"(在所有情况下强制压缩)或数字整数值(等效于"on",但指定在压缩输出之前的最小数据量)。如果内容长度未知,并且压缩设置为"on"或更激进,则输出也将被压缩。如果未指定,则此属性设置为"off"。注意:使用压缩(节省带宽)和使用发送文件功能(节省CPU周期)之间存在权衡。如果连接器支持 sendfile 功能(例如 NIO 连接器),则使用 sendfile 将优先于压缩。症状将是大于48 Kb的静态文件将被解压缩地发送。您可以通过设置连接器的属性来关闭 sendfile(如下所述),也可以在默认或 Web 应用程序的默认服务配置中更改 sendfile 使用阈值。useSendfile``conf/web.xml``web.xml |
compressionMinSize |
如果压缩设置为"on",则此属性可用于指定压缩输出之前的最小数据量。如果未指定,则此属性默认为"2048"。单位以字节为单位。 |
connectionLinger |
此连接器使用的套接字在关闭时将徘徊的秒数。默认值为禁用套接字徘徊。-1 |
connectionTimeout |
此连接器在接受连接后将等待显示请求 URI 行的毫秒数。使用值 -1 表示无(即无限)超时。默认值为 60000(即 60 秒),但请注意,Tomcat 附带的标准服务器.xml将其设置为 20000(即 20 秒)。除非disableUploadTimeout设置为 ,否则在读取请求正文(如果有)时也将使用此超时。false |
connectionUploadTimeout |
指定在进行数据上传时要使用的超时(以毫秒为单位)。仅当disableUploadTimeout设置为 时,此操作才会生效。false |
disableUploadTimeout |
此标志允许 servlet 容器在数据上传期间使用不同的、通常更长的连接超时。如果未指定,则将此属性设置为禁用此较长超时。true |
executor |
对Executor元素中名称的引用。如果设置了此属性,并且指定的执行程序存在,则连接器将使用执行程序,并且将忽略所有其他线程属性。请注意,如果未为连接器指定共享执行程序,则连接器将使用专用的内部执行程序来提供线程池。 |
executorTerminationTimeoutMillis |
专用内部执行程序在继续停止连接器的过程之前等待请求处理线程终止的时间。如果未设置,则默认值为(5 秒)。5000 |
keepAliveTimeout |
此连接器在关闭连接之前将等待另一个 HTTP 请求的毫秒数。默认值是使用已为connectionTimeout属性设置的值。使用值 -1 表示无(即无限)超时。 |
maxConnections |
服务器在任何给定时间接受和处理的最大连接数。当达到此数字时,服务器将接受(但不处理)另一个连接。此附加连接将被阻止,直到正在处理的连接数低于**maxConnections,**此时服务器将再次开始接受和处理新连接。请注意,一旦达到限制,操作系统可能仍会根据设置接受连接。缺省值为 。acceptCount``8192 仅对于 NIO/NIO2,将值设置为 -1 将禁用 maxConnections 功能,并且连接数将不计算在内。 |
maxCookieCount |
请求允许的最大 Cookie 数。小于零的值表示没有限制。如果未指定,则将使用默认值 200。 |
maxExtensionSize |
限制分块 HTTP 请求中块扩展的总长度。如果值为 ,则不会施加任何限制。如果未指定,将使用 默认值。-1``8192 |
maxHttpHeaderSize |
请求和响应 HTTP 标头的最大大小,以字节为单位指定。如果未指定,则此属性设置为 8192 (8 KB)。 |
maxKeepAliveRequests |
在服务器关闭连接之前,可以流水线处理的 HTTP 请求的最大数量。将此属性设置为 1 将禁用 HTTP/1.0 保持活动状态,以及 HTTP/1.1 保持活动状态和流水线传输。将此值设置为 -1 将允许无限量的管道或保持活动状态的 HTTP 请求。如果未指定,则此属性设置为 100。 |
maxSwallowSize |
Tomcat 在中止上传时将被吞噬的请求正文字节数(不包括传输编码开销)。中止上传是指 Tomcat 知道请求正文将被忽略,但客户端仍会发送它。如果Tomcat不吞下身体,客户不太可能看到反应。如果未指定,则将使用默认值 2097152(2 兆字节)。小于零的值表示不应强制实施任何限制。 |
maxThreads |
因此,此连接器要创建的最大请求处理线程数决定了可以同时处理的最大请求数。如果未指定,则此属性设置为 200。如果执行程序与此连接器关联,则忽略此属性,因为连接器将使用执行程序而不是内部线程池执行任务。请注意,如果配置了执行程序,则将正确记录为此属性设置的任何值,但将报告(例如通过 JMX)以明确未使用它。-1 |
maxTrailerSize |
限制分块 HTTP 请求的最后一个块中尾随标头的总长度。如果值为 ,则不会施加任何限制。如果未指定,将使用 默认值。-1``8192 |
minSpareThreads |
最小数量的线程始终保持运行。这包括活动线程和空闲线程。如果未指定,则使用默认值。如果执行程序与此连接器关联,则忽略此属性,因为连接器将使用执行程序而不是内部线程池执行任务。请注意,如果配置了执行程序,则将正确记录为此属性设置的任何值,但将报告(例如通过 JMX)以明确未使用它。10``-1 |
noCompressionStrongETag |
此标志配置是否考虑压缩具有强 ETag 的资源。如果 为 ,则不会压缩具有强 ETag 的资源。缺省值为 。true``true 此属性已弃用。它将在Tomcat 10及以后被删除,它将被硬编码为。true |
noCompressionUserAgents |
该值是一个正则表达式(使用),与不应使用压缩的 HTTP 客户端的标头匹配,因为这些客户端虽然确实宣传了对该功能的支持,但其实现已损坏。默认值为空字符串(禁用正则表达式匹配)。java.util.regex``user-agent |
processorCache |
协议处理程序缓存处理器对象以加快性能。此设置指示缓存这些对象的数量。 表示无限制,默认值为 。如果不使用 Servlet 3.0 异步处理,则默认使用与 maxThreads 设置相同的设置。如果使用 Servlet 3.0 异步处理,则一个好的默认值是使用 maxThreads 中较大的一个和最大预期并发请求数(同步和异步)。-1``200 |
rejectIllegalHeader |
如果收到的 HTTP 请求包含非法标头名称或值(例如,标头名称不是令牌),则此设置确定是会拒绝请求并显示 400 响应 (),还是忽略非法标头 ()。默认值为将导致请求被拒绝的值。true``false``true |
rejectIllegalHeaderName |
此属性已弃用。它将在Tomcat 10之后被删除。它现在是拒绝IllegalHeader的别名。 |
relaxedPathChars |
HTTP/1.1 规范要求在 URI 路径中使用某些字符时对某些字符进行 %nn 编码。不幸的是,包括所有主流浏览器在内的许多用户代理都不符合此规范,并以未编码的形式使用这些字符。为了防止 Tomcat 拒绝此类请求,此属性可用于指定要允许的其他字符。如果未指定,则不允许使用其他字符。该值可以是以下字符的任意组合:。值中存在的任何其他字符都将被忽略。" < > [ \ ] ^ { |
relaxedQueryChars |
HTTP/1.1 规范要求在 URI 查询字符串中使用某些字符时对某些字符进行 %nn 编码。不幸的是,包括所有主流浏览器在内的许多用户代理都不符合此规范,并以未编码的形式使用这些字符。为了防止 Tomcat 拒绝此类请求,此属性可用于指定要允许的其他字符。如果未指定,则不允许使用其他字符。该值可以是以下字符的任意组合:。值中存在的任何其他字符都将被忽略。" < > [ \ ] ^ { |
restrictedUserAgents |
该值是一个正则表达式(using ),与 HTTP 客户端的标头匹配,不应使用 HTTP/1.1 或 HTTP/1.0 保持活动状态,即使客户端通告支持这些功能也是如此。默认值为空字符串(禁用正则表达式匹配)。java.util.regex``user-agent |
server |
覆盖 http 响应的服务器标头。如果设置,则此属性的值将覆盖 Web 应用程序设置的任何服务器标头。如果未设置,则使用应用程序指定的任何值。如果应用程序未指定值,则不会设置服务器标头。 |
serverRemoveAppProvidedValues |
如果 ,则将删除 Web 应用程序设置的任何服务器标头。请注意,如果设置了服务器,则实际上会忽略此属性。如果未设置,则将使用 的默认值。true``false |
SSLEnabled |
使用此属性可在连接器上启用 SSL 流量。要在连接器上打开 SSL 握手/加密/解密,请将此值设置为 。缺省值为 。在转换此值时,您还需要设置 和 属性,以便将正确的值和值传递给 servlet,请参阅SSL 支持以获取更多信息。true``false``true``scheme``secure``request.getScheme()``request.isSecure() |
tcpNoDelay |
如果设置为 ,则将在服务器套接字上设置TCP_NO_DELAY选项,这在大多数情况下可提高性能。默认情况下,此选项设置为。true``true |
threadPriority |
JVM 中请求处理线程的优先级。默认值为(常量的值)。有关此优先级含义的更多详细信息,请参阅该类的 JavaDoc。如果执行程序与此连接器关联,则忽略此属性,因为连接器将使用执行程序而不是内部线程池执行任务。请注意,如果配置了执行程序,则将正确记录为此属性设置的任何值,但将报告(例如通过 JMX)以明确未使用它。5``java.lang.Thread.NORM_PRIORITY``java.lang.Thread``-1 |
throwOnFailure |
如果连接器在生命周期转换期间遇到异常,是否应重新引发或记录异常?如果未指定,将使用默认值。请注意,系统属性可以更改默认值。false``org.apache.catalina.startup.EXIT_ON_INIT_FAILURE |
useAsyncIO |
(布尔值)使用此属性可以启用或禁用异步 IO API 的使用。默认值为,除非由于性能低下而使用 APR 连接器。true |
useKeepAliveResponseHeader |
(布尔值)使用此属性可以启用或禁用 HTTP 响应标头的添加,如本 Internet 草稿中所述。缺省值为 。Keep-Alive``true |
给出一个完整的server.xml配置文件
[root@web01 conf]# cat server.xml
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<Service name="Catalina">
<!-- Executor -->
<Executor name="tomcatThreadPool"
namePrefix="catalina-exec-"
maxThreads="1500"
minSpareThreads="50"
prestartminSpareThreads="true"
maxIdleTime="60000"
maxQueueSize = "100" />
<!-- Connector use Executor -->
<Connector executor="tomcatThreadPool"
port="8080"
protocol="HTTP/1.1"
connectionTimeout="60000"
maxConnections="10000"
enableLookups="false"
acceptCount="100"
maxPostSize="10485760"
maxHttpHeaderSize="8192"
compression="on"
disableUploadTimeout="true"
compressionMinSize="2048"
acceptorThreadCount="2"
URIEncoding="utf-8"
processorCache="20000"
tcpNoDelay="true"
connectionLinger="-1"
server="Server Version 11.0"
redirectPort="8443"/>
<!--
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
-->
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern=" %{X-FORWARDED-FOR}i %h %l %u %t "%r" %s %b %{User-Agent}i %T" />
</Host>
<!-- add new host1 ..n -->
</Engine>
</Service>
</Server>
2.Java程序出现oom如何解决,什么场景下会出现oom
如何解决:
优化JVM参数,主要调整堆Heap内存的jvm相关参数
-Xms 设置应用程序初始使用的堆内存大小(年轻代+老年代)
-Xmx 设置应用程序能获得的最大堆内存
-Xms4096m -Xmx4096m -Xmn1g -Xss512m
出现oom的原因:
JVM因为没有足够的Heap内存来为对象分配空间(年轻代 老年代都满了)并且垃圾回收器也已经没有空间可回收时,就会抛出 oom
出现oom的主要原因?
java应用程序代码不断申请内存,jvm未能及时GC,导堆内存被沾满,又未能及时回收或者无法回收
3.简述redis的特点及应用场景
redis 特性
速度快 10w qps 基于内存 c 语言实现
单线程
持久化
支持多种数据格式
支持多种编程语言
主从复制
支持高可用和分布式
Redis 6.0版本前一直是单线程方式处理用户的请求
redis 应用场景
session共享,常见于web集群中的tomcat或者php中多web服务器session共享
缓存数据查询,电商网站商品信息新闻内容
计数器 访问排行榜 商品浏览数等和次数相关的数值统计场景
微博/微信社交场合 共同好友 粉丝数 关注 点赞评论等
消息队列 ELK 的日志缓存 部分业务的订阅 发布系统
地理位置 基于 geo(地理信息定位) 实现摇一摇 附近的人 外卖等功能
4.对比redis的RDB、AOF模式的优缺点
redis 虽然是一个内存级别的缓存程序,也就是 redis 是使用内存进行数的缓存的,但是其可以将内存的数据按照一定的策略保存到硬盘上,从而实现数据持久保存的目的
目前 redis 支持两种不同方式的数据持久化保存机制,分别是rdb和aof
rdb 模式的优点
rdb 快照保存了某个时间点的数据,可以通过脚本执行 redis 指令 bgsave(非阻塞,后台执行)或者save(会阻塞写操作,不推荐)命令自定义时间点备份,可以保留多个备份,当出现问题可以恢复到不同时间点的版本,很适合备份,并且此文件格式也支持有不少第三方工具可以进行后续的数据分析,比如可以在最近的24小时内,每小时备份一次 rdb文件,并且每个月的每一天,也备份一个 rdb 文件,这样的话,即使遇上问题,也可以随时将数据集还原到不同的版本.
rdb 可以最大化 redis 的性能,父进程在保存 rdb 文件时唯一要做的就是 fork 出一个子进程,然后这个子进程就会处理接下来的所有保存工作父进程无需只任何磁盘i/o操作rdb 在大量数据,比如几个G的数据,恢复的速度比 aof 的速度快
rdb模式的缺点
不能实时保存数据,可能会丢失子上一次执行rdb 备份到当前的内存数据
如果你需要尽量避免在服务器故障时丢失数据,那么 rdb 并不合适。 虽然 redis 允许设置不同的保存点(save point )来控制保存 rdb文件的频率,但是,因为 rdb文件需要保存整个数据集的状态
,所以他并不是一个轻松的操作,因此一般会超过5分钟以上才保存一次 rdb 文件,在这种情况下,一旦发生故障停机,你就可能会丢失好几分钟的数据,当数据量非常大的时候,从父进程 for 子进程保存至rdb 文件时需要一点时间,可能是毫秒或者秒,取决于磁盘IO性能在数据集比较庞大时,fork() 可能会非常耗费时间,造成服务器在一定时间内停止处理客户端,如果数据集非常巨大,并且CPU 时间非常紧张的话,那么这种停止时间甚至可能长达整整一秒或更久,虽然 AOF 重写也需要进行fork()但无论 aof重写执行间隔有多长,数据的持久性都不会有任何损失
aof 模式的优点
数据安全性相对较高,根据所使用的的 fsync 策略(fsync 是同步内存中的 redis 所有已经修改的文件到存储设备),默认是 appendfsync everysec,即每秒执行一次 fsync ,在这种配置下redis 仍然保持良好性能,并且就算发生故障停机,也最多只会丢失一秒钟的数据(fsync 会在后台线程执行,所在主线程可以继续努力地处理命令请求)
由于该机制对日志文件的写入操作采用的是 append 模式,因此在写入过程中不需要 seek 即使出现宕机现象,也不会破坏日志文件中已经存在的内容,然而如果本次操作只是写入了一半数据就出现子系统崩溃问题,不用担心,在 redis 下一次启动之前,可以通过 redis-check-aof 工具来解决数据一致性问题
redis 可以在 aof 文件体积变得过大时,自动地在后台对 aof 进行重写,重写后的新 aof 文件包含了恢复当前数据集所需的最小命令集合。整个重写操作时绝对安全的,因为 redis 在创建新的 aof 文件的过程中,append模式不断的将修改数据追加到现有的 aof 文件里面,即使重写过程中发生停机,现有的 aof 文件也不会丢失,而一旦新 aof 文件创建完毕, redis 就会从旧的 aof 文件切换到新 aof 文件,并开始对新 aof 文件包含了恢复当前数据集所需的最小命令集合,整个重写操作时绝对安全的,因为进行追加操作
AOF 包含一个格式清晰,易于理解的日志文件用于记录所有的修改操作,事实上,也可以通过该文件完成数据的重建
AOF 文件有序地保存了对数据库执行的所有写入操作,这些写入操作以 redis 协议的格式保存,因此 aof 文件的内容非常容易被人读懂,对文件进行分析(parse)也很轻松,
导出(export) AOF 文件也非常简单,举个例子,如果不小心执行了 flushall 命令,但只要 aof 文件未被重写,那么只要停止服务器,移除 AOF 文件末尾的 flush 命令,并重启 redis ,就可以将数据集恢复到flush执行之前的状态
AOF 的缺点:
即使有些操作是重复的也会全部纪录, aof 文件大小要大于 rdb 格式的文件
AOF 在恢复大数据集时的速度比 rdb 的恢复速度要慢
根据fsync 策略不同,AOF 速度可能会慢于rdb
bug 出现的可能性更多
5.实现redis哨兵、模拟master 故障场景
redis 主从架构没有实现 redis 的高可用,高并发、高性能,横向扩展
redis 哨兵(sentinel) 实现了 redis 的主从复制的高可用,是redis主从复制的高可用解决方案
redis 哨兵(sentinel)
redis 集群介绍
主从架构无法实现master和slave 角色的自动切换,即当 master 出现 redis 服务异常。主机断电,磁盘损坏等问题导致 master 无法使用,而 redis 主从复制无法实现自动故障转移
(将 slave 自动提升为新 master),需要手动修改环境配置,才能切换到 slave redis 服务器,另外当单台 redis 服务器性能无法满足业务写入需要的时候,也无法横向扩展redis 服务的并行写入性能。
哨兵(sentinel)工作原理:
sentinel架构和故障转移
redis sentinel 架构
客户端从 sentinel 获取redis信息
sentinel故障转移
1.多个sentinel 发现并确认master由问题
2.选举出一个 sentinel作为领导
3.选出一个slave作为master
4.通知其余slave 成为新的 master 的 slave
5.通知客户端主动变化
6.等待老的master 复活成为新master 的slave
sentinel 进程是用于监控 redis集群中 master 主服务器工作的状态,在master 主服务器发生故障的时候,可以实现 master 和 slave服务器的切换,保证系统的高可用,此功能在redis2.6+的版本中已经应用,
redis的哨兵模式到了2.8版本之后就稳定了下来,一般在生产环境下建议使用 redis的2.8版本之后的版本
哨兵(sentinel)是一个分布式系统,可以在一个架构中运行多个哨兵(sentinel)进程,这些进程使用流言协议(gossip protocols) 来接收关于 master 主服务器是否下线的信息,并使用投票协议(Agreement Protocols)来
决定是否执行自动故障迁移,以及选择哪个 slave 作为新的 master
每个哨兵(sentinel)进程会向其他哨兵(sentinel) master slave 定时发送消息,以确认对方是否 活着,如果发现对方在指定配置时间(此项可配置)内未能得到回应,则暂时认为对方已经离线,
也就是所谓的主观任务宕机(主观:是每个成员都具有的独自的而且可是相同也可能不同的意识),英文 subjective down 简称 sdown
有主观宕机,对应的有客观宕机,当 哨兵群中的多数 sentinel 进程在对 master 主服务器做出 sdown的判断,并且通过 sentinel is-master-down-by-add 命令互相交流之后,得出的 master server 下线判断
这种方式就是 客观宕机(客观: 不依赖某种意识而已经实际存在的一切事物) 英文名称 objectivel down 简称 odown
通过一定的vote 算法,从剩下的 slave 从服务器节点中,选一台提升为 master 服务器节点,然后自动修改相关配置,并开启故障转移(failover)
sentinel 机制可以解决 master 和 slave 角色自动切换的问题,但单个 master 的性能瓶颈问题无法解决,类似于mysql中的 MHA 功能
redis sentinel 中的 sentinel 节点个数应该为大于等于3 且做好为奇数,偶数可能发生脑裂
客户端初始化时连接的是 sentinel 节点集合,不再是具体的 redis 节点,但是 sentinel 只是配置中心不是代理
redis sentinel 节点与普通redis 没有区别,要实现读写分离依赖于客户端程序
redis3.0 之前版本中,生产环境一般使用哨兵模式,3.0推出 redis cluster 功能,可以支持更大规模的生产环境
sentinel 中的三个定时任务
每10秒每个 sentinel 对master和 slave执行info
发现 slave节点
确认主从关系
每2秒每个 sentinel 通过master节点的 channel 交换信息(pub/sub)
通过sentinel_:hello频道交互
交互对节点的看法和自身信息
每1秒每个sentinel对其他 sentinel 和redis 执行ping
实现哨兵(sentinel)
哨兵的前提是已经实现了一个redis的主从复制运行环境,从而实现一个一主两从基于哨兵的高可用redis架构
注意:master 的配置文件中masterauth和slave都必须相同
1.环境需求
需要三台服务器:
node0.magedu.com 192.168.80.8 master 主
node1.magedu.com 192.168.80.18 slave1 从
node2.magedu.com 192.168.80.28 slave2 从
实现哨兵
哨兵的准备实现主从复制架构
哨兵的前提是已经实现了一个 redis 的主从复制的运行环境,从而实现一个 一主两从,基于哨兵的高可用 redis 架构
注意:master 的配置文件中 masterauth和 slave 都必须相同
2.redis安装
#在三台主机上执行redis 的基础安装配置,主从及哨兵的配置再单独进行配置
vim install_redis_rdb_aof.sh
#!/bin/bash
#install_redis_v03.sh
#redis
#rdb aof
redis_file="redis-6.2.6.tar.gz"
redis_url="http://download.redis.io/releases/$redis_file"
redis_version=$(echo ${redis_file%*.tar.gz})
redis_base=/app
redis_home=$redis_base/redis
redis_password=123456
cpus=`lscpu|awk -F: '/^CPU\(s\):/ {print $2}'`
install_redis() {
for i in "gcc jemalloc-devel wget"
do
rpm -q $i &>/dev/null || yum install -y -q $i || { echo "安装软件包失败,请检查网络配置" false ; exit; }
done
if [ -f $redis_file ];then
echo "$redis_file is ok "
else
wget $redis_url || { echo "redis 源码包下载失败"; exit ; }
fi
tar -xvf $redis_file
cd $redis_version
make -j $cpus PREFIX=$redis_base/$redis_version install && echo "redis 编译安装完成"|| { echo "redis 编译安装失败"; exit ; }
ln -sf $redis_base/$redis_version $redis_home
ln -s $redis_home/bin/redis-* /usr/bin/ &>/dev/null
mkdir -p ${redis_home}/{etc,log,data,run}
cp redis.conf $redis_home/etc/
#sed -i.org -e 's/bind 127.0.0.1/bind 0.0.0.0/' -e "/# requirepass/a requirepass $redis_password" -e '/masterauth/a masterauth 123456' -e "/^dir .*/c dir ${redis_home}/data/" -e "/logfile .*/c logfile ${redis_home}/log/redis-6379.log" -e "/^pidfile .*/c pidfile ${redis_home}/run/redis_6379.pid" -e '/# cluster-enabled yes/a cluster-enabled yes' -e '/# cluster-config-file nodes-6379.conf/a cluster-config-file nodes-6379.conf' -e '/cluster-require-full-coverage yes/c cluster-require-full-coverage no' ${redis_home}/etc/redis.conf
sed -i.org -e 's/bind 127.0.0.1/bind 0.0.0.0/' -e "/# requirepass/a requirepass $redis_password" -e '/masterauth/a masterauth 123456' -e "/^dir .*/c dir ${redis_home}/data/" -e "/logfile .*/c logfile ${redis_home}/log/redis-6379.log" -e "/^pidfile .*/c pidfile ${redis_home}/run/redis_6379.pid" -e '/appendonly no/c appendonly yes' ${redis_home}/etc/redis.conf
id redis &&>/dev/null || useradd -r -s /sbin/nologin redis &>/dev/null
chown -R redis:redis $redis_home
chown -R redis:redis $redis_base/$redis_version
cat>>/etc/sysctl.conf<<EOF
net.core.somaxconn=1024
vm.overcommit_memory=1
EOF
sysctl -p
echo 'echo never>/sys/kernel/mm/transparent_hugepage/enabled' >>/etc/rc.d/rc.local
chmod u+x /etc/rc.d/rc.local
/etc/rc.d/rc.local
cat>/usr/lib/systemd/system/redis.service<<EOF
[Unit]
Description=Redis Sentinel
After=network.target
[Service]
ExecStart=$redis_home/bin/redis-server $redis_home/etc/redis.conf --supervised systemd
ExecReload=/bin/kill -s HUP \$MAINPID
ExecStop=/bin/kill -s QUIT \$MAINPID
Type=notify
User=redis
Group=redis
RuntimeDirectory=redis
RuntimeDirectoryMode=0755
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now redis &>/dev/null && echo "redis 服务启动成功,信息如下:" || { echo "reis 服务启动失败"; exit; }
sleep 3
redis-cli -a ${redis_password} info server 2>/dev/null
}
main() {
install_redis
}
main
#在三台服务器上都执行 install_redis_v03.sh
[root@node0 ~]# sh install_redis_rdb_aof.sh
[root@node1 ~]# sh install_redis_rdb_aof.sh
[root@node2 ~]# sh install_redis_rdb_aof.sh
3.配置redis主从复制
#在所有的从节点都执行命令,复制关系指向主节点主机IP
[root@node1 etc]# sed -i.org '/^# replicaof/a\replicaof 192.168.80.8 6379' /app/redis/etc/redis.conf
[root@node2 etc]# sed -i.org '/^# replicaof/a\replicaof 192.168.80.8 6379' /app/redis/etc/redis.conf
#重启三个主机的redis服务
[root@node0 ~]# systemctl restart redis
[root@node1 etc]# systemctl restart redis
[root@node2 etc]# systemctl restart redis
#查看主节点复制关系,主下带着2个从
[root@node0 ~]# systemctl restart redis
[root@node0 ~]# redis-cli -a 123456 --no-auth-warning
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.80.28,port=6379,state=online,offset=252,lag=0
slave1:ip=192.168.80.18,port=6379,state=online,offset=266,lag=0
master_failover_state:no-failover
master_replid:fdc2139ee03c7ed3f85f2061e939a3007aea65aa
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:266
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:266
127.0.0.1:6379>
#查看2个从节点复制关系
#从的复制关系都执行主 192.168.80.8
[root@node1 etc]# redis-cli -a 123456 --no-auth-warning
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.80.8
master_port:6379
master_link_status:up
master_last_io_seconds_ago:7
master_sync_in_progress:0
slave_read_repl_offset:350
slave_repl_offset:350
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:fdc2139ee03c7ed3f85f2061e939a3007aea65aa
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:350
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:350
127.0.0.1:6379>
[root@node2 ~]# redis-cli -a 123456 --no-auth-warning
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.80.8
master_port:6379
master_link_status:up
master_last_io_seconds_ago:4
master_sync_in_progress:0
slave_read_repl_offset:378
slave_repl_offset:378
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:fdc2139ee03c7ed3f85f2061e939a3007aea65aa
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:378
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:378
4.配置redis 哨兵
编辑哨兵(sentinel) 配置文件
sentinel 配置
sentinel实际上是一个特殊的redis服务器,有些redis指令支持,但很多指令并不支持,默认监听在26379/tcp端口
哨兵可以不和 redis 服务器部署在一起,但是一般部署在一起以节约成本
所有 redis 节点使用相同的以下示例的配置文件
#所有节点
vim /app/redis/etc/sentinel.conf
bind 0.0.0.0
port 26379
daemonize yes
pidfile /app/redis/run/redis-sentinel.pid
logfile /app/redis/log/sentinel_26379.log
dir /tmp
sentinel monitor mymaster 192.168.80.8 6379 2 #mymaster 是集群的名称,此行指定mymaster集群中的master 服务器的地址和端口
#2 为法定人数限制(quorum) 即有几个sentinel认为 master down 就进行故障转移,一般此值是所有sentinel 节点(一般总数是 >=3 奇数 3 5 7 等)的一半以上的整数值,比如,总数是3,即3/2=1.5取整数为2 ,是 master 的odown 客观下线的依据
sentinel auth-pass mymaster 123456 #mymaster 集群中 master 的密码,注意此行要在上面行下下面
sentinel down-after-milliseconds mymaster 3000#改为3秒3000 毫秒,默认30000即30秒 (sdown)判断 mymaster集群中所有节点的主观下线的时间,单位,毫秒,建议 3000
sentinel parallel-syncs mymaster 1 #发送故障转移后,可以同时向master 同步数据的 slave 的数量,数字越小总同步时间越长,但可以减轻新 master 的负载压力
sentinel failover-timeout mymaster 180000 所有slave 指向新的 master 所需的超时时间,单位 毫秒
sentinel deny-scripts-reconfig yes # 禁止修改此脚本
哨兵(sentinel) redis-sentinel.service 文件,在三个节点都要配置
#如果是源码编译安装在所有节点生成新的 service 文件
vim /usr/lib/systemd/system/redis-sentinel.service
[Unit]
Description=Redis Sentinel
After=network.target
[Service]
ExecStart=/app/redis/bin/redis-sentinel /app/redis/etc/sentinel.conf --supervised systemd
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
Type=notify
User=redis
Group=redis
RuntimeDirectory=redis
RuntimeDirectoryMode=0755
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
#注意所有节点的目录权限,否则无法正常启动
[root@node0 ~]# chown -R redis:redis /app/redis/
[root@node1 ~]# chown -R redis:redis /app/redis/
[root@node2 ~]# chown -R redis:redis /app/redis/
启动哨兵(sentinel)
/app/redis/etc/sentinel.conf 在 sentinel 服务启动之后会自动生成 一些配置信息,其中自动生成 myid 每个节点的 myid 必须是不同的,如果相同需要 进行修改 然后重启 sentinel 服务
三台哨兵服务器都要启动
[root@node0 ~]# systemctl daemon-reload
[root@node1 ~]# systemctl daemon-reload
[root@node2 ~]# systemctl daemon-reload
[root@node0 ~]# systemctl enable --now redis-sentinel.service
[root@node1 ~]# systemctl enable --now redis-sentinel.service
[root@node2 ~]# systemctl enable --now redis-sentinel.service
验证哨兵端口 三个哨兵服务器监听 26379
root@node0 data]# ss -lnt |grep 6379
LISTEN 0 511 *:26379 *:*
LISTEN 0 511 *:6379 *:*
LISTEN 0 511 [::1]:6379 [::]:*
[root@node0 data]#
[root@node1 data]# ss -lnt |grep 6379
LISTEN 0 511 *:26379 *:*
LISTEN 0 511 *:6379 *:*
LISTEN 0 511 [::1]:6379 [::]:*
[root@node2 data]# ss -lnt |grep 6379
LISTEN 0 511 *:26379 *:*
LISTEN 0 511 *:6379 *:*
LISTEN 0 511 [::1]:6379 [::]:*
[root@node2 data]#
检查哨兵(sentinel)运行状态
[root@node0 data]# redis-cli -p 26379
127.0.0.1:26379> info sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=192.168.80.8:6379,slaves=2,sentinels=3
[root@node1 ~]# redis-cli -p 26379 info sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=192.168.80.8:6379,slaves=2,sentinels=3
[root@node2 ~]# redis-cli -p 26379 info sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=192.168.80.8:6379,slaves=2,sentinels=3
当前 sentinel 状态
在 sentine 状态中尤其是最后一行,涉及到 master IP 是多少, 有几个 slave 有几个 sentinels, 必须符合全部服务器数量
5.测试redis 故障转移
停止主节点 node0.magedu.com 192.168.80.8 redis 服务
[root@node0 data]# redis-cli -p 6379 -a '123456'
127.0.0.1:6379> shutdown
#观察从节点的哨兵日志
#slave1
[root@node1 ~]# tail -f /app/redis/log/sentinel_26379.log
32385:X 18 Jan 2022 20:11:54.995 # Redis version=6.2.6, bits=64, commit=00000000, modified=0, pid=32385, just started
32385:X 18 Jan 2022 20:11:54.995 # Configuration loaded
32385:X 18 Jan 2022 20:11:54.997 * monotonic clock: POSIX clock_gettime
32385:X 18 Jan 2022 20:11:55.000 * Running mode=sentinel, port=26379.
32385:X 18 Jan 2022 20:11:55.000 # Sentinel ID is bd5b5cdb4125c599ef571045c6ccd392e3001b9b
32385:X 18 Jan 2022 20:11:55.000 # +monitor master mymaster 192.168.80.8 6379 quorum 2
32385:X 18 Jan 2022 20:11:55.010 * +slave slave 192.168.80.28:6379 192.168.80.28 6379 @ mymaster 192.168.80.8 6379
32385:X 18 Jan 2022 20:11:55.011 * +slave slave 192.168.80.18:6379 192.168.80.18 6379 @ mymaster 192.168.80.8 6379
32385:X 18 Jan 2022 20:11:56.989 * +sentinel sentinel 9d36f2a1301565b619b9c7cd15b8d61c4260d4e5 192.168.80.28 26379 @ mymaster 192.168.80.8 6379
32385:X 18 Jan 2022 20:11:57.003 * +sentinel sentinel f3bdac30ae6928b43d68b974f072275e9a42e3f0 192.168.80.8 26379 @ mymaster 192.168.80.8 6379
32385:X 18 Jan 2022 20:31:43.276 # +sdown master mymaster 192.168.80.8 6379
32385:X 18 Jan 2022 20:31:43.361 # +new-epoch 1
32385:X 18 Jan 2022 20:31:43.363 # +vote-for-leader 9d36f2a1301565b619b9c7cd15b8d61c4260d4e5 1
32385:X 18 Jan 2022 20:31:44.369 # +odown master mymaster 192.168.80.8 6379 #quorum 3/2
32385:X 18 Jan 2022 20:31:44.369 # Next failover delay: I will not start a failover before Tue Jan 18 20:37:44 2022
32385:X 18 Jan 2022 20:31:44.492 # +config-update-from sentinel 9d36f2a1301565b619b9c7cd15b8d61c4260d4e5 192.168.80.28 26379 @ mymaster 192.168.80.8 6379
32385:X 18 Jan 2022 20:31:44.492 # +switch-master mymaster 192.168.80.8 6379 192.168.80.28 6379
32385:X 18 Jan 2022 20:31:44.494 * +slave slave 192.168.80.18:6379 192.168.80.18 6379 @ mymaster 192.168.80.28 6379
32385:X 18 Jan 2022 20:31:44.495 * +slave slave 192.168.80.8:6379 192.168.80.8 6379 @ mymaster 192.168.80.28 6379
32385:X 18 Jan 2022 20:31:47.533 # +sdown slave 192.168.80.8:6379 192.168.80.8 6379 @ mymaster 192.168.80.28 6379
#slave2
[root@node2 ~]# tail -f /app/redis/log/sentinel_26379.log
32626:X 18 Jan 2022 20:11:54.986 # Sentinel ID is 9d36f2a1301565b619b9c7cd15b8d61c4260d4e5
32626:X 18 Jan 2022 20:11:54.986 # +monitor master mymaster 192.168.80.8 6379 quorum 2
32626:X 18 Jan 2022 20:11:57.005 * +sentinel sentinel f3bdac30ae6928b43d68b974f072275e9a42e3f0 192.168.80.8 26379 @ mymaster 192.168.80.8 6379
32626:X 18 Jan 2022 20:11:57.052 * +sentinel sentinel bd5b5cdb4125c599ef571045c6ccd392e3001b9b 192.168.80.18 26379 @ mymaster 192.168.80.8 6379
32626:X 18 Jan 2022 20:31:43.287 # +sdown master mymaster 192.168.80.8 6379
32626:X 18 Jan 2022 20:31:43.356 # +odown master mymaster 192.168.80.8 6379 #quorum 2/2
32626:X 18 Jan 2022 20:31:43.356 # +new-epoch 1
32626:X 18 Jan 2022 20:31:43.356 # +try-failover master mymaster 192.168.80.8 6379
32626:X 18 Jan 2022 20:31:43.358 # +vote-for-leader 9d36f2a1301565b619b9c7cd15b8d61c4260d4e5 1
32626:X 18 Jan 2022 20:31:43.363 # f3bdac30ae6928b43d68b974f072275e9a42e3f0 voted for 9d36f2a1301565b619b9c7cd15b8d61c4260d4e5 1
32626:X 18 Jan 2022 20:31:43.365 # bd5b5cdb4125c599ef571045c6ccd392e3001b9b voted for 9d36f2a1301565b619b9c7cd15b8d61c4260d4e5 1
32626:X 18 Jan 2022 20:31:43.443 # +elected-leader master mymaster 192.168.80.8 6379
32626:X 18 Jan 2022 20:31:43.443 # +failover-state-select-slave master mymaster 192.168.80.8 6379
32626:X 18 Jan 2022 20:31:43.526 # +selected-slave slave 192.168.80.28:6379 192.168.80.28 6379 @ mymaster 192.168.80.8 6379
32626:X 18 Jan 2022 20:31:43.527 * +failover-state-send-slaveof-noone slave 192.168.80.28:6379 192.168.80.28 6379 @ mymaster 192.168.80.8 6379
32626:X 18 Jan 2022 20:31:43.611 * +failover-state-wait-promotion slave 192.168.80.28:6379 192.168.80.28 6379 @ mymaster 192.168.80.8 6379
32626:X 18 Jan 2022 20:31:44.395 # +promoted-slave slave 192.168.80.28:6379 192.168.80.28 6379 @ mymaster 192.168.80.8 6379
32626:X 18 Jan 2022 20:31:44.395 # +failover-state-reconf-slaves master mymaster 192.168.80.8 6379
32626:X 18 Jan 2022 20:31:44.482 * +slave-reconf-sent slave 192.168.80.18:6379 192.168.80.18 6379 @ mymaster 192.168.80.8 6379
32626:X 18 Jan 2022 20:31:45.450 * +slave-reconf-inprog slave 192.168.80.18:6379 192.168.80.18 6379 @ mymaster 192.168.80.8 6379
32626:X 18 Jan 2022 20:31:45.450 * +slave-reconf-done slave 192.168.80.18:6379 192.168.80.18 6379 @ mymaster 192.168.80.8 6379
32626:X 18 Jan 2022 20:31:45.501 # -odown master mymaster 192.168.80.8 6379
32626:X 18 Jan 2022 20:31:45.501 # +failover-end master mymaster 192.168.80.8 6379
32626:X 18 Jan 2022 20:31:45.502 # +switch-master mymaster 192.168.80.8 6379 192.168.80.28 6379
32626:X 18 Jan 2022 20:31:45.502 * +slave slave 192.168.80.18:6379 192.168.80.18 6379 @ mymaster 192.168.80.28 6379
32626:X 18 Jan 2022 20:31:45.502 * +slave slave 192.168.80.8:6379 192.168.80.8 6379 @ mymaster 192.168.80.28 6379
32626:X 18 Jan 2022 20:31:48.555 # +sdown slave 192.168.80.8:6379 192.168.80.8 6379 @ mymaster 192.168.80.28 6379
#查看各节点复制状态
#启动之前的主
[root@node0 data]# systemctl start redis.service
#master 节点 已经变成 slave 节点
[root@node0 data]# redis-cli -a 123456
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.80.28
master_port:6379
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_read_repl_offset:4028125
slave_repl_offset:4028125
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:71f1e0e21c87d8e425ea8ffc24adf0c3837f74f4
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:4028125
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:4018341
repl_backlog_histlen:9785
#slave 2 节点 已经变成了 master 节点
[root@node2 ~]# redis-cli -a '123456' info replication
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.80.18,port=6379,state=online,offset=4051489,lag=1
slave1:ip=192.168.80.8,port=6379,state=online,offset=4051489,lag=1
master_failover_state:no-failover
master_replid:71f1e0e21c87d8e425ea8ffc24adf0c3837f74f4
master_replid2:fdc2139ee03c7ed3f85f2061e939a3007aea65aa
master_repl_offset:4051489
second_repl_offset:3959129
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:3002914
repl_backlog_histlen:1048576
[root@node1 ~]# redis-cli -a '123456'
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.80.28
master_port:6379
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_read_repl_offset:4049365
slave_repl_offset:4049365
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:71f1e0e21c87d8e425ea8ffc24adf0c3837f74f4
master_replid2:fdc2139ee03c7ed3f85f2061e939a3007aea65aa
master_repl_offset:4049365
second_repl_offset:3959129
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:3000790
repl_backlog_histlen:1048576
127.0.0.1:6379>
故障转移后的redis配置文件会被自动修改