介绍
Redis
的keys
命令可以获取所有的key
, 时间复杂度是O(n)
, 一旦数据量大了, 因为Redis
是单线程的, 就会导致Redis
阻塞的情况.
为了解决阻塞问题, Redis 2.8.0
推出了scan
命令, scan
可以返回默认大小为10
的key
, 并返回一个游标, 作为下次调用scan
的参数.
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| 127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 k4 v4 k5 v5 k6 v6 k7 v7 k8 v8 k9 v9 k10 v10 k11 v11 k12 v12 k13 v13 k14 v14 127.0.0.1:6379> keys * 1) "k14" 2) "k1" 3) "k13" 4) "k5" 5) "k2" 6) "k12" 7) "k11" 8) "k10" 9) "k4" 10) "k8" 11) "k9" 12) "k3" 13) "k6" 14) "k7" 127.0.0.1:6379> scan 0 match * count 10 1) "11" 2) 1) "k5" 2) "k3" 3) "k6" 4) "k7" 5) "k1" 6) "k11" 7) "k14" 8) "k12" 9) "k2" 10) "k13" 127.0.0.1:6379> scan 11 match * count 10 1) "0" 2) 1) "k10" 2) "k4" 3) "k8" 4) "k9"
|
初始化一堆key
- 用
keys
命令获取到所有的key
- 用
scan
命令两次获取到所有的key
多线程下原子性问题思考
我们在上面使用了两次scan
命令, 就说明在这两次scan
中, 可能会发生set
或者del
操作, 不是一个原子性操作.
Elements that were not constantly present in the collection during a full iteration, may be returned or not: it is undefined.
根据官方文档, 也就是说, 如果在scan
过程中set
或del
了某个key
, 那么这个key
就变成了玄学状态. 可能被返回, 也可能不被返回.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| 127.0.0.1:6379> scan 0 match * count 10 1) "11" 2) 1) "k5" 2) "k3" 3) "k6" 4) "k7" 5) "k1" 6) "k11" 7) "k14" 8) "k12" 9) "k2" 10) "k13" 127.0.0.1:6379> set k15 v15 OK 127.0.0.1:6379> scan 11 match * count 10 1) "0" 2) 1) "k10" 2) "k4" 3) "k8" 4) "k9"
127.0.0.1:6379> scan 0 match * count 10 1) "13" 2) 1) "k5" 2) "k3" 3) "k6" 4) "k7" 5) "k1" 6) "k11" 7) "k14" 8) "k15" 9) "k12" 10) "k2" 127.0.0.1:6379> scan 13 match * count 10 1) "0" 2) 1) "k13" 2) "k10" 3) "k4" 4) "k8" 5) "k9"
|
可以看到, k15
没有在第一次扫描时返回, 而在第二次扫描时返回.
所以这个玄学状态, 应该是取决于set
或del
的元素的位置.
整合 Spring Data Redis
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class Main { public void scan(String pattern, Consumer<String> consumer) { RedisSerializer<String> keySerializer = (RedisSerializer<String>) stringRedisTemplate.getKeySerializer();
ScanOptions options = ScanOptions.scanOptions().match(pattern).count(10).build(); try(Cursor<String> cursor = getStringRedisTemplate().executeWithStickyConnection((RedisCallback<Cursor<String>>) connection -> new ConvertingCursor<>(connection.scan(options), keySerializer::deserialize))) {
if(cursor == null) { return; } while (cursor.hasNext()) { String key = cursor.next(); consumer.accept(key); } } catch (IOException e) { e.printStackTrace(); } } }
|
整合到Spring Data Redis
中就是这样, 奇怪的是, 最新的Spring Data Redis 2.1.6
实现了hscan
命令, 但是却没有实现scan
, 只能自己写execute
实现了.
可以参考我的工具类集合RedisHelper
.
参考资料