Redis


Redis基础

redis是一个基于C语言开发的分布式高性能 KV 存储数据库,被广泛应用于缓存方向。

  • Redis 基于内存,内存的访问速度是磁盘的上千倍;
  • Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型,主要是单线程事件循环和 IO 多路复用;
  • Redis 内置了多种优化过后的数据结构实现,性能非常高。

why-redis-so-fast

redis的应用

  • 缓存:高性能(内存)+高并发(QPS)
  • 分布式锁:基于 Redisson 来实现分布式锁
  • 限流: Redis + Lua 脚本
  • 消息队列:Redis Stream 类型和List类型的数据结构可以用来做消息队列。
  • 复杂业务场景:通过 bitmap 统计活跃用户、通过 sorted set 维护排行榜。

Redis数据结构

小林coding

  • 5 种基础数据结构 :String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合)。
  • 3 种特殊数据结构 :HyperLogLogs(基数统计)、Bitmap (位存储)、Geospatial (地理位置)。

基本数据结构

String(字符串)

img

String 是一种二进制安全的数据结构,可以用来存储任何类型的数据比如字符串、整数、浮点数、图片(图片的 base64 编码或者解码或者图片的路径)、序列化后的对象。

底层实现: int 和 SDS(简单动态字符串)

img

应用场景

  • 需要存储常规数据的场景

    • 缓存 session、token、图片地址、序列化后的对象(相比较于 Hash 存储更节省内存)。
    • 相关命令 : SETGET
  • 需要计数的场景

    • 举例 :用户单位时间的请求数(简单限流可以用到)、页面单位时间的访问数。
    • 相关命令 :SETGETINCRDECR
  • 分布式锁

    • 利用 SETNX key value 命令可以实现一个最简易的分布式锁(存在一些缺陷,通常不建议这样实现分布式锁)

List(列表)

img

List 列表是简单的字符串列表,按照插入顺序排序,可以从头部或尾部向 List 列表添加元素。

底层实现:双向链表或压缩列表(但是在 Redis 3.2 版本之后,List 数据类型底层数据结构就只由 quicklist 实现了,替代了双向链表和压缩列表)

应用场景:

  • 信息流展示
    • 举例 :最新文章、最新动态。
    • 相关命令 : LPUSHLRANGE
  • 消息队列
    • 消息保序:使用 LPUSH + RPOP
    • 阻塞读取:使用 BRPOP
    • 重复消息处理:生产者自行实现全局唯一 ID;
    • 消息的可靠性:使用 BRPOPLPUSH

Hash(哈希)

img

Hash 是一个键值对(key - value)集合,其中 value 的形式如: value=[{field1,value1},...{fieldN,valueN}]。Hash 特别适合用于存储对象。

Hash 类型的底层数据结构是由压缩列表或哈希表实现的(在 Redis 7.0 中,压缩列表数据结构已经废弃了,交由 listpack 数据结构来实现了。)

应用场景

  • 对象数据存储场景
    • 举例 :用户信息、商品信息、文章信息、购物车信息。
    • 相关命令 :HSET (设置单个字段的值)、HMSET(设置多个字段的值)、HGET(获取单个字段的值)、HMGET(获取多个字段的值)。

String 还是 Hash 存储对象数据更好呢?

  • String 存储的是序列化后的对象数据,存放的是整个对象。相对来说更加节省内存,缓存相同数量的对象数据,String 消耗的内存约是 Hash 的一半。并且,存储具有多层嵌套的对象时也方便很多。如果系统对性能和资源消耗非常敏感的话,String 就非常适合。
  • Hash 是对对象的每个字段单独存储,可以获取部分字段的信息,也可以修改或者添加部分字段,节省网络流量。如果对象中某些字段需要经常变动或者经常需要单独查询对象中的个别字段信息,Hash 就非常适合。

在绝大部分情况,我们建议使用 String 来存储对象数据即可!

购物车信息用 String 还是 Hash 存储更好呢?

由于购物车中的商品频繁修改和变动,购物车信息建议使用 Hash 存储:

  • 用户 id 为 key
  • 商品 id 为 field,商品数量为 value

Hash维护简单的购物车信息

Set(集合)

img

Set 类型是一个无序并唯一的键值集合,它的存储顺序不会按照插入的先后顺序进行存储。

Set 类型除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集,以及判断某个元素是否在集合中;

Set 类型的底层数据结构是由哈希表或整数集合实现的:

应用场景

  • 需要存放的数据不能重复的场景

    • 举例:网站 UV 统计(数据量巨大的场景还是 HyperLogLog更适合一些)、文章点赞、动态点赞等场景。
    • 相关命令:SCARD(获取集合数量) 。
  • 需要获取多个数据源交集、并集和差集的场景

    • 举例 :共同好友(交集)、共同粉丝(交集)、共同关注(交集)、好友推荐(差集)、音乐推荐(差集) 、订阅号推荐(差集+交集) 等场景。
    • 相关命令:SINTER(交集)、SINTERSTORE (交集)、SUNION (并集)、SUNIONSTORE(并集)、SDIFF(差集)、SDIFFSTORE (差集)。
  • 需要随机获取数据源中的元素的场景

    • 举例 :抽奖系统、随机。
    • 相关命令:SPOP(随机获取集合中的元素并移除,适合不允许重复中奖的场景)、SRANDMEMBER(随机获取集合中的元素,适合允许重复中奖的场景)。

Zset(有序集合)

img

Zset 类型(有序集合类型)相比于 Set 类型多了一个排序属性 score(分值),对于有序集合 ZSet 来说,每个存储元素相当于有两个值组成的,一个是有元素值,一个是排序值。

Zset 类型的底层数据结构是由压缩列表或跳表实现的

应用场景

  • 需要随机获取数据源中的元素根据某个权重进行排序的场景

    • 举例 :各种排行榜比如直播间送礼物的排行榜、朋友圈的微信步数排行榜、王者荣耀中的段位排行榜、话题热度排行榜等等。
    • 相关命令 :ZRANGE (从小到大排序) 、 ZREVRANGE (从大到小排序)、ZREVRANK (指定元素排名)。
  • 需要存储的数据有优先级或者重要程度的场景 比如优先级任务队列。

    • 举例 :优先级任务队列。
    • 相关命令 :ZRANGE (从小到大排序) 、 ZREVRANGE (从大到小排序)、ZREVRANK (指定元素排名)。

特殊数据结构

BitMap(位图)

Bitmap,即位图,是一串连续的二进制数组(0和1),可以通过偏移量(offset)定位元素。BitMap通过最小的单位bit来进行0|1的设置,表示某个元素的值或者状态,特别适合一些数据量大且使用二值统计的场景

Bitmap 本身是用 String 类型作为底层数据结构实现的一种统计二值状态的数据类型。

img

应用场景:

  • 需要保存状态信息(0/1 即可表示)的场景。
    • 举例 :用户签到情况、活跃用户情况、用户行为统计(比如是否点赞过某个视频)。
    • 相关命令 :SETBITGETBITBITCOUNTBITOP

HyperLogLog(基数计数概率算法)

是一种用于「统计基数」的数据集合类型,基数统计就是指统计一个集合中不重复的元素个数。但要注意,HyperLogLog 是统计规则是基于概率完成的,不是非常准确,标准误算率是 0.81%。

简单来说 HyperLogLog 提供不精确的去重计数

Redis 提供的 HyperLogLog 占用空间非常非常小,只需要 12k 的空间就能存储接近2^64个不同元素

应用场景:

  • 数量量巨大(百万、千万级别以上)的计数场景
    • 举例 :热门网站每日/每周/每月访问 ip 数统计、热门帖子 uv 统计、
    • 相关命令 :PFADDPFCOUNT

Geospatial index(地理空间索引)

Geospatial index(地理空间索引,简称 GEO) 主要用于存储地理位置信息,基于 Sorted Set 实现。

通过 GEO 我们可以轻松实现两个位置距离的计算、获取指定位置附近的元素等功能。

应用场景:

  • 需要管理使用地理空间数据的场景
    • 举例:附近的人。
    • 相关命令: GEOADDGEORADIUSGEORADIUSBYMEMBER

Redis 线程模型

Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型,主要是单线程事件循环和 IO 多路复用,这套事件处理模型对应的是 Redis 中的文件事件处理器(file event handler)。

  • 文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字,并根据套接字目前执行的任务来为套接字关联不同的事件处理器。
  • 当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关 闭(close)等操作时,与操作相对应的文件事件就会产生,这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。

img

对于读写命令来说,Redis 一直是单线程模型。不过,在

  • Redis 2.6 版本之后,会启动 2 个后台线程,分别处理关闭文件、AOF 刷盘这两个任务;

  • Redis 4.0 版本之后引入了多线程来执行一些大键值对的异步删除操作

  • Redis 6.0 版本之后引入了多线程来处理网络请求(提高网络 IO 读写性能)

redis为什么使用单线程?

  • 单线程编程容易并且更容易维护;

  • Redis 的性能瓶颈不在 CPU ,主要在内存和网络;

    • Redis 的大部分操作都在内存中完成,并且采用了高效的数据结构

    • 单线程采用 I/O 多路复用机制可以处理大量的客户端 Socket 请求

  • 多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能。


redis内存管理

redis过期删除策略

一般情况下,我们设置保存的缓存数据的时候都会设置一个过期时间,可以有助于缓解内存的消耗。

Redis 自带了给缓存数据设置过期时间的功能,比如:

exp key 60 # 数据在 60s 后过期
setex key 60 value # 数据在 60s 后过期 (setex:[set] + [ex]pire)
ttl key # 查看数据还有多久过期

每当我们对一个 key 设置了过期时间时,Redis 会把该 key 带上过期时间存储到一个过期字典(expires dict)中,也就是说「过期字典」保存了数据库中所有 key 的过期时间。

过期字典存储在 redisDb 结构中,如下:

typedef struct redisDb {
    dict *dict;    /* 数据库键空间,存放着所有的键值对 */
    dict *expires; /* 键的过期时间 */
    ....
} redisDb;

过期字典数据结构结构如下:

  • 过期字典的 key 是一个指针,指向某个键对象;
  • 过期字典的 value 是一个 long long 类型的整数,这个整数保存了 key 的过期时间;

img

当我们查询一个 key 时,Redis 首先检查该 key 是否存在于过期字典中:

  • 如果不在,则正常读取键值;
  • 如果存在,则会获取该 key 的过期时间,然后与当前系统时间进行比对,如果比系统时间大,那就没有过期,否则判定该 key 已过期。

Redis 使用的过期删除策略是「惰性删除+定期删除」这两种策略配和使用

  • 惰性删除 :不主动删除过期键,每次从数据库访问 key 时,都检测 key 是否过期,如果过期则删除该 key。这样对 CPU 最友好,但是可能会造成太多过期 key 没有被删除。

  • 定期删除 :每隔一段时间「随机」从数据库中取出一定数量的 key 进行检查,并删除其中的过期key。这样对内存友好,并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响。

redis内存淘汰机制

当大量过期key堆积在内存里, Redis 的运行内存会达到了某个阀值,触发内存淘汰机制。

不进行数据淘汰

  1. noeviction(Redis3.0之后,默认的内存淘汰策略) :它表示当运行内存超过最大设置内存时,不淘汰任何数据,而是不再提供服务,直接返回错误。

在设置了过期时间的数据中进行淘汰

  1. volatile-random:随机淘汰设置了过期时间的任意键值;
  2. volatile-ttl:优先淘汰更早过期的键值。
  3. volatile-lru(Redis3.0 之前,默认的内存淘汰策略):淘汰所有设置了过期时间的键值中,最久未使用的键值;
  4. volatile-lfu(Redis 4.0 后新增的内存淘汰策略):淘汰所有设置了过期时间的键值中,最少使用的键值;

在所有数据范围内进行淘汰:

  1. allkeys-random:随机淘汰任意键值;
  2. allkeys-lru:淘汰整个键值中最久未使用的键值;
  3. allkeys-lfu(Redis 4.0 后新增的内存淘汰策略):淘汰整个键值中最少使用的键值。

Redis 是如何实现 LRU 算法的?

传统 LRU 算法的实现是基于「链表」结构,链表中的元素按照操作顺序从前往后排列,最新操作的键会被移动到表头,当需要内存淘汰时,只需要删除链表尾部的元素即可,因为链表尾部的元素就代表最久未被使用的元素。

Redis 实现的是一种近似 LRU 算法,目的是为了更好的节约内存,它的实现方式是在 Redis 的对象结构体中添加一个额外的字段,用于记录此数据的最后一次访问时间

当 Redis 进行内存淘汰时,会使用随机采样的方式来淘汰数据,它是随机取 5 个值(此值可配置),然后淘汰最久没有使用的那个

Redis 是如何实现 LFU 算法的?

LFU 算法相比于 LRU 算法的实现,多记录了「数据的访问频次」的信息,根据数据访问次数来淘汰数据的,

Redis 持久化机制

Redis 共有三种数据持久化的方式:

  • AOF 日志:每执行一条写操作命令,就把该命令以追加的方式写入到一个文件里;

    • AOF的数据安全性较高,可以实时或者秒级持久化数据,支持秒级数据丢失(取决 fsync 策略,如果是 everysec,最多丢失 1 秒的数据)
    • AOF 以一种易于理解和解析的格式包含所有操作的日志。你可以轻松地导出 AOF 文件进行分析,你也可以直接操作 AOF 文件来解决一些问题。
  • RDB 快照:将某一时刻的内存数据,以二进制的方式写入磁盘;

    • RDB 文件很小,适合做数据的备份,灾难恢复。
    • 使用 RDB 文件恢复数据,直接解析还原数据即可,不需要一条一条地执行命令,速度非常快。
  • 混合持久化方式:Redis 4.0 新增的方式,集成了 AOF 和 RBD 的优点;

    • 在 AOF 重写日志时,fork 出来的重写子进程会先将与主线程共享的内存数据以 RDB 方式写入到 AOF 文件,然后主线程处理的操作命令会被记录在重写缓冲区里,重写缓冲区里的增量命令会以 AOF 方式写入到 AOF 文件,写入完成后通知主进程将新的含有 RDB 格式和 AOF 格式的 AOF 文件替换旧的的 AOF 文件。

    • 这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。

RDB 持久化

Redis 可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis 创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用。

Redis 提供了两个命令来生成 RDB 快照文件:

  • save : 主线程执行,会阻塞主线程;
  • bgsave : 子线程执行,不会阻塞主线程,默认选项。

AOF 持久化

开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到内存缓存 server.aof_buf 中,然后再根据 appendfsync 配置来决定何时将其同步到硬盘中的 AOF 文件。

AOF 写回策略有几种?

在 Redis 的配置文件中存在三种不同的 AOF 持久化方式,它们分别是:

appendfsync always    #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
appendfsync everysec  #每秒钟同步一次,显式地将多个写命令同步到硬盘
appendfsync no        #让操作系统决定何时进行同步

为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec 选项 ,让 Redis 每秒同步一次 AOF 文件,Redis 性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。

为什么先执行命令,再把数据写入日志呢?

关系型数据库(如 MySQL)通常都是执行命令之前记录日志(方便故障恢复),而 Redis AOF 持久化机制是在执行完命令之后再记录日志。

好处

  • 避免额外的检查开销,AOF 记录日志不会对命令进行语法检查;
  • 在命令执行完之后再记录,不会阻塞当前的命令执行。

风险

  • 如果刚执行完命令 Redis 就宕机会导致对应的修改丢失;
  • 可能会阻塞后续其他命令的执行(AOF 记录日志是在 Redis 主线程中进行的)。

AOF 日志过大,会触发什么机制?

AOF 日志是一个文件,随着执行的写操作命令越来越多,文件的大小会越来越大。 如果当 AOF 日志文件过大就会带来性能问题,比如重启 Redis 后,需要读 AOF 文件的内容以恢复数据,如果文件过大,整个恢复的过程就会很慢。

当 AOF 文件的大小超过所设定的阈值后,Redis 就会启用 AOF 重写机制,来压缩 AOF 文件。

AOF 重写机制是在重写时,读取当前数据库中的所有键值对,然后将每一个键值对用一条命令记录到「新的 AOF 文件」,等到全部记录完后,就将新的 AOF 文件替换掉现有的 AOF 文件。

Redis缓存

常见缓存问题

缓存雪崩:避免缓存的集中失效

当大量缓存数据在同一时间过期(失效)时,如果此时有大量的用户请求,都无法在 Redis 中处理,于是全部请求都直接访问数据库,从而导致数据库的压力骤增,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃

image-20221106151341609

对于缓存雪崩问题,我们可以采用两种方案解决。

  • 将缓存失效时间随机打散: 批量加载的场景,将过期时间在一个固定时间段内以毫秒级别进行随机打散,比如本来要设置每条记录过期时间为5分钟,则批量加载的时候可以设置过期时间为5~10分钟之间的任意一个毫秒数。
  • 互斥锁:当业务线程在处理用户请求时,如果发现访问的数据不在 Redis 里,就加个互斥锁,保证同一时间内只有一个请求来构建缓存
  • 双 key 策略:我们对缓存数据可以使用两个 key,一个是主 key,会设置过期时间,一个是备 key,不会设置过期,当业务线程访问不到「主 key 」的缓存数据时,就直接返回「备 key 」的缓存数据,然后在更新缓存的时候,同时更新「主 key 」和「备 key 」的数据。

缓存击穿:有效的冷数据预热加载机制

如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮,这就是缓存击穿的问题。

image-20221106151955154

缓存击穿和前面提到的缓存雪崩产生的原因其实很相似。区别点在于:

  • 缓存雪崩是大面积的缓存失效导致大量请求涌入数据库。
  • 缓存击穿是少量缓存失效的时候恰好失效的数据遭遇大并发量的请求,导致这些请求全部涌入数据库中。

对于缓存击穿问题,我们可以采用两种方案解决。

  • 缓存续期:可以为热点数据设置一个过期时间续期的操作,比如每次请求的时候自动将过期时间续期一下。
  • 分布式锁方案(Redis 中使用 setNX 方法设置一个状态位,表示这是一种锁定状态):当缓存不可用时,仅持锁的线程负责从数据库中查询数据并写入缓存中,其余请求重试时先尝试从缓存中获取数据,避免所有的并发请求全部同时打到数据库上。

恶意程序(爬虫)一直在刷历史数据,容易将内存中的热点数据变为历史数据,导致真正的用户请求被打到数据库层。这时该怎么设计缓存?

缓存指定时间段内的数据(比如最近1年),且数据不存在时从DB获取内容之后也不会回写到缓存中。同时设计冷数据加热机制,可以约定同一秒内对某条冷数据的请求超过10次,则将此条冷数据加热作为临时热点数据存入缓存,设定缓存过期时间为30天(一般一个陈年八卦一个月足够消停下去了)。通过这样的机制,来解决冷数据的突然窜热对系统带来的不稳定影响。

缓存穿透:合理的防身自保手段

当用户访问的数据,既不在缓存中,也不在数据库中,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据,没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增,这就是缓存穿透的问题。

image-20221106153912931

缓存穿透的情况往往出现在一些外部干扰或者攻击情景中,比如外部爬虫、比如黑客攻击等等。

应对缓存穿透的方案,常见的方案有三种。

  • 非法请求的限制:当有大量恶意请求访问不存在的数据的时候,也会发生缓存穿透,因此在 API 入口处我们要判断求请求参数是否合理,请求参数是否含有非法值、请求字段是否存在,如果判断出是恶意请求就直接返回错误,避免进一步访问缓存和数据库。
  • 设置空值或者默认值:当我们线上业务发现缓存穿透的现象时,可以针对查询的数据,在缓存中设置一个空值或者默认值,这样后续请求就可以从缓存中读取到空值或者默认值,返回给应用,而不会继续查询数据库。
  • 使用布隆过滤器快速判断数据是否存在,避免通过查询数据库来判断数据是否存在:我们可以在写入数据库数据时,使用布隆过滤器做个标记,然后在用户请求到来时,业务线程确认缓存失效后,可以通过查询布隆过滤器快速判断数据是否存在,如果不存在,就不用通过查询数据库来判断数据是否存在,即使发生了缓存穿透,大量请求只会查询 Redis 和布隆过滤器,而不会查询数据库,保证了数据库能正常运行,Redis 自身也是支持布隆过滤器的。

常见缓存更新策略

  • Cache Aside(旁路型缓存)策略;(实际开发中,Redis 和 MySQL 的更新策略)

    旁路型缓存模式中,业务自行负责与缓存以及数据库之间的交互,可以自由决定缓存未命中场景的处理策略,更加契合大部分业务场景的定制化诉求。

    由于业务模块自行实现缓存与数据库之间的数据写入与更新的逻辑,实际实现的时候需要注意下在高并发场景的数据一致性问题,以及可能会出现的缓存击穿缓存穿透缓存雪崩等问题的防护。

  • 穿透型策略;

    image-20221106155437477
  • 异步型缓存;

image-20221106155603860

缓存的数据一致性

对于基于旁路型缓存的大部分业务而言,数据更新操作,一般可以组合出几种不同的处理策略:

  • 先更新缓存,再更新数据库
  • 先更新数据库, 再更新缓存
  • 先删除缓存,再更新数据库
  • 先更新数据库,再删除缓存

对于一些读多写少、写操作并发量不是特别大且对一致性要求不是特别高的情况下,可以采用数据库事务(高隔离级别) + 先更新数据库再更新/删除缓存的方式来达到数据一致的诉求。

image-20221106160235183

对于并发要求较高、且数据一致性要求较好的时候,推荐选择先更新数据库,再删除缓存,并结合删除重试 + 补偿逻辑 + 缓存过期TTL等综合手段

image-20221106160549940

Redis 实战

redis事务

  • redis事务不提供提供回滚机制,虽然 Redis 提供了 DISCARD 命令,但是这个命令只能用来主动放弃事务执行,把暂存的命令队列清空,起不到回滚的效果。

  • redis 事务并不一定保证原子性,事务执行过程中,如果命令入队时没报错,而事务提交后,实际执行时报错了,正确的命令依然可以正常执行,

redis bigkey

如果一个 key 对应的 value 所占用的内存比较大,那这个 key 就可以看作是 bigkey。

  • String 类型的值大于 10 KB;
  • Hash、List、Set、ZSet 类型的元素的个数超过 5000个;

bigkey的影响?

  • 客户端超时阻塞。由于 Redis 执行命令是单线程处理,然后在操作大 key 时会比较耗时,那么就会阻塞 Redis,从客户端这一视角看,就是很久很久都没有响应。
  • 引发网络阻塞。每次获取大 key 产生的网络流量较大,如果一个 key 的大小是 1 MB,每秒访问量为 1000,那么每秒会产生 1000MB 的流量,这对于普通千兆网卡的服务器来说是灾难性的。
  • 阻塞工作线程。如果使用 del 删除大 key 时,会阻塞工作线程,这样就没办法处理后续的命令。
  • 内存分布不均。集群模型在 slot 分片均匀情况下,会出现数据和查询倾斜情况,部分有大 key 的 Redis 节点占用内存多,QPS 也会比较大。

如何发现bigkey?

  • 使用 Redis 自带的 --bigkeys 参数来查找。

  • 使用 RdbTools 工具查找大 key

如何删除big key?

  • 分批次删除
  • unlink 命令异步删除(Redis 4.0版本以上)

redis实现延迟队列

延迟队列是指把当前要做的事情,往后推迟一段时间再做。延迟队列的常见使用场景有以下几种:

  • 在淘宝、京东等购物平台上下单,超过一定时间未付款,订单会自动取消;
  • 打车的时候,在规定时间没有车主接单,平台会取消你的单并提醒你暂时没有车主接单;
  • 点外卖的时候,如果商家在10分钟还没接单,就会自动取消订单

在 Redis 可以使用有序集合(ZSet)的方式来实现延迟消息队列的,ZSet 有一个 Score 属性可以用来存储延迟执行的时间。

用多个线程轮询Zset获取到期的任务进行处理,以此来实现延迟消息队列等,步骤如下:

  1. 利用 zadd 向集合中插入元素,以元素的时间戳(超时时间)作为 score
  2. 利用 zrangebyscore0 < score <= 当前时间戳 进行获取需要处理的元素
  3. 当有满足的条件的元素, 先删除zrem该元素(保证不被其他进程取到),再进行业务逻辑处理;

文章作者: liuzhangjie
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 liuzhangjie !
  目录