前言

相关介绍主要围绕着如下的一些常用的命令, 来看看 字符串 相关操作的具体 api 

如下常用的命令来自于我们常见的教程 : https://www.runoob.com/redis/redis-strings.html 

本文的相关代码 拷贝自 redis-6.2.0  

代码来自于 https://redis.io/ 

数据存储

string 数据的存储是基于 sds 来进行存储的 

SET key value - 执行 set name jerry ex 100 xx 

12 string 相关操作_客户端

这里可以看出的是 参数的数量要求大于等于 3 个, set 里面又诸多选项, 我们接下来看 

我们来看一看 setCommand 

两步走, 首先是解析 输入命令中的各种标记 

接着是执行 set 的相关实际业务处理 

flags 中标记了相关的一些特性, 诸如是否 传入了 NX, XX, SET_GET, KEEPTTL, EX, PX, EXAT, PXAT 等等 options 

expire 表示的是传入的过期时间, unit 表示过期时间的单位 

12 string 相关操作_客户端_02

对于传入参数列表的解析 

这里可以直接看上面的 "规范", 说的很明晰 

只有 GET 可用的选项 : PERSIST, DEL 

只有 SET 可用的选项 : XX, NX, GET 

SET, GET 都可以用的选项 : EX, EXAT, PX, PXAT, KEEPTTL 

如果参数不符合规范, 给客户端返回 语法错误  

下面的代码是上面的 "规范" 的实现而已 

12 string 相关操作_redis_03

关于 flags 

flags 各个标记位对应意义如下, 我们这里 flags 为 6, 表示 SET_XX, EX 标记为 true, 用户传入了这两个 options 

12 string 相关操作_数据_04

我们来看一下 set 的具体的业务的实现 

获取 ttl, 计算 key 对应的 entry 的过期时间 when 

如果是有 NX 选项, 并且 key 对应的 entry 存在, 返回 null 

如果是有 XX 选项, 并且 key 对应的 entry 不存在, 返回 null 

如果是有 SET_GET 选项, 并且 key 对应的 entry 不存在, 返回 null 

c->db->dict 中保存 key -> value 对应的 entry, 如果是需要 KEEPTTL, 移除过期时间, 更新 dirty

如果传入了 ttl, 设置 key 对应的 entry 的过期时间, 返回 OK  

12 string 相关操作_src_05

最终客户端这边展示的结果如下, 展示的是 set 的结果 reply 的是 null 

12 string 相关操作_src_06

GET key - 执行 get name 

12 string 相关操作_string_07

这里可以看出的是 参数的数量要求等于 2 个 

我们来看一下 getCommand 

获取 key 对应的 entry, 如果不存在, 直接返回 null 

确保 o 的类型为 OBJ_STRING, 类型不匹配抛出 wrongtypeerr 

给客户端输出 o 的数据信息, 这里为 "jerry" 

12 string 相关操作_redis_08

最终客户端这边展示的结果如下, 展示的是 name 对应的 entry 的 value 为 "jerry"

12 string 相关操作_redis_09

GETRANGE key start end - 执行 getrange name 1 3

12 string 相关操作_数据_10

这里可以看出的是 参数的数量要求等于 4 个 

我们来看一看 getrangeCommand 

从客户端传递过来的参数中获取 key, start, end 如果格式不正确, 或者 key 存在问题, 或者类型存在问题, 返回给客户端错误信息 

获取存储的字符串的信息, 以及字符串的长度  

校验 start, end 索引是否合法, 不合法 给客户端响应 emptybulk 

对于 start, end 为负的约定进行真实索引计算, 如果还不合法 补偿更新为 0, 如果 end 太大 补偿更新为字符串末尾 

返回 key 对应的 value 的 substring(start, end), 包含 start, end 响应给客户端 

12 string 相关操作_客户端_11

最终客户端这边展示的结果如下, 展示的是 name 对应的 entry 的 value 为 "jerry" 的 [1, 3] 子串 "err" 

12 string 相关操作_src_12

GETSET key value - 执行 getset name jerry.he

12 string 相关操作_src_13

这里可以看出的是 参数的数量要求等于 3 个 

我们来看一下 getsetCommand 

首先是 读取 key 对应的 entry 响应个客户端 

然后设置 key -> newValue 到 c->db->dict, 更新 dirty 

后面的重写 c->argc, c->argv, 将传递的 getset name jerry.he 更新为 set name jerry.he, 可能这是组合的命令, 将它归为了 set 系列操作, 后面的 context 可能需要读取参数做其他的业务处理吧 

12 string 相关操作_string_14

最终客户端这边展示的结果如下, 展示的是 name 对应的 entry 的 value 为 "jerry", 然后将 name 对应的 value 设置成了 "jerry.he" 

12 string 相关操作_string_15

我再吧 name 对应的 value 设置回 jerry 

12 string 相关操作_redis_16

GETBIT key offset - 执行 getbit name 6 

12 string 相关操作_redis_17

这里可以看出的是 参数的数量要求等于 3 个 

我们来看看 getbitCommand 

从参数中获取 偏移, 如果格式不正确 响应异常信息给客户端 

获取 name 对应的 entry, 并校验类型, 格式不正确 响应异常信息给客户端 

通过 bitOffset 来计算属于哪一个字节 byte, 以及这一个字节的哪一个 bit 

获取第 byte 字节的第 bit 位的数据, 是 0 还是 1, 直接返回 

12 string 相关操作_redis_18

呵呵 这里支持的按位系列操作, string 就是一个天然的 bitmap 

最终客户端这边展示的结果如下, 展示的是 name 对应的 entry 的 value 为 "jerry", 的第 6 bit, "je" 存储的是 "0b0110 1010 0110 0101", 第 6bit 为1, 即为这里客户端接收到的结果 

12 string 相关操作_客户端_19

我们来看一下 这里的 第一个 byte 的所有的数据, 确实是我们上面期望的 "j" 存储的数据 "0b0110 1010"

12 string 相关操作_string_20

SETBIT key offset value - 执行 setbit name 3 1 

12 string 相关操作_src_21

这里可以看出的是 参数的数量要求等于 4 个 

我们来看一下 setbitCommand 

同样的先定位是那一字节 byte, 在定位是这一字节的那一位 bit 

然后 在内存中更新 这一位 

12 string 相关操作_redis_22

我们这里 name 对应的 value 为 "jerry" 

"j" 为 "0b0110 1010", 我们这里将第三个 bit 更新为 1, 更新之后为 "0b0111 1010"

"0b0111 1010" 对应于十进制为 122, 为 "z", 因此从 value 上来看, "jerry" 更新成了 "zerry"

SETRANGE key offset value - 执行 

12 string 相关操作_redis_23

这里可以看出的是 参数的数量要求等于 3 个 

我们来看下 setrangeCommand 

获取 offset, 并校验, 不合法 响应错误信息给客户端 

获取 key 对应的 entry, 如果不存在, 如果 传入value 为空, 直接返回, 否则 创建 robj, 并添加到 c->db->dict 里面 

如果 key 对应的 entry 存在, 确保类型为 STRING, 如果 传入value 为空, 直接返回, 确保 更新之后的 size 合法 

更新 value 的长度, 确保 在offset处 能够存储 传入value, 复制 传入value 到 offset 处, 更新 dirty 

返回 value 更新之后的长度 

12 string 相关操作_数据_24

最终客户端这边展示的结果如下, 展示的是 name 对应的 entry 的 value 为 "jerry", 的第三byte之后的 6byte 更新为 "rrrrrr", 返回的是 9 表示的是 "jerrrrrrr" 的长度 

更新之后 name 的值为 "jerrrrrrr"

12 string 相关操作_src_25

STRLEN key

12 string 相关操作_redis_26

这里可以看出的是 参数的数量要求等于 2 个 

我们来看看 strlenCommand 

获取 key 对应的 entry, 并确保类型为 STRING, 如果不合法 响应错误信息给客户端 

获取 key 对应的额 value 的length[如果是字符串编码, 直接获取长度, 如果是整形编码, 通过整形值计算所需长度] 

12 string 相关操作_redis_27

最终客户端这边展示的结果如下, 展示的是 name 对应的 entry 的 value 为 "jerrrrrrr" 的长度为 9 

12 string 相关操作_string_28

MSET key value [key value ...] - 执行 mset name jerry age 77 

12 string 相关操作_客户端_29

这里可以看出的是 参数的数量要求大于等于 3 个 

mset, msetnx 都是差不多, 这里只介绍一个 

我们来看一下 msetCommand 

其实就是一顿快捷操作, 设置多个 key value 对 

更新 dirty 

12 string 相关操作_string_30

最终客户端这边展示的结果如下, 展示的是 设置了 name -> jerry, age -> 77 成功 

12 string 相关操作_客户端_31

MGET key [key...] - 执行 mget name age 

12 string 相关操作_客户端_32

这里可以看出的是 参数的数量要求大于等于 2 个

我们来看一下 mgetCommand 

响应给客户端的数据为一个数组, 先响应了长度  

然后再逐步响应了 keyList 中各个 key 对应的 value 的信息 

12 string 相关操作_客户端_33

最终客户端这边展示的结果如下, 展示的是 name 和 age 的结果列表  

12 string 相关操作_redis_34

SETEX key seconds value - setex name 100 jerry

12 string 相关操作_string_35

这里可以看出的是 参数的数量要求等于 4 个 

setex, psetex 都是差不多, 这里只介绍一个 

我们来看看 setexCommand 

是基于 setGenericCommand 来处理的业务, 这里不再赘述 可以参见 "SET key value" 

12 string 相关操作_redis_36

最终客户端这边展示的结果如下, 展示的是 设置了 name -> jerry ttl 为 100s 

12 string 相关操作_客户端_37

INCR key - 执行 incr counter

12 string 相关操作_客户端_38

这里可以看出的是 参数的数量要求等于 2 个 

incr, decr, incrBy, decrBy 都是差不多, 这里只介绍一个 

我们来看一下 incrCommand 

根据 key 获取 entry 的数据, 校验类型为 OBJ_STRING 

获取 key 对应 value 的值, 如果不能转换为 long long, 响应错误信息给客户端 

如果可能存在 溢出的可能, 抛出错误信息 

value 累计增加 incr 

更新 key 对应的 entry 的 value 的值为更新之后的值 

更新 dirty, 给客户端返回 累增 之后的结果 

12 string 相关操作_string_39

最终客户端这边展示的结果如下, 之前 不存在 counter 这个 entry, incr 之后 counter 为 1, 客户端的接受到的结果为 1 

12 string 相关操作_string_40

APPEND key value - 执行 append name .x.he

12 string 相关操作_src_41

这里可以看出的是 参数的数量要求等于 3 个 

我们来看一下 appendCommand 

获取 key 对应的 entry, 如果不存 新增 key -> value 到 c->db->dict 

如果 key 对应的 entry 存在, sdscat 添加 value 到原有的 entry 的值上面 

更新 dirty, 发送 db key 的更新通知 

返回 更新之后的 value 的长度 

12 string 相关操作_string_42

最终客户端这边展示的结果如下, name 的值为 "jerry", 然后我这里 append 了 ".x.he" 

append 之后 name 的值为 "jerry.x.he", 这里客户端展示的是 服务器返回的 "jerry.x.he" 的长度 为 10 

12 string 相关操作_src_43

完