乐于分享
好东西不私藏

redis7.2源码解析

redis7.2源码解析

redis7.2源码解析

[toc]

背景

学习redis7.2源码并分析redis的IO模型,基本的数据结构。

分析

epoll模型

// 以下只是一个demo
// 创建监听 socket

int
 listenfd = socket(AF_INET, SOCK_STREAM, 0);
bind(listenfd, ...);
listen(listenfd, 128);

// 创建 epoll 实例

// 一棵红黑树(存储所有被监控的 FD)。

// 一个就绪链表(存储已经发生事件的 FD)

// 一个等待队列(用于阻塞 epoll_wait 的进程)

int
 epfd = epoll_create(1024);
// 把监听 socket 加入 epoll

struct epoll_event event;

event.events = EPOLLIN | EPOLLET; // 边缘触发
event.data.fd = listenfd;

epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &event);

//进入事件循环

while
 (1) {
    int
 n = epoll_wait(epfd, events, MAX_EVENTS, -1);
    for
 (i=0; i<n; i++) {

        if
 (events[i].data.fd == listenfd) {
            // 新连接建立后epoll_ctl将conn_fd添加到epoll中

            accept()
        } else {
            // 客户端数据

            read()
            // 处理

            write()
        }
    }
}

整体架构

dispatch read

read done

execute cmd

dispatch write

write done

Client

epoll_wait

MainThread

IOThreads

DB

networking.c readQueryFromClientsocket.c connSocketEventHandlersocket.c connSocketSetReadHandlernetworking.c connSetReadHandlernetworking.c createClientnetworking.c acceptCommonHandlerae_epoll.c epoll_waitae.c aeProcessEvents()server.c aeMain()ae.c aeCreateFileEventserver.c createSocketAcceptHandlersocket.c listenToPort/anetListenserver.c initListeners()ae_epoll.c aeApiCreate()ae.c aeCreateEventLoop()server.c connTypeInitialize()server.c initServer()server.c main()networking.c readQueryFromClientsocket.c connSocketEventHandlersocket.c connSocketSetReadHandlernetworking.c connSetReadHandlernetworking.c createClientnetworking.c acceptCommonHandlerae_epoll.c epoll_waitae.c aeProcessEvents()server.c aeMain()ae.c aeCreateFileEventserver.c createSocketAcceptHandlersocket.c listenToPort/anetListenserver.c initListeners()ae_epoll.c aeApiCreate()ae.c aeCreateEventLoop()server.c connTypeInitialize()server.c initServer()server.c main()此时注册的不是 readQueryFromClient\n而是 connSocketEventHandlerconnTypeInitialize()initServer()aeCreateEventLoop()aeApiCreate() 创建epoll实例initListeners()listenToPort() / anetListen()\n创建socket + bind + listencreateSocketAcceptHandler()aeCreateFileEvent()\n注册accept回调aeMain()aeProcessEvents()epoll_wait()新连接到来acceptCommonHandler()createClient()connSetReadHandler(readQueryFromClient)connSocketSetReadHandler()aeCreateFileEvent()\nrfileProc = connSocketEventHandler\nclientData = connection*客户端发送数据(可读事件)connSocketEventHandler()conn->read_handler(conn)\n即 readQueryFromClient()

redis 6 引入IO线程且默认是关闭的。

IO线程开启场景

  1. 1. 高网络吞吐瓶颈:当redis服务器CPU在主线程处理网络IO上花费大量时间而命令执行本身很快时候。
  2. 2. 多核CPU环境:服务器拥有较多CPU核心
  3. 3. 宽带密集型业务:业务涉及大量数据传输,网络带宽称为主要瓶颈,而非内存操作速度。

开启后使用redis-benchmark进行压测对比QPS以及latency。

命令执行

  1. 1. 初始化命令表:server.c main -> server.c initServerConfig() -> server.c populateCommandTable() 填充命令表
  2. 2. 解析执行命令:networking.c readQueryFromClient -> processInputBuffer() -> processCommandAndResetClient() -> server.c processCommand()
    1. 1. 查命令表:server.c lookupCommand
    2. 2. 执行命令:server.c call

数据结构

String

  1. 1. 结构

    struct sdshdr {
        int
     len;     // 当前长度
        int
     alloc;   // 总容量
        char
     buf[];
    };
  2. 2. 特点

    O(1) 获取长度

    预分配

    惰性释放

    二进制安全:C中的字符串char *  \0 空字节为字符串结束,不能完整的表示二进制流。

List

  1. 1. 结构

    “双向链表 + 每个节点是一个 listpack(压缩连续内存块)”

    quicklist
       ↓
    head <-> node <-> node <-> node <-> tail
               ↓
            listpack
               ↓
         element element element

    listpack:
    [total_bytes]
    [element1]
    [element2]
    [element3]
    ...
    [end_mark]
    typedefstruct quicklist {
        quicklistNode *head;
        quicklistNode *tail;
        unsigned
     long count;     // 元素总数
        unsigned
     long len;       // 节点数
        int
     fill;                // 每个节点最大容量
        int
     compress;            // 压缩深度
    } quicklist;

    typedef
    struct quicklistNode {
    struct quicklistNode *prev;

    struct quicklistNode *next;

        unsigned
     char *entry;   // 指向 listpack
        size_t
     sz;              // 当前大小
        unsigned
     int count;     // 该节点元素数量
    } quicklistNode;
  2. 2. 特点

    特点
    说明
    混合结构
    链表 + 压缩块
    减少 malloc
    多元素共享一个块
    cache 友好
    连续内存
    支持压缩
    降低内存占用
    两端 O(1)
    保留链表优势

Hash

  1. 1. 结构

    逻辑结构:
    user:1
     ├── name  → "Tom"
     ├── age   → "20"
     └── city  → "Tokyo"

    小 Hash:listpack
    listpack:
    [total_bytes] [tail_offset] [num_elements] [entry1] [entry2] ... [entryN] [end_marker]
    特点:连续内存,非常节省空间,适合小对象,查找需要遍历


    大 Hash:hashtable
    typedef struct dict {
        dictht ht[2];
        long rehashidx;
    } dict;
    typedef struct dictht {
        dictEntry **table;   // 哈希桶数组
        unsigned long size;  // 桶数量
        unsigned long sizemask;
        unsigned long used;  // 已使用 entry 数量
    } dictht;
    typedef struct dictEntry {
        void *key;
        union {
            void *val;
        } v;
        struct dictEntry *next;
    } dictEntry;

    table
      ↓
    +------+--------+------+--------+
    | NULL |   *    | NULL |   *    |
    +------+--------+------+--------+
             |                 |
             ↓                 ↓
         dictEntry         dictEntry
         (name,Tom)        (age,20)
             |
             ↓
         dictEntry
         (city,Tokyo)
    编码
    使用场景
    备注
    listpack
    小 Hash
    hash-max-listpack-entries 512
    hash-max-listpack-value 64
    hashtable
    大 Hash
  2. 2. 特点

    listpack:连续内存,非常节省空间,适合小对象,查找需要遍历

    hashtable:平均 O(1) 查找,允许哈希冲突(拉链法),内存不是连续的,支持渐进式 rehash,空间换时间,删除效率高,不保证顺序(插入顺序不保留,遍历顺序不可预测)

Set

  1. 1. 结构

    编码
    使用场景
    intset
    全是整数且数量少。可以忽略
    hashtable
    普通情况。同hash结构中的hashtable只是value = NULL
  2. 2. 特点

    同hash中的hashtable

Zset

  1. 1. 结构

    编码
    使用场景
    listpack
    小 ZSet。同上
    skiplist + dict
    大 ZSet
    typedefstruct zset {
        dict *dict;          // 哈希表
        zskiplist *zsl;      // 跳表
    } zset;

    typedef
    struct zskiplist {
    struct zskiplistNode *header;
       // 头节点
    struct zskiplistNode *tail;
         // 尾节点
        unsigned
     long length;           // 元素数量
        int
     level;                      // 当前最大层数
    } zskiplist;

    typedef
    struct zskiplistNode {
        sds ele;                         // 元素
        double
     score;                    // 分数
    struct zskiplistNode *backward;
      // 后退指针,指向直接前驱节点 (用于反向遍历)

        // 层数组 (Level Array)

        // 每个元素代表一层,level[0] 是底层,level[N] 是高层

    struct zskiplistLevel {

    struct zskiplistNode *forward;
      // 前进指针,指向该层的下一个节点
            unsigned
     long span;  // 跨度:forward 指针跨越了多少个节点 (用于计算排名)
        } level[];
    } zskiplistNode;  
    随机层高是随机生成的有一个确定算法。Level 0 永远完整有序。LevelN 随机层高>= N+1的节点 根据score排序并连接起来

    | 节点 | score | 随机层高 |
    | -- | ----- | ---- |
    | A  | 10    | 3    |
    | B  | 20    | 1    |
    | C  | 30    | 2    |
    | D  | 40    | 1    |
    | E  | 50    | 3    |
    | F  | 60    | 1    |
    | G  | 70    | 2    |


    Level2:  A -------- E
    Level1:  A ---- C ---- E ---- G
    Level0:  A <-> B <-> C <-> D <-> E <-> F <-> G

    查找路径示例(找 G)
    查找 G(70):
    Level2:A → E
    E.forward=NULL → 下到 Level1
    E → G
    下到 Level0 精确定位
    移动次数远小于 7。
  2. 2. 特点

    listpack:同上

    skiplist + dict: 有序结构(按 score 排序);多层索引;支持范围查询;支持快速排名

总结

  1. 1. 整体架构总结: 单线程命令执行 + 多路复用 IO + 可选 IO 线程
  2. 2. 命令执行流程总结

    readQueryFromClient
        ↓
    processInputBuffer
        ↓
    processCommand
        ↓
    lookupCommand
        ↓
    call
    阶段
    特点
    命令表初始化
    启动时一次性构建
    命令查找
    O(1) 字典查找
    命令执行
    函数指针调用
    写回客户端
    统一事件循环
  3. 3. 核心数据结构总结: 小数据节省内存,大数据保证性能

Redis 快的根本原因:内存操作;单线程避免锁;精心设计的数据结构;IO 事件驱动;空间与时间的极致平衡。

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » redis7.2源码解析

评论 抢沙发

2 + 8 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
本站作品均采用知识共享署名-非商业性使用-相同方式共享 4.0进行许可,资源收集于网络仅供用于学习和交流,本站一切资源不代表本站立场,我们尊重软件和教程作者的版权,如有不妥请联系本站处理!

 沪ICP备2023009708号

© 2017-2026 夜雨聆风   | sitemap | 网站地图