问题背景
在做拉新活动期间,由于用户访问量陡增,导致Redis服务器压力突然增大,CPU直接飙升到90%以上,然后报警系统收到大量Reids异常报警信息。
■【腾讯云可观测平台告警】
您好!您账号(账号ID: ****,昵称: ****)的腾讯云可观测平台告警已触发
告警内容: 云数据库-Redis-内存版(5秒粒度)-实例汇总|节点最大CPU使用率 > 80%
当前数据: 99.735% (节点最大CPU使用率)
告警对象: ID:****|Instance Name:投放归因生产|Ip Port:127.0.0.1:6379
项目|地域: 默认项目 | 北京
告警策略: redis服务监控
触发时间: 2024-01-22 13:40:40 (UTC+08:00)
您可以登录腾讯云可观测平台控制台查看告警详情,或在腾讯云助手小程序查看告警详情
解决思路
对于这种突发性问题,我们有三板斧
- 三板斧之一:对症下药
既然已有报警信息:当前CPU数据: 99.735% (节点最大CPU使用率),那么根据这个信息进行检索,直接登陆监控平台查询Redis的CPU和内存使用情况,发现CPU和内存使用正常,CPU和内存使用情况如图:
- 三板斧之二:望闻观切
Redis监控正常,我们查询服务器是否有异常日志信息,异常日志:
exception:java.net.SocketTimeoutException: Read timed out; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out。Redis是对内存进行操作,速度应该都在毫秒级,这是我们通常的认知。那有什么原因会导致读取超时呢?从监控信息查询到Redis最大OPS 为10629.383次/秒,请求数并不高,服务器压力并不大。那有没有可能是有请求被堵住了,导致查询慢呢?我们监控慢查询信息,果然有不少命令执行时间过长,有些命令执行竟然超过2s。
- 三板斧之三:庖丁解牛
既然定位到Redis查询慢的问题,那想办法让他快起来吧!我们先看下Redis集群的部署情况
Redis 集群有40个分片,每个分片为2GB。部署为主从架构。
Redis命令执行流程
Redis Server是单线程执行所有连接发送过来的命令的,也就是说不管并发中有多少个client在发送命令,Redis-Server端是单线程处理的,并按照默认的FIFO方式处理请求。
我们是通过socket 对Redis Server进行操作,命令写操作通过socket.getOutputStream()输出流将命令信息发送到Redis Server,当写完命令后要通过socket.getInputStream()得到输入流将命令执行结果返回,这中间必然会有一个命令执行到结果返回的延时时间,这就是一个Jedis调用Redis命令操作所用的时间。
专库专用
通过慢查询Key , 发现 Redis 集群未做到 专库专用,有不少埋点的缓存信息存储在拉新业务缓存中,这是极其不合理的。根据单一职责原则,需新申请一个埋点Redis集群供埋点业务专用。
Redis 命令优化
通常Redis执行命令速度非常快,但也存在例外,如对一个包含上万个元素的hash结构执行
hgetall 操作,由于数据量比较大且命令算法复杂度是O(n),这条命令执行速度必然很慢。这个问题就是典型的不合理使用API和数据结构。对于高并发的场景我们应该尽量避免在大对象上执行算法复杂度超过O(n)的命令。从慢查询监控中,发现HGETALL命令,HINCRBY命令等执行很慢。
连接池配置
连接池通常包括以下几个关键参数:
max-active:最大活跃连接数,即连接池中可以同时使用的最大连接数,为负值时没有限制。
max-idle:最大空闲连接数,即连接池中可以保持空闲状态的最大连接数,为负值时没有限制。。
min-idle:最小空闲连接数,即连接池中必须保持的最小空闲连接数。
max-wait:最大等待时间,即当连接池中没有可用连接时,最大等待时间限制,单位毫秒(million seconds)。
我们应该根据程序实际情况合理设置这三个参数的值,同时在我们获取一个连接的程序方法中也应该合理的处理这个异常,当没有连接可用时,等待一段时间再获取也许是个比较好的选择。
Redis服务器在connectionTimeout时间内未返回数据,这个超时时间是在JedisConnectionFactory通过setTimeOut方法来设置的,如果客户端没有进行配置会采用默认值2000ms作为超时时间,我们配置的timeout: 3000,按理说5000ms对于支持高并发高吞吐的Redis来说压力不大,但是考虑到Redis处理请求的,当遇到大量的网络请求,大数据量的网络IO时或者执行某些比较耗时的查询时,可能会超时。
redis2:
host: 127.0.0.1
port: 6379
password: ******
jedis:
pool:
max-active: 20
max-wait: -1
max-idle: 8
min-idle: 5
timeout: 3000
CPU饱和
单线程的Redis处理命令时只能使用一个CPU。而CPU饱和是指Redis把单核CPU使用率跑到100%,使用统计命令 redis-cli -h {ip} -P {port} --stat 获取当前 Redis使用情况,该命令每秒输出一行统计信息,如果它每秒平均处理 6万 +的请求。对于这种情况,垂直层面的命令优化很难到达效果,这时就需要做集群化水平扩展来分摊OPS压力,我们需要扩容更多的分片来分担压力。
总结
遇到故障并不可怕,可怕的是不知道怎么下手解决故障!!
本文暂时没有评论,来添加一个吧(●'◡'●)