Redis 管道及事务
redis pipeline 是一个客户端提供的,而不是服务端提供的。
Redis 管道(Pipeline)是一种优化 Redis 批量操作的机制,可以将多个命令一次性发送到 Redis 服务器,并一次性接收它们的回复。这样可以减少客户端和服务器之间的网络往返次数,从而提高了操作的效率。
使用管道可以在以下场景中获得性能提升:
批量写入操作:当需要执行多个写入操作时,使用管道可以将它们一次性发送到服务器,减少了每个操作之间的网络往返时间。
**批量读取操作:**类似地,当需要获取多个键的值时,使用管道可以一次性发送多个命令,并一次性接收它们的回复,提高了读取操作的效率。
**原子性操作:**在管道中,多个命令被打包成一个原子操作,保证了它们的执行是连续的、不可中断的。
type Conn interface {
// Close closes the connection.
Close() error
// Err returns a non-nil value when the connection is not usable.
Err() error
// Do sends a command to the server and returns the received reply.
Do(commandName string, args ...interface{}) (reply interface{}, err
error)
// Do = Send + Flush + Receive
// Send writes the command to the client's output buffer.
Send(commandName string, args ...interface{}) error
// Flush flushes the output buffer to the Redis server.
Flush() error
// Receive receives a single reply from the Redis server
Receive() (reply interface{}, err error)
}
Send(commandName string, args …interface{}) error:将命令写入客户端的输出缓冲区。参数和 Do 方法类似,用于发送命令但不立即执行。
**Flush() error:**将输出缓冲区中的内容刷新到 Redis 服务器。执行此操作后,客户端将向服务器发送所有已经排队的命令。
Receive() (reply interface{}, err error):从 Redis 服务器接收单个回复。通常与 Send 和 Flush 方法一起使用,用于接收之前发送的命令的回复。
管道使用技巧
// 创建一个管道
c := redis.NewPipeline(conn)
// 批量发送,批量接收
c.Send(cmd1, ...)
c.Send(cmd2, ...)
c.Send(cmd3, ...)
c.Flush() // 将上面的三个命令发送出去
c.Receive() // cmd1 的返回值
c.Receive() // cmd2 的返回值
c.Receive() // cmd3 的返回值
// 如果不需要关注返回值
c.Send(cmd1, ...)
c.Send(cmd2, ...)
c.Send(cmd3, ...)
c.Do("")
// 如果只关注最后一个命令的返回值
c.Send(cmd1, ...)
c.Send(cmd2, ...)
c.Do(cmd3, ...)
Redis 网络事件处理
Redis 是单线程处理逻辑的,这意味着 Redis 服务器在任意时刻只能处理一个命令,而不是像传统的多线程服务器一样并行处理多个命令。每条连接的读缓冲区相当于一个队列;线程会交错执行活跃连接的命令;
在 Redis 中,网络事件处理和命令处理都是在同一个线程中进行的。**服务器通过轮询每个连接,从连接的读缓冲区中读取数据包,并将其分派给相应的命令处理逻辑。****每条连接的读缓冲区相当于一个队列,线程会交错执行活跃连接的命令,**以确保所有连接都得到及时的处理。
由于 Redis 是单线程处理逻辑的,它具有以下优点和特点:
简单高效:单线程模型减少了线程间的竞争和同步开销,使得 Redis 在处理命令时非常高效。
避免了复杂性:单线程模型减少了并发编程的复杂性,避免了锁和同步等并发问题。
原子性操作:Redis 命令的执行是原子性的,即每个命令在执行时不会被中断,可以确保数据的一致性和可靠性。
非阻塞 I/O:由于 Redis 使用非阻塞 I/O 模型,可以处理大量的并发连接,而不会因为 I/O 操作而阻塞线程。
Redis 事务
Redis 事务使用 MULTI、EXEC、DISCARD 和 WATCH 四个命令来管理事务的执行过程:
MULTI:标记事务的开始,之后的所有命令都会被放入事务队列中,直到执行 EXEC 或 DISCARD 命令。
EXEC:执行事务中的所有命令。如果在执行期间没有出现错误,事务中的所有命令都会被执行;如果其中有任何一个命令执行出错,所有命令都会被取消。
DISCARD:取消事务,清空事务队列中的所有命令,放弃事务的执行。
WATCH:用于在事务开始前监视一个或多个键,如果在事务执行期间这些键被其他客户端修改,事务将被取消。在事务开启前调用,乐观锁实现(cas);若被取消则事务返回 。
应用
# 实现 string 的 加倍操作
WATCH score:10001
val = GET score:10001
MULTI
SET score:10001 val*2
EXEC
lua脚本实现原子性
通过 Lua 脚本,可以将多个 Redis 命令组合成一个原子性操作,在执行过程中不会被中断,保证了数据的一致性和可靠性。
注意:如果项目中使用了 lua 脚本,不需要使用上面的事务命令;
# 从文件中读取 lua脚本内容
cat test1.lua | redis-cli script load --pipe
# 加载 lua脚本字符串 生成 sha1
> script load 'local key = KEYS[1];local s = redis.call("get",
key);redis.call("set", key, s*2);return s*2'
"8f7d021dcc386a422e0febe38befdc6084357610"
# 检查脚本缓存中,是否有该 sha1 散列值的lua脚本
> script exists "8f7d021dcc386a422e0febe38befdc6084357610"
1) (integer) 1
# 清除所有脚本缓存
> script flush
OK
# 如果当前脚本运行时间过长,可以通过 script kill 杀死当前运行的脚本
> script kill
(error) NOTBUSY No scripts in execution right now.
EVAL
EVAL 是 Redis 中用于执行 Lua 脚本的命令之一。通过 EVAL 命令,可以在 Redis 服务器端执行 Lua 脚本,并获取执行结果。EVAL 命令的基本语法如下:
EVAL script numkeys key [key ...] arg [arg ...]
- script 是要执行的 Lua 脚本;
- numkeys 表示键的数量,即 Lua 脚本中访问的 Redis 键的数量;
- key [key …] 是 Lua 脚本中访问的 Redis 键;
- arg [arg …] 是传递给 Lua 脚本的参数。
EVAL 命令执行 Lua 脚本的过程如下:
- 如果 **Lua 脚本中访问了 Redis 的键,需要将这些键作为参数传递给 EVAL 命令。**这些键会被传递给 Lua
脚本,并在脚本中以 KEYS[1], KEYS[2], … 的形式访问。 - 如果 Lua 脚本需要接收参数,这些参数也需要作为 EVAL 命令的参数传递进来。这些参数会被传递给 Lua 脚本,并在脚本中以
ARGV[1], ARGV[2], … 的形式访问。 - Redis 服务器执行 Lua 脚本,并返回执行结果。
下面是一个简单的示例,演示了如何使用 EVAL 命令执行一个 Lua 脚本:
EVAL "return 'Hello, world!'" 0
在这个示例中,Lua 脚本是简单的返回了一个字符串 ‘Hello, world!’。numkeys 参数是 0,表示 Lua 脚本没有访问 Redis 键。因此命令只包含了脚本本身,没有传递任何键。执行结果会返回字符串 ‘Hello, world!’。
EVALSHA
EVALSHA 是 Redis 中用于执行 Lua 脚本的另一个命令,它与 EVAL 类似,但是 EVALSHA 使用的是已经缓存的脚本的 SHA1 散列值,而不是直接传递脚本的字符串。这样可以减少网络传输的开销,并且提高了安全性。
EVALSHA 命令的基本语法如下:
EVALSHA sha1 numkeys key [key ...] arg [arg ...]
- sha1 是 Lua 脚本的 SHA1 散列值;
- numkeys 表示键的数量,即 Lua 脚本中访问的 Redis 键的数量;
- key [key …] 是 Lua 脚本中访问的 Redis 键;
- arg [arg …] 是传递给 Lua 脚本的参数。
EVALSHA 命令执行 Lua 脚本的过程与 EVAL 类似,但是它使用脚本的 SHA1 散列值来识别和执行脚本。
使用 EVALSHA 命令的好处是,如果多个客户端同时执行相同的 Lua 脚本,服务器只需要缓存一个副本,不需要重复解析和编译脚本,从而节省了服务器的资源,并提高了执行效率。
下面是一个示例,演示了如何使用 EVALSHA 命令执行一个已经缓存的 Lua 脚本:
EVALSHA "16f99e1b4a265e29d33c68c7b2f673b1f53f82d9" 0
在这个示例中,16f99e1b4a265e29d33c68c7b2f673b1f53f82d9 是 Lua 脚本的 SHA1 散列值,表示要执行的 Lua 脚本。numkeys 参数是 0,表示 Lua 脚本没有访问 Redis 键。因此命令只包含了脚本的 SHA1 散列值,没有传递任何键。执行结果会根据 Lua 脚本的返回值而变化。
应用
# 1: 项目启动时,建立redis连接并验证后,先加载所有项目中使用的lua脚本(script load);
# 2: 项目中若需要热更新,通过redis-cli script flush;然后可以通过订阅发布功能通知所有服
务器重新加载lua脚本;
# 3:若项目中lua脚本发生阻塞,可通过script kill暂停当前阻塞脚本的执行;
事务 ACID 特性分析
**A 原子性;**事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败;redis不支持回滚;即使事务队列中的某个命令在执行期间出现了错误,整个事务也会继续执行下去,直到将事务队列中的所有命令都执行完毕为止。
C 一致性;事务使数据库从一个一致性状态到另外一个一致性状态;这里的一致性是指预期的一致性而不是异常后的一致性;所以redis也不满足;
I 隔离性;事务的操作不被其他用户操作所打断;redis命令执行是串行的,redis事务天然具备隔离性;
D 持久性;redis只有在 aof 持久化策略的时候,并且需要在 redis.conf 中 appendfsync=always 才具备持久性;实际项目中几乎不会使用