[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。

  1. session 1> scan 0 count 1
  2. 1) "6"
  3. 2) 1) "b"
  4. session 1> scan 6 count 1
  5. 1) "1"
  6. 2) 1) "f"
  7. 2) "d"
  8. session 2> scan 6 count 1
  9. 1) "1"
  10. 2) 1) "f"
  11. 2) "d"

上面是一个redis的执行结果。如果数据不变,redis可以保证使用相同游标连续访问可以获得相同的结果,即使是不同会话。例如,对于会话1和会话2,scan都从6开始,可以得到相同的结果。

Tendis存储版的实现中,如果CursorMap使用会话级,那么会话2是无法获知游标6的状态。

因此,CursorMap使用全局保存会更好。例如,放到ServerEntry中。

Tendis存储版中cursor的语义

redis的cursor语义有点复杂,算法也比较复杂,大概就是跟全局字典的哈希桶建立了一定的关系。

Tendis存储版的’cursor`的语义可以大概定义为在rocksdb内排序的第几个key。

例如,希望Tendis存储版的执行结果

  1. session 1> scan 0 count 2 ## 建立关系 {2} -> {kvstoreid, "f"}
  2. 1) "2"
  3. 2) 1) "b"
  4. 2) "c"
  5. session 1> scan 2 count 1 ## 建立关系 {3} -> {kvstoreid, "x"}
  6. 1) "3"
  7. 2) 1) "f"
  8. session 2> scan 2 count 1
  9. 1) "3"
  10. 2) 1) "f"
  11. session 1> scan 3 count 3 ## 建立关系 {6} -> {kvstoreid, "aa"}
  12. 1) "6"
  13. 2) 1) "x"
  14. 1) "y"
  15. 1) "z"

这里表示”f”就是全局第3个key。

CursorMap每次使用精确匹配?

接着上面的例子,如果用户执行scan 4 count 3。由于CursorMap没有建立过游标4,如果CursorMap使用精确匹配,是查不到这个对应关系的。

有两个解决办法:

  1. 如果CursorMap找不到,将cursor置为0,重新开始。
  2. 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信息。

其他考虑

  1. 不需要支持snapshot
  2. 只保证静态数据的完整遍历