《Redis设计与实现》

·  阅读 95

Redis

6大数据结构

动态字符串(sds)

链表

字典

  • dict结构

跳跃表(zkiplist)

  • zskiplistNode、zskiplist结构组成

整数集合(intset)

  • 支持根据数据类型自动升级

压缩列表(ziplist)

  • 支持由后向前访问
  • 连锁更新

5种对象

字符串(String)

  • set、sget

  • 数据结构

    • 整数值的字符串对象、embstr编码的简单动态字符串对象、简单动态字符串对象

哈希(Hash)又名字典

  • hmset、hget

  • 数据结构

    • ziplist、hashtable

列表(List)

  • lpush、lrange

  • 无序列表

  • 数据结构

    • ziplist、linkedlist

集合(Set)

  • sadd、amembers

  • 数据结构

    • intset、hashtable[hash元素中key指向的value为null]

有序集合(zset)

  • zadd、zrangebyscore

  • 数据结构

    • ziplist、skiplist

其他内容

  • 类型检查

    • 检查redisObject中type属性(键对应的值对象)类型
  • 命令多态

    • 不区分类型:按照键类型多态
    • 区分类型:按照值编码方式多态
  • 内存回收

    • redisObject中引用计数器
  • 对象共享

  • 对象空转时长

    • redisObejct中lru属性:记载上一次访问时间

应用业务

大key定义:单个key中存储的value很大;列表中元素过多

单个value超过1MB、列表元素超过1万个

服务器

不同数据库的存储

  • redisServer.db[]数组中,select命令切换

单个数据库中键空间

  • redisDb.dict字典中,key是字符串对象,value是其他类型对象

键的过期时间

  • redisDb.expires字典中,字典中key是键,value是键过期时间戳

过期键判定

  • is_expired(key)

过期键删除策略

  • 定时删除

    • cpu占用时间长
  • 惰性删除

    • 内存占用多
  • 定期删除

    • 定期频率和时长

持久化

RDB持久化:保存数据库中键值对

  • 生成RDB文件

    • save:主进程
    • bgsave:子进程
  • 触发持久化条件

    • redisServer中saveParams数组:多久修改操作多少次
    • dirty计数器
    • lastsave属性
  • RDB文件组成

    • REDIS + 数据库版本 + database[0] / datebase[1] + EOF+check_sum(校验和)

AOF:保存Redis写命令记录数据库状态

  • 持久化实现

    • 记录命令:reidsServer.aof_buf

    • 写入AOF文件:aof_buf的内容写入AOF文件。何时同步依赖apendfsync配置

      • always
      • everysec
      • no
  • AOF文件载入与数据还原

    • 启动程序时进行文件命令的重执行
  • AOF文件重写

    • 背景:命令积累多。只记录一个key终态数据
    • 实现:遍历数据库,遍历key,用一条命令记录键值状态;过期时间
    • 后台重写:开启子进程进行重写,父进程追加AOF缓冲区 + AOF重写缓冲区;子进程执行完,父进程调用信号处理函数将重写缓冲区内容保存进新的AOF文件中

事件

文件事件

  • 客户端-服务端:套接字

  • 文件事件处理器

    • IO多路复用程序

    • 文件事件分派器

    • 事件处理器

      • 连接应答处理器
      • 命令请求处理器
      • 命令回复处理器
  • 事件类型

    • 读事件:AE_READABLE
    • 写事件:AE_WRITEABLE

时间事件:serverCron函数

  • 定时事件
  • 实际执行:周期事件

客户端

服务器状态结构中保存了clients链表:保存所有客户端状态

客户端状态属性

  • 套接字描述符

    • int fd
  • 标志:标志客户端角色

    • int flags
  • 输入缓冲区

    • sds querybuf:最大1GB
  • 命令与命令参数

    • robj **argv
    • int argc
  • 命令的实现函数

    • redisCommand *cmd
  • 输出缓冲区

    • 可变大小

      • list *reply
    • 固定大小

      • char buf[16KB]
      • int bufpos:已使用的字节数量
  • 身份验证

    • int authenticated:决定能否处理该客户端命令
  • 时间

    • ctime、lastinteraction

客户端类型

  • 普通客户端
  • 伪客户端

XMind - Trial Version

复制

作用:从服务器复制主服务器数据内容

命令:slaveof 主ip 主port

旧版复制功能缺陷

  • 2.8版本之前从服务器与主服务器断开重连之后需要执行sync同步操作,耗时
  • 与新版区别:新版支持断开重连之后部分重同步(psync)

主要模块

  • 复制偏移量

    • 主从服务器都维护一个偏移量,通过偏移量值是否一致判断同步状态是否达到一致
  • 复制积压缓冲区

    • 主服务器维护
    • 固定FIFO队列,默认1MB
    • 传播命令时同时写入队列
  • 服务器运行ID

  • psync实现

    • 第一次复制:完整重同步;非第一次->psync 主运行ID offset->①fullresync(完整重同步)②continue(部分重同步)

具体实现

  • 1、设置主服务器ip和port -> 2、建立套接字连接

  • 3、发送ping命令 -> 4、身份验证

  • 5、发送端口信息 -> 6、同步

  • 7、初次同步后的命令传播

  • 8、心跳检测

    • 命令:replconf ack offset / info replication

    • 作用

      • 检测主从之间网络
      • 辅助实现min-slaves配置
      • 检测命令丢失

集群

集群中的节点通过发送和接受消息来进行通信

节点

  • 一个集群由多个节点组成,多个主节点、从节点

  • 节点中数据结构

    • clusterNode记录本节点管理的槽信息
    • clusterState记录槽被指派给了哪些节点信息
  • 主节点

    • 管理槽信息
  • 从节点

    • 复制主节点信息,主节点下线时竞争主节点[故障转移]

  • 一个集群总共是16384个槽、每个槽有多个键值对

  • 槽指派

    • n个槽可以指派给单个节点
  • 重新分片

    • redis-trib:将某个槽的键值对转移给另一个节点

    • MOVED错误

      • 槽负责权已经交接完毕,客户端访问旧节点返回信息
    • ASK错误

      • 槽负责权交接过程中,客户端访问旧节点返回信息

消息

  • 消息类型

    • MEET:某个节点申请加入集群
    • PING、PONG:保持节点之间的沟通
    • FAIL:针对主节点下线的广播
    • PUBLISH:客户端向节点发送命令
  • 结构

    • 消息头

      • 发送者自身节点信息
      • 消息正文
    • 消息正文

      • 不同类型的消息结构

Sentinel

没看下去!下次有兴趣再看

XMind - Trial Version

发布与订阅

频道

  • 订阅方式:subscribe c1,c2...

  • 退订:unsubscribe c1,c2....

  • 作用

    • 客户端订阅频道,频道收到消息通知所有订阅者;以及所有命中频道的模式订阅者
  • 实现方式

    • redisServer中字典结构:pubsub_channels
    • 字典结构:key,value(链表)

模式

  • 订阅方式:psubscribe p1,p2....

  • 退订方式:unpsubscribe p1,p2....

  • 实现方式

    • redisServer中链表结构:pubsub_patterns

消息发送

  • publish 频道 消息内容

查看订阅

  • 频道

    • 查询频道订阅者数量

      • pubsub numsub [channel1,channel2.....]
    • 查询频道/与频道模式匹配的所有频道

  • 模式

    • 查询被订阅模式数量

      • pubsub numpat

Q:为什么频道的数据结构与模式的数据结构不一样

  • 猜测是因为频道的名称很精准,hash很方便;模式的名称是模糊的,用hash不方便

事务

使用方式

  • multi、命令1、命令2....、exec

实现原理

  • 客户端切换至事务状态:client.flags|=REDIS_MULTI 打开事务标识

  • 事务存储结构

    • 事务状态:MultiState

      • 命令队列数组

        • 先进先出
      • 命令个数计数器

  • ACID性质

    • 原子性

      • 要么命令都执行要么都不执行。不支持回滚
    • 一致性

      • 处理错误

        • 入队错误:命令不存在->事务被拒绝
        • 执行错误:命令参数不对->该条命令不会执行
        • 服务器停机:持久化模式都会保证数据一致
    • 隔离性

      • redis事务是单线程的,串行保证事务的隔离
    • 耐久性

      • 由持久化模式决定

        • 无持久化:无耐久性
        • RDB->无耐久性
        • AOF + appendfsync=always -> 有耐久性
        • AOF + appendfsync=everysec -> 无耐久性
        • AOF + appendfsync=no -> 无耐久性
      • 含义:事务结果保存在永久性存储介质里

WATCH

  • 监听某个键是否有被修改

    • 被修改了->打开客户端REDIS_DIRTY_CAS标识

      • 事务不能执行
  • 实现:redisDB结构中有一个键监听字典。key键、value监听的客户端列表

Lua脚本

初始化lua环境

  • 1、创建lua环境、2、载入函数库、3、创建redis表格包含redis一些函数;4、替换lua随机函数;5、创建排序辅助函数。等等

  • lua环境协作组件

    • 1、伪客户端
    • 2、lua_scripts字典:保存sha1校验和、lua脚本内容

执行

  • eval "脚本命令" numkeys
  • redis.call/redis.pcall(redis命令)

命令

  • eval

    • 执行过程:定义脚本函数->保存脚本到lua_scripts字典中 -> 执行脚本函数
  • evalsha

    • evalsha + 校验和 -> 执行脚本
  • 脚本管理命令

    • script flush:清空Lua_script字典内容

    • script exists:判断sha1对应的脚本是否存在

    • script load:加载一个脚本函数。创建函数+保存到lua_scripts字典中

    • script kill:命令停止函数执行

      • 与shutdown nosave区别:kill未有写操作;nosave已经执行过写操作

脚本复制

  • eval、script flush、script load:直接广播给所有从服务器

  • evalsha

    • 从服务器已经都载入了lua脚本?执行广播evalsha命令:置换成等效的eval命令广播

慢查询日志

记录执行时间超过指定时长的命令

存储结构

  • 链表

实现方式

  • 命令执行前后时差>=指定时长;头插法插入一条数据、链表中数据超过指定长度,尾部删除一调数据FIFO

XMind - Trial Version

分类:
后端
标签: