[TOC] 850 字 | 3 分钟
背景
Tendis存储版
在2.1.0版本以前只支持iteall
命令来遍历所有数据,与redis不太一样,需要支持scan
语法。
scan的语法
redis的scan语法
SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
Tendis存储版
的scan语法,增加了一个SLOT标签,用于处理混合存储场景。
SCAN cursor [MATCH pattern] [COUNT count] [TYPE type] [SLOT a-b]
实现关键
redis中scan返回的cursor
是一个数字,表示key所在哈希桶的下标。
在Tendis存储版
中实现同样的功能,关键就在于这个cursor
数字。如果需要在rocksdb中表达上次扫描到的key位置,需要使用字符串来表达。因此,如果cursor
使用数字,需要建立一个cursor
->{kvstoreid, last_scan_key}的映射关系。可以使用一个map,这里称为CursorMap
CursorMap
是全局还是会话级?
按正常理解应该是一个会话级的map。
session 1> scan 0 count 1
1) "6"
2) 1) "b"
session 1> scan 6 count 1
1) "1"
2) 1) "f"
2) "d"
session 2> scan 6 count 1
1) "1"
2) 1) "f"
2) "d"
上面是一个redis的执行结果。如果数据不变,redis可以保证使用相同游标连续访问可以获得相同的结果,即使是不同会话。例如,对于会话1和会话2,scan
都从6开始,可以得到相同的结果。
在Tendis存储版
的实现中,如果CursorMap
使用会话级,那么会话2是无法获知游标6的状态。
因此,CursorMap
使用全局保存会更好。例如,放到ServerEntry
中。
Tendis存储版中cursor
的语义
redis的cursor
语义有点复杂,算法也比较复杂,大概就是跟全局字典的哈希桶建立了一定的关系。
而Tendis存储版
的’cursor`的语义可以大概定义为在rocksdb内排序的第几个key。
例如,希望Tendis存储版
的执行结果
session 1> scan 0 count 2 ## 建立关系 {2} -> {kvstoreid, "f"}
1) "2"
2) 1) "b"
2) "c"
session 1> scan 2 count 1 ## 建立关系 {3} -> {kvstoreid, "x"}
1) "3"
2) 1) "f"
session 2> scan 2 count 1
1) "3"
2) 1) "f"
session 1> scan 3 count 3 ## 建立关系 {6} -> {kvstoreid, "aa"}
1) "6"
2) 1) "x"
1) "y"
1) "z"
这里表示”f”就是全局第3个key。
CursorMap
每次使用精确匹配?
接着上面的例子,如果用户执行scan 4 count 3
。由于CursorMap
没有建立过游标4,如果CursorMap
使用精确匹配,是查不到这个对应关系的。
有两个解决办法:
- 如果
CursorMap
找不到,将cursor置为0,重新开始。 CursorMap
使用lower_bound
的方式search,例如搜索4,返回3的关系。
正常的scan使用,不应该随意指定一个cursor的。因此,暂定使用方法1来解决。
因此,CursorMap
使用精确匹配。
CursorMap
的资源消耗
CursorMap
是全局资源,需要定时清理,例如10000个就足够了。(足够10000个并发scan请求) 因此,映射关系需要增加时间戳,定时或按需清理最老的信息。
n:m 混合存储的考虑
由于n:m存在redis和tendis的slot不完全对应的情况,因此增加了[SLOT a-b]
的语法,redis将scan命令转发是修改下,加上它负责的那部分slot信息。
其他考虑
- 不需要支持snapshot
- 只保证静态数据的完整遍历