背景:
项目需要做redis(旧)到redis的数据迁移,考虑到三种方案:
1 redis和旧redis同时写,读的时候根据配置选择从redis还是旧redis读取数据。
2 通过旧redis到redis的数据同步,代码中直接修改redis的地址。
3 通过scan的方式扫描redis中的数据,再通过Pipeline的方式写入。
对比了一下方案的优缺点:
1 缺点是时间成本高,业务中有些数据过期时间长,切换周期长;优点是可以平滑切换;
2 缺点是需要dba的同学配合;优点是时间成本低。
3 缺点是对于一些有过期时间的key,写入的时候过期时间会有误差,同时写入的时候会有一些增量的数据无法写入,导致数据不一致的情况。
综上决定通过第二种方式来做redis和旧redis的数据同步。
实现:
客户端选择:项目是一个springboot项目,已有的客户端是在jedis上做的一层封装。如果使用springboot集成的redistemplate的话,需要新增redistemplate的工具类,以及改动所有涉及到redis的代码,或者在原有的工具类中修改jedis为redistemplate;如果使用jedis的话则不需要改动其他代码。所以选择了jedis作为客户端。
问题1:
在这个环节就出了问题,在配置redis连接池的时候,使用的redis的最大连接数是20,业务测试通过后,dba也做好了数据的同步,然后就进行了正式环境的升级。
结果:
刚上线后,cpu飙升,系统开始报警。当时也没有意识到是哪里的问题,就开始排查之路
问题排查:
1 查看cpu的进程:top 命令查看系统中占用cpu高的进程;
2 查看cpu高的线程:确认是自己代码导致的cpu过高后,通过top -Hp 进程号 按cpu占用率查看线程;
3 线程号转换十六进制:拿到占用cpu高的线程号之后,通过printf %x 线程号 打印出十六进制的线程号;
4 查看线程的栈信息:jstack 进程 | grep 0xd5352 -A 20其中0x后面的即为线程号的十六进制
5 栈中的信息大多是大多是gc的信息和catalina的信息。
6 排查gc问题,通过jstat -gcutil 进程号 1000 30 每1秒查看一下该进程的gc信息,发现一直在进行full gc,FGCT时间也特别高
7 jmap查看堆的信息,(jmap -histo 可以查看堆中所有的对象的引用数量,jmap -histo:live 1314| head -20 查看存活对象大小排名前20的),jmap -dump:live,format=b,file=/home/work/1310.dump 1310想存一份线上的堆快照(可以进行压缩,以便下载 tar -zcf 1331.dump.tar.gz 1331.dump)分析一下,使用MAT进行分析,由于问题比较紧急,下载dump比较慢,先准备从监控系统分析。
8 由于仅仅是升级了redis,所以考虑是不是配置有问题,所以照搬了一份艾泽拉斯系统的配置,重新升级。(后续才发现,kibana上面一直在报错,Could not get a resource from the pool at redis.clients.util.Pool.getResource ,如果可以刚出问题就看一下kibana上面的错误信息,就可以很快的定位到是redis配置的问题)。
9 升级完成之后本以为可以解决cpu过高的问题,刚升级后没多久,cpu又开始爆满,重新开始上面的1-5的步骤,发现占用cpu高的线程里面指向了一些具体的业务代码,该代码是从memcache拿数据,拿不到就从数据库拿,发现业务代码栈顶信息指向了mybatis,说明缓存没有起作用。在比较紧急的情况下,将memcache改为了redis,并重新上线。(改了redis配置之后从cdb的监控中可以看到数据库的qps是比往常高的,也可以说明缓存并没有起作用)。
10 升级完成之后redis开始报警,提示出口流量过高, 看redis的监控信息,请求都从数据库转移到了redis上。
11 看了一下业务代码,主要是多层循环,循环次数可能会到几十万次,而循环体内会很多次调用redis。该接口是个查询接口(数据会变),所以将查询结果做了个缓存,定期更新查询结果,放入redis中,外部请求直接从redis中拿数据,升级后问题解决。
总结:
出现问题的根本原因就在于1 redis连接数配置有问题,2 该接口数据量比较大,调用量增多导致的。
心得:
1 遇到线上问题需要先结合kibana、以及内部的监控系统结合起来排查信息。
2 配置类参数需要慎重修改。