Redis总结与实践

232 阅读7分钟

基础数据结构

  • 字符串
    常数复杂度获取字符串长度,减少内存重分配
    常用在缓存、计数、共享Session

  • 哈希
    压缩列表和字典
    存放用户信息,比如购物车

  • 集合
    整形集合(可能会导致升级,重新分配长度)和字典,不允许又重复元素
    利用 Set 的交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。

  • 有序集合
    压缩列表、跳表和字典(跳表实现 lgn 的范围查找,字典实现 O(1)的单个查找)
    通过score排序。常用来实现延时任务和排行榜

  • 列表
    压缩列表和双端链表
    可以实现简单的消息队列

高级数据结构

  • BloomFilter
    • 判断一个数是否在 BloomFilter 里面,判断为在则不一定在,不存在的话就肯定不存在
    • BloomFilter 需要提供一个初始化大小,和误判率,然后根据这两个参数决定空间占用
    • bloomFilter 其实就是一个大型的位数组,当添加一个数进来时,会利用多个 hash 函数对 key 进行 hash 运算在对位数组进行取余,每个 hash 函数都会的到一个位置,在把位数组的这些位置置为 1
    • 判断是否存在也是进行多个 hash 运算,再把得到的结果拿去位数组里面比较,如果都是 1 的话则有可能存在,否则不存在
  • HyperLogLog
    统计 uv
  • PubSub
  • geo
    位置运算,将立体坐标切换,进制运算(就像分蛋糕一样),越长越精确
  • redis为什么快
    io 多路复用模型(一个 select 多个)和非阻塞式 io(有数据就返回)
    纯内存操作
    合理的数据结构 单线程操作,避免了上下午切换

缓存雪崩、击穿、穿透

  • 缓存穿透
    恶意用户通过不存在的 key 去查缓存,当缓存不存在,就落入数据库,会导致每条请求都进入数据库
    解决:一方面可以在程序端进行拦截,一方面可以使用 BloomFilter 判断 key 是否存在,如果不存在就返回,存在则访问缓存和数据库
  • 缓存雪崩
    大量的 key 失效,一方面会导致 redis 花大量的时间去清理 key 其次是大量的数据库请求会打进来,导致挂掉 解决:给 key 设置过期时间的时候加一个随机时间并使用集群模式,还有限流
  • 缓存击穿
    大量的请求查询同一个 key,恰好 key 失效了,这时候大量的会去请求数据库
    解决:加锁或者分布式锁,当第一个线程拿到了锁,并从数据库拿到了数据就重新将数据缓存到 redis 中,其它的线程直接从缓存里面拿

持久化

  • RDB
    rdb 保存服务器的所有键值对数据,可以用 save 和 bgsave 命令执行
    sava 命令会导致服务器阻塞
    bgsave 是产生一个子进程去处理,处理完成通知主进程,不会产生阻塞
    服务器启动的时候就会去加载 RDB 文件,如果启用了 AOF,redis 加载会优先加载 AOF 文件 服务器通过用户配置的 save 条件。自动执行 bgsave

  • AOF
    AOF 通过存储服务器的写命令来保存服务器的状态
    当 AOF 打开时,客户端的写命令会被追加到 AOF 的缓冲区末尾
    缓冲区何时同步到 AOF 文件中,是通过配置 appendfsnyc 选项来决定
    选项如下:默认是 everysec
    always:客户端写一条命令就同步到 AOF 文件中(最多丢失 1 条命令,安全性最高,效率最慢)
    everysec: 每秒将缓冲区的数据同步到 AOF 文件(可能 1 秒前的数据)
    no:何时同步由操作系统决定(会导致内存的数据丢失)

    AOF 加载如下图:

AOF 重写
AOF重写会产生一个新的AOF文件,新的文件比原来的AOF文件小 会将新的命令写入AOF缓存中,当新的AOF产生创建完成,就会吧缓冲区的内容追加到AOF文件中,保持新旧的两个AOF文件一致,在用原子操作将新的AOF文件替换过去。

过期策略

  • 通过 expire 指定过期时间,ttl 查看过期时间。指定过期了的 key 会单独存在过期字典中
  • 可以通过 ttl 去查看还剩多久过期
  • 定时删除(因为 redis 是单线程的,在系统有多余的内存且在处理大量的任务时候,定时删除会占用一定的 cpu,但是它对内存最友好)
  • 惰性删除(对内存不友好,可能永久不会删除即内存泄漏)
  • 定期删除采用贪心算法,每次随机从过期数据库抽取 20key,如果过期的 key 超过 4 分之一则循环,直到不满足条件或者时间大于指定时间(25ml)
  • redis 采用惰性删除和定期删除结合的方式

哨兵(Sentinel)

哨兵会通过项主服务器发送info命令获取所有从服务气地址,以及连向这些从服务器
当哨兵检测到主服务器有故障,会广播给这台服务器的所有哨兵,查看它们是否同意主服务器下线,当满足哨兵配置的下线个数时,该哨兵认为服务器下线,开始选出领头哨兵(超过半数投票)
领头哨兵进行故障转移切换选出从库当主库
当主节点发生故障时,客户端会重新向 sentinel 要地 址,sentinel 会将最新的主节点地址告诉客户端

主从

内存淘汰

当内存不够用时,内存淘汰机制就会上场。淘汰策略分为:

  • 当内存不足以容纳新写入数据时,不会继续服务写请求 (DEL 请求可以继续服务),读请求可以继续进行。(Redis 默认策略)
  • 当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 Key。(LRU推荐使用)
  • 当内存不足以容纳新写入数据时,在键空间中,随机移除某个 Key。
  • 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,最少使用的 key 优先被淘汰。。这种情况一般是持久化存储的时候才用。
  • 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 Key。
  • 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 Key 优先移除

分布式锁

分布式锁+过期时间来实现(防止程序出错导致永远不过期
超时问题:随机value,在释放锁用lua指令去匹配随机value是否一致

Redis与Mysql双写一致性方案

先更新数据库,再删缓存
(1)缓存刚好失效
(2)请求A查询数据库,得一个旧值
(3)请求B将新值写入数据库
(4)请求B删除缓存
(5)请求A将查到的旧值写入缓存 ok,如果发生上述情况,确实是会发生脏数据. 但是由于数据库的读操作(第5步)的速度远快于写操作(第三步)的,所以脏数据很难出现。可以对异步延时删除策略,保证读请求完成以后,再进行删除操作。

应用场景

  • 秒杀系统
  • 限流
  • 统计 pv、uv

本文使用 mdnice 排版