上篇文章:Reids命令原理与应用1 - Redis命令与原理-CSDN博客
个人代码仓库:橘子真甜 (yzc-YZC) - Gitee.com
目录
[一. Redis网络层](#一. Redis网络层)
[二. Redis pipeline](#二. Redis pipeline)
[三. Redis 发布订阅](#三. Redis 发布订阅)
[3.1 发布订阅的使用](#3.1 发布订阅的使用)
[3.2 发布订阅的缺点和使用场景](#3.2 发布订阅的缺点和使用场景)
[四. Redis事务](#四. Redis事务)
[4.1 事务与指令](#4.1 事务与指令)
[4.2 lua实现事务](#4.2 lua实现事务)
[4.3 Redis事务的acid特性](#4.3 Redis事务的acid特性)
一. Redis网络层
我们知道,redis是网络内存数据库,模型是请求-响应模型。为了高效缓存/处理请求/应答/数据安全,redis的网络层是如何处理的呢?
redis为了数据安全,对数据的操作和处理只有一个线程。同时为了高效处理不同客户端的请求,有多个辅助线程进行网络io数据。
而且redis对于请求的处理是:
对于单个连接的多个请求,处理是串行的
对于多个连接的多个请求,处理是并行的
二. Redis pipeline
pipeline是用于提高客户端发送数据的效率的,减少不必要的网络io冗余和浪费。
原理:将多个请求一起发送给redis服务器,redis返回多个响应。减少客户端发送请求的次数
目的:节约网络IO的时间,避免时间浪费。提高整体IO效率
过程图如下:

这种方式在很多网络应用使用,典型的有HTTP2对于HTTP1的升级
三. Redis 发布订阅
3.1 发布订阅的使用
发布订阅模式可以支持多播,假如我们有三个redis客户端c1,c2,c3,都和服务端s1连接。现在有一个客户端c4,c4如何发送一个信息同时告知c1,c2,c3?
这个时候就需要发布订阅:

方式是:
1 s1 s2 s3 通过subscribe 订阅频道 channel。
2 web通过 publish channel message 将message广播所有订阅channel的客户端
3 广播结束后,s1,s2,s3就能接收消息
注意:每一个发布订阅的客户端都需要一个tcp连接管理
具体命令如下:
# 订阅频道
subscribe 频道
# 订阅模式频道
psubscribe 频道
# 取消订阅频道
unsubscribe 频道
# 取消订阅模式频道
punsubscribe 频道
# 发布具体频道或模式频道的内容
publish 频道 内容
# 客户端收到具体频道内容
message 具体频道 内容
# 客户端收到模式频道内容
pmessage 模式频道 具体频道 内容


3.2 发布订阅的缺点和使用场景
发布信息的一方只负责发送信息,对方有没有收到我不管。并且如果redis重启之后,我们发布的信息是不会进行持久化的。
即:不确保消息送达,不会持久化消息。
使用场景:
如果我们需要发送一个普通消息,其他用户有没有收到我不管。此时就能使用发布订阅模式
redis集群就使用了发布订阅
如果我们想要确保消息被送达并且能够持久化消息,
可以使用redis中的stream或者使用其他工具比如 kafka消息队列
四. Redis事务
4.1 事务与指令
我们知道,redis单个命令是具有原子性的,一条命令要么被执行,要么不会执行。
如果现在需要执行一个操作,这个操作包含命令 1 2 3 呢?如何保证这些命令的原子性?123要么被完成,要么不执行,不能出现中间状态。
此时就需要使用redis事务:redis单个命令是原子的,多个命令原子需要开启事务
MULTI:开启事务
EXEC:提交事务
DISCARE:关闭事务
WATCH:检查key是否变动,如果变动,回取消这个事务
事务执行过程中,单个命令是入队列操作,直到调用 EXEC 才会一起执行;
操作流程如下:




可以看到,multi本质是开启一个任务队列。
每次执行新命令后,先将命令放入队列
调用dicard后回清空这个命令队列,调用exec提交这个任务队列到客户端执行。
调用了watch,如果检查到key变动,这个事务不执行,返回nil
并且redis服务器执行multi exec提交的命令期间,不会被其他命令打断
注意:redis事务的执行是无法回滚的,如果执行事务期间由于服务器崩溃导致命令中断。一个事务中已经执行的命令不会回滚,未执行的命令之后也不会执行。
4.2 lua实现事务
直接使用multi非常麻烦,实际应用一般都使用lua脚本实现事务。redis服务端启动的时候会加载一个lua虚拟机,用于执行lua脚本。注意lua脚本执行是原子性的,这样就能实现事务
实际应哟我们不会/很少使用multi,使用lua更为方便和快捷。
lua脚本的使用有两种方式
# 测试使用
EVAL script numkeys key [key ...] arg [arg ...]
eval '【Lua脚本代码】' 【键的数量】 【键名1】 【附加参数1】 ...
# 线上使用
EVALSHA sha1 numkeys key [key ...] arg [arg ...]
evashal '【Lua脚本代码的哈希值】' 【键的数量】 【键名1】 【附加参数1】 ...
测试使用:
用于将某一个key * 2
eval 'local key = KEYS[1]; local val = redis.call("get", key);if not val then val = 1 end; redis.call("set", key, val * 2); return val * 2;' 1 score1
分行就是
eval 'local key = KEYS[1];
local val = redis.call("get", key);
if not val then val = 1 end; redis.call("set", key, val * 2);
return val * 2;'
1 score1

注意:和multi一样,lua脚本执行出错了,已经执行的命令不会回滚,未执行的命令不再执行
不过我们线上更多的使用方式是2
线上使用:
首先使用 script load 加载lua并且返回一个哈希值(此时末尾不需要带上我们的key参数)
script load 'local key = KEYS[1]; local val = redis.call("get", key);if not val then val = 1 end; redis.call("set", key, val * 2); return val * 2;'
分行
script load 'local key = KEYS[1];
local val = redis.call("get", key);
if not val then val = 1 end;
redis.call("set", key, val * 2); return val * 2;' # 末尾是没有score1这个参数的


然后我们就能使用这个哈希值来替代我们的lua脚本。
evalsha 74ecc3d632197ba3a1119a65ec55200f42bbb709 1 score1

总结:
eval:
直接向redis中的lua虚拟机发送lua脚本,服务器编译执行命令完毕后向我返回响应。
script load + evalsha:
先向redis发送lua脚本,服务器编译好后不会执行命令。而是保存这个编译程序,然后向我们返回该命令对应的hash值,之后我们通过 evalsha + 哈希值向服务端发送命令。
由于此时该命令已经被编译好了,所以运行速度更快
4.3 Redis事务的acid特性
acid特性指的是:原子性,一致性,隔离性,持久性
原子性:一个事务要么完成,要么失败。redis事务虽然表面上能够保证原子性,但是redis不支持回滚。redis原子性是有问题的
一致性:需要保证数据一致和用户的逻辑一致,redis只能保证数据一致性,对于逻辑一致,一旦出现异常(由于原子性无法保证),逻辑有可能会不一致。redis一致性是有问题的
隔离性:一个事务的操作不会被其他事务打断,由于redis对数据的操作是单线程的。天然具有隔离性。
持久性:保证数据库下线后,数据能够保存在磁盘中,下次重启之后能够读取。redis支持aof持久化和rdb持久化,开启之后就能实现持久性。