Redis Scan代替Keys优化
接手的项目破破烂烂,总是让我修修补补——生无可恋的程序员。
事情大概是这样,接手了这个项目后,发现线上经常出现Broken pipe异常,查了一下,大概意思就是客户端等待服务端过程中超时了,后来连接被复用时发现已关闭。最终定位是请求redis导致的程序被hang住,然后发现redis没有配置超时时间,于是配了超时,但是没有解决请求redis超时的问题,线上开始出现大量redis超时的异常。
对于redis的超时,首先查了下redis上的慢查询日志,发现有大量的执行keys命令的记录,排查代码,发现了这部分keys的来源,业务逻辑是每分钟统计一次活跃用户数,每一个用户在redis中以一个key保存,key的结构是"前缀:用户ID"这个样子。keys命令查询的就是符合"前缀:*"格式的所有缓存数据。有职业素养的大家,一定都被教育过,不要在生产环境使用keys命令。或者,至少大家应该都背过这个八股。所以,解决方案呼之欲出,用scan替换keys。
新增的scan方法代码如下:
public Set<String> scan(String matchKey) {Set<String> keys = redisTemplate.execute((RedisConnection connection) -> {Set<String> keySet = new HashSet<>();//定义起始游标,获取lettuce原生引用,定义scan参数ScanCursor scanCursor = ScanCursor.INITIAL;RedisKeyAsyncCommands commands = (RedisKeyAsyncCommands) connection.getNativeConnection();ScanArgs scanArgs = ScanArgs.Builder.limit(1000).match(matchKey + "*");try {do {//最少scan一次,当返回不为空时将扫描到的key添加到统一key列表中KeyScanCursor<byte[]> keyScanCursor = (KeyScanCursor) commands.scan(scanCursor, scanArgs).get();if (keyScanCursor != null) {if (!CollectionUtils.isEmpty(keyScanCursor.getKeys())) {keyScanCursor.getKeys().forEach(b -> keySet.add(new String(b)));}scanCursor = keyScanCursor;} else {scanCursor = ScanCursor.FINISHED;}} while (!scanCursor.isFinished());} catch (Exception e) {log.error("redisClient scanKey fail patternKey:[{}]", matchKey, e);}return keySet;});return keys;}
参考文献
SpringBoot中RedisCluster的scan命令实践-阿里云开发者社区