博主有个springboot项目有个获取在线人数的需求,session使用的是shiro、redis分布式存储。一开始直接用了 Collection sessions = redisSessionDAO.getActiveSessions();这个方法内部是使用了redis命令中的keys *。
keys *命令的原理是扫描整个redis里面所有的key,然后根据通配符模糊查找出来,所以他的时间复杂度是O(N)。在匹配key少的话是没问题的。超过1w的情况下,查询时间能达到10S。因为redis是单线程的,这样就会长时间占用cpu,可能导致其他的redi连接超时。所以就想优化下。在网上搜了版本,发现了可以用scan命令替代keys *命令。
相比于keys命令,scan命令有两个比较明显的优势:
- scan命令的时间复杂度虽然也是O(N),但它是分次进行的,不会阻塞线程。
- scan命令提供了limit参数,可以控制每次返回结果的最大条数。
不过scan命令也并不是完美的,它返回的结果有可能重复,因此需要客户端去重。但博主的获取在线人数只是个参考,不需要非常精确,所以也没有去重O(∩_∩)O哈哈~ 。至于scan为什么会重复,大家可以查看 redis scan原理.下面直接上代码
int size = 0;
String realKey="shiro_redis_session" + "*";
Long count = 1000L;
List<Integer> numList = new ArrayList<>();
redisTemplate.execute((RedisCallback<Integer>) connection -> {
Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match(realKey).count(count).build());
while (cursor.hasNext()) {
byte bytes[] = cursor.next();
Session session = (Session) SerializeUtils.deserialize(redisSessionDAO.getRedisManager().get(bytes));
if(session == null){
continue;
}
Map<String,Object> mapUser = (Map<String,Object>) SerializeUtils.deserialize((byte[]) session.getAttribute("user"));
if(CollectionUtils.isEmpty(mapUser)){
continue;
}
numList.add(1);
}
return 1;
});
size = numList.size();
后记
后面部署上线发现,redis慢查询没有了,但是cpu却是每次执行这个命令,会飙升至 90%。因为我们这个代码执行逻辑是1小时一次。所以cpu每隔1小时就会飙升,然后迅速下降。这个问题让我很是纠结。下面是redis cpu使用率图
大家有什么改善的方法吗,可以给我留言。