本机数据持久化

  • save:将当前redis内存中的所有数据dump出来保存到磁盘,主线程执行(线上慎用)。所有还有bgsave,方式是从master线程fork出一个子线程出来用于处理数据落盘。
  • 上述两种生成的是rdb文件,成本高,一般是定时执行,所以存在数据丢失,优点是不影响主线程,也就是对性能没啥影响。
  • aof方式会在每一次操作后记录下来,类似LMS日志、或redolog方式,这里也需要根据对数据丢失的权衡来选择数据刷盘的的方式,。优点是数据持久性好,缺点是影响性能。
  • rdb和aof的区别
    • 从对性能的影响来看,rdb通过folk子线程的方式,可以对master操作无影响,而aof需要进行数据持久化会影响性能(影响程度取决于刷盘配置,fsync=0/1/2)。
    • 从数据恢复的角度来看rdb文件因为是直接复原内存数据,所以恢复非常快,aof文件需要回放命令,所以相对慢。
    • 从文件大小来看,rdb随着内存大小线性增加,aof的大小取决于命令的可合并度,但一般来说aof的文件会比rdb文件大,虽然可以通过bgrewriteaof命令重写aof文件,通过合并指令缩小文件大小。
  • 为了结合两者的优点,一般两种方式共同开启,很显然就是rdb数据作为存量备份,aof数据作为增量备份。

    主从数据同步

    从节点的数据备份其实和宕机的数据恢复过程差不多,但首先它是异步的,不影响主线程的工作。
  1. 从节点 -> ping -> 主节点
  2. 主节点触发bgsave,期间增量变更命令会保存在内存中,dump完发送给从节点。
  3. 从节点基于主节点的rdb数据进行全量数据备份。
  4. 增量命令主节点通过异步发送从节点。

    集群模式

    在集群模式下,一般存在多个redis实例节点,每个节点分配持有多个slot,该映射关系存放在每个节点中。
    客户端通过将请求的数据进行分片(CRC16(key) % 16535)并将数据散列到各个节点上所持有的slot范围内。
    带来的好处:
    1、吞吐量增加:每个节点虽然只持有一部分数据,但可以接受任何客户端的读写链接,会在节点内部根据映射关系进行请求路由。
    2、水平扩容:通过slot重新分配,可以做到水平扩容,且扩容过程中保持系统的持续提供服务。
    3、高可用:通过主备模式进行数据冗余,不同节点之间通过心跳保持连接,当某个主节点不可用时,其他节点可以进行投票(少数服从多数机制)完成主备切换。如果单个节点的主备都不可用,系统也可能部分提供服务。

如何开启集群模式

开启集群的最简配置

  1. port 7000
  2. cluster-enabled yes // 开启集群
  3. cluster-config-file nodes.conf // 设定保存节点配置文件的路径
  4. cluster-node-timeout 5000
  5. appendonly yes

要让集群正常运作至少需要三个主节点,不过在刚开始试用集群功能时, 强烈建议使用六个节点: 其中三个为主节点, 而其余三个则是各个主节点的从节点。

如何进行reshard和新增节点

不是rehash,也不是rebalance,而是reshard

  1. // 添加master节点,第一个ip是新节点,第二个ip是任意一个已存在的节点,可以通过cluster nodes命令查看
  2. ./redis-trib.rb add-node 127.0.0.1:7006 127.0.0.1:7000
  3. // 触发重新分片
  4. ./redis-trib.rb reshard 127.0.0.1:7000

渐进式hash的过程

在Redis中,键值对(Key-Value Pair)存储方式是由字典(Dict)保存的,而字典底层是通过哈希表来实现的。通过哈希表中的节点保存字典中的键值对。在字典内部,维护了两张哈希表。 一般情况下, 字典只使用 ht[0] 哈希表, ht[1] 哈希表只会在对 ht[0] 哈希表进行 rehash 时使用。

  • 什么时候触发rehash?
    • 每次操作都会判断负载因子的值是否达到某个值,从而触发扩容。
  • 如何rehash?
    • 为ht[1]分配内存空间,一般是ht[0]的两倍。
    • 初始化进度指针rehashidx=0。
    • 每一次操作,都将rehashidx下标下的链表进行迁移,针对当前操作:
      • 增:直接增加到ht[1]
      • 删改查:先操作ht[0],没有再操作ht[1]。
    • 当前下标迁移完成,rehashidx++,等待下一次。
  1. 为 ht[1] 分配空间, 让字典同时持有 ht[0] 和 ht[1] 两个哈希表。
  2. 在字典中维持一个索引计数器变量 rehashidx , 并将它的值设置为 0 , 表示 rehash 工作正式开始。
  3. 在 rehash 进行期间, 每次对字典执行添加、删除、查找或者更新操作时, 程序除了执行指定的操作以外, 还会顺带将 ht[0] 哈希表在 rehashidx 索引上的所有键值对 rehash 到 ht[1] , 当 rehash 工作完成之后, 程序将 rehashidx 属性的值增一。
  4. 随着字典操作的不断执行, 最终在某个时间点上, ht[0] 的所有键值对都会被 rehash 至 ht[1] , 这时程序将 rehashidx 属性的值设为 -1 , 表示 rehash 操作已完成。

渐进式 rehash 的好处在于它采取分而治之的方式, 将 rehash 键值对所需的计算工作均滩到对字典的每个添加、删除、查找和更新操作上, 从而避免了集中式 rehash 而带来的庞大计算量。
因为在进行渐进式 rehash 的过程中, 字典会同时使用 ht[0] 和 ht[1] 两个哈希表, 所以在渐进式 rehash 进行期间, 字典的删除(delete)、查找(find)、更新(update)等操作会在两个哈希表上进行: 比如说, 要在字典里面查找一个键的话, 程序会先在 ht[0] 里面进行查找, 如果没找到的话, 就会继续到 ht[1] 里面进行查找, 诸如此类。
另外, 在渐进式 rehash 执行期间, 新添加到字典的键值对一律会被保存到 ht[1] 里面, 而 ht[0] 则不再进行任何添加操作: 这一措施保证了 ht[0] 包含的键值对数量会只减不增, 并随着 rehash 操作的执行而最终变成空表。
渐进式rehash避免了redis阻塞,可以说非常完美,但是由于在rehash时,需要分配一个新的hash表,在rehash期间,同时有两个hash表在使用,会使得redis内存使用量瞬间突增,在Redis 满容状态下由于Rehash会导致大量Key驱逐。