美文网首页
Redis 中间件

Redis 中间件

作者: Chuck_Liu | 来源:发表于2019-05-06 11:35 被阅读0次
    本文试图让读者对 Redis 有一个整体的认识。如果想深入学习,可以先阅读掘金小册上的 “老钱 Redis”,然后再找一本 Redis 实体书啃啃。
    

    1. 是什么

    Redis「Remote Dictionary Service」是基于内存的存储中间件,通常用于数据库、缓存、消息队列。

    2. 基础 - 数据结构

    Redis 有 5 中数据结构:string、list、hash、set 和 zset(有序集合),分别对应 Java 中的 String、LinkedList、HashMap、Set、SortedSet。

    string 字符串

    string 是 Redis 中最常见的数据结构,也称 SDS「Simple Dynamic String」,通常作为 key 或 value(最大长度 512 M)。图中粉色部分为 Redis 对象的通用头部,ptr 指向 SDS。string 按长短分以 embstr(len <= 44) 和 raw 的形式存储。

    字符串在长度小于 1 M 时采用加倍扩容;大于 1 M 时采用 +1 M 扩容。

    list 列表

    list 对应的是链表而不是数组,插入删除快而索引慢,可以用于消息队列。list 的底层不是简单的 LinkedList,而是 ziplist(压缩列表)或 quicklist(快速列表)。

    quicklist

    压缩列表使用一块连续的内存,元素间紧挨着存储没有空隙。

    ziplist

    每个元素中包括前一个元素的长度、当前元素的编码和内容,在编码中标识当前元素的长度。在没有前后指针时,能够快速的前后索引。

    struct entry {
        int<var> prevlen; // 前一个 entry 的字节长度
        int<var> encoding; // 元素类型编码
        optional byte[] content; // 元素内容
    }
    

    list 在增加元素时会根据内存分配算法和当前内存大小,决定是在原有地址上扩展还是重新分配内存

    当某个元素的长度超过 254 个字节时,后面元素的 prevlen 需要从 1 字节改成 5 字节用于表示上一元素的长度。这个操作可能会造成后面的元素雪崩式的更改 prevlen,即 联级更新。因此,list 中存储的元素不应该太多、太大。

    hash 字典

    hash 是 Redis 中的字典结构,内部实现与 Java 中的 HashMap 一致(数组 + 链表)。字典内部有两个 HashTable,Rehash(扩容或缩容)时用于存储新旧两份数据。

    扩容时机:元素个数等于数组长度(bgsave 时 5 倍数组长度时扩容)
    扩容方式:2 倍数组长度
    缩容时机:元素个数少于数组长度 10%

    set 集合

    set 是没有排序的字符串集合,不允许出现重复元素,内部结构与 hash 字典一致,只是 value 为 null。

    zset 有序集合

    zset 是有序集合,内部实现为跳跃链表,用于支持随机地插入和删除。

    skiplist

    跳跃链表其实是基于 LinkedList + 多级索引 的实现。链表用于前后索引,多层级索引通过类似 2 分的思路帮助快速定位,复杂度 O(lgn)。跳跃链表相比二叉树实现更加简单,通过随机层数来解决平衡的问题。

    节点更新的过程是先删除再添加。

    3. 应用

    分布式锁

    Redis 底层是单线程、事件循环

    // 先争抢再设置过期时间,容易死锁
    setnx lock_abc true
    expire lock_abc 5

    // 原子命令
    set lock_abc true ex 5 nx

    延时队列

    异步消息队列

    // 阻塞读 notify-queue 队列
    blpop notify-queue

    // 使用 redis zset 来实现延时队列,按 score 获取需要执行的任务
    redis.zrangebyscore("delay-queue", 0, time.time(), start=0, num=1)

    HypeLogLog

    HypeLogLog 是 Redis 的高级数据结构,用于获取唯一元素总数。基于数学概率公式,通过计算某一事件连续发生的最大次数估算实验的次数。Redis 通过对元素进行哈希,记录从某位开始连续出现 0 的最大次数,来估算元素样本的大小。

    布隆过滤器

    布隆过滤器是一个不精确的 set 结构,用于判断某个元素是否存在过,存在一定误判。内部实现是一个大型的位数组,通过多次哈希进行散落。效果与空间向量类似,同样的元素散落结果一定相同。

    GeoHash

    在 Redis 中基于 zset 实现,score 是 GeoHash 的值。原理是将经纬度经过哈希算法映射到一条直线上的多个值,哈希值约接近表示两个点约接近。

    4. 原理

    IO 模型

    Redis 是单线程程序。

    使用非阻塞 IO:能读时读,能写时写,无线程阻塞。

    非阻塞 IO 基于操作系统底层的事件轮询 API,注册监听基于回调。

    持久化

    Redis 提供两种持久化方式:

    1. RDB - 一段时间内生成指定时间点生成数据集快照 (snapshot)
    2. AOF - 增量日志

    Redis 提供了 SAVE 和 BGSAVE 两个命令来生成 RDB 文件,前者是阻塞的,后者是后台 fork 子进程。

    AOF 持久化实现可以分为命令追加 (append)、文件写入 (write)、文件同步 (fsync) 三个步骤。Append 追加命令到 AOF 缓冲区,Write 将缓冲区的内容写入到程序缓冲区,Fsync 将程序缓冲区的内容写入到文件。

    由于 AOF 比 RDB 文件更加完整,Server 启动时优先采用 AOF 文件进行恢复。

    主从复制

    主从复制是指一个 Server 复制另一个 Server 的数据。

    主从复制的对象:mater 和 多 Slaver,和集群的概念区分开。

    主从复制的三个阶段:复制初始化(建立连接)、数据同步(同步现有数据)和命令传播(同步增量数据)。

    主从复制的策略:从未同步的 Slaver 全量同步,同步过的 Slaver 部分同步

    5. 集群

    大规模数据存储系统都会面临水平扩展的问题。水平扩展可以通过数据分片来解决,通过一致性哈希来避免大量的 rehash 数据迁移。但是数据分片如何屏蔽业务,在 Redis 3.0 支持集群以前,业界有两种的解决方案。

    一是使用中间代理层来屏蔽集群分布,优点是数据迁移和运维方便,缺点是转发有一定的性能损失,代表有 Twitter 的 Twemproxy 和豌豆荚的 Codis。

    二是将数据路由和故障转移封装到 client,优点是去中心化、可扩展性高,缺点是运维困难。

    Redis 3.0 版本开始官方正式支持分集群,集群通过分片进行数据共享,分片内采用一主多从的形式进行副本复制,并提供复制和故障恢复功能。

    哈希槽

    Redis Cluster 的数据分片依赖哈希槽(slot)实现,集群预先划分 16384 个 slot,每个分片负责一部分 slot。请求时先通过 key 计算哈希值,然后映射到唯一的 slot,最后直连对应的数据分片。

    分片

    Redis Cluster 的数据分片通常有多台服务组成,一主多从(master and slavers)。master 负责写(或读),slaver 负责读(或转发写请求)。主从服务使用 Raft 协议完成 Leader 选举(故障转移),使用 Epoch(纪元)的概念来给事件增加版本号。

    相关文章

      网友评论

          本文标题:Redis 中间件

          本文链接:https://www.haomeiwen.com/subject/xplwnqtx.html