1. 景象
某天忽然收到运维告警音讯,反馈产线有接口在某一段时间慢了,随之性能优化的 JIRA 工作也开过去了,大抵内容如下:
API 告警 - Base Tomcat Accesslog:
微服务: xxxapi
接口: /xxxx/xxx POST
在 [2022/xx/xx 10:42:00 ~ 10:43:00] 工夫窗口,RT > 阈值 (300ms) 产生 141 次。
公司外部对 API 的响应工夫要求是低于 300ms,超过的都属于有性能问题的接口,接下来就排查吧。
2. 排查
2.1. 查监控平台:接口速度
首先就去查看公司的 API 监控平台,确认这个接口确实在这段时间确实在响应速度上,有 100 屡次超过 300ms。
不过发现几个有意思的景象:
- 在那段时间前后,该接口都很失常,所以应该只是那段时间产生了什么。
- 不只是那一个接口超过 300ms 了,其实那段时间内,也有个别其余接口超过阈值。只不过因为那个接口的并发比拟高,很显著的被告警机制检测进去。
2.2. 查监控平台:全链路日志
尽管下面发现,可能不只是那一个接口的问题,但还是要仔细分析一下那个接口的全链路日志。
通过调用链路图,能够显著的发现,次要的耗时都在 redis 命令的执行阶段。又找了那段时间,其余的几个接口,后果都是一样的。
接下来就应该看看对应接口的代码,是不是执行 redis 命令时,有什么影响性能的问题。
2.3. 剖析代码
找到对应接口的代码,代码中惟一用到 redis 的命令是 HMGET key field1 [field2]
,剖析后排除该代码的问题:
- 代码中就那一次的 redis 调用,
HMGET
不是耗时的命令(不在后文 O(n) 命令之列),且field
的数量是可枚举的,最多也就几十个,不会有多耗时。 - 该接口因为之前预计到并发高,所以上线之前通过压测,上万 QPS 齐全不成问题,不会在这里栽跟头。
2.4. 查监控平台:redis 连接池
排除了代码层面上该接口的问题,那只能从运维侧看看执行 redis 命令慢的问题了,正好监控平台上有 mysql、redis 连接池的监控。不看不晓得,一看吓一跳。
在出问题时间段,redis 连接数霎时飙升,间接超过了最大闲暇连接数。在工夫节点上,和产线呈现慢响应的工夫齐全吻合。
能够揣测出,这段时间可能执行了某些奇怪的命令,造成了 redis 阻塞,其余 redis 申请只能频繁的创立新的连贯,从而导致 redis 连接池中连接数飙升。
想要揣测那段时间 redis 经验了什么,只能托付于运维的共事了。
2.5. 运维:查 redis 慢申请
通过从运维共事导出来的,那段时间 redis 执行过的慢申请日志,果然和料想的一样。那段时间执行了大量的慢申请命令,尤其是一段 lrange
命令,每次执行工夫都超过了 300ms,一分钟执行了几百次。
通过具体的 redis 命令,倒推找到了对应的代码,该代码对应的其实是一段对应用户登录后,通过 MQ 异步刷新用户权限的逻辑。因为是异步生产,所以就算执行的慢,也并不会因为响应工夫阈值而被告警。
再联合 ELK 中无关该接口的调用日志,能够还原出问题产生的残缺过程。
2.6. 场景还原
该 lrange
命令独自执行还行,可那段时间在产线有大批量用户的登录并发,导致该慢申请命令阻塞住了 redis,大量 redis 申请都在阻塞排队,redis 连接池中连接数也在一直扩大。
所以说,那段时间所有的 redis 申请命令,都会因为阻塞而提早执行实现。告警的那个接口属于“躺枪”,因为它调用的并发高,所以体现显著,被告警程序当典型抓住了。
如果没有这一步步排查,很难置信一个 API 的告警,是因为另外一个毫无关系的 API 影响的。
3. 解决
3.1. 解决
lrange
是工夫复杂度为 O(n) 的命令,原则上是不应该被频繁调用的。剖析代码上下文逻辑,其实只是为了全量存储和读取 List 类型数据。齐全能够用 redis 中的 String 代替 List。
对于 redis 中 keys *
、flushdb
、flushall
等超耗时的命令,能够间接作为禁用命令,保护进 redis.conf
的禁用命令清单中。
但像 lrange
这种能够用,但要严格扫视利用场景的命令,就须要研发人员特地留神。上面整顿了一下 redis 常见的 O(n) 命令。
3.2. O(n) 命令
- String: MSET、MSETNX、MGET
- List: LPUSH、RPUSH、LRANGE、LINDEX、LSET、LINSERT
- Hash: HDEL、HGETALL、HKEYS/HVALS
- Set: SADD、SREM、SRANDMEMBER、SPOP、SMEMBERS、SUNION/SUNIONSTORE、SINTER/SINTERSTORE、SDIFF/SDIFFSTORE
- Sorted Set: ZADD、ZREM、ZRANGE/ZREVRANGE、ZRANGEBYSCORE/ZREVRANGEBYSCORE、ZREMRANGEBYRANK/ZREMRANGEBYSCORE
3.3. 其余
因为 Spring 中 Lettuce 连接池的 bug,公司要求都改回 Jedis 连接池。感觉在该问题的防止上,Jedis 连接池应该也有优化空间。待后续实际后再分享。