redis 源码阅读杂记

Misc

  • rehash 是分 db 的
  • redis db 中的 字典什么情况下会自动 rehash?
  • redis 中的 key 淘汰, 定时被动淘汰(有2 种模式)。 另外则是每次访问到 key 都会检查一下 key 是否过期则删除(也能减少部分 key)
  • key 的读写分多套接口,基本上读写的功能函数是分离的(lookupKeyRead、lookupKeyWrite、lookupKeyReadOrReply、lookupKeyWriteOrReply)。作为一个缓存系统,需要统计命中率。
  • 设置过 expires 时间的 key 居然放在另外的 一个 dict 中, 想象也是合理。不然就要全量遍历 dict 中的所有 key。 不过 expires dict 和 dict 底层存储的数据是共享的
  • redis master-slave 中的 slave node 上的数据无法保证是最新的啊 (slave不会主动删除过期键,会读到脏数据) 。 redis master-salve 的意义是啥?是为了读写分离?还是为了在主节点 宕机后,从节点升级为新的主节点?
  • scan 的游标如何保证在多次 scan 之间新增数据的会被 scan 到?(官方文档已经写了,只保证在 scan 开始的时候数据能被读取到,但是 scan 开始后新增的数据无法保证被读取到!)
    • 同一个值在 scan 时可能会多次返回(通常说的多次就是 2次, 因为有2 个哈希表。如果在旧表中被被返回过一次。rehash 时候被迁移到新桶时候又被访问一次。另外只有缩容才可能会返回 2 !)
    • hash 用的还是开放地址法解决冲突
    • 高位的进一能有效避免重复返回数据(重复返回数据只会和 rehash 有关, 高位进一的方法能保证在 rehash 的时候知道哪些桶被访问过。因为 rehash 后桶的序号信息中还是能看出来 这个桶在分裂前的序号是多少(做一次0111 这样的与运算就可以了)。 低位进一则不可以。如果 序号位 7 的桶,采用低位进一的方法活变成序号为14,15 两个桶。如果 cursor 为 6 的话。则依然会返回大量的重复数据!。这也能解释为什么游标开始后的新增的数据是否能被访问到是未定义行为
  • 慎用命令 rename, 因为 rename 可能会删除旧的同名 key (如果同名 key 是个超大 hash key,删除这个 hash key 会拖慢系统), 所以 rename 可能会影响 redis 响应速度
  • db 也会 rehash ? 如果会,那就有个很奇特的现象。即db 中的 hash key rehash 的同时 db 也在 rehash。这就好玩呢。
    • db 和 hash 的底层存储是用 dict 结构 (hash 的底层数据结构也有可能是 ziplist)。 db 的 rehash 比较复杂,采用的是渐进式 rehash (在 db 每次的 find、add、delete 都会完成部分的 rehash 操作。 而且 rehash 和上面的 scan 命令还有一点关系。 更多细节参见美团 blog https://tech.meituan.com/2018/07/27/redis-rehash-practice-optimization.html)
    • hash 只有在 del key 时才会触发 resize 功能 (如果底层结构是 dict, 且版本是 3.0.0。 没看过其他版本的源码,不保证其他版本也是如此)
  • redis dict 不同于常规的 dict 的 c 实现的部分,就是有大量的 rehash 、iterator 相关的逻辑
    • 我也不知道当时,自己写的是啥
  • 如果在 rehash 的过程中。 db 突然被插入了很多 key。会怎么样?
    • 直接插入到 ht[1] 表,和插入 key 数量无关
  • redis dict 中 unsafe iterator 是干什么的? 和 safe iterator 有什么区别?
    • 依然不是很了解这部分的逻辑
    • redis command 基本上都没有用到 iterator 相关的逻辑, master-slave、replicate 反而用到了 iterator 逻辑
  • redis dict find 算法不会使用 cuckoo filter 做快筛?
    • 因为 leveldb 和 pg 在查询数据时会使用到 bloom filter 来判断数据是否在某个文件中(表述不是很准确)。但是 redis 本身就是在内存中,使用 bloom filter 不比 hash 快多少。 而且 bloom filter 在大数据量时的 fpr 数据贼差,不如 cuckoo~
  • redis set 命令会检查所有的 watch key 的 clients,所以对同一个 key 还是不要 watch 的好。虽然 redis 性能很高,但是也经不起这样的折腾啊!
    • 除了 set, expire 等任何和 key 相关的指令都会给所有 watch 该 key 的clients 加标志的!
  • keys 命令会使用到 dict safe iterator
    • scan 和 dict iterator 是两回事,但是这两者在语义上又有混淆。1) 第一做的事情差不多。 2)在有的人的描述 scan 命令直接就用 迭代(iterator) 这个词。 那么就有个问题,为什么 scan 要用 curos (算法有精妙的部分,但是还是很粗糙。即用了某一方面的精妙,做了另外一方面粗糙的事情)。为什么 scan 不用常规意义上的 iterator 呢?还是因为 redis 是多少客户端的。使用 iterator 如何保持每个客户端的 iterator 状态呢? 此外,每个 client 也可以有多个 iterator, 这又该如何迭代呢
    • 为什么 keys 命令可以直接使用 iterator? 因为它只要返回所有就好了。由 client 保证一切~~(如 keys 命令拖慢了线上系统~~)
    • dict safe iterator 有什么特性? 会暂时将 dict find、add、delete 命令触发的 rehash 冻结住。等所有的 safe iterator 全部迭代完后才会重新做 渐进式 rehash
  • 为什么 redis 在 addRelpy 前 要 incrRefCount 呢 (shared 变量是不会由 incr、decr 操作)?
    • redis 是单线程的。一个 key 不用 incrRefcount、dec, 也不用担心会被 expire 掉啊。难道 networking 发送数据是在另外那里一个线程里面?(networking 不是另外一个线程。需要 incr 的原因是,要等到 epoll 可写的时候才会写数据。所以需要保证数据不被回收掉)
  • hash 的插入和查找的复杂度 o(1) 操作还是 o(lgN)
    • 如果 hash 的结构是固定的 slot 且用 链表法解决冲突,则可以认为是 o(1)。 会占用大量的空间, 且需要不定期 rehash
    • 如果 hash 的底层存储使用的 红黑树 或这 skiplist, 则认为是 o(lgN)
    • t_set.c 、t_string.c 、t_list.c 等 t_ 开头的文件是什么? 意思是基本数据类型?
      • t_ 开头代表是用户能直接访问的数据类型,底层可能是 intset、dict、skiplist 等实现的~。 如 t_hash 的底层数据结构可能是 intset 或者 dict
    • redis 最大只支持 512 M 的字符串是后台硬编码实现的 

 

pubsub

  • 看样子使用 pubsub 不会拖慢系统~。以前认为 pubsub 会 make system slowdown (以前认为 watch 可能会拖慢系统, 其实 watch 也不会。但是一个 key 里面有 上千个 client 在 watch 可能有问题)
  • client A publish 信息后会什么时候返回?等发送消息给所有 client 后? 还是 redis-server 收到消息后里面返回? 还是其他情况?
    • 从代码上来看,会把消息发送给所有 clients 后才会返回, 关键问题能保证所有的 client 都接受到数据后才会返回? 还是还是会把发送请求 pending ,等待 epoll 可以发送的时候才会真正地发送数据?
    • addReply 只是会发送数据,不代表能发送成功把。比如 客户端 宕机了
    • 客户端已经宕机了很久,会被 redis 感知到?(server 是无法感知的,可以依靠定时任务将 idle time 超过 max_idle_time 的 client 回收掉。但是默认配置是 max_idle_time 为无限)
  • 需要等看懂网络层后才可以理解 pubsub 的工作原理!
    • 其实也没有啥好看的

Aof

    redis 中的随机命令会不会影响到 aof
  • redis 基本上只有 “读” 非确定命令,如果 srandmembers、time
  • 这些随机 “读” 命令只有在 lua 脚本中,才有可能产生随机写的操作~。 所以 redis lua 对随机命令做了特别处理。能保证对同样的数据,使用随机命令获取的结果是一样的, 还有一个 time 命令直接只能用于打印。无法输出~

transaction 和 pipeline

    • redis 的事务真是搞笑, 自定义一套的 acid 标准。想一想也对,毕竟事务相关的代码只有 300 行,也不不可实现出多么牛逼的功能
    • 原子的 lua 操作,也只是保证中间不会有其他命令插入进来。lua 脚本在 pop list 等操作后执行失败,list 中的数据不会被 rollback
        
        local v1 = redis.call("LPOP", "lst")
        print(v1)
        redis.call("LPUSH", "nlist", "DDDD") // 这里会出错, nlist 是一个 字符键不是 列表键。
        return v1
        
    
    • pipeline
        
            redis-cli 不支持 pipeline, 但是 sdk 支持。理论上所有使用 协议 交互的 client-server 架构服务都支持 pipeline (redis 等),只要 server 从 recieve buffer 接受一条不完整的数据能继续等待,以及接受到一条完整的数据后不会将多余的数据当作错误数据清除掉。 大多数人都犯了一个错误,即认为 pipeline 具有原子性。pipeline 支持批量发送数据。另外,pipeline 和 multi 没有木有关系。multi 中的命令是一条条返送到 server 端 queued 住,等到一条 exec 后才会执行的。 但是有的语言的 sdk 实现比较奇特,会在 client 端 queue multi 命令,直到用户程序请求 exec 时才会将 multi 到 exec 之间的命令通过 pipeline 方式发送到 redis !
            redis-cli 中的 recieve buffer 有多大? 普通客户端默认是 1 G, 参考文档 https://redis.io/topics/clients

            一个用于测试 pipeline 的命令: (printf "PING\r\nPING\r\nPING\r\n"; sleep 1) | nc localhost 6379
        
    

client server 握手过程

    
        ./server *:6379(print_trace+0x60) [0x562da7feb6e0]
        ./server *:6379(aeCreateFileEvent+0x49) [0x562da7f969b9]  # 将readQueryFromClient 注册为 epoll 回调函数
        ./server *:6379(createClient+0x55) [0x562da7fa6b25]
        ./server *:6379(+0x2d2de) [0x562da7fa72de]
        ./server *:6379(acceptTcpHandler+0x63) [0x562da7fa73d3]
        ./server *:6379(aeProcessEvents+0x128) [0x562da7f96e08]
        ./server *:6379(aeMain+0x2b) [0x562da7f9717b]
        ./server *:6379(main+0x2b6) [0x562da7f95d76]
    

此外 redis-cli 版本大于 4.0 的话,在连接上 server 后会自动执行一下 command 命令, 而且 执行结果不会展示给用户

redis 事件模型

    
    每次进入到事件循环前,会调用 beforeSleep 函数。 beforeSleep 函数会主动 expire key、主从同步、AOF 数据写回到磁盘(非强制,可能还在 linux io buffer 中)、处理哪些 blocked on key 中的 clients(blocked client 本身不是时间敏感的。所以某个 client  set key 后不会立即处理 blocked client 的请求)
    进入到事件循环后,会先计算最早需要处理的时间事件,根据这个计算出 epoll_wait 的等待时间。后续调用 epoll_wait 获取需要处理的 io 事件。处理完 io 事件后会处理所有需要处理的时间事件

    当然改了系统时钟,redis 会做处理。具体的逻辑记得不太清楚了
    
posted @ 2020-06-15 14:48  tmortred  阅读(218)  评论(0编辑  收藏  举报