美文网首页
Redis内核实现技术

Redis内核实现技术

作者: 谭英智 | 来源:发表于2021-11-15 00:35 被阅读0次

    数据结构与对象

    简单动态字符串(SDS)

    用于保存会变化的字符串和用于缓冲区

    例如键值对里面的key/value,AOF缓冲之类

    struct sdshdr {
        int len;//字符串长度
        int free;//未使用的字节
        char buf[];
    };
    

    SDS与C字符串的区别

    • 常数时间获取字符串长度

    • 避免内存溢出

    • 内存预分配,减少内存重申请

      如果申请的len小于1MB,那么申请内存会是len*2+1的空间;如果申请len大于1MB,那么申请内存回事len+1+1MB

    • 惰性释放内存

    • 二进制安全

    • 兼容C字符串函数

    链表

    用于发布订阅、慢查询、监视、链表键等功能

    哈希表

    struct dictht{
        dictEntry ** table;
        unsigned long size;
        unsigned long sizemask;
        unsigned long used;
    };
    struct dictEntry {
        void * key;
        union{
          void* val;
            uint64_tu64;
            int64_ts64;
        }v;
        struct dictEntry* next;
    };
    
    redis-hash-table
    struct listNode {
        struct listNode* prev;
        struct listNode* next;
        void* value;
    };
    

    字典

    key/value数据结构

    用于哈希键底层实现

    struct dict {
        dictType* type;
        void* privdata;
        dictht ht[2]; //hash table
        int trehashidx;//rehash in progress flag
    };
    
    redis-dict

    使用MurmurHash算法来计算hash值

    rehash

    根据需要进行缩容和扩容,以2的n次方为单元

    过程如下:

    redis-rehash-1
    redis-rehash-2
    redis-rehash-3
    redis-rehash-4

    rehash的触发场景:

    • 目前没有执行BGSAVE或者BGREWRITEAOF,且哈希表的负载因子大于1
    • 目前执行BGSAVE或者BGREWRITEAOF,且哈希表的负载因子大于5
    • 负载因子小于0.1时,缩容

    渐进式rehash

    通过分批次,从哈希表0慢慢迁移到哈希表1,避免服务器暂停服务

    delete、find、update会同时在两个哈希表操作

    跳跃表

    是一种有序的链表结构

    查找算法复杂度平均logN,最坏N

    作为有序集合键的底层实现

    redis-skip-list

    整数集合

    struct intset {
        uint32_t encoding;
        uint32_t length;
        int8_t contents[]; //int16/int32/int64
    };
    

    通过排序数组,来保持set内整数唯一

    压缩列表

    是列表键和哈希键的底层实现之一

    如果键是小整数和短字符串为主,就会使用压缩列表来存储

    用于节省内存

    对象

    redis-obj

    内存回收

    使用引用计数来回收内存

    数据库

    读写操作

    数据库读写除了对键空间进行读写操作,还会包含以下操作:

    • 更新是否读命中或者不命中
    • 更新键的LRU
    • 如果读的一个键过期,则删除过期键
    • 如果客户端使用了watch来监视某个键,服务器对此键修改后,会把此键标记为脏
    • 每次修改一个键后,会把脏计数加一,这个计数会触发持久化和复制操作
    • 如果服务器开启了数据库通知功能,会在对键修改后,服务器会按配置发送相应的数据库通知

    过期处理

    键过期的时间保存在redisDb的expires字典中

    struct reidstDb {
        dict* expires;
    };
    
    redis-expired

    过期键删除策略

    • 惰性删除

      如果读到某个过期键,则删除此键

    • 定期随机删除

      定期随机挑选一些键进行检查,并删除其中的过期键

    时间事件

    通过一个链表来维持时间超时事件

    每次都必须遍历整个链表才知道哪些事件需要触发

    持久化与复制

    生成RDB文件

    • 执行SAVE或者BGSAVE会创建一个新的RDB文件,程序会对数据库的键进行检查,已过期的键不会保存到新创建的RDB文件中。
    • 通过配置定期执行,通过时间间隔内,写入的次数来决定
    • SAVE会阻塞Redis;BGSAVE会产生一个子进程,来生成RDB文件

    载入RDB文件

    • 服务器启动时,检测是否存在RDB文件,如果存在自动载入
    • 如果是主服务器,载入RDB文件时,会过滤过期键
    • 如果是从数据库,载入RDB文件时,不会过滤过期键,因为从数据库将会被主数据库覆盖
    • 载入RDB时,服务器会一直阻塞

    AOF文件写入

    • 写操作会写入AOF缓冲区
    • 每一秒/每一次/每个大循环,写操作的命令的缓冲区会sync到磁盘
    • 过期键被清除时,会写入一个del命令

    AOF载入

    • 如果开启了AOF持久化,服务器会使用AOF来还原数据库

    AOF重写

    • 单纯的通过命令来维持AOF文件,会造成文件太大。通过重写,把冗余状态的写命令合并,可以减少AOF文件的大小
    • 重写期间,新的写命令会同时写入AOF缓冲区和AOF重写缓冲区,来保证重写后的AOF文件的一致性
    • AOF重写,会过滤过期键

    复制

    • 通过sync初始同步RDB,接着同步写命令来保持主从服务器的一致性
    • 断线重连psync部分重传来把断线期间的写命令同步给从服务器,来恢复一致性
      • 复制积压缓冲区是固定大小的队列,默认1MB
      • 如果主从的offset偏差超过了复制挤压缓冲区,那么psync将不能适用,同步会退化成全同步sync
    • 主服务器删除一个过期键,会发送一条DEL到从服务器
    • 从服务器不主动删除过期键
    • 主从通过ping pong来维持心跳检测

    Sentinel

    redis-sentinel
    • Server1是主数据库
    • 其他为从数据库
    • 如果server1对sentinel的应答超时,那么sentinel会对server1进行下线
    • 并从其他从数据库中挑选一个出来作为主数据库
    • 其他从数据库则复制新的主数据库
    • 如果server1又重新上线,则会作为从数据库存在

    Sentinel与服务器的连接

    redis-sentinel-connect
    • 由于redis的发布订阅功能,被发送的消息不会保存在redis中,如果发送时,接收信息的客户端不在线,那么客户端就会丢失这条信息
    • 为了不丢失任何信息,sentinel专门用一个订阅连接来接收该频道的信息

    获取服务器信息

    redis-get-master
    redis-get-slave
    • Sentinel每10秒向master服务器发送INFO命令
    • 从INFO信息获取master的信息并更新内存结构
    • 从INFO信息获取slave的信息,并向slave发起订阅连接

    感知其他sentinel节点

    redis-feel-others-sentinel
    redis-connect-sentinel
    • sentinel每两秒会发送hello到master服务器
    • 同时sentinel会订阅hello信息
    • sentinel发送的hello信息会进入订阅队列,并被所有的sentinel消费
    • 通过订阅hello信息,sentinel可以发现其他连接着同一个主服务器的sentinel
    • 并发起连接操作

    主观下线

    redis-sentinel-ping
    • sentinel会向其他所有节点每秒发送ping
    • 其他节点如果返回pong失败,在一定时间后,sentinel会判定此服务器主观下线

    客观下线

    sentinel monitor master 127. 0. 0. 1 6379 2
    

    通过配置指定达到多少个sentinel判定服务器为主观下线,来触发客观下线

    上面的命令表达达到2个sentinel判定服务器为主观失效,则此服务器为客观下线

    Sentinel master选举

    得到半数以上票数的sentinel,将成为master

    sentinel master的工作

    • 选举redis数据库主服务器
    • 修改从服务器的复制目标
    • 将旧的主服务器变为从服务器

    集群

    通过对key进行hash分片,通过分布式集群统一对外提供数据访问。集群提供复制和分片功能,实现高可用与大数据存储量功能

    集群加节点

    redis-cluster-meet
    • 通过发送meet命令加入集群
    • 加入后的新节点通过gossip协议同步给集群中的其他节点
    • 加入后,如果整个集群还没完全分配槽,则集群不可用

    槽slot

    • 整个数据库有16384个槽
    • 集群中的每个节点可以处理0到16384个槽
    • 当16384个槽都有节点处理,并处理的节点都在线,则集群处于上线状态;否则为下线状态
    • 节点通过广播来告诉其他节点本节点处理的槽信息
    • 节点通过cluster addslots来加槽

    执行命令

    客户端向在线的集群的其中一个节点发送命令

    • 如果key的槽正好是这个节点,则这个节点接受命令并处理
    • 如果key的cao不在这个节点,则此节点会返回MOVE错误,客户端会redirect到正确的节点

    节点数据库

    • 单机数据库可以使用0到16个不同的数据库
    • 节点数据库只能使用第0个数据库

    slots_to_keys

    • 使用跳跃表来组织结构
    • 使用key的槽号来排序
    • 用来管理同一槽的数据,例如要批量迁移

    重新分片

    • 可以将任意数量已经指派给某个节点的槽改为指派给另一个节点
    • 重新分片过程中,不影响集群的在线状态
    • 源节点和目标节点都可以继续处理命令
    redis-slot-reballance

    重分片过程中执行命令

    • 正在迁移的源节点接受到客户端请求

    • 如果key是本节点处理,则查找key是否在本数据库中

    • 如果在本数据库,则执行命令并返回

    • 否则返回客户端一个ASK错误,指引客户端转向目标节点执行命令

    redis-ask

    复制与故障转移

    • 集群中的节点分为主节点和从节点
    • 从节点关联一个节点为主节点,通过复制来同步主节点的数据
    • 但主节点下线,从节点会上升为主节点
    • 旧的主节点重新上线,则会作为从节点继续服务

    状态检测

    • 集群中的每个节点都会向其他节点发送ping来检测其他节点的状态
    • 如果发现某个节点pong返回异常,则会标志为主观下线
    • 当集群中的半数以上主节点标志某个节点为主观下线,则此节点会被判定为下线,并会通知集群中的所有节点,此节点下线
    • 此时如果此节点有从节点
    • 则从节点会选举一个新的主节点,并把主节点的所有槽指向新的主节点
    • 选举新的主节点的规则是,从节点向所有的主节点请求投票给自己,如果节点收到超过半数以上的票,则会成为master。依据先到先得的规则

    Redis的不足与改进

    • 利用多核优势,使用多线程处理
    • 有序列表使用数组,更好的利用计算机缓存
    • 更丰富的数据结构类型
    • 时间事件不要使用链表,应该使用更高级的数据结构
    • 利用SIMD计数,提高并行度
    • 对整数的处理可以引入protobuf的技术来压缩
    • 如果内存不足时,可以引入磁盘辅组存储,而不是直接拒绝服务
    • 更丰富的热点统计数据,例如top 10的key访问
    • 热点倾斜数据自动re-balance

    相关文章

      网友评论

          本文标题:Redis内核实现技术

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