[提前声明]
文章由作者:张耀峰 结合自己生产中的使用经验整理,最终形成简单易懂的文章
写作不易,转载请注明,谢谢!
spark代码案例地址: https://github.com/Mydreamandreality/sparkResearch
线上问题
- 定时任务通过keys* 通配符匹配对应的key
- 在这段时间内的其它服务(需要用到Redis)告警,无法进行正常服务
- 在运维平台查看日志:服务告警这段时间内的请求全部抛出redis连接超时,服务中设置的redis超时时间为200ms
问题定位
- 首先确定是keys命令引起的
- 而且Redis中数据量要特别大,否则是不会触发这个bug的,之前数据量小的时候根本没有这个问题
- 其次
keys
命令本身在redis中数据量特别大的情况下(百万及以上)就会特别慢 - 最致命的是这个命令会阻塞redis多路复用的io主线程,如果这个线程被阻塞了,在这个期间其它服务到redis的请求会全部被阻塞,导致一系列反应,响应卡顿,连接超时等等
问题解决
- 在线上环境,这种会阻塞主线程的操作,并且算法时间复杂度是O(n)的就应该禁止使用
替代方案
- redis.scan
scan如何解决线上问题
- scan和keys都是O(n)复杂度
- scan同样支持通配字符模糊查找
- scan不会阻塞主线程
- scan支持游标按批次迭代返回数据
注:scan返回的数据有可能会重复,需要客户端主动去重
scan注意事项
- scan命令的游标从0开始从0结束
- scan命令每次返回的数据都会携带下次游标所需的值,根据这个值再进行下一次的访问,如果返回的数据为空,不一定是没有数据了,需要通过游标来确认
scan命令使用案例
scan 0 match my*key count 10000
JavaRedisApi-scan命令使用案例
public Set<String> scan(String key, long count) {
Set<String> keys = redisTemplate.execute(
(RedisCallback<Set<String>>) connection -> {
Set<String> keyTmp = new HashSet<>();
Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match(key + "*").count(count).build());
while (cursor.hasNext()) {
keyTmp.add(new String(cursor.next()));
}
return keyTmp;
});
return keys;
}
回归测试结果
- 服务正常执行