采坑记录-Redis使用scan代替keys

[提前声明]
文章由作者:张耀峰 结合自己生产中的使用经验整理,最终形成简单易懂的文章
写作不易,转载请注明,谢谢!
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;
    }
回归测试结果
  • 服务正常执行
发布了55 篇原创文章 · 获赞 329 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/youbitch1/article/details/103901747