Pub/Sub
定义
- 实现生产者 - 消费者模型
- 生产者不直接将消息发布给特定的消费者,而是发布到频道中,而不关注消费者
- 消费者订阅一个或多个频道,只接收感兴趣的消息,而不关注生产者
特点
- 可靠性:消息只会被传递一次,如果消费者接收消息后无法处理消息,该消息将永久丢失。如果想要更强的可靠性,可以使用Redis的
Streams
,流中的消息是持久化的,并支持重新传递 - 作用域:Pub/Sub 与键空间无关,在 数据库10 上发布消息,会被 数据库1 上的消费者接收到
命令(Commands | Docs)
SUBSCRIBE channel1 channel2 ...
:订阅一个或多个频道。- 消费者会按照消息发布的顺序接收消息(因为Redis是单线程模型)
PUBLISH channel message
:发布消息到频道中PSUBSCRIBE news.*
:订阅匹配的频道,支持通配符风格。- 通配符:
- *:匹配任意多个字符
- ?:匹配单个任意字符
- […]:匹配**"[]"内的任意一个**字符
- 如果消费者订阅了多个匹配同一频道的模式,可能会多次收到同一条消息
- 通配符:
消息格式
- 订阅消息:“subscribe 频道名称1 当前订阅的频道数量 频道名称2 当前订阅的频道数量 …”
- 退订消息:“unsubscribe 频道名称1 当前订阅的频道数量 频道名称2 当前订阅的频道数量 …”
- 接收的消息:“message 频道名称 消息内容”
- 根据匹配模式接收的消息:“pmessage 匹配模式 频道名称”
事务
定义
-
定义:Redis 事务是一组命令的集合,这些命令会被一次性、按顺序执行。事务使用
MULTI
命令开始,用EXEC
命令提交执行。 -
机制:开启事务 -> 入队命令 -> 提交事务
MULTI
命令开始事务- 后续所有命令被排队(并没有立即执行)
EXEC
命令执行时,才依次执行排队的命令。
命令:命令 | 文档 — Commands | Docs
命令 | 说明 |
---|---|
MULTI | 开启事务 |
EXEC | 提交事务,此时才执行从 MULTI 之后入队的所有命令 |
DISCARD | 取消事务,丢弃所有已排队的命令 |
WATCH key1 key2 ... | 监视一个或多个键,在事务执行前(EXEC ),如果这些键被其他客户端修改,则事务失败 |
UNWATCH | 取消监视 |
特点
特性 | 是否支持 | 说明 |
---|---|---|
命令打包执行 | ✅ | 所有命令按顺序依次执行,不会被其他客户端打断。 |
原子性 | ❌ | 不支持回滚。若中间某命令执行失败,其它命令仍继续执行。 |
隔离性 | ❌ | 没有类似数据库的事务隔离级别,读的是实时值,除非配合 WATCH 。 |
并发控制 | ✅(手动) | 可用 WATCH 实现乐观锁控制。 |
问题
-
为什么Redis不具备像Mysq那样完整的事务机制?
因为Redis执行所有命令都在一个主线程中,天然避免了多线程的写并发冲突问题,因此不需要额外的调度机制(MVCC,锁)
-
为什么Redis事务执行过程中,键会被其他客户端修改?
Redis事务在**"命令入队阶段"时,Redis 不会阻止其它客户端修改其中使用的键,所以你以为事务内部用的是旧值,但实际上外部已经修改了。因此Redis 提供了一个简洁的乐观锁机制**:
WATCH
监视某些键- 如果在
MULTI
到EXEC
之间这些键被修改过 - 则
EXEC
执行前会提前失败,返回null
- 此时可以选择重试事务
索引(全文索引,Full Text)
定义
SET user:1001 "{\"name\": \"Alice\", \"age\": 25}"
- 在Redis中,主键索引就是每个键,比如这里的
user:1001
- 这里的索引指二级索引,如果想通过
name = "Alice"
或age = 25
来查询用户,就需要二级索引。因为Redis 本身是键值存储,默认无法通过 value 中的字段来查找,所以索引主要用于HASH
和JSON
结构
命令
-
创建索引
# 创建一个名为 idx:bicycle 的全文索引 FT.CREATE idx:bicycle # 表示索引的数据是JSON格式的文档,也可以选择HASH ON JSON # 表示这个索引只会应用于以 "bicycle:" 开头的 key。1 的意思是后面有1个前缀。 PREFIX 1 bicycle: # 设置文档的默认分数,用于相关性排序(不常用,可省略) SCORE 1.0 # 定义要索引的字段、字段类型和相关参数 SCHEMA # path为$.brand,命名为 brand,类型是 TEXT,权重1.0(用于相关性排序)。 $.brand AS brand TEXT WEIGHT 1.0 $.model AS model TEXT WEIGHT 1.0 $.description AS description TEXT WEIGHT 1.0 $.price AS price NUMERIC $.condition AS condition TAG SEPARATOR ,
-
使用索引进行查询:查询数据 | 文档 — Querying data | Docs
-
通配符查询:
FT.SEARCH "idx:bicycle" "*" LIMIT 0 10
查询
idx:bicycle
索引下的所有文档 -
根据某个字段查询:
FT.SEARCH “idx:bicycle” “@model:jugger” LIMIT 0 10
查询
model
字段中包含jugger
该词的文档
-
管道(Pipeline)
- 定义:Redis 是基于客户端-服务端模型和请求/响应协议的 TCP 服务。通常,客户端发送请求后会阻塞等待响应。通过管道技术,客户端可在未收到响应前连续发送多个请求,最后一次性读取所有响应,提高通信效率。
- 注意:管道化请求中的命令按发送顺序执行,但在这些命令之间可能会与其他客户端的命令交错执行。
- 返回结果:执行完管道后,Redis 会把所有命令的回复按顺序打包成一个“回复数组”返回给客户端。每一条命令对应一条回复:
- 返回值:该命令执行后的返回值
- 错误回复:如果该条命令执行失败,就会在对应位置放一个错误对象或消息
脚本
-
定义:Redis 允许用户在服务器上上传并执行 Lua 脚本。Redis 保证了脚本的原子执行。在执行脚本期间,服务器在脚本的整个运行时间内都会被阻塞,不会被打断。当Redis执行脚本中的某个命令失败时不会进行回滚。
-
使用:
-
普通使用:
EVAL "return 'Hello, scripting!'" 0
"0"表示无参数
-
带参数使用:
redis> EVAL "return { KEYS[1], KEYS[2], ARGV[1], ARGV[2], ARGV[3] }" 2 key1 key2 arg1 arg2 arg3 1) "key1" 2) "key2" 3) "arg1" 4) "arg2" 5) "arg3"
"2"表示KEYS参数的数量
-
通过
redis.call(操作类型、键参数列表、值参数列表)
在脚本中调用Redis命令:EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 foo bar
也可以使用
redis.pcall()
,区别在于redis.call()
函数遇到的错误会直接返回给执行该命令的客户端。而redis.pcall()
函数遇到的错误会返回给脚本的执行上下文,以便可能提前进行处理。
-
-
缓存:
- 介绍:使用
EVAL
执行的每个脚本都会存储在服务器维护的一个专用缓存中,因此可以通过缓存来减少上传脚本的时间。当服务器重启或通过SCRIPT FLUSH
显式清除时,缓存会被清空。 - 使用:
SCRIPT LOAD "脚本内容"
:将脚本加载到服务器中,并返回其SHA1
值EVALSHA sha1 0
:通过SHA1
值执行缓存中的脚本,"0"表示无参数SCRIPT FLUSH
:强制清空脚本缓存SCRIPT EXISTS sha1 [sha1 ...]
:通过SHA1
值判断每个脚本是否被缓存,会返回一个由1和0构成的数组SCRIPT KILL
:中断长时间运行的脚本
- 介绍:使用
-
主从同步:
- 在集群环境中,Redis 确保所有由脚本执行的写操作也会发送到从服务器以保持一致性。
- 模式:
- 逐字复制:主节点将脚本的源代码发送给从节点。从节点再运行脚本,以节省带宽。它要求所有写脚本必须是确定的,即脚本的执行结果不会因为环境的改变而改变。
- 效果复制:主节点将脚本的写命令发送给从节点。从节点再运行这些命令,可能在网络流量方面更长,但避免了重新计算脚本和脚本本身的不确定性。