Redis

Redis 除了做缓存之外,Redis 也经常⽤来做分布式锁,甚⾄是消息队列。

Redis 和 Memcached 的区别和共同点:
共同点:

  • 都是基于内存的数据库,一般都用来当作缓存使用
  • 都有过期策略
  • 两者的性能都非常高

区别:

  • Redis 由灾难恢复机制。因为可以把缓存中数据持久化到磁盘上
  • Redis 在服务器内存使用完之后,可以将不用的数据放到磁盘上。但是 Memcached 在服务器内存使用完之后,就会直接报异常
  • Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 Redis 目前是原生支持 cluster 模式的
  • Memcached 是多线程,非阻塞的 IO 复用的网络模型;Redis 使用单线程的多路复用模型(Redis 6.0 引入了多线程 IO)
  • Redis 支持发布订阅模型、Lua 脚本、事务等功能,而 Memcached 不支持。并且,Redis 支持更多的编程语言
  • Memcached 过期数据的删除策略只用了惰性删除,而 Redis 同时使用了惰性删除与定期删除

⼀般像 MySQL 这类的数据库的 QPS ⼤概都在 1w 左右(4 核 8g) ,但是使⽤ Redis 缓存之后很容易达到 10w+,甚⾄最⾼能达到 30w+(就单机 redis 的情况,redis 集群的话会更⾼)。

数据类型:

  • string:

    常用命令: setgetstrlenexistsincrincrbydecrdecrbysetexset + expire)等 应用场景:⼀般常⽤在需要计数的场景,⽐如⽤户的访问次数、热点⽂章的点赞转发数量等等

  • list:

    常用命令: rpushlpushrpoplpoplrangellen 等 应用场景:发布订阅或者消息队列、慢查询

    • 通过 rpush / lpop 实现队列
    • 通过 rpush / rpop 实现栈
  • hash

    常用命令: hsethmsethexistshgetallhkeyshvals 等 应用场景:系统中对象数据的存储

  • set

    常用命令: saddspopsmemberssismemberscardsinterstoresunion 应用场景:需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景

  • sorted set:

    常用命令: zaddzcardzscorezrangezrevrangezrem 等 应用场景:需要对数据根据某个权重进⾏排序的场景。⽐如在直播系统中,实时排⾏信息包含直播间在线⽤户列表,各种礼物排⾏榜,弹幕消息(可以理解为按消息维度的消息排⾏榜)等信息。

Redis 单线程模型:
Redis 基于 Reactor 模式来设计开发了⾃⼰的⼀套⾼效的事件处理模型,这套事件处理模型对应的是 Redis中的⽂件事件处理器(file event handler)。由于⽂件事件处理器(file event handler)是单线程⽅式运⾏的,所以我们⼀般都说 Redis 是单线程模型

文件事件处理器(file event handler)主要包含四个部分:

  • 多个 Socket (客户端连接)
  • IO 多路复用程序(支持多个客户端连接的关键)
  • 文件事件分派器(将 Socket 关联到相应的事件处理器)
  • 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)

5 种 IO 模型:

  • 阻塞 IO 模型
  • 非阻塞 IO 模型
  • 多路复用 IO 模型(IO multiplexing)
  • 信号驱动 IO 模型
  • 异步 IO 模型

Redis 6 之前为什么不使用多线程:

  • 单线程编程容易并且更容易维护
  • Redis 的性能瓶颈不在 CPU,主要在内存和网络
  • 多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能

Redis 6 之后为什么引入了多线程:
Redis6.0 引⼊多线程主要是为了提⾼⽹络 IO 读写性能,因为这个算是 Redis 中的⼀个性能瓶颈(Redis 的瓶颈主要受限于内存和⽹络)

Redis 是如何判断数据是否过期的:
Redis 通过⼀个叫做过期字典(可以看作是hash表)来保存数据过期的时间。过期字典的键指向Redis数据库中的某个key(键),过期字典的值是⼀个long long类型的整数,这个整数保存了key所指向的数据库键的过期时间(毫秒精度的UNIX时间戳)。

Redis 过期的数据的删除策略:

  • 惰性删除:只会在取出key的时候才对数据进⾏过期检查。这样对CPU最友好,但是可能会造成太多过期 key 没有被删除
  • 定期删除:每隔⼀段时间抽取⼀批 key 执⾏删除过期key操作。并且,Redis 底层会通过限制删除操作执⾏的时⻓和频率来减少删除操作对CPU时间的影响。

Redis 内存淘汰机制:

  1. # volatile-lru -> Evict using approximated LRU, only keys with an expire set. 从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使⽤的数据淘汰
  2. # allkeys-lru -> Evict any key using approximated LRU. 当内存不⾜以容纳新写⼊数据时,在键空间中,移除最近最少使⽤的 key(这个是最常⽤的)
  3. # volatile-lfu -> Evict using approximated LFU, only keys with an expire set. (4.0)从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使⽤的数据淘汰
  4. # allkeys-lfu -> Evict any key using approximated LFU. (4.0)当内存不⾜以容纳新写⼊数据时,在键空间中,移除最不经常使⽤的 key
  5. # volatile-random -> Remove a random key having an expire set. 从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  6. # allkeys-random -> Remove a random key, any key. 从数据集(server.db[i].dict)中任意选择数据淘汰
  7. # volatile-ttl -> Remove the key with the nearest expire time (minor TTL) 从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  8. # noeviction -> Don't evict anything, just return an error on write operations. 禁⽌驱逐数据,也就是说当内存不⾜以容纳新写⼊数据时,新写⼊操作会报错。这个应该没⼈使⽤吧!

Redis 持久化机制:

  • 快照(Snapshotting,RDB): ```shell save 900 1 # 在900秒(15分钟)之后,如果⾄少有1个key发⽣变化,Redis就会⾃动触发BGSAVE命令创建快照。

save 300 10 # 在300秒(5分钟)之后,如果⾄少有10个key发⽣变化,Redis就会⾃动触发BGSAVE命令创建快照。

save 60 10000 # 在60秒(1分钟)之后,如果⾄少有10000个key发⽣变化,Redis就会⾃动触发BGSAVE命令创建快照。

  1. - 只追加文件(Append-onlyAOF):
  2. > 与快照持久化相⽐,AOF 持久化 的实时性更好,因此已成为主流的持久化⽅案。
  3. > 三种不同的 AOF 持久化方式:
  4. ```shell
  5. appendfsync always # 每次有数据修改发⽣时都会写⼊AOF⽂件,这样会严重降低Redis的速度
  6. appendfsync everysec # 每秒钟同步⼀次,显示地将多个写命令同步到硬盘
  7. appendfsync no # 让操作系统决定何时进⾏同步

Redis Key 设计: <table_name>:<column_name>:<pk_name>:<pk_value>

Redis 事务:
Redis 可以通过 MULTIEXECDISCARD WATCH 等命令来实现事务(transaction)功能。
使⽤ MULTI命令后可以输⼊多个命令。Redis不会⽴即执⾏这些命令,⽽是将它们放到队列,当调⽤了EXEC命令将执⾏所有命令。

缓存穿透:缓存穿透说简单点就是⼤量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上

解决方案:

  • 缓存无效 Key
  • 布隆过滤器:布隆过滤器是⼀个⾮常神奇的数据结构,通过它我们可以⾮常⽅便地判断⼀个给定数据是否存在于海量数据中。

    把所有可能存在的请求的值都存放在布隆过滤器中,当⽤户请求过来,先判断⽤户发来的请求的值是否存在于布隆过滤器中。 不存在的话,直接返回请求参数错误信息给客户端,存在的话才会⾛下⾯的流程。

    布隆过滤器说某个元素存在,⼩概率会误判。布隆过滤器说某个元素不在,那么这个元素⼀定不在。

    当一个元素加入布隆过滤器的时候会进行的操作:

    1. 使用布隆过滤器中的哈希函数对元素值进行计算,得到哈希值(有几个哈希函数得到几个哈希值)
    2. 根据得到大的哈希值,在位数组中把对应下标的值置为 1

    当需要判断一个元素是否存在于布隆过滤器的时候会进行的操作:

    1. 对给定元素再次进行相同的哈希计算
    2. 得到值之后判断位数组中的每个元素是否都为 1 ,如果都为 1 ,那么说明这个值在布隆过滤器中,如果存在一个值不为 1 ,说明该元素不在布隆过滤器中。

    不同字符串可能哈希出来的位置相同:可以适当的增加位数组大小或者调整哈希函数来降低概率

缓存雪崩:缓存在同⼀时间⼤⾯积的失效,后⾯的请求都直接落到了数据库上,造成数据库短时间内承受⼤量请求。
场景:

  • Redis 服务不可用:系统的缓存模块出了问题⽐如宕机导致不可⽤。造成系统的所有访问,都要⾛数据库

    • 采用 Redis 集群,避免单机出现问题整个缓存服务器都没办法使用
    • 限流,避免同时处理大量的请求
  • 热点缓存失效:有⼀些被⼤量访问数据(热点缓存)在某⼀时刻⼤⾯积失效,导致对应的请求直接落到了数据库上

    • 设置不同的失效时间比如随机设置缓存的失效时间
    • 缓存永不失效

如何保证缓存和数据库数据的一致性:

  • 旁路缓存模式(Cache Aside Pattern):更新 DB,然后直接删除 cache

    如果更新数据库成功,而删除缓存失败的解决方案:

    • 缓存失效时间变短(不推荐,治标不治本)
    • 增加 cache 更新重试机制(常用)