Redis 入门看这一篇就够了

1 Redis简介

Redis 是一个开源的基于内存的 key-value 型数据结构存储系统,可以被用作数据库、缓存、消息中心、流引擎。

Redis 提供的数据结构包括 strings, hashes, lists, sets, sorted sets, bitmaps, hyperloglogs, geospatial indexes, and streams。

2 安装和启动

这里我们以 Windows 下的 Linux 子系统即 WSL 举例演示下安装步骤,其余环境自行百度。

2.1 WSL 介绍

Windows Subsystem for Linux(简称 WSL)是一个在 Windows 10\11 上能够运行原生 Linux 二进制可执行文件(ELF 格式)的兼容层。它是由微软与 Canonical 公司合作开发,其目标是使纯正的 Ubuntu、Debian 等映像能下载和解压到用户的本地计算机,并且映像内的工具和实用工具能在此子系统上原生运行。

以往我们想要在 Windows 系统下操作 Linux 的话,一般是安装 Vmware 虚拟机,然后在虚拟机中安装 Linux 系统,这种方式有几个明显缺点:

  • Vmware 安装起来麻烦,费时费力还要配置网络等一大堆东西,别人代码都写完了你可能还在鼓捣环境
  • 刚需电脑不友好,Vmware 安装后电脑卡的飞起,电脑没有点配置带不起来

2.2 Windows 下启用 WSL

WSL 开关位于:设置->程序和功能->启用或关闭 Windows 功能,选中打开即可。

2.3 安装 Linux 子系统

打开微软自带的软件超市,搜索 "WSL" 关键字,找到 Ubuntu 22.04.2 LTS,直接点击安装,静静等待安装完成。安装完成后首次进入系统需要设置用户名,随意设置个即可。

2.4 安装 Redis

bash 复制代码
curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list

sudo apt-get update
sudo apt-get install redis

2.5 启动 Redis

sql 复制代码
sudo service redis-server start

2.6 连接 Redis 测试

ruby 复制代码
redis-cli 
127.0.0.1:6379> ping
PONG

3 Redis 数据结构

Redis 是一个提供各种数据结构数据存储的服务器,提供一系列原生数据类型来解决多样化的问题,比如存储、队列、事件处理等。接下来针对每种数据类型,我们做个简单介绍。

3.1 字符串 Strings

Redis 字符串存储字节序列,包括文本、序列化对象和二进制数组。因此,字符串是你可以与 Redis 键关联的最简单的值类型。它们通常用于缓存,但它们也支持其他功能,允许您实现计数器和执行按位操作。String 常见的几种指令如下:

  • GET:GET key,查询指定 key 对应的value,返回值:
  • SET:SET key value,设置值,如果对应的 key 存在则直接覆盖,这里要注意的是执行完 set 后,原有的 ttl 会自动失效,需要重新设置
  • MGET:MGET key [key ...] ,get 的批量版本,可以一次返回多个 key 的值
  • MSET:MSET key value [key value ...] ,set 的批量版本
  • INCR:INCR key,指定 key 的值加1,如果 key 首次操作的话,设置为 1
  • INCRBY:INCRBY key increment,其中 increment 参数支持自定义递增步长
  • DECR:DECR key,指定 key 的值减1
  • DECRBY:DECRBY key decrement,其中 increment 参数支持自定义递增减步长
  • SETNX:SETNX key value,SET if not exists,即 key 不存在时才会执行 set操作
  • DEL:DEL key [key ...] ,删除指定的 key,key 不存在时直接忽略
ruby 复制代码
127.0.0.1:6379> SET bike:1 Deimos
OK
127.0.0.1:6379> GET bike:1
"Deimos"

使用 SETGET 指令是我们设置和检索字符串值的方式。注意,在键已经存在的情况下,SET 将替换已经存储到键中的任何现有值,即使该键与非字符串值相关联。因此 SET 执行赋值操作。

Redis 的值可以是各种类型的数据,注意字符串的值大小不能超过 512mb。当然针对后面几种数据类型的话,单个键值对上限为 512mb。

使用 Redis 还能实现类似计数器的功能,要用到的是 INCRINCRBY 指令:

ruby 复制代码
127.0.0.1:6379> SET total_crashes 0
OK
127.0.0.1:6379> INCR total_crashes
(integer) 1
127.0.0.1:6379> INCRBY total_crashes 10
(integer) 11
  • incr 的话每次递增区间为 1,返回递增后的值,incrby 的话可以指定递增区间。与之相反的就是 DESRDECRBY
  • 这里的递增、递减指令是原子性的,即多个客户端同时操作相同 key 时,不会发生竞争或者幻读等操作。

当然 GET、SET 指令只能实现单个 key 值的获取、写入,想要批量的话需要用到 MGET、MSET

ruby 复制代码
127.0.0.1:6379> MSET total_crashes 0 total_labels 1
OK
127.0.0.1:6379> MGET total_crashes total_labels
1) "0"
2) "1"

除此之外用的较多的还有 SETNX 指令:只有 key 不存在时,才设置对应的值,否则啥也做:

ruby 复制代码
127.0.0.1:6379> SET bike:1 Deimos
OK
127.0.0.1:6379> SETNX bike:1 Lily
(integer) 0
127.0.0.1:6379> GET bike:1
"Deimos"

上面 SETNX 执行结束之后,打印出来设置成功的条数,可以看到为 0,表示没有更新成功,再次 GET 也发现确实如此。SETNX 指令常被用来实现锁,后面会介绍到。

3.2 有序集合 Lists

Redis Lists 是 Redis 提供的有序集合,Lists 最常见的是用来实现栈 Stack 和队列 Queue,另外也同来实现类似发布/订阅器。Lists 常见的指令如下:

  • LPUSH:LPUSH key element [element ...] ,在集合头部插入一个或多个元素,与之对应的是 rpush 在尾部插入
  • LPOP:LPOP key [count] ,在集合头部删除一个或多个元素
  • LLEN:LLEN key,返回集合长度
  • LMOVE:LMOVE source destination <LEFT | RIGHT> <LEFT | RIGHT> ,将 source 集合中的 LEFT 或者 RIGHT 元素移动到 destination 集合的 LEFT 或者 RIGHT 位置,其中的 LEFT 代表集合头部、RIGHT 指代尾部
  • LTRIM:LTRIM key start stop,根据 start、stop 位置索引进行集合截取,start 和 stop 位置的元素都是包含在内,位置索引从 0 开始,当然可以使用 -1 或 -2 这种代表最后一个位置索引或者倒数第二位
  • BLPOP:BLPOP key [key ...] timeout,同 lpop,不过 blpop 是阻塞式的,即当集合元素为空时阻塞当前操作,通过 timeout 可以指定等待时长,timeout 为 0 时代表无限期等待,知道集合有元素为止
  • BLMOVE:BLMOVE source destination <LEFT | RIGHT> <LEFT | RIGHT> timeout,即 lmove 的阻塞式版本
ruby 复制代码
127.0.0.1:6379> LPUSH bikes:repairs bike:1
(integer) 1
127.0.0.1:6379> LPUSH bikes:repairs bike:2
(integer) 2
127.0.0.1:6379> RPOP bikes:repairs
"bike:1"
127.0.0.1:6379> RPOP bikes:repairs
"bike:2"

可以看到,通过 LPUSH + RPOP 可以实现先进先出(FIFO),即队列功能,而通过 LPUSH + LPOP 可以实现栈的特性,即先进后出(FILO)。

ruby 复制代码
127.0.0.1:6379> LPUSH bikes:repairs bike:1
(integer) 1
127.0.0.1:6379> LPUSH bikes:repairs bike:2
(integer) 2
127.0.0.1:6379> LPOP bikes:repairs
"bike:2"
127.0.0.1:6379> LPOP bikes:repairs
"bike:1"

LMOVE 指令可以实现两个集合的数据交换,如下操作将 bikes:repairs 集合中的头部元素移到 bikes:finished 集合的头部:

ruby 复制代码
127.0.0.1:6379> LMOVE bikes:repairs bikes:finished LEFT LEFT
"bike:2"
127.0.0.1:6379> LRANGE bikes:repairs 0 -1
1) "bike:1"
127.0.0.1:6379> LRANGE bikes:finished 0 -1
1) "bike:2"

当然,我们也可以将集合 bikes:repairs 的尾部元素移到集合 bikes:finished 的头部:

ruby 复制代码
127.0.0.1:6379> LRANGE bikes:repairs 0 -1
1) "bike:2"
2) "bike:1"
127.0.0.1:6379> LRANGE bikes:finished 0 -1
1) "bike:3"
127.0.0.1:6379> LMOVE bikes:repairs bikes:finished RIGHT LEFT
"bike:1"
127.0.0.1:6379> LRANGE bikes:repairs 0 -1
1) "bike:2"
127.0.0.1:6379> LRANGE bikes:finished 0 -1
1) "bike:1"
2) "bike:3"

LTRIM 可以直接截取集合,如下仅保留集合 bikes:repairs 0-2位置的元素:

ruby 复制代码
127.0.0.1:6379> LTRIM bikes:repairs 0 2
OK
127.0.0.1:6379> LRANGE bikes:repairs 0 -1
1) "bike:2"

以往我们使用 Redis 实现发布/订阅消息的话,最常见是使用 LPUSH + RPOP,一个进程不停往集合生产数据,另一个进程不停从集合消费。假设集合为空时消费进程挂起等待然后继续重试,这种模式有几个缺点:

  • 强制 Redis 和消费进程处理无用的指令(当集合为空时,所有的请求都不会完成实际的工作,它们只会返回 NULL)
  • 增加消费进程处理的延迟,因为在消费进程接收到 NULL 后,它会等待一段时间。为了使延迟更小,我们可以减少调用 RPOP 之间的等待时间,从而放大问题 1 的效果,即对 Redis 进行更多无用的调用

针对以上情况,Redis 推出了 BRPOP 和 BLPOP 指令作为 RPOP 和 LPOP 的变种,额外加了假设集合为空时 pop 操作自动阻塞,当然可以设置等待时间,否则会一直阻塞下去。使用 BRPOP 实现的发布/订阅模式实例文章后面几节我们会讲到。

3.3 无序集合 Sets

Sets 是一种无序集合且元素不能重复,假设对集合元素顺序不敏感且要求元素唯一的话,Sets 不失为一种好的解决思路。Sets 常见的几种指令如下:

  • SADD:SADD key member [member ...] ,向集合中添加一个或多个元素,已存在的元素会被忽略、当集合不存在时会自动创建
  • SREM:SREM key member [member ...] ,删除集合中的几个或多个元素,不存在的元素会被忽略
  • SISMEMBER:SISMEMBER key member,判断指定元素是否存在,存在时返回 1,元素不存在或者集合不存在时都返回 0
  • SINTER:SINTER key [key ...] ,取多个集合的交集
  • SCARD:SCARD key,返回集合大小
ruby 复制代码
127.0.0.1:6379> SADD bikes:racing:france bike:1
(integer) 1
127.0.0.1:6379> SADD bikes:racing:france bike:1
(integer) 0
127.0.0.1:6379> SADD bikes:racing:france bike:2 bike:3
(integer) 2
127.0.0.1:6379> SADD bikes:racing:usa bike:1 bike:4
(integer) 2
127.0.0.1:6379> SISMEMBER bikes:racing:usa bike:1
(integer) 1
127.0.0.1:6379> SISMEMBER bikes:racing:usa bike:2
(integer) 0
ruby 复制代码
127.0.0.1:6379> SINTER bikes:racing:france bikes:racing:usa
1) "bike:1"
127.0.0.1:6379> SCARD bikes:racing:france
(integer) 3

3.4 哈希 Hashes

Redis 的哈希 Hashes 是一种结构化的数据存储,使用 key-value 键值对。常见的指令如下:

  • HSET:HSET key field value [field value ...] ,批量设置 key-value 键值对
  • HGET:HGET key field,指定 field 查询
  • HMGET:HMGET key field [field ...] ,批量查询多个 field
  • HGETALL:HGETALL key,返回整个集合
  • HINCRBY:HINCRBY key field increment,指定的 field 按照 increment 递增;如果集合不存在则会自动创建,假设 field 不存在,那么执行递增前会先设置 field 初始化值为 0 再进行递增
  • HKEYS:HKEYS key,返回 键的集合
  • HVALS:HVALS key,返回值的集合
  • HSETNX:HSETNX key field value,指定 field 不存在时才设置
ruby 复制代码
127.0.0.1:6379> HSET bike:1 model Deimos brand Ergonom type 'Enduro bikes' price 4972
(integer) 4
127.0.0.1:6379> HGET bike:1 model
"Deimos"
127.0.0.1:6379> HGET bike:1 price
"4972"
127.0.0.1:6379> HGETALL bike:1
1) "model"
2) "Deimos"
3) "brand"
4) "Ergonom"
5) "type"
6) "Enduro bikes"
7) "price"
8) "4972"
ruby 复制代码
127.0.0.1:6379> HKEYS bike:1
1) "model"
2) "brand"
3) "type"
4) "price"
127.0.0.1:6379> HVALS bike:1
1) "Deimos"
2) "Ergonom"
3) "Enduro bikes"
4) "4972"

3.5 排序集 Sorted sets

Redis 排序集是按关联分数排序的一种集合,最多包含 2^32-1 个元素。排序集的元素 key 是唯一的(由唯一的非重复字符串元素组成),当多个元素具有相同的分数时,元素将按字典顺序排序。排序集最常见的使用场景一个是排行榜、一个是限流器,后面我们会讲到。

虽然集合中的元素不是有序的,但排序集中的每个元素都与一个浮点值相关联,称为分数(这就是为什么 Sorted sets 类似 Hashes,因为每个元素都映射到一个值)。

  • 如果 A 和 B 是两个分数不同的元素,且 A.score < B.score,那么在集合中 A 排于 B 前面,即默认分数小的排前面
  • 如果 A 和 B 是两个分数相同的元素,则按照字典顺序排序,A 的字典排序在 B 前面,同样 A 排于 B 前面

Sorted sets 最常见的指令如下:

  • ZADD:ZADD key score member [score member...] ,将具有指定分数的所有指定成员添加到存储在 的 key 排序集中。可以指定多个分数/成员对。注意:如果指定成员已经是排序集的成员,则会更新分数并将元素重新插入到正确的位置,以确保正确的排序。score 值可以是整数值或双精度浮点数。看用法的话有点类似 Sets 的 SADD 指令
  • ZRANGE:ZRANGE key start stop [BYSCORE | BYLEX] [REV] [LIMIT offset count] [WITHSCORES] ,返回排序集中的指定范围的元素。ZRANGE 可以执行不同类型的范围查询:按索引(排名)、按分数(BYSCORE)或按字典顺序(BYLEX)。可选 REV 参数代表反转排序,即指定 REV 时,元素按从最高到最低的分数排序,按照字典顺序反向排序;可选 WITHSCORES 参数表示是否返回元素的值及其分数(没有 WITHSCORES 时近返回元素值,如:value1,...,valueN),如:value1,score1,...,valueN,scoreN;注意这里的 start、stop 参数代表元素索引,并非元素分值,当 start、stop 超出范围时越界时并不会报出错误
  • ZRANGEBYSCORE:ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] ,返回排序集中 key 分数介于 min 和 max 之间所有元素(包括分数等于 min 或 max 的元素)。这些元素被认为是从低到高的顺序排列的,这里的 min 和 max 并且可以是 -inf 和 +inf,代表无穷小和无穷大。默认情况下 min、max 包含在内,想要不包含的话可以使用 (min 或者 (max,代表不含 min 或者 max
ruby 复制代码
127.0.0.1:6379> ZADD hackers 1940 "Alan Kay"
(integer) 1
127.0.0.1:6379> ZADD hackers 1957 "Sophie Wilson"
(integer) 1
127.0.0.1:6379> ZADD hackers 1953 "Richard Stallman"
(integer) 1
127.0.0.1:6379> ZADD hackers 1949 "Anita Borg"
(integer) 1
127.0.0.1:6379> ZADD hackers 1957 "Yukihiro Matsumoto"
(integer) 1
127.0.0.1:6379> ZRANGE hackers 0 -1
1) "Alan Kay"
2) "Anita Borg"
3) "Richard Stallman"
4) "Sophie Wilson"
5) "Yukihiro Matsumoto"
127.0.0.1:6379> ZRANGE hackers -2 -1
1) "Sophie Wilson"
2) "Yukihiro Matsumoto"

再来看看 ZRANGE 加参数时的样子:

ruby 复制代码
127.0.0.1:6379> ZRANGE hackers 0 -1 REV
1) "Yukihiro Matsumoto"
2) "Sophie Wilson"
3) "Richard Stallman"
4) "Anita Borg"
5) "Alan Kay"
127.0.0.1:6379> ZRANGE hackers 0 -1 WITHSCORES
 1) "Alan Kay"
 2) "1940"
 3) "Anita Borg"
 4) "1949"
 5) "Richard Stallman"
 6) "1953"
 7) "Sophie Wilson"
 8) "1957"
 9) "Yukihiro Matsumoto"
10) "1957"

ZRANGEBYSCORE 的用法我们看下:

ruby 复制代码
127.0.0.1:6379> ZRANGEBYSCORE hackers -inf +inf
1) "Alan Kay"
2) "Anita Borg"
3) "Richard Stallman"
4) "Sophie Wilson"
5) "Yukihiro Matsumoto"
127.0.0.1:6379> ZRANGEBYSCORE hackers 1953 +inf
1) "Richard Stallman"
2) "Sophie Wilson"
3) "Yukihiro Matsumoto"
127.0.0.1:6379> ZRANGEBYSCORE hackers (1953 +inf
1) "Sophie Wilson"
2) "Yukihiro Matsumoto"

3.6 地理空间数据类型 Geospatial

Geospatial 类型使得 Redis 具备了地理空间数据的存储和检索的能力,Geospatial 常见的指令如下:

  • GEOADD key [NX | XX] [CH] longitude latitude member [longitude latitude member ...] ,将指定的地理空间项(经度、纬度、名称)添加到指定的键。有效经度为 -180 到 180 度,纬度为 -85.05112878 到 85.05112878 度,当输入范围之外的经纬度时会报错。XX:仅更新已存在的元素,切勿添加元素;NX:不要更新现有的元素,始终添加新元素。注意:XX 和 NX 选项是互斥的
  • GEOSEARCH key <FROMMEMBER member | FROMLONLAT longitude latitude> <BYRADIUS radius <M | KM | FT | MI> | BYBOX width height <M | KM | FT | MI>> [ASC | DESC] [COUNT count [ANY]] [WITHCOORD] [WITHDIST] [WITHHASH] ,返回使用 填充 GEOADD 了地理空间信息的排序集的成员,这些成员位于给定形状指定的区域边界内。此命令扩展了命令 GEORADIUS,因此除了在圆形区域内搜索外,它还支持在矩形区域内搜索。这里命令参数较多,就不一一列举了

向地理空间索引添加多个位置:

ruby 复制代码
127.0.0.1:6379> GEOADD bikes:rentable -122.27652 37.805186 station:1
(integer) 1
127.0.0.1:6379> GEOADD bikes:rentable -122.2674626 37.8062344 station:2
(integer) 1
127.0.0.1:6379> GEOADD bikes:rentable -122.2469854 37.8104049 station:3
(integer) 1

查找给定位置 5 公里半径内的所有位置,并给出具体距离:

ruby 复制代码
127.0.0.1:6379> GEOSEARCH bikes:rentable FROMLONLAT -122.2612767 37.7936847 BYRADIUS 5 km WITHDIST
1) 1) "station:1"
   2) "1.8523"
2) 1) "station:2"
   2) "1.4979"
3) 1) "station:3"
   2) "2.2441"

3.7 位图 Bitmaps

位图不是实际的数据类型,而是在 String 类型上定义的一组面向位的操作,它被视为位向量。即位图可以对 String 类型数据每一位进行按位操作,由于字符串是二进制安全 blob,一个字节由8个bit位构成,其最大长度为 512 MB,因此 Bitmaps最多可以存储 2^32 位。当然也可以对一个或多个字符串执行按位运算。

  • SETBIT:SETBIT key offset value,设置或清除存储在键的字符串值中的偏移位,value 的值只能为 0 或 1,注意:SETBIT 返回值为给定偏移位置设置前的原始位值,并非是否设置成功,别混淆了
  • GETBIT:GETBIT key offset,返回存储在键的字符串值中偏移处的位值,如果未set,则默认为 0
  • BITOP:BITOP <AND | OR | XOR | NOT> destkey key [key ...] ,该 BITOP 命令支持四种按位运算:AND、OR、XOR 和 NOT
  • BITCOUNT:BITCOUNT key [start end [BYTE | BIT]] ,统计字符串类型值中二进制位为 1 的个数,0,-1 或者不加参数表示统计全部

需要注意的是,在第一次初始化 Bitmap 时,假如偏移量 offset 非常大,由于需要分配所需要的内存,整个初始化过程执行会比较慢,可能会造成 Redis 的阻塞

BITOP 四种指令操作时,格式如下:

  • BITOP AND destkey srckey1 srckey2 srckey3 ... srckeyN
  • BITOP OR destkey srckey1 srckey2 srckey3 ... srckeyN
  • BITOP XOR destkey srckey1 srckey2 srckey3 ... srckeyN
  • BITOP NOT destkey srckey

4 Java 客户端

Jedis 是 Redis 的 Java 客户端,专为性能和易用性而设计,这里以 Jedis 举例说明:

xml 复制代码
pom.xml 引入依赖
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>5.0.0</version>
</dependency>

代码举例:

java 复制代码
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.util.HashMap;
import java.util.Map;

@Slf4j
public class JedisExample {
    public static void main(String[] args) {
        JedisPool pool = new JedisPool("localhost", 6379);

        try (Jedis jedis = pool.getResource()) {
            // 字符串 Strings
            jedis.set("foo", "bar");
            log.info("{}", jedis.get("foo")); // bar
            jedis.incr("count");
            log.info("{}", jedis.get("count")); // 1
            jedis.mset("name", "lily", "age", "2");
            log.info("{}", jedis.mget("name", "age")); // [lily, 2]

            // Hash
            Map<String, String> hash = new HashMap<>();
            hash.put("name", "John");
            hash.put("surname", "Smith");
            hash.put("company", "Redis");
            hash.put("age", "29");
            jedis.hset("user-session:123", hash);
            log.info("{}", jedis.hgetAll("user-session:123"));
            // {name=John, surname=Smith, company=Redis, age=29}
        }
    }
}

5 Redis 的消息通知和发布订阅

Redis 的 Keyspace 俗称键空间,提供对 Redis 中键的过期(EXPIRE 指令)、扫描(SCAN 指令)、替换、查询等操作。

Redis 从 2.8.0 版本开始提供了 Keyspace notifications 机制,即键空间通知,原理就是基于 Redis 的 PUB/SUB 机制,当键被发生了某种操作时,Redis 会往特定的 channel 发布特定格式的消息,Redis 客户端订阅相关 channel 就可以接受到消息并作相应的处理了。

当然,默认 notifications 机制是关闭的,

需要手动启用,打开 redis.conf 添加如下配置:

matlab 复制代码
notify-keyspace-events Ex
css 复制代码
Eg 对某个键调用DEL,EXPIRE操作时,会收到通知
Ex 当某个键过期后会收到通知
Em 当检索某个不存在的键时会收到通知

这里的 Ex 含义如下:

vbnet 复制代码
K     Keyspace events, published with __keyspace@<db>__ prefix.
E     Keyevent events, published with __keyevent@<db>__ prefix.
g     Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ...
$     String commands
l     List commands
s     Set commands
h     Hash commands
z     Sorted set commands
t     Stream commands
d     Module key type events
x     Expired events (events generated every time a key expires)
e     Evicted events (events generated when a key is evicted for maxmemory)
m     Key miss events (events generated when a key that doesn't exist is accessed)
n     New key events (Note: not included in the 'A' class)
A     Alias for "g$lshztxed", so that the "AKE" string means all the events except "m" and "n".

修改配置后,重新启动 Redis 服务,配置才生效。

channel 格式:keyevent@数据库ID:操作类型,比如:

ruby 复制代码
# 删除键
__keyevent@0__:del
# 键过期
__keyevent@0__:expired

Redis 发布/订阅是 fire and forget 的,也就是说,如果您的发布/订阅客户端断开连接,并在稍后重新连接,则在客户端断开连接期间传递的所有事件都将丢失。所以不适合需要持久化消息的场景,比如支付订单过期自动取消等。

假设我们下单后,30min 内用户不支付的话需要自动取消订单,这种简单场景可以使用如下几种方案来实现:

5.1 Keyspace notifications 实现

引入 Redis 依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置监听器:

typescript 复制代码
@Slf4j
@Configuration
public class RedisConfig {
    @Resource
    private OrderService orderService;

    @Bean
    public RedisMessageListenerContainer listenerContainer(RedisConnectionFactory connectionFactory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        return container;
    }

    @Bean
    public KeyExpirationEventMessageListener keyExpirationEventMessageListener(RedisMessageListenerContainer listenerContainer) {
        return new KeyExpirationEventMessageListener(listenerContainer);
    }

    @Bean
    public ApplicationListener applicationListener() {
        return (ApplicationListener<RedisKeyExpiredEvent<String>>) event -> {
            log.info("监听到键过期消息,消息的key为:{}", new String(event.getSource()));
            orderService.cancelOrder();
        };
    }
}

使用 SET 指令模拟一条过期消息:

ruby 复制代码
127.0.0.1:6379> SET user:1:order:2022090899876781 1 EX 5
OK

注意必须先启动服务在执行上面 SET 操作,否则会拿不到消息

5s 之后观察控制台打印输出如下:

yaml 复制代码
2023-09-08 17:55:26.260  INFO 11420 --- [enerContainer-1] com.example.javaexamples.RedisConfig     : 监听到键过期消息,消息的key为:user:1:order:2022090899876781
2023-09-08 17:55:26.263  INFO 11420 --- [enerContainer-1] com.example.javaexamples.OrderService    : 订单取消成功

5.2 发布订阅模式实现

基于 Redis 的发布订阅,我们也可以实现上面订单到期自动取消的功能,Redis 的发布订阅分 2 种,

  • 基于频道的发布订阅:

    订阅者订阅频道;发布者向「频道」发布消息;所有订阅「频道」的订阅者收到消息

  • 基于 Pattern 的发布订阅

这里我们以基于频道的发布订阅来讲下怎么实现订单过期自动取消,这里以 Redisson 为例,伪代码如下:

java 复制代码
// in other thread or JVM
RTopic topic = redisson.getTopic("myTopic");
long clientsReceivedMessage = topic.publish(new SomeObject());

RTopic topic = redisson.getTopic("myTopic");
int listenerId = topic.addListener(SomeObject.class, new MessageListener<SomeObject>() {
    @Override
    public void onMessage(String channel, SomeObject message) {
        //...
    }
});

6 其它应用场景介绍

Redis 使用的场景远远不止上文介绍到的那些,比如分布式锁、分布式 id 生成器、全局计数器、限流、位统计、点赞/签到/打卡/排行榜、消息队列、Session 会话共享等等,后面我们会单独抽几期进行介绍。

7 可视化工具

Redis 可视化工具,官方推荐的是 RedisInsight,个人也比较推荐,该工具使用 Nodejs+Electron 开发,界面简洁明了、运行流畅、除了基本功能外,还额外提供比如发布订阅、分析工具、触发器和函数等功能,比较推荐。

8 总结

本节中我们从零开始介绍了 Redis,包括 Redis 基本介绍、数据结构、发布订阅、可视化工具等,比较适合入门人员学习阅读,正如第 6 节介绍的,Redis 仍不止于此,后面我们再慢慢介绍吧。

相关推荐
水月梦镜花12 小时前
redis:list列表命令和内部编码
数据库·redis·list
掘金-我是哪吒13 小时前
微服务mysql,redis,elasticsearch, kibana,cassandra,mongodb, kafka
redis·mysql·mongodb·elasticsearch·微服务
ketil2715 小时前
Ubuntu 安装 redis
redis
王佑辉17 小时前
【redis】redis缓存和数据库保证一致性的方案
redis·面试
Karoku06617 小时前
【企业级分布式系统】Zabbix监控系统与部署安装
运维·服务器·数据库·redis·mysql·zabbix
gorgor在码农18 小时前
Redis 热key总结
java·redis·热key
想进大厂的小王18 小时前
项目架构介绍以及Spring cloud、redis、mq 等组件的基本认识
redis·分布式·后端·spring cloud·微服务·架构
Java 第一深情18 小时前
高性能分布式缓存Redis-数据管理与性能提升之道
redis·分布式·缓存
minihuabei1 天前
linux centos 安装redis
linux·redis·centos
monkey_meng1 天前
【Rust中多线程同步机制】
开发语言·redis·后端·rust