myesn

myEsn2E9

hi
github

Redis 一览无余

简略总结#

  • 认识 Redis
    • 内存数据库
    • 常用于缓存、消息队列、分布式锁
    • 原子性操作
  • Redis 值的数据结构(五种常见)
    • String 字符串:缓存对象、常规计数、分布式锁、共享 session 信息等
    • List 列表(链表):消息队列等
    • Hash 哈希(无序散列表):缓存对象、购物车等
    • Set 集合(字符串的无序集合):聚合计算(并集、交集、差集)场景,比如点赞、共同关注、抽奖活动等
    • Zset 有序集合:排序场景,比如排行榜、电话和姓名排序等
    • …(还有 4 种,先不写了)
  • Redis 线程模型
    • 单线程是指
      • [接收客户端请求> 解析 > 数据读写 > 发送数据给客户端] 由主线程完成。
      • redis 程序并不是单线程
    • 6.0 前后
      • 前 - 单线程:可维护高,简单,无锁问题
      • 后 - 多线程:采用多个 I/O 线程处理网络请求(因为网络 I/O 性能瓶颈)
      • 总结:执行命令还是单线程,引入多线程为了提供网络 I/O 处理性能
  • Redis 持久化
    • AOF 日志:执行命令后,将命令写入 AOF 文件,重启时加载该文件
      • 策略:
        • Always:执行完命令后同步写入
        • Everysec:先将命令写入内核缓冲区,然后每秒将缓冲区的内容写入磁盘
        • NO:由操作系统控制何时将缓冲区的内容写进磁盘
      • 过大后:触发 AOF 重写机制
      • 缺点:文件过大,加载时间长
    • RDB 快照:将某一时刻的内存数据(频率可控),以二进制的方式写入磁盘
      • 方式:
        • save 命令:通过主线程生成,会阻塞线程
        • bgsave 命令:通过创建子进程生成,不会阻塞线程
      • 缺点:数据丢失风险高
    • 混合:4.0 新增,解决 AOF 恢复慢,RDB 数据丢失风险高,集合两者优点
  • Redis 集群
    • 方式
      • 主从复制:主同步到从(首次全量, 后续增量),主写从读
      • 哨兵模式:在主从复制的基础上,增加哨兵监控主、从、哨兵的健康状态,自动故障转移
      • 切片集群:将数据分布到不同的服务器,降低单点依赖,提高读写性能
    • 集群脑裂:网络分区(不同网络下)导致双主
      • 解决:
        • min-slaves-to-write x,限制至少 x 个从连接到主,低于时主禁写
        • min-slaves-max-lag x,限制主从数据同步的延迟小于 x 秒,超过时主禁写
  • Redis 过期删除与内存淘汰
    • 过期删除:对比过期字典,未过期返回数据,已过期删除
    • 删除策略
      • 惰性删除:访问 key 时检测是否过期,是则删除 key 返回 null
      • 定期删除:定时随机取一定数量的 key 检查,删除其中过期的
        • 流程:抽取 key → 检查是否过期,并删除 → 过期数量超过 25% 时,重复之前步骤 → 小于 25% ,等待下一轮检查
    • 持久化时过期键处理
      • AOF:
        • 对过期键添加删除指令到 AOF 文件
        • 加载时过期键不会写入 AOF
      • RDB:
        • 生成 RDB 时忽略过期键
        • 加载时主过期不加载,从全部加载
    • 主从过期键处理:从依赖主(不扫描过期键),主检查删除过期键后同步到从
    • 内存满载:达到阈值触发内存淘汰机制,配置 maxmemory
    • 内存淘汰机制策略:共种,归类为 [不进行淘汰] 和 [进行淘汰] 两类
      • 不进行淘汰:超过阈值,直接返回错误
      • 进行淘汰:分为 [在设置了过期时间的数据中] 和 [所有数据] 内进行淘汰两类,最好的是设置了过期时间且使用率最低的键
  • Redis 缓存设计
    • 雪崩:为了数据一致性,对缓存的数据设置过期时间,过期后从数据库请求并重新更新缓存,当大量键过期,请求直接到 DB 导致宕机。或 Redis 宕机。
      • 解决:
        • 过期:
          • 打散过期时间
          • 互斥锁:同一时间仅一个业务线程更新缓存
          • 双 key(主设过期,备永久)
        • 宕机:
          • 服务熔断
          • 请求限流
          • 高可用
    • 击穿热点数据过期,请求直接到 DB(雪崩的子集情况)导致宕机
      • 解决:
        • 互斥锁:同一时间仅一个业务线程更新缓存
        • 不设置过期时间
    • 穿透数据不在缓存中,也不在 DB 中,一般是业务误操作和恶意攻击两种
      • 解决:
        • 限制非法请求
        • 缓存默认值
        • 使用布隆过滤器判断数据是否存在,避免查 DB
    • 动态缓存热点数据:通过数据最新访问时间排序,留下 top 数据
    • 常见缓存更新策略
      • Cache Aside 旁路缓存(实际开发时使用,最常用,适合读多写少):应用程序直接与 [数据库、缓存] 交互,负责维护缓存
        • 写策略:先更新 DB,再删除 Cache(不能改变顺序,否则导致数据不一致,删除是懒加载思想)
        • 读策略
          • 有缓存,直接返回数据
          • 没缓存,读 DB,写入 Cache,返回数据
      • Read/Write Through 读穿 / 写穿
      • Write Back 写回:有数据丢失风险
    • 保证 Cache 和 DB 数据一致性更新数据库 + 更新缓存(使用分布式锁,加上较短的过期时间)
  • Redis 实战
    • 延迟执行队列:使用 ZSet(有序集合),其中 Score 属性存储延迟执行的时间,再利用 zrangebysocre 查询并对比时间找到待处理的
    • Redis 大 key:指 key 的 value 很大
      • 条件
        • String 类型的值大于 10 KB
        • Hash、List、Set、ZSet 类型的元素超过 5k
      • 影响
        • 客户端超时阻塞:redis 单线程执行命令,操作大 key 耗时,阻塞线程
        • 引发网络堵塞:数据 * 访问量 = 产生大流量
        • 阻塞工作线程:del 大 key,阻塞工作线程
        • 内存分布不均:集群模型在 slot 分片均匀时,出现数据和查询倾斜情况,部分有大 key 的节点占用内存多,QPS 较大
      • 排查
        1. redis-cli --bigkeys
          • 注意:
            • 在从节点执行,因为会阻塞线程
            • 无从时在低峰时间执行,可使用 -i 控制扫描间隔,降低性能影响
        2. SCAN :先用 SCAN 扫描,再用 TYPE 获取 key 的类型
          • String 类型:使用 STRLEN 获取长度
          • 集合类型:
            • 平均大小 * 数量
            • MEMORY USAGE 查询键占用空间(Redis 4.0+)
        3. https://github.com/sripathikrishnan/redis-rdb-tools:解析 RDB 文件
      • 删除:直接删除会释放大量内存,同时会造成空闲内存块链表操作时间增加,从而 Redis 主线程阻塞,致使各种请求超时,最终 Redis 连接耗尽,产生各种异常
        • 方法:
          • 分批次
            • 大 Hash:使用 hscan 每次获取 100 个字段,再用 hdel one by one 删除
            • 大 Set:使用 sscan 每次扫描 100 个元素,再用 srem one by one 删除
            • 大 ZSet:使用 zremrangebyrank 每次删除 top 100 个元素
          • 异步(Redis 4.0+):用 unlink 代替 del
            • 方式:
              • 主动调用 unlink 命令
              • 配置参数,满足条件时异步删除
    • Redis 管道(Pipeline)是客户端而非服务器提供的一种批处理技术,一次处理多个 Redis 命令,解决多个命令执行时的网络等待
    • Redis 事务不支持回滚:不支持事务运行时错误的事务回滚,只有 DISCARD 放弃事务执行
      • 不支持原因:
        • 作者认为生产环境很少出错,没必要开发
        • 事务回滚较复杂与 Redis 简单高效设计不符
    • 分布式锁:在分布式环境下并发控制的一种机制,用于控制资源在同一时刻只能被一个应用使用
      • 原理SET 命令的 NX 参数可以实现 [key 不存在才插入]

        • key 不存在:显示插入成功,表示加锁成功
        • key 存在:显示插入失败,表示加锁失败
      • 加锁条件

        • 加锁是对锁变量进行 [读取、检查和设置] 三个操作,需要原子操完成,所以使用 SET +NX
        • 锁变量需要设置过期时间,使用 EX/PX 选项,单位为毫秒
        • 锁变量的值使用客户端的唯一标识区分来源
        # 满足三条件的命令:
        SET lock_key unique_value NX PX 10000
        
        - lock_key: key
        - unique_value: 客户端唯一标识
        - NX: lock_key 不存在时才进行设置
        - PX 10000: 设置 lock_key 过期时间为 10
        
      • 解锁:有两个操作,需要 Lua 脚本保证原子性,即先比较值是否相等,再删除键

        // 释放锁时,先比较 unique_value 是否相等,避免锁的误释放
        if redis.call("get",KEYS[1]) == ARGV[1] then
            return redis.call("del",KEYS[1])
        else
            return 0
        end
        
      • 优点:

        • 性能高
        • 实现方便
        • 避免单点故障(Redis 集群部署)
      • 缺点:

        • 超时时间不好设置:过长影响性能,过短失去锁保护
        • Redis 主从复制模式中数据是异步复制的,可能导致分布式锁的不可靠性。
      • 集群下的可靠性:官方设计了一个分布式锁算法 Redlock(红锁)。基于多个 Redis 节点的分布式锁,官方推荐至少 5 个孤立的主节点

        • 原理:让客户端和多个独立的 Redis 节点依次请求申请加锁和半数以上的节点加锁成功,就认为客户端加锁成功
        • 过程(加锁成功 = 超半数成功获取锁 & 总耗时 < 锁有效时间)
          1. 客户端获取当前时间(t1)
          2. 客户端按顺序依次向 N 个节点执行加锁
          3. 客户端从超过半数(大于等于 N/2+1)的节点上成功获取到了锁,就再次获取当前时间(t2),然后计算耗时(t2-t1),t2-t1 < 锁的过期时间,认为加锁成功

参考#

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.