Skip to content

Redis

1. 支持五种数据类型

  • string 字符串 (value最大存储512M)
  • hash 哈希(元素最多2^32-1)
  • list 列表(元素最多2^32-1)
  • set 集合(元素最多2^32-1)
  • sorted set 有序集合(同set)

2. 单线程但性能依然好原因

  • 单线程避免线程切换的消耗(redis6已支持多线程)
  • 基于内存,内存读写都很快
  • 使用高性能数据结构,比如 hash 和跳表
  • 使用非阻塞的IO多路复用机制

3. 持久化

  • RDB(Redis Database):通过快照的方式在指定时间间隔内将内存中的数据集写入磁盘。单文件灾难恢复操作简单,性能较高;由于是每隔一段时间持久化,故障可能导致内存中未写入磁盘的数据丢失。

  • AOF(Append Only File):以日志的形式记录每一个写和删除操作,查询不会记录,以文本的方式记录。由于对日志文件的写入操作是 append 模式,宕机也不会破坏日志文件中已经存在的内容,在 Redis 下一次启动可以用 redis-check-aof 工具解决数据一致性问题;缺点是持久化文件较大,恢复速度慢。

4. 过期键删除策略

  • 定时删除(创建定时器达到过期时间删除)
  • 惰性删除(当获取键时才检查是否删除)
  • 定期删除(定期统一检查删除)

redis采用惰性删除和定期删除结合的策略

5. 常用客户端

Redisson、Jedis、Lettuce

6. 处理过大量的key同一时间过期吗?需要注意什么?

可能导致Redis短时间卡顿现象,量大时还可能出现缓存雪崩。过期时间不要求很精确的话,在时间上加一个随机值,使过期时间分散一些

7. Redis 适用场景

  • 数据缓存
  • 计数器、排行榜
  • 集合队列,发布和订阅

8. key搜索禁止使用keys命令(key很多时搜索会导致线程堵塞),应该使用scan命令

scan 命令语法

SCAN cursor [MATCH pattern] [COUNT count]
- cursor - 游标。 - pattern - 匹配的模式。 - count - 指定从数据集里返回多少元素,默认值为 10 。

scan 0 返回游标和数据列表,如果游标是0表示没有更多数据了,不是0的话比如是15,那么使用 scan 15 继续遍历剩余数据,以此类推。

9. 分布式锁

用 Redis 实现分布式锁的两种方式。

  • 自己实现。定义一个 key,value 为请求唯一id,设置超时时间,setnx 操作成功则表示获得锁,业务执行完后删除 key 要校验 value(可用 lua 脚本把读和删除原子化)是不是同一个请求来源。

  • 使用框架 Redisson

10. pipeline

建立管道长链路,一次性处理多个命令,提高吞吐量,但不能保证原子操作

11. 缓存穿透、击穿和雪崩

  • 缓存穿透:key 对应的数据在数据源并不存在,每次针对此 key 的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源。比如用一个不存在的用户 id 获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。

    • 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力
    • 查询返回的数据为空仍然进行缓存,设置一个较短过期时间。
  • 缓存击穿:key 对应的数据存在,但在 redis 中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

    • 使用互斥锁(mutex key)SETNX。缓存失效的时候,不立即去 load db,而是去 set 一个 mutex key,成功时,再进行 load db 的操作并回设缓存;否则,就重试整个 get 缓存的方法。
    • 缓存不会更新的热点数据可以设置永不过期
    • 利用定时线程在缓存过期前主动重新构建缓存
  • 缓存雪崩:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如 DB)带来很大压力。

    • 主从+哨兵或 cluster 做高可用,提供抗压能力避免全面崩盘
    • 在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件
    • 本地缓存(Ehcache)+ 限流降级(Hystrix)。先查本地缓存再查 Redis,大量缓存失效后的查询做限流降级,避免 MySQL 压力过大
    • 做持久化,重启快速从磁盘恢复数据而不是去查数据库

12. 主从、哨兵和 cluster 集群

  • 主从:分摊读写压力。高并发
  • 哨兵:高可用
  • cluster 集群:兼顾以上两者

13. Redis集群不可用(集群三主三从,至少需要三台机器,互为主从,一台服务器宕机还能用)

  • 半数宕机(fail一个主节点需要一半主以上投票通过)
  • 某一结点主从全都宕机

14. 消息队列实现方式

  • 基于异步消息队列 List lpush-brpop(rpush-blpop)

    • 使用rpush和lpush操作入队列,lpop和rpop操作出队列
    • 当队列为空时,lpop和rpop会一直空轮训,消耗资源;所以引入阻塞读blpop和brpop(b代表blocking),阻塞读在队列没有数据的时候进入休眠状态
  • PUB/SUB 订阅/发布模式

    • SUBSCRIBE,用于订阅信道
    • PUBLISH,向信道发送消息
    • UNSUBSCRIBE,取消订阅
  • 基于 sorted set 的实现

    • zadd 添加带分数元素
    • zrange 或 zrevrange 返回有序集合,指定区间的成员。使用0,0区间来获取处于顶部的元素
    • zrem 移除元素,消费后移除
  • 基于 stream 类型的实现。stream 是redis 5.0后新增的数据结构

15. 延迟消息实现

利用 zadd 和 zrangebyscore 来实现存入和读取消息

16. Sortes Set(有序列表)实现,压缩列表和跳表

16.1. 压缩列表 ziplist

  • 有序集合保存的元素数量小于128个
  • 有序集合保存的所有元素的长度小于64字节

16.2. 跳表 skiplist

  • 一种基于有序链表的扩展,跳表会维护多个索引链表和原链表
  • 查找次数近似于层数,时间复杂度为 O(logn),插入、删除也为 O(logn)
  • 跳表是一种随机化的数据结构(通过抛硬币来决定插入层数)
  • 空间复杂度为 O(n)

17. 一致性哈希和哈希槽

17.1. 一致性哈希(一致性 hash 是一个0-2^32的闭合圆)

  • 用于解决分布式缓存系统中的数据选择节点存储问题和数据选择节点读取问题以及在增删节点后减少数据缓存的消失范畴,防止雪崩的发生。
  • 顺时针找到归属的节点

17.2. 哈希槽(redis cluster 一共有2^14=16384个槽)

  • redis cluster集群没有采用一致性哈希方案,而是采用数据分片中的哈希槽来进行数据存储与读取的。
  • 根据CRC-16(key)384的值来判断属于哪个槽区,从而判断该key属于哪个节点

18. Redis 使用场景

  • 会话缓存(Session Cache),是 Redis 最常使用的一种情景
  • 全页缓存(FPC)
  • 用作网络版集合和队列
  • 排行榜和计数器,Redis 在内存中对数字递增、递减的操作实现的非常好。Set 和 Sorted Set 使得我们在执行这些操作的时候非常简单
  • 发布和订阅
  • 如果对丢失部分数据不敏感的话当数据库用