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 < 锁的过期时间,认为加锁成功

参考#

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。