Redis核心技术实战

  • 简单动态字符串
  • 双向链表
  • 压缩列表
  • 哈希表
  • 跳表
  • 整数数组

.

  • global hash table 保存了所有的键值对
    • 双全局表
    • 渐进式 rehash (以保证分布均匀, 查询效率)

基于多路复用的高性能 IO 模型

.

modules

  • index (hash, skiplist)
  • storage (allocator, durability)
  • high available (master/slave, sentinel)
  • high extensive (data sharding)

Redis Concepts

  • AOF
    • 为了避免额外的检查开销, Redis 在向 AOF 里面记录日志的时候, 并不会去对这些命令进行语法检查; 在命令执行后才记录日志, 不会阻塞当前的写操作
    • Always, EverySec, No (刷盘时机由操作系统控制)
    • AOF 重写机制 后台线程 bgrewriteaof, 类似于 SST 压缩
  • RDB Snapshot
    • save, bgsave – COW (旧版本数据备份)
  • 主从复制 (replicaof)
    • steps
      • 主从库建立连接、协商同步 (psync, FULLRESYNC[runId, offset])
      • 主库将所有数据同步给从库 (RDB)
      • 主库把第二阶段执行过程中新收到的写命令, 在发送给从库
    • 网络波动 - 异常
      • prev 2.8 主从全量复制
      • post 2.8 增量复制 repl_backlog_buffer 缓冲区 (buffer + offset)
        • repl_backlog_buffer 是一个环形缓冲区, 主库会记录自己写到的位置, 从库会记录自己已经读到的位置

哨兵

  • 监控
  • 选主
  • 通知
  • 主观下线/客观下线 - down-after-milliseconds - N/2+1

主库宕机, 从库选举依据

  1. 优先级
  2. 与旧主库同步程度最接近
  3. ID 小的从库得分高 (1,2 一致的情况下)

  • 基于 pub/sub 机制的哨兵集群 __sentinel__:hello
  • 基于 INFO 命令的从库列表, 帮助哨兵和从库建立连接
  • 基于哨兵自身的 pub/sub 功能, 实现了客户端和哨兵之间的时间通知

要保证所有哨兵实例的配置是一致的, 尤其是主观下线的判断值 down-after-milliseconds

cluster

在手动分配哈希槽时, 需要把 16384 个槽都分配完, 否则 Redis 集群无法正常工作.

重定向机制 MOVED

redis 集合

  • set (差集, 交集, 并集)
  • sorted set 排序统计
  • hash
  • list
  • bitmap 二值状态 - 精确统计, 内存开销大于 hyperloglog
  • hyperloglog 概率统计, 基数统计

RESP (Redis Serialization Protocol)

  • 命令
    • SET/GET
  • 单个值
  • 集合值
  • OK
  • 整数回复
    • LLEN
    • 集合命令成功操作时, 实际变动的元素个数
  • 错误信息
    • error
    • 具体的错误信息

RESP2 的五种编码类型

  • 简单字符串类型 +
    • +OK\r\n
  • 长字符串类型 $
    • $9 testvalue\r\n
  • 整数类型 :
    • :3\r\n
  • errors -
    • -ERR unknown command 'PUT'...
  • arrays *
    • *2\r\n$3\r\nGET\r\n$7\r\ntestkey\r\n

Redis Problems

.

网络连接的处理、网络请求的解析,以及数据存取的处理,是用一个线程、多个线程,还是多个进程来交互处理呢?该如何进行设计和取舍呢?

单个核心线程处理 IO 事件, 采用了多路复用机制以避免单个客户端导致的阻塞. 同时有额外的线程来完成持久化、异步删除、集群数据同步等

单线程模型可以避免多线程带来的上下文切换开销, 以及并发访问控制问题.

Redis 单线程潜在的问题

  • 大 key 操作
  • key * 或者 get all -> scan
  • 大量的客户端连接

String 的额外开销

  • 当保存 64 位有符号整数时, String 类型会保存为一个 8 字节的 Long 类型证书 (int 编码)
  • 一旦数据中包含字符串, String 类型就会使用 Simple Dynamic String 来保存
    • “\0” 额外 1 字节
    • len 4 字节
    • alloc 实际分配长度 4 字节
  • RedisObject (类型信息, 算是一种 overhead, 用于区分 Redis 中的具体数据类型)
    • 8 字节元数据
      • type
      • encoding
      • lru
      • refcount
    • 8 字节指针 - SDS

.

possible blocking

  • Redis 内部的 blocking 操作
  • CPU 核和 NUMA 架构的影响
  • Redis 核心系统配置
  • Redis 内存碎片
  • Redis 缓冲区

blocking points

  • 客户端: 网络 IO, 键值对增删改查操作, 数据库操作
  • 磁盘: 生成 RDB 快照, 记录 AOF 日志, AOF 日志重写
  • 主从: 主库生成, 传输 RDB 文件, 从库接收 RDB 文件, 清空数据库, 加载 RDB 文件
  • 切换集群实例: 向其他实例传输 hash 槽信息, 数据迁移

.

  1. 集合全量查询和聚合操作
  2. bigKey 删除
  3. 清空数据库
  4. AOF 日志同步写
  5. 从库加载 RDB 文件

sub-threads in redis

after redis 4.0 - pthread_create

  • aof write
  • key-value delete
  • close file (async)

redis with cpu

NUMA to reduce context switch (socket change while processing redis)

slow redis

9 check list

  • 获取 redis 实例在当前环境下的基线性能
  • 是否用了慢查询命令 - redis-cli –intrinsic-latency
    • 考虑采用其他命令替代
    • 客户端聚合
  • 是否对过期 key 设置了相同的过期时间
  • 是否存在 bigkey?
  • AOF 的配置级别是什么, 业务层面是否是强需求
    • 磁盘压力过大, 阻塞 AOF 重写子进程, 进而阻塞住了 fsync 后台线程
  • 实例内存使用是否过大, swap 如何
  • 实例运行环境是否启用了透明大页机制
  • 是否运行了 Redis 主从集群
    • 把主库实例控制在 2~4GB, 以避免从库加载 RDB 文件导致阻塞
  • 是否使用了多核 CPU 或 NUMA 架构的机器运行 Redis 实例

内存碎片

info memory, 三项指标, use_memory (应用所需)、used_memory_rss (实际分配)、mem_fragmentation_ratio 内存碎片率 (1-1.5 算是正常水平)

  • 重启 Redis
  • 启用内存碎片自动清理
    • active-defrag-ignore-bytes 内存碎片的字节数达到 100MB, 开始清理
    • active-defrag-threshold-lower 内存碎片空间占操作系统分配给 Redis 的总空间比例的 10%, 开始清理
    • active-defrag-cycle-min 自动清理过程所用 CPU 时间的比例不低于 25%
    • active-defrag-cycle-max 自动清理过程所用 CPU 时间的比例不高于 75%

缓冲区

CLIENT LIST (缓冲区溢出时, Redis 会主动关闭客户端连接)

  • cmd: 客户端最新执行的命令
  • qbuf: 输入缓冲区已经使用的大小
  • qbuf-free: 输入缓冲区尚未使用的大小

输出缓冲区: 两部分, 一部分是大小为 16KB 的固定缓冲空间, 用来暂存 OK 响应和错误信息; 另一部分时可以动态调整空间的缓冲区域.

缓冲区溢出的三种情况

  • 服务器返回 bigkey 的大量数据
  • 执行了 MONITOR 命令
    • 输出结果持续占用输出缓冲区
  • 缓冲区大小设置不合理
    • client-output-buffer-limit

复制缓冲区

在全量复制的过程中, 主节点在向从节点传输 RDB 文件时, 会继续接收客户端发送的写命令请求. 这些写命令就会先保存在复制缓冲区, 等 RDB 文件传输完成后, 再发送给从节点去执行. 如果在全量复制时, 从节点接收和加载 RDB 比较慢, 同时主节点接收到了大量的写命令, 就可能导致复制缓冲区溢出.

Redis 数据库双写不一致问题

.

缓存问题

.

事物

MULTI/EXEC/DISCARD/WATCH

  • 命令入队时就报错, 会放弃事务执行, 保证原子性
  • 命令入队时正常, 执行时报错, 不保证原子性
  • EXEC 执行时实例故障, 如果开启了 AOF,可以保证原子性

主从同步

读取过期数据

  • 异步复制, 无法保证强一致性 | 保证良好网络环境, 使用监控程序监控从库复制进度, 一旦从库复制进度延迟超出阈值, 关闭从库与客户端的连接
  • 使用 EXPIREAT/PEXPIREAT 设置过期时间点, 而不是相对时间 (考虑到同步延迟, NTP 同步)

practical experience

redis 变慢 checklist

  • 使用复杂度过高命令或一次查询全量数据
  • 操作 bigkey
  • 大量 key 集中过期
  • 内存达到 maxmemory
  • 客户端使用短连接
  • 当 redis 示例的数据量过大时, RDB 和 AOF 都会导致 fork 耗时严重
  • AOF 的写回策略为 always, 次次刷盘
  • Redis 实例运行机器的内存不足, 导致 swap
  • 进程绑定 CPU 不合理
  • Redis 开启了透明大页机制
  • 网卡压力过大

慢查询日志 latency monitor

SLOWLOG GET 1

  • slowlog-log-slower-than 微秒
  • slow-max-len 最多纪录多少命令

排查 bigkey

redis-cli –bigkeys

cmd info

  • server
  • client
  • stat
  • keyspace
  • commandstats
  • cpu
  • memory
  • persistence
  • replication
  • cluster
  • modules

使用建议

.

Redis 实战经验

  • 针对持久化需求, 使用全量 RDB + 增量 AOF 策略
  • AOF 日志刷盘时, 使用额外的 BIO 线程
  • 增加了 aofnumber 配置, 控制日志文件的数量上限, 避免磁盘写满
  • 定制化数据结构, 尽可能充分利用内存空间
  • 结合 rocksdb (SSD), 实现冷热数据分离
  • 使用 Redis 集群来服务不同的业务场景需求, 每个业务线拥有独立的资源, 互不干扰
    • 客户端连接监听和端口自动增删
    • Redis 协议解析: 确认路由, 拦截非法/不支持请求
    • 路由转发
    • 指标采集
    • 平台平滑扩容/多租户支持/业务数据隔离/灵活的路由规则/丰富的监控功能

References

  • Redis 核心技术实战 - 极客时间
Get Things Done
Built with Hugo
Theme Stack designed by Jimmy