Redis设计与实现
2025-3-28
| 2025-4-13
0  |  Read Time 0 min
type
status
date
slug
summary
tags
category
icon
password

第一章 数据结构与对象

 
redis数据库里面的每个key-value pair都是对象组成的
key总是一个string obj
value可以是字符串,list obj, hash obj, set obj, sorted set obj
 

简单动态字符串

 
 
127.0.0.1:6379[15]> set msg "hello world" OK 127.0.0.1:6379[15]> rpush fruits "apple" "banana" (integer) 2
 
key是一个字符串对象
value存的字符串底层是SDS实现的
 
sds的定义
struct sdshdr { int len; // 已使用的字节数 int free; // 未使用的字节数 char buf[]; // 字符数组,用于保存字符串 };
 
 
 
与C字符串区别:
SDS 相比传统 C 字符串有以下优点:
  1. 获取字符串长度的时间复杂度是 O(1),而不是 O(n)
  1. 杜绝缓冲区溢出
  1. 减少字符串修改时的内存重分配次数
  1. 二进制安全,可以存储任何二进制数据
  1. 兼容部分 C 字符串函数
 
notion image
获取字符串的时间复杂度不同
 
 
 
杜绝溢出
notion image
 
notion image
 
 
 
 
 
内存重分配是个耗时的操作,
sds利用空间预分配和惰性空间释放两个策略
 
空间预分配:
当 SDS 字符串需要增长时,Redis 不仅会分配所需的空间,还会额外分配一些预留空间。具体规则如下:
  1. 当新长度小于 1MB 时:额外分配与当前长度相同的未使用空间
      • 例如:如果扩展后字符串长度为 11 字节,则会额外分配 11 字节的空闲空间,总容量为 22 字节
  1. 当新长度大于等于 1MB 时:额外固定分配 1MB 的未使用空间
      • 例如:如果扩展后字符串长度为 5MB,则会额外分配 1MB 的空闲空间,总容量为 6MB
 
调用sdscat(s,”World”)
等额分配
notion image
 
 

链表

 
listNode结构:
 
typedef struct listNode { struct listNode *prev; // 前置节点 struct listNode *next; // 后置节点 void *value; // 节点的值 } listNode;
 
 
 
list结构:
typedef struct list { listNode *head; // 表头节点 listNode *tail; // 表尾节点 unsigned long len; // 链表所包含的节点数量 void *(*dup)(void *ptr);// 节点值复制函数 void (*free)(void *ptr);// 节点值释放函数 int (*match)(void *ptr, void *key);// 节点值比较函数 } list;
 
 

Redis 链表的特性

  1. 双向链表:每个节点都有指向前后节点的指针,支持双向遍历
  1. 无环:表头节点的 prev 指针和表尾节点的 next 指针都指向 NULL,链表不会形成环
  1. 带链表长度计数器:通过 len 属性可以 O(1) 时间复杂度获取链表节点数量
  1. 多态:链表节点的 value 指针可以指向不同类型的值,通过 dup、free 和 match 函数可以针对不同类型的值进行适配
 
notion image
 
 
 

字典

notion image
 
key 里面又有key-value 就是字典
 
 
 
 
notion image
 
 
 

哈希算法与索引计算

当要将一个新的键值对添加到字典中时,Redis 会执行以下步骤:
  1. 使用哈希函数计算键的哈希值
  1. 通过哈希值与 sizemask 进行位运算(按位与,&)得到索引值
  1. 根据索引值将包含键值对的节点放入哈希表数组的指定位置
    // 计算哈希值与索引的伪代码 hash = dict->type->hashFunction(key); index = hash & dict->ht[0].sizemask;
     
     

    哈希冲突解决

    Redis 使用链地址法(separate chaining)来解决哈希冲突。当两个或更多键被分配到哈希表数组的同一个索引上时,Redis 会将这些键值对以链表的形式连接起来。
     
    notion image
     
     
     
    渐进式rehash
    当哈希表需要扩展或收缩时,Redis 不会一次性地将所有键值对从旧表迁移到新表,而是分多次、渐进式地完成。这样可以避免大字典的 rehash 操作阻塞服务器。
     
    notion image
     

    阶段 1:开始前的状态

    如上图所示,在 rehash 开始前:
    • dict 结构体中的 rehashidx 字段为 -1,表示没有在进行 rehash
    • 字典只使用 ht[0] 哈希表,ht[1] 为空
    • 假设当前有 3 个键值对存储在 ht[0] 中,ht[0].size = 4,负载因子为 0.75

    阶段 2:rehash 开始

    当负载因子超过阈值,rehash 开始:
    1. 为 ht[1] 分配空间
        • 扩容情况下,ht[1].size = 第一个 >= ht[0].used * 2 的 2^n (保证为 2 的幂)
        • 缩容情况下,ht[1].size = 第一个 >= ht[0].used 的 2^n
        • 在我们的例子中,ht[1].size = 8(因为 3 * 2 = 6,而 8 是大于 6 的最小 2 的幂)
    1. 设置 rehashidx = 0
        • 将 dict 的 rehashidx 设置为 0,标志着 rehash 正式开始
        • rehashidx 表示当前正在迁移 ht[0] 中的哪个索引下的链表

    阶段 3:渐进式迁移

    在 rehash 进行期间,每次字典操作(如增删改查)都会顺带迁移一部分键值对:
    1. 迁移过程
        • 对 ht[0].table[rehashidx] 索引上的所有键值对,使用 ht[1] 的大小和掩码重新计算索引值
        • 将这些键值对放到 ht[1] 的对应位置
        • 清空 ht[0].table[rehashidx]
        • rehashidx++ (将 rehashidx 加 1,表示处理下一个索引)
    1. 图中可视化
        • 在阶段 3 的图示中,rehashidx = 2,表示索引 0 和 1 已经完成迁移
        • "name" 和 "score" 键值对已经被迁移到了 ht[1] 中
        • "age" 键值对位于 ht[0] 的索引 2 上,还未被迁移
        • 绿色虚线框表示已经迁移的索引,红色虚线框表示当前正在迁移的索引
    1. 查找过程
        • 在 rehash 期间,查找一个键时,会先在 ht[0] 中查找,如果没找到再到 ht[1] 中查找
        • 这确保了在 rehash 过程中能够正确访问所有的键值对
    1. 新增键值对
        • 在 rehash 期间,新增的键值对会直接添加到 ht[1] 中

    阶段 4:rehash 完成

    当 ht[0] 中的所有键值对都迁移到 ht[1] 后:
    1. 释放 ht[0] 的内存空间
    1. 将 ht[1] 设置为 ht[0],创建新的空 ht[1]
    1. 将 rehashidx 重置为 -1,表示 rehash 完成
     
     
    • 分摊时间复杂度
      • 避免了一次性迁移大量键值对造成的服务阻塞
      • 将 O(n) 的操作分散到多次操作中进行,每次只处理一小部分
    • 定时任务辅助迁移
      • 除了正常操作触发的迁移外,Redis 还会在定时任务中主动执行一定量的 rehash 工作
      • 这确保了即使字典没有新的操作,rehash 也能逐步完成
    • 双表并存期间的空间开销
      • 在 rehash 期间,需要同时维护两个哈希表,会占用更多内存
      • 这是用空间换时间的一种策略
     
     
     
    notion image

    跳跃表

     
    用途:
    • 有序集合(Sorted Set):跳表是Redis有序集合的底层实现之一,另一种是压缩列表(ziplist)。
      • 当有序集合的元素数量较少且元素值较小时,Redis使用压缩列表
      • 当元素数量增长或出现较大元素时,Redis会将其转换为跳表实现
    • 排行榜功能:利用有序集合可以实现游戏排行榜、积分排名等功能。
    • 范围查询:跳表特别适合范围查询操作,如ZRANGEBYSCORE命令
     
     
     
    notion image
     
     
     
     
     
    notion image
     
     
     
     
     
     
     
     
    notion image
     

    整数集合

     
    有序的整数集合
     
    127.0.0.1:6379[15]> sadd numbers 1 3 5 7 9 (integer) 5 127.0.0.1:6379[15]> object encoding numbers "intset"
     
     
     
    结构:
    typedef struct intset { uint32_t encoding; // 编码方式 uint32_t length; // 集合包含的元素数量 int8_t contents[]; // 保存元素的数组(柔性数组成员) } intset;
     
    notion image
     
    由encoding决定编码方式
     
     
    升级
    如果新元素添加到集合里,这个新元素比里面的都要长,那就要先升级
     
    升级过程包括以下步骤:
    1. 根据新编码方式,扩展整数集合底层数组的空间
    1. 将现有的所有元素转换为新编码方式,并放到正确的位置上
    1. 将新元素添加到整数集合中
     
    notion image
     
     
    notion image
     
     

    压缩列表

     
    压缩列表(ziplist)是 Redis 中一种为节约内存而设计的特殊数据结构,主要用于存储少量数据项的列表和哈希表。
     
    127.0.0.1:6379[15]> hmset profile "jacl" 28 OK 127.0.0.1:6379[15]> object encoding profile "ziplist"
     
    notion image
     
     
     
    节点结构:
    notion image
     
    notion image
     
    连锁更新
    notion image
     

    压缩列表的特性与优缺点

    优点

    1. 内存效率高:通过特殊编码存储,节省了大量指针开销,并针对小整数值做了特殊优化
    1. 存取效率高:数据存储在连续内存空间中,有利于CPU缓存
    1. 实现简单:相比于其他复杂数据结构,实现较为简单

    缺点

    1. 连锁更新问题:当一个节点的长度变化导致其prevlen字段编码方式改变时,可能会引发"连锁更新",最坏情况下的时间复杂度为O(N²)
    1. 不适合大量元素:随着元素增多,操作效率会降低
    1. 整体复制开销:任何修改操作可能需要重新分配内存并复制整个列表
     
     
     
     

    对象

     
    Redis 实现了一个对象系统,它在数据类型的底层实现和高层数据类型命令之间充当了中间层
     
    typedef struct redisObject { unsigned type:4; // 类型 unsigned encoding:4; // 编码 unsigned lru:LRU_BITS; // LRU时间(相对于全局lru_clock)或LFU数据 int refcount; // 引用计数 void *ptr; // 指向实际实现数据结构的指针 } robj;
     
     
    Redis 支持五种主要的对象类型:
    • REDIS_STRING: 字符串对象
    • REDIS_LIST: 列表对象
    • REDIS_HASH: 哈希对象
    • REDIS_SET: 集合对象
    • REDIS_ZSET: 有序集合对象
     

    对象编码 (encoding)

    每种对象类型可以由不同的底层数据结构实现,这些实现方式被称为对象的编码:
    • 字符串对象:int、embstr、raw
    • 列表对象:ziplist、linkedlist(3.2版本后改为quicklist)
    • 哈希对象:ziplist、hashtable
    • 集合对象:intset、hashtable
    • 有序集合对象:ziplist、skiplist
     
     
    Redis 使用引用计数来管理内存。每个对象都有一个引用计数,当计数变为0时,该对象会被释放。 这种机制支持对象共享——当创建一个新对象时,Redis 会尝试查找是否已经存在一个完全相同的对象,如果存在,则增加该对象的引用计数而不是创建新对象。
     
     
    对象共享:
     
    notion image
     
     
    lru:
    这个字段记录了对象最后一次被访问的时间(LRU模式)或者访问频率信息(LFU模式)。Redis 使用这个信息来实现内存回收策略,当内存超出限制时淘汰最近最少使用的对象。
     

    对象系统的好处

    1. 节省内存:针对不同的使用场景选择最高效的数据结构实现
    1. 对象共享:通过引用计数实现对象共享,进一步节约内存
    1. 类型检查:确保对不同类型的键执行正确的命令
    1. 内存回收:通过引用计数实现内存回收机制
    1. 内存淘汰:通过LRU/LFU机制实现内存淘汰策略
     
    notion image
     
     

    第二章 redis实现单机数据库

     
     

    数据库

     
    typedef struct redisDb { dict *dict; // 数据库键空间,保存所有键值对 dict *expires; // 键过期时间字典 dict *blocking_keys; // 处于阻塞状态的键 dict *ready_keys; // 可以解除阻塞的键 dict *watched_keys; // 被WATCH命令监视的键 int id; // 数据库号码 long long avg_ttl; // 数据库平均TTL统计 unsigned long expires_cursor; // 过期遍历的游标 list *defrag_later; // 推迟碎片整理的键列表 } redisDb;
     
     
    默认16个数据库
     
    select <dbid> 来切换 每个客户端都有一个 redisClient.db 指针,指向当前选择的数据库
    notion image
     
     
     
    数据库隔离,不同数据库的key空间隔离
     
     
     
    数据库key空间例子
    notion image
     
     
    key的过期机制
     
    127.0.0.1:6379[15]> EXPIRE key 5 (integer) 1 127.0.0.1:6379[15]> get key "value" 127.0.0.1:6379[15]> get key (nil)
     
     
    127.0.0.1:6379[15]> set key value OK 127.0.0.1:6379[15]> expire key 20 (integer) 1 127.0.0.1:6379[15]> ttl key (integer) 17
     
    • 过期字典
      • 每个 Redis 数据库都有一个过期字典(expires
      • 过期字典的键是数据库中的键,值是该键的过期时间(UNIX 时间戳)
    • 设置过期时间
      • EXPIRE key seconds:设置键的过期时间为指定的秒数
      • PEXPIRE key milliseconds:以毫秒为单位设置过期时间
      • EXPIREAT key timestamp:设置键过期的时间戳
      • PEXPIREAT key milliseconds-timestamp:以毫秒为单位设置过期时间戳
    • 移除过期时间
      • PERSIST key:移除键的过期时间,使其变为永久有效
    • 查询过期时间
      • TTL key:返回键的剩余生存时间(秒)
      • PTTL key:以毫秒为单位返回剩余生存时间
     
     

    过期键清除策略

    Redis 使用三种策略来删除过期的键:
    1. 惰性删除
        • 当客户端尝试访问一个键时,Redis 检查它是否已过期
        • 如果已过期,则删除该键并返回nil/null
        • 这种策略不会主动删除过期键,只在访问时才检查
    1. 定期删除
        • Redis 定期随机选择一些键检查是否过期
        • 默认每 100ms 执行一次,随机选择一些数据库,然后从每个选定的数据库中随机检查一部分键
        • 这是惰性删除和定时删除的折中方案
    1. 主从复制中的过期处理
        • 从服务器不会主动删除过期键,而是等待主服务器删除
        • 当主服务器删除一个过期键时,会向从服务器发送 DEL 命令
        • 这确保了主从服务器的数据一致性
     

    RDB 和 AOF 中的过期键处理

    1. RDB 文件
        • 生成 RDB 文件时,Redis 不会保存已过期的键
        • 加载 RDB 文件时:
          • 如果是主服务器,会忽略已过期的键
          • 如果是从服务器,会加载所有键,包括过期键
    1. AOF 文件
        • 当键过期后,如果还没被删除(惰性或定期删除),AOF 文件不会因此产生任何影响
        • 当过期键被删除时,Redis 会在 AOF 文件中追加一条 DEL 命令
        • AOF 重写时会忽略已过期的键
     
    notion image
     

    RDB持久化

     

    手动触发

    • SAVE 命令:阻塞 Redis 服务器进程,直到 RDB 文件创建完成
      • BGSAVE 命令:派生一个子进程来创建 RDB 文件,不会阻塞服务器进程
         
         
        notion image
         
         
        如果开启了aof,优先选择aof文件
         
         
         
        save执行时,服务器会被阻塞;
        bgsave fork了子进程;
         
         
        rdb文件载入会一直处于阻塞状态
         
         

        自动触发

        Redis 配置文件中的 save 配置项可以设置自动触发 BGSAVE 的条件:
        其他自动触发情况:
        • 执行 FLUSHALL 命令时(可配置)
        • 执行主从复制时,如果主节点没有正在执行 BGSAVE,则自动触发
        • 正常关闭 Redis 服务器
         
         
        RDB 文件的结构包含以下部分:
        • 文件头:包含 "REDIS" 字符串和 RDB 版本号
        • 数据库部分:包含每个数据库中的键值对数据
        • EOF 标记:表示文件结束
        • 校验和:用于验证文件完整性的数据
         

        RDB 持久化的优缺点

        优点

        1. 文件紧凑:RDB 是一个紧凑的单一文件,非常适合于备份和恢复
        1. 恢复速度快:与 AOF 相比,RDB 文件恢复速度更快
        1. 性能影响小:子进程负责持久化工作,对主进程性能影响较小
        1. 适合灾难恢复:可以将 RDB 文件保存到远程位置用于灾难恢复

        缺点

        1. 数据丢失风险:两次快照之间的数据可能丢失
        1. fork() 操作开销:在内存较大的情况下,fork() 可能导致服务短暂停顿
        1. 不适合实时持久化:无法做到像 AOF 那样的秒级或命令级持久化

        RDB 文件的恢复

        恢复 RDB 文件非常简单:
        1. 将 RDB 文件放置到 Redis 的工作目录中
        1. 启动 Redis 服务器
        1. Redis 会自动检测 RDB 文件并加载其中的数据
         
         
        notion image
         
         
         
         
         
         
         

        AOF

         
        AOF 持久化的核心思想是将服务器执行的写命令以追加的方式记录到 AOF 文件,当 Redis 重启时,重新执行这些命令来恢复数据
         
        AOF 持久化的三个步骤
        1. 命令追加(append):当 Redis 执行完写命令后,会将该命令追加到 AOF 缓冲区
        1. 文件写入(write):Redis 将 AOF 缓冲区中的内容写入 AOF 文件
        1. 文件同步(sync):Redis 调用 fsync() 或 fdatasync() 将 AOF 文件同步到磁盘
         
         
        AOF 文件使用 Redis 命令请求协议(RESP)格式保存命令,这是一种纯文本格式,可以直接阅读和编辑。
        例如,执行 SET key value 命令后,AOF 文件中会追加如下内容:
        这种格式的好处是:
        • 具有可读性
        • 与 Redis 通信协议一致,便于解析
        • 支持增量追加
         
         
        AOF 同步策略(fsync 策略)
        Redis 提供了三种 AOF 同步策略,可以通过 appendfsync 配置项设置:
        1. always:每次写命令执行完后都立即进行同步
            • 最安全,最多只会丢失一个命令的数据
            • 性能最差
        1. everysec(默认):每秒进行一次同步
            • 在安全性和性能之间取得平衡
            • 最多可能丢失 1 秒钟的数据
        1. no:由操作系统决定何时同步
            • 性能最好
            • 安全性最差,可能丢失较多数据
         
         
         
        AOF文件的载入和数据还原:
         
        notion image
         
         
         

        AOF 重写的原理

        AOF 重写不会读取和分析现有的 AOF 文件,而是通过读取服务器当前的数据库状态来实现的:
        1. Redis 创建一个新的 AOF 文件
        1. 遍历数据库中的所有键值对
        1. 用一条命令记录键的当前值,而不是之前的多条命令
        1. 用新的 AOF 文件替换旧的 AOF 文件
        例如,如果一个键先后执行了 SET key value1SET key value2SET key value3,重写后的 AOF 文件只会包含 SET key value3
         
         
        AOF 重写的实现
        为了不阻塞主进程,AOF 重写是通过子进程来完成的:
        1. Redis 创建一个子进程用于 AOF 重写
        1. 子进程创建一个新的 AOF 文件,并写入当前数据库状态
        1. 主进程继续处理命令请求,并将这些命令追加到现有 AOF 文件
        1. 主进程同时将这些新命令存入 AOF 重写缓冲区
        1. 子进程完成重写后,主进程将 AOF 重写缓冲区中的内容追加到新 AOF 文件
        1. 主进程用新的 AOF 文件替换旧的 AOF 文件
         
         
        AOF 持久化的优缺点
        优点
        1. 数据安全性高:根据同步策略的不同,最多只丢失 1 秒钟的数据
        1. 适合实时性要求高的应用:可以做到秒级或命令级持久化
        1. 文件具有可读性:AOF 文件是纯文本格式,可以直接查看和编辑
        1. 自动重写机制:避免 AOF 文件过大
        缺点
        1. 文件体积大:通常比 RDB 文件大得多
        1. 恢复速度慢:需要重新执行所有命令,相比 RDB 恢复较慢
        1. 对性能有一定影响:尤其是使用 always 同步策略时
         
         
        aof-use-rdb-preamble yes|no:是否在 AOF 文件开头使用 RDB 格式(混合持久化)
         
         
         
         
         
         

        事件

         
         
        Redis 的事件处理系统主要处理两类事件:
        1. 文件事件 (File Events):处理 Redis 服务器与客户端的网络通信
        1. 时间事件 (Time Events):处理需要在特定时间执行的任务,如定期清理过期键
         
         
        文件事件
         
        notion image
         
         
         
        notion image
        Redis 的文件事件处理器由四个组件组成:
        1. 套接字 (Socket):客户端与服务器之间的连接
        1. I/O 多路复用程序 (I/O Multiplexing):监听多个套接字的状态变化
        1. 文件事件分派器 (Event Dispatcher):接收 I/O 多路复用程序传来的套接字事件,并根据事件类型调用相应的事件处理器
        1. 事件处理器 (Event Handlers):处理具体事件的函数
        常见的文件事件处理器包括:
        • 连接应答处理器:处理客户端连接请求
        • 命令请求处理器:处理客户端发送的命令请求
        • 命令回复处理器:将命令执行结果返回给客户端
        • 复制处理器:处理主从复制相关的网络事件
         
         
         
        过程:
        • I/O 多路复用程序监听套接字状态变化
        • 当套接字变为可读或可写时,产生相应的文件事件
        • 文件事件分派器接收事件并调用相应的事件处理器
        • 事件处理器处理事件,如接受连接、读取命令、返回结果等
         
         
         
         
        时间事件
         
        1. 定时事件:在指定时间后执行一次,然后被删除
        1. 周期性事件:按照一定周期重复执行
        实际上,Redis 主要使用周期性事件,定时事件很少被使用。
         
         
         
        主要的时间事件:
        1. serverCron:Redis 服务器的周期性函数,执行以下操作:
            • 更新服务器统计信息
            • 清理过期键值对
            • 执行客户端超时检测
            • 执行数据库大小调整
            • 执行 AOF/RDB 持久化操作
            • 如果是主服务器,对从服务器进行定期同步
            • 如果是集群模式,执行集群操作
        默认情况下,serverCron 每 100 毫秒执行一次
         
         
         
         
         
        notion image
         
         
        notion image
         
         
         

        客户端

         
        常见的客户端redis-cli
         
        内部实现:
         
        typedef struct client { int fd; // 客户端套接字描述符 robj *name; // 客户端名字 int flags; // 客户端状态标志 sds querybuf; // 查询缓冲区 size_t querybuf_peak; // 查询缓冲区峰值 int argc; // 参数个数 robj **argv; // 参数数组 struct redisCommand *cmd; // 命令指针 list *reply; // 回复链表 unsigned long long reply_bytes; // 回复链表中的字节数 size_t sentlen; // 已发送字节数 time_t ctime; // 客户端创建时间 time_t lastinteraction; // 最后一次交互时间 // 其他字段... } client;
         
        输入缓冲区:
        客户端的输入缓冲区用于存储客户端发送的命令。Redis 服务器通过读取这个缓冲区来获取并执行命令。
        输入缓冲区特点:
        • 没有大小限制,但会在超过 1GB 时关闭客户端连接
        • 缓冲区过大可能导致内存占用过多
         
         
        输出缓冲区:
        输出缓冲区用于存储服务器返回给客户端的数据。Redis 使用了两种输出缓冲区:
        1. 固定大小缓冲区:用于小型回复,通常是一些状态回复
        1. 可变大小缓冲区:用于较大的回复,如大型列表、集合等
        输出缓冲区可以通过 client-output-buffer-limit 配置项来限制大小,防止单个客户端占用过多内存。
         
         
        管道化(Pipelining)
        管道化允许客户端一次性发送多个命令,而不必等待每个命令的响应:
         

        事务(Transactions)

        Redis 事务允许客户端一次性执行多个命令:
        事务的特点:
        • 命令在 EXEC 后一次性执行
        • 执行过程中不会被其他客户端打断
        • 没有回滚机制,即使有命令失败
         
         
         
         
         
         

        服务端

         

        服务器初始化与启动

        Redis 服务器的启动过程大致包括以下步骤:
        1. 初始化服务器配置:设置默认配置,然后加载配置文件
        1. 初始化数据结构:创建命令表、客户端链表、数据库等
        1. 创建事件循环:初始化事件处理机制
        1. 创建监听套接字:绑定端口,准备接受客户端连接
        1. 初始化数据库:创建指定数量的数据库实例
        1. 加载持久化数据:如果有 AOF 或 RDB 文件,从中恢复数据
        1. 初始化后台任务:设置定时任务,如清理过期键、持久化等
        1. 进入事件循环:开始处理网络事件和时间事件
         
         
         
        当客户端发送命令到 Redis 服务器时,服务器的处理流程如下:
        1. 接收请求:服务器接收客户端发送的命令请求
        1. 解析命令:将命令请求转换为命令对象
        1. 查找命令:在命令表中查找命令对应的实现函数
        1. 执行预备操作:权限检查、内存检查等
        1. 调用命令处理函数:执行具体命令
        1. 执行后续操作:更新统计信息、记录慢查询等
        1. 返回结果:将命令执行结果返回给客户端
         
         
         
         
         
         

        第三章 多机数据库的实现

         
         

        第四章 独立功能的一些实现

         
         
         
         
         
         
         
         
         
         
         
         

        常用的操作

         
        好的,Redis 是一个非常流行的键值(Key-Value)存储系统,它支持多种数据结构。以下是 Redis 最常见的五种基本数据类型及其常用的命令行操作(使用 redis-cli):
        1. String (字符串)
        • 描述: 最基本的数据类型,一个 Key 对应一个 Value。Value 不仅可以是字符串,也可以是数字(Redis 可以对其进行原子性的增加/减少操作)。最大能存储 512MB。
        • 常见用途: 缓存、计数器、分布式锁、存储 Session 信息等。
        • 常用命令:
          • SET key value [EX seconds | PX milliseconds | KEEPTTL] [NX | XX]:设置指定 key 的值。
            • EX seconds: 设置过期时间(秒)。
            • PX milliseconds: 设置过期时间(毫秒)。
            • NX: 只在 key 不存在时设置。
            • XX: 只在 key 存在时设置。
            • KEEPTTL: 保留 key 原有的 TTL。
            • 示例: SET mykey "hello"
            • 示例 (带过期时间): SET counter 10 EX 60
          • GET key: 获取指定 key 的值。
            • 示例: GET mykey
          • GETSET key value: 设置 key 的新值,并返回旧值。
            • 示例: GETSET mykey "world"
          • MSET key value [key value ...]: 同时设置一个或多个 key-value 对。
            • 示例: MSET key1 "v1" key2 "v2"
          • MGET key [key ...]: 获取一个或多个 key 的值。
            • 示例: MGET key1 key2 non_existing_key
          • INCR key: 将 key 中储存的数字值增一(如果 key 不存在,则初始化为 0 再执行 INCR)。
            • 示例: INCR page_views
          • DECR key: 将 key 中储存的数字值减一。
            • 示例: DECR items_left
          • INCRBY key increment: 将 key 所储存的值加上指定的增量值。
            • 示例: INCRBY user_score 10
          • DECRBY key decrement: 将 key 所储存的值减去指定的减量值。
            • 示例: DECRBY user_score 5
          • STRLEN key: 返回 key 所储存的字符串值的长度。
            • 示例: STRLEN mykey
          • APPEND key value: 如果 key 已经存在并且是一个字符串,将 value 追加到 key 原来的值的末尾。
            • 示例: APPEND mykey "!!!"
        2. List (列表)
        • 描述: 简单的字符串列表,按照插入顺序排序。可以在列表的头部(Lef)或尾部(Righ)添加元素。底层实际是个双向链表或压缩列表。
        • 常见用途: 消息队列、栈、最新 N 个数据(如用户最近访问的文章)。
        • 常用命令:
          • LPUSH key element [element ...]: 将一个或多个值插入到列表头部。
            • 示例: LPUSH mylist "world" "hello" (列表内容: "hello", "world")
          • RPUSH key element [element ...]: 将一个或多个值插入到列表尾部。
            • 示例: RPUSH mylist "!" (列表内容: "hello", "world", "!")
          • LPOP key [count]: 移除并获取列表的第一个元素(可指定数量)。
            • 示例: LPOP mylist (返回 "hello")
          • RPOP key [count]: 移除并获取列表的最后一个元素(可指定数量)。
            • 示例: RPOP mylist (返回 "!")
          • LLEN key: 获取列表的长度。
            • 示例: LLEN mylist
          • LRANGE key start stop: 获取列表指定范围内的元素(0 是第一个,-1 是最后一个)。
            • 示例: LRANGE mylist 0 -1 (获取所有元素)
            • 示例: LRANGE mylist 0 1 (获取前两个元素)
          • LINDEX key index: 通过索引获取列表中的元素。
            • 示例: LINDEX mylist 0 (获取第一个元素)
          • LSET key index element: 通过索引设置列表元素的值。
            • 示例: LSET mylist 0 "new_hello"
          • LREM key count element: 根据参数 count 的值,移除列表中与参数 element 相等的元素。
            • count > 0: 从表头开始向表尾搜索,移除与 element 相等的元素,数量为 count。
            • count < 0: 从表尾开始向表头搜索,移除与 element 相等的元素,数量为 -count。
            • count = 0: 移除表中所有与 element 相等的值。
            • 示例: LREM mylist 1 "world"
          • LTRIM key start stop: 对一个列表进行修剪,只保留指定区间内的元素。
            • 示例: LTRIM mylist 0 99 (保留最新的 100 个元素)
          • BLPOP key [key ...] timeout: 阻塞式列表的弹出原语,从列表头部弹出。如果列表为空,会阻塞连接直到等待超时或发现可弹出元素为止。
          • BRPOP key [key ...] timeout: 阻塞式列表的弹出原语,从列表尾部弹出。
        3. Hash (哈希/字典)
        • 描述: 一个键值(key-value)对集合,其中 key 是唯一的。非常适合用于存储对象。
        • 常见用途: 存储用户信息(如用户ID为Key,用户信息字段如name, age, email为field-value)、存储对象。
        • 常用命令:
          • HSET key field value [field value ...]: 将哈希表 key 中的字段 field 的值设为 value (如果字段不存在,则创建)。
          • HGET key field: 获取存储在哈希表中指定字段的值。
            • 示例: HGET user:1000 name
          • HMGET key field [field ...]: 获取所有给定字段的值。
            • 示例: HMGET user:1000 name age non_existing_field
          • HGETALL key: 获取在哈希表中指定 key 的所有字段和值。
            • 示例: HGETALL user:1000
          • HDEL key field [field ...]: 删除一个或多个哈希表字段。
            • 示例: HDEL user:1000 email
          • HLEN key: 获取哈希表中字段的数量。
            • 示例: HLEN user:1000
          • HEXISTS key field: 查看哈希表的指定字段是否存在。
            • 示例: HEXISTS user:1000 age
          • HKEYS key: 获取哈希表中的所有字段名(keys)。
            • 示例: HKEYS user:1000
          • HVALS key: 获取哈希表中的所有值(values)。
            • 示例: HVALS user:1000
          • HINCRBY key field increment: 为哈希表 key 中的指定字段的整数值加上增量 increment。
            • 示例: HINCRBY user:1000 age 1
        4. Set (集合)
        • 描述: String 类型的无序集合。集合成员是唯一的,不允许重复。
        • 常见用途: 标签(tagging)、共同好友、独立 IP 访问统计、判断用户是否点赞/收藏。
        • 常用命令:
          • SADD key member [member ...]: 向集合添加一个或多个成员。
            • 示例: SADD myset "a" "b" "c" "a" (最终集合为 "a", "b", "c")
          • SMEMBERS key: 返回集合中的所有成员。
            • 示例: SMEMBERS myset
          • SISMEMBER key member: 判断 member 元素是否是集合 key 的成员。
            • 示例: SISMEMBER myset "b"
          • SCARD key: 获取集合的成员数(基数)。
            • 示例: SCARD myset
          • SREM key member [member ...]: 移除集合中一个或多个成员。
            • 示例: SREM myset "c"
          • SPOP key [count]: 移除并返回集合中的一个或多个随机元素。
            • 示例: SPOP myset
          • SRANDMEMBER key [count]: 返回集合中一个或多个随机元素(不移除)。
            • 示例: SRANDMEMBER myset 2
          • SINTER key [key ...]: 返回给定所有集合的交集。
            • 示例: SADD set1 "a" "b" "c", SADD set2 "c" "d" "e", SINTER set1 set2 (返回 "c")
          • SUNION key [key ...]: 返回给定所有集合的并集。
            • 示例: SUNION set1 set2 (返回 "a", "b", "c", "d", "e")
          • SDIFF key [key ...]: 返回第一个集合与其他集合之间的差集。
            • 示例: SDIFF set1 set2 (返回 "a", "b")
        5. Sorted Set (ZSet / 有序集合)
        • 描述: 和 Set 一样也是 String 类型的元素的集合, 且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数(score)。Redis 正是通过分数来为集合中的成员进行从小到大排序。成员是唯一的, 但分数(score)可以重复。
        • 常见用途: 排行榜、带权重的消息队列、范围查找(如查找积分在某个范围的用户)。
        • 常用命令:
          • ZADD key [NX|XX] [CH] [INCR] score member [score member ...]: 向有序集合添加一个或多个成员,或者更新已存在成员的分数。
            • 示例: ZADD leaderboard 100 "player1" 95 "player2" 110 "player3"
          • ZRANGE key start stop [WITHSCORES]: 通过索引区间返回有序集合成指定区间内的成员(按分数从小到大)。
            • 示例: ZRANGE leaderboard 0 -1 (获取所有成员,按分升序)
            • 示例: ZRANGE leaderboard 0 -1 WITHSCORES (获取所有成员及分数)
          • ZREVRANGE key start stop [WITHSCORES]: 返回有序集中指定区间内的成员,通过索引,分数从高到底。
            • 示例: ZREVRANGE leaderboard 0 2 WITHSCORES (获取排名前三的玩家及其分数)
          • ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]: 通过分数区间返回有序集合的成员(默认从小到大)。
            • min 和 max 可以是 -inf 和 +inf。
            • 使用 ( 表示不包含边界,例如 (90 100 表示分数大于90小于100。
            • 示例: ZRANGEBYSCORE leaderboard 90 100 WITHSCORES
            • 示例: ZRANGEBYSCORE leaderboard (90 +inf LIMIT 0 10 WITHSCORES (获取分数大于90的前10名)
          • ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]: 返回有序集合中指定分数区间的成员,分数从高到低排序。
          • ZCARD key: 获取有序集合的成员数。
            • 示例: ZCARD leaderboard
          • ZSCORE key member: 返回有序集中,成员的分数值。
            • 示例: ZSCORE leaderboard "player1"
          • ZREM key member [member ...]: 移除有序集合中的一个或多个成员。
            • 示例: ZREM leaderboard "player2"
          • ZCOUNT key min max: 计算在有序集合中指定区间分数的成员数。
            • 示例: ZCOUNT leaderboard 90 100
          • ZINCRBY key increment member: 对有序集合中指定成员的分数加上增量 increment。
            • 示例: ZINCRBY leaderboard 5 "player1"
          • ZRANK key member: 返回有序集合中指定成员的排名(按分数从小到大,排名从 0 开始)。
            • 示例: ZRANK leaderboard "player1"
          • ZREVRANK key member: 返回有序集合中指定成员的排名(按分数从大到小,排名从 0 开始)。
            • 示例: ZREVRANK leaderboard "player1"
        通用 Key 操作命令:
        • DEL key [key ...]: 删除一个或多个 key。
          • 示例: DEL mykey mylist user:1000
        • EXISTS key [key ...]: 检查给定 key 是否存在。
          • 示例: EXISTS mykey
        • KEYS pattern: 查找所有符合给定模式 pattern 的 key (生产环境慎用,可能阻塞)。
          • 示例: KEYS user:*
        • TYPE key: 返回 key 所储存的值的类型。
          • 示例: TYPE mykey (可能返回 "string", "list", "hash", "set", "zset")
        • EXPIRE key seconds: 为给定 key 设置生存时间(秒)。
          • 示例: EXPIRE mykey 3600 (1小时后过期)
        • TTL key: 以秒为单位,返回给定 key 的剩余生存时间。
          • 示例: TTL mykey (返回 -1 表示永不过期,-2 表示 key 不存在)
        • PERSIST key: 移除给定 key 的生存时间,将这个 key 从“易失的”(带生存时间 key )转换成“持久的”(一个不带生存时间、永不过期的 key )。
          • 示例: PERSIST mykey
        • RENAME key newkey: 修改 key 的名称。
          • 示例: RENAME mykey my_new_key
        • FLUSHDB: 清空当前数据库中的所有 key (慎用!)
        • FLUSHALL: 清空所有数据库中的所有 key (极度慎用!)
        这些是 Redis 最核心的数据类型和常用命令,掌握它们就能应对大部分常见的 Redis 应用场景。你可以通过 redis-cli 连接到 Redis 服务器,然后直接输入这些命令进行尝试。输入 HELP @<datatype> (例如 HELP @string) 可以获取该数据类型相关的命令帮助。
        mysql是怎么样运行的Goroutine
        Loading...
        Catalog