1、总结tomcat优化方法
由于Tomcat的运行依赖于JVM,从虚拟机的角度把Tomcat的调整分为外部环境调优 JVM 和 Tomcat 自身调优两部分
1 内存空间优化,启动时告诉JVM我要一大块内存(调优内存是最直接的方式)
Tomcat的JVM参数设置
默认不指定,-Xmx大约使用了1/4的内存,当前本机内存指定约为1G。
在/usr/local/tomcat/bin/catalina.sh中增加一行
......
# OS specific support. $var _must_ be set to either true or false.
#添加下面一行
JAVA_OPTS="-server -Xms512m -Xmx512m -XX:NewSize=100m -XX:MaxNewSize=200m"
cygwin=false
darwin=false
........
systemctl restart tomcat
JAVA_OPTS="-server -Xms4g -Xmx4g -XX:NewSize= -XX:MaxNewSize= "
-server:服务器模式
-Xms:堆内存初始化大小
-Xmx:堆内存空间上限
-XX:NewSize=:新生代空间初始化大小
-XX:MaxNewSize=:新生代空间最大值
生产案例:
[root@centos8 ~]#vim /usr/local/tomcat/bin/catalina.sh
JAVA_OPTS="-server -Xms4g -Xmx4g -Xss512k -Xmn1g
-XX:CMSInitiatingOccupancyFraction=65 -XX:+AggressiveOpts -XX:+UseBiasedLocking
-XX:+DisableExplicitGC -XX:MaxTenuringThreshold=10 -XX:NewRatio=2
-XX:PermSize=128m -XX:MaxPermSize=512m -XX:CMSFullGCsBeforeCompaction=5
-XX:+ExplicitGCInvokesConcurrent -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection
-XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods"
#一台tomcat服务器并发连接数不高,生产建议分配物理内存通常 4G 到 8G较多,如果需要更多连接,一般会利用虚拟化技术实现多台tomcat
2 线程池调整
[root@centos8 ~]#vim /usr/local/tomcat/conf/server.xml
......
......
常用属性:
connectionTimeout :连接超时时长,单位ms
maxThreads:最大线程数,默认200
minSpareThreads:最小空闲线程数
maxSpareThreads:最大空闲线程数
acceptCount:当启动线程满了之后,等待队列的最大长度,默认100
URIEncoding:URI 地址编码格式,建议使用 UTF-8
enableLookups:是否启用客户端主机名的DNS反向解析,缺省禁用,建议禁用,就使用客户端IP
就行
compression:是否启用传输压缩机制,建议 "on",CPU和流量的平衡
- compressionMinSize:启用压缩传输的数据流最小值,单位是字节
- compressableMimeType:定义启用压缩功能的MIME类型text/html, text/xml, text/css,
text/javascript
3 Java压力测试工具
PerfMa 致力于打造一站式IT系统稳定性保障解决方案,专注于性能评测与调优、故障根因定位与解决,
为企业提供一系列技术产品与专家服务,提升系统研发与运行质量。
社区产品 https://opts.console.perfma.com/
2、java程序出现oom如何解决?什么场景下会出现oom?
什么是OOM
OOM为out of memory的简称,来源于java.lang.OutOfMemoryError,指程序需要的内存空间大于系统分配的内存空间,OOM后果就是程序crash;可以通俗理解:程序申请内存过大,虚拟机无法满足,然后自杀了。
导致OOM问题的原因
为什么会没有内存了呢?原因不外乎有两点:
1)分配的少了:比如虚拟机本身可使用的内存(一般通过启动时的VM参数指定)太少。
2)应用用的太多,并且用完没释放,浪费了。此时就会造成内存泄露或者内存溢出。
内存泄露:申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人用。
内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出。
最常见的OOM情况有以下三种:
- java.lang.OutOfMemoryError: Java heap space ------>java堆内存溢出,此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。对于内存泄露,需要通过内存监控软件查找程序中的泄露代码,而堆大小可以通过虚拟机参数-Xms,-Xmx等修改。
- java.lang.OutOfMemoryError: PermGen space 或 java.lang.OutOfMemoryError:MetaSpace ------>java方法区,(java8 元空间)溢出了,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。此种情况可以通过更改方法区的大小来解决,使用类似-XX:PermSize=64m -XX:MaxPermSize=256m的形式修改。另外,过多的常量尤其是字符串也会导致方法区溢出。
- java.lang.StackOverflowError ------> 不会抛OOM error,但也是比较常见的Java内存溢出。JAVA虚拟机栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。可以通过虚拟机参数-Xss来设置栈的大小。
Jprofiler定位OOM的问题原因
JProfiler是一款功能强大的Java开发分析工具,它可以快速的帮助用户分析出存在的错误,软件还可对需要的显示类进行标记,包括了内存的分配情况和信息的视图等
JProfiler官网:http://www.ej-technologies.com/products/jprofiler/overview.html
安装jprofiler工具定位OOM原因和源码问题的位置
#安装OpenJDK
[root@centos8 ~]# dnf -y install java-1.8.0-openjdk-devel
[root@centos8 ~]# vim HeapOom.java
import java.util.ArrayList;
import java.util.List;
public class HeapOom {
public static void main(String[] args) {
List list =new ArrayList();
int i = 0;
boolean flag = true;
while(flag){
try{
i++;
list.add(new byte[1024* 1024]);//每次增加一个1M大小的数组对象
Thread.sleep(1000);
}catch(Throwable e){
e.printStackTrace();
flag = false;
System.out.println("count="+i);//记录运行的次数
}
}
}
}
[root@centos8 ~]# javac HeapOom.java
[root@centos8 ~]# java -Xms5m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError HeapOom
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid96271.hprof ...
……
[root@centos8 ~]# sz java_pid96271.hprof
[root@centos8 ~]# vim HeapOom2.java
import java. util. Random;
public class HeapOom2 {
public static void main(String[] args) {
String str = "I am lao wang";
while (true){
str += str + new Random().nextInt(88888888);
}
}
}
[root@centos8 ~]# javac HeapOom2.java
[root@centos8 ~]# java -Xms5m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError HeapOom2
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid96339.hprof ...
[root@centos8 ~]# sz java_pid96339.hprof
下载并安装Jprofiler
下载并安装JProfiler(傻瓜式安装略)后,双击打开前面生成的两个文件java_pid96271.hprof和java_pid96339,可以看到下面显示,从中分析OOM原因
3、简述redis特点及其应用场景
redis特点
- 速度快: 10W QPS,基于内存,C语言实现
- 单线程
- 持久化
- 支持多种数据结构
- 支持多种编程语言
- 功能丰富: 支持Lua脚本,发布订阅,事务,pipeline等功能
- 简单: 代码短小精悍(单机核心代码只有23000行左右),单线程开发容易,不依赖外部库,使用简单
- 主从复制
- 支持高可用和分布式
redis应用场景
- Session 共享:常见于web集群中的Tomcat或者PHP中多web服务器session共享
- 缓存:数据查询、电商网站商品信息、新闻内容
- 计数器:访问排行榜、商品浏览数等和次数相关的数值统计场景
- 微博/微信社交场合:共同好友,粉丝数,关注,点赞评论等
- 消息队列:ELK的日志缓存、部分业务的订阅发布系统
- 地理位置: 基于GEO(地理信息定位),实现摇一摇,附近的人,外卖等功能
4、对比redis的RDB、AOF模式的优缺点
1 RDB 模式优点
- RDB快照保存了某个时间点的数据,可以通过脚本执行redis指令bgsave(非阻塞,后台执行)或者save(会阻塞写操作,不推荐)命令自定义时间点备份,可以保留多个备份,当出现问题可以恢复到不同时间点的版本,很适合备份,并且此文件格式也支持有不少第三方工具可以进行后续的数据分析
比如: 可以在最近的24小时内,每小时备份一次RDB文件,并且在每个月的每一天,也备份一个RDB文件。这样的话,即使遇上问题,也可以随时将数据集还原到不同的版本。 - RDB可以最大化Redis的性能,父进程在保存 RDB文件时唯一要做的就是fork出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘 I/O 操作。
- RDB在大量数据,比如几个G的数据,恢复的速度比AOF的快
2 RDB 模式缺点
- 不能实时保存数据,可能会丢失自上一次执行RDB备份到当前的内存数据
如果你需要尽量避免在服务器故障时丢失数据,那么RDB并不适合。虽然Redis允许设置不同的保存点(save point)来控制保存RDB文件的频率,但是,因为RDB文件需要保存整个数据集的状态,所以它并不是一个轻松快速的操作。因此一般会超过5分钟以上才保存一次RDB文件。在这种情况下,一旦发生故障停机,你就可能会丢失好几分钟的数据。 - 当数据量非常大的时候,从父进程fork子进程进行保存至RDB文件时需要一点时间,可能是毫秒或者秒,取决于磁盘IO性能
3 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文件末尾的FLUSHAL命令,并重启Redis ,就可以将数据集恢复到FLUSHALL执行之前的状态。
4 AOF模式缺点
- 即使有些操作是重复的也会全部记录,AOF 的文件大小要大于 RDB 格式的文件
- AOF 在恢复大数据集时的速度比 RDB 的恢复速度要慢
- 根据fsync策略不同,AOF速度可能会慢于RDB
- bug 出现的可能性更多
5、实现redis哨兵,模拟master故障场景
1 哨兵的准备,实现主从复制架构
哨兵的前提是已经实现了一个redis的主从复制的运行环境,从而实现一个一主两从基于哨兵的高可用redis架构
注意:master 的配置文件中masterauth 和slave 都必须相同
所有主从节点的redis.conf中关健配置
准备主从环境配置
#在所有主从节点执行
[root@centos8 ~]#dnf -y install redis
[root@centos8 ~]#vim /apps/redis/etc/redis.conf
bind 0.0.0.0
masterauth "123456"
requirepass "123456"
#或者非交互执行
[root@centos8 ~]#sed -i -e 's/bind 127.0.0.1/bind 0.0.0.0/' -e 's/^# masterauth.*/masterauth 123456/' -e 's/^# requirepass .*/requirepass 123456/' /apps/redis/etc/redis.conf
#在所有从节点执行
[root@centos8 ~]#echo "replicaof 10.0.0.8 6379" >> /apps/redis/etc/redis.conf
#在所有主从节点执行
[root@centos8 ~]#systemctl enable --now redis
master服务器状态
[root@master ~]#redis-cli -a 123456
127.0.0.1:6379> INFO replication
role:master
connected_slaves:2
……
2 编辑哨兵的配置文件
sentinel配置
Sentinel实际上是一个特殊的redis服务器,有些redis指令支持,但很多指令并不支持.默认监听在26379/tcp端口
哨兵可以不和Redis服务器部署在一起,但一般部署在一起以节约成本
所有redis节点使用相同的以下示例的配置文件
#如果是编译安装,在源码目录有sentinel.conf,复制到安装目录即可,如:/apps/redis/etc/sentinel.conf
[root@master redis-6.2.4]#cp sentinel.conf /apps/redis/etc/
[root@master ~]#mv /apps/redis/etc/sentinel.conf /apps/redis/etc/redis-sentinel.conf
[root@master ~]#vim /apps/redis/etc/redis-sentinel.conf
bind 0.0.0.0
port 26379
daemonize yes
pidfile /apps/redis/run/redis-sentinel.pid
logfile "/apps/redis/log/sentinel_26379.log"
dir /tmp #工作目录
sentinel monitor mymaster 10.0.0.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
#(SDOWN)判断mymaster集群中所有节点的主观下线的时间,单位:毫秒,建议3000
sentinel parallel-syncs mymaster 1
#发生故障转移后,可以同时向新master同步数据的slave的数量,数字越小总同步时间越长,但可以减轻新master的负载压力
sentinel failover-timeout mymaster 180000
#所有slaves指向新的master所需的超时时间,单位:毫秒
sentinel deny-scripts-reconfig yes #禁止修改脚本
三个哨兵服务器的配置都如下
[root@master ~]#grep -vE "^#|^$" /apps/redis/etc/redis-sentinel.conf
bind 0.0.0.0
port 26379
daemonize yes
pidfile /apps/redis/run/redis-sentinel.pid
logfile "/apps/redis/log/sentinel_26379.log"
dir /tmp
sentinel monitor mymaster 10.0.0.8 6379 2
sentinel auth-pass mymaster 123456
sentinel down-after-milliseconds mymaster 3000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
sentinel deny-scripts-reconfig yes
#以下内容自动生成,不需要修改
sentinel myid 50547f34ed71fd48c197924969937e738a39975b
#此行自动生成必须唯一,修改此值需重启redis和sentinel服务
[root@master ~]#scp /apps/redis/etc/redis-sentinel.conf 10.0.0.17: /apps/redis/etc/
[root@master ~]#scp /apps/redis/etc/redis-sentinel.conf 10.0.0.27: /apps/redis/etc/
3 启动哨兵
三台哨兵服务器都要启动
#确保每个哨兵主机myid不同
[root@slave1 ~]#vim /apps/redis/etc/redis-sentinel.conf
sentinel myid 50547f34ed71fd48c197924969937e738a39975c
[root@slave2 ~]#vim /apps/redis/etc/redis-sentinel.conf
sentinel myid 50547f34ed71fd48c197924969937e738a39975d
#如果是编译安装在所有节点生成新的service文件
[root@centos8 ~]#vim /lib/systemd/system/redis-sentinel.service
[Unit]
Description=Redis Sentinel
After=network.target
[Service]
ExecStart=/apps/redis/bin/redis-sentinel /apps/redis/etc/redis-sentinel.conf --supervised systemd
ExecStop=/bin/kill -s QUIT $MAINPID
User=redis
Group=redis
RuntimeDirectory=redis
RuntimeDirectoryMode=0755
[Install]
WantedBy=multi-user.target
#注意所有节点的目录权限,否则无法启动服务
[root@master ~]#chown -R redis.redis /apps/redis/
[root@slave1 ~]#chown -R redis.redis /apps/redis/
[root@slave2 ~]#chown -R redis.redis /apps/redis/
[root@master ~]#systemctl enable --now redis-sentinel.service
[root@slave1 ~]#systemctl enable --now redis-sentinel.service
[root@slave2 ~]#systemctl enable --now redis-sentinel.service
4 验证哨兵端口
[root@master ~]#ss -ntl # *:6379、*:26379端口打开
[root@slave1 ~]#ss -ntl
[root@slave2 ~]#ss -ntl
5 查看哨兵日志
#master的哨兵日志
[root@master ~]#tail -f /apps/redis/log/sentinel_26379.log
#slave的哨兵日志
[root@slave1 ~]#tail -f /apps/redis/log/sentinel_26379.log
[root@slave2 ~]#tail -f /apps/redis/log/sentinel_26379.log
6 当前sentinel状态
在sentinel状态中尤其是最后一行,涉及到masterIP是多少,有几个slave,有几个sentinels,必须是符合全部服务器数量
[root@master ~]#redis-cli -p 26379
127.0.0.1:26379> INFO sentinel
# Sentinel
……
master0:name=mymaster,status=ok,address=10.0.0.8:6379,slaves=2,sentinels=3 #两个slave,三个sentinel服务器,如果sentinels值不符合,检查myid可能冲突
7 停止Redis Master 节点测试故障转移
[root@master ~]#killall redis-server
8 故障转移后的redis配置文件会被自动修改
故障转移后redis.conf中的replicaof行的master IP会被修改
[root@slave2 ~]#grep ^replicaof /apps/redis/etc/redis.conf
replicaof 10.0.0.18 6379
哨兵配置文件的sentinel monitor IP 同样也会被修改
[root@slave1 ~]#grep "^[a-z]" /apps/redis/etc/redis-sentinel.conf
……
sentinel monitor mymaster 10.0.0.18 6379 2 #自动修改此行
……
[root@slave2 ~]#grep "^[a-z]" /apps/redis/etc/redis-sentinel.conf
……
sentinel monitor mymaster 10.0.0.18 6379 2 #自动修改此行
……
9 当前 redis状态
新的master 状态
[root@slave1 ~]#redis -a 123456
127.0.0.1:6379> INFO replication
# Replication
role:master #提升为master
connected_slaves:1
……
另一个slave指向新的master
[root@slave2 ~]#redis -a 123456
127.0.0.1:6379> INFO replication
# Replication
role:slave
master_host:10.0.0.18 #指向新的master
master_port:6379
master_link_status:up
……
10 恢复故障的原master重新加入redis集群
[root@master ~]#cat /apps/redis/etc/redis.conf
#sentinel会自动修改下面行指向新的master
replicaof 10.0.0.18 6379
在原 master上观察状态
[root@master ~]#redis-cli -a 123456
127.0.0.1:6379> INFO replication
# Replication
role:slave
master_host:10.0.0.18
master_port:6379
master_link_status:up
……
[root@master ~]#redis-cli -p 26379
127.0.0.1:26379> INFO sentinel
# Sentinel
……
master0:name=mymaster,status=ok,address=10.0.0.18:6379,slaves=2,sentinels=3
观察新master上状态和日志
[root@slave1 ~]#redis-cli -a 123456
127.0.0.1:6379> INFO replication
# Replication
role:master
connected_slaves:2
[root@slave1 ~]#tail -f /apps/redis/log/sentinel_26379.log
25717:X 28 Apr 2022 17:42:33.757 # +sdown slave 10.0.0.8:6379 10.0.0.8 6379@mymaster 10.0.0.18 6379
25717:X 28 Apr 2022 18:41:29.566 # -sdown slave 10.0.0.8:6379 10.0.0.8 6379 @mymaster 10.0.0.18 6379