redis协议与异步方式

发布于:2025-05-12 ⋅ 阅读:(25) ⋅ 点赞:(0)

一、redis pipeline

       redis pipeline 是一个客户端提供的机制,而不是服务端提供的;

        注意:pipeline 不具备事务性;
        目的:节约网络传输时间;
        通过一次发送多次请求命令,从而减少网络传输的时间。

 

二、redis事务

        事务:用户定义一系列数据库操作,这些操作视为一个完整的逻辑处理工作单元,要么全部执行,

        要么全部不执行,是不可分割的工作单元。
        MULTI 开启事务,事务执行过程中,单个命令是入队列操作,直到调用 EXEC 才会一起执行;
        乐观锁实现,所以失败需要重试,增加业务逻辑的复杂度;
MULTI
开启事务
begin / start transaction
EXEC
提交事务
commit
DISCARD
取消事务
rollback
WATCH
检测 key 的变动,若在事务执行中, key 变动则取消事务;在事务开启前调用,乐观锁实现
cas ;
若被取消则事务返回 nil ;

lua 脚本 

        lua脚本实现原子性;

        redis 中加载了一个 lua 虚拟机;用来执行 redis lua 脚本;redis lua 脚本的执行是原子性的;当某 个脚本正在执行的时候,不会有其他命令或者脚本被执行;

        lua 脚本当中的命令会直接修改数据状态;
        lua 脚本 mysql 存储区别: MySQL 存储过程不具备事务性,所以也不具备原子性;
        注意:如果项目中使用了 lua 脚本,不需要使用上面的事务命令;

一、从文件加载 Lua 脚本(高效批量加载)

命令
cat script.lua | redis-cli script load --pipe
作用

        将本地 Lua 脚本文件通过标准输入(cat)传递给 redis-cli,利用 --pipe 选项批量加载脚本到 Redis 缓存,避免逐行传输的网络开销。

实例

假设脚本文件 test.lua 内容为:

 
-- 功能:获取键对应的值并返回
local value = redis.call('GET', KEYS[1])
return value

执行加载:

 
cat test.lua | redis-cli -h 127.0.0.1 -p 6379 script load --pipe
输出
SHA1哈希值(如): "a1b2c3d4e5f67890abcdef1234567890abcdef12"
注意
  • --pipe 用于批量执行命令,此处仅加载单个脚本,实际生产中可批量加载多个脚本。
  • 加载后脚本存储在 Redis 服务器的脚本缓存中,后续可通过 EVALSHA 命令调用,减少网络传输量。

二、加载 Lua 脚本字符串(手动生成 SHA1)

命令
redis-cli script load "脚本内容"
作用

        直接传入 Lua 脚本字符串,Redis 会执行语法检查并生成 SHA1 哈希值,用于后续通过 EVALSHA 调用,避免重复传输完整脚本。

实例
# 加载一个简单脚本(返回第一个传入的键名)
> script load 'local val = KEYS[1]; return val'
输出

"b8059ba43af6ffe8bed3db65bac35d452f8115d8"  # SHA1 哈希值

三、检查脚本是否存在于缓存(避免重复加载)

命令
redis-cli script exists SHA1哈希值 [SHA1哈希值 ...]
作用

        检查指定的 SHA1 哈希值对应的脚本是否已加载到 Redis 缓存中,返回 1(存在)或 0(不存在)。

实例
# 检查之前生成的 SHA1 是否存在
> script exists "b8059ba43af6ffe8bed3db65bac35d452f8115d8"
输出
1) (integer) 1  # 表示存在
应用场景
  • 在调用 EVALSHA 前先执行 script exists,若脚本不存在则先用 script load 加载,避免 EVALSHA 报错(NOSCRIPT 错误)。

四、清除所有脚本缓存(释放内存)

命令
redis-cli script flush
作用

        删除 Redis 服务器中缓存的所有 Lua 脚本,释放相关内存。

实例
> script flush
输出
OK  # 表示清除成功
注意
  • 清除后,所有通过 script load 或 EVAL 加载的脚本都会被删除,后续调用 EVALSHA 会报错,需重新加载。
  • 生产环境中慎用,避免影响正在运行的依赖脚本的业务。

五、终止长时间运行的脚本(处理阻塞)

命令
redis-cli script kill
作用

        杀死当前正在执行的、运行时间过长的 Lua 脚本(仅能终止 非阻塞写操作 的脚本,即未执行过 WRITE 命令的脚本)。

实例
# 假设存在一个死循环脚本,尝试终止
> script kill
输出
(error) NOTBUSY No scripts in execution right now.  # 无正在执行的脚本时提示
注意
  • 若脚本已执行过写操作(如 SETDEL),script kill 无法终止,只能通过重启 Redis 实例强制停止(可能导致数据不一致,需谨慎)。
  • Redis 的单线程模型决定了脚本会阻塞其他命令,应避免编写耗时过长的脚本(建议控制在毫秒级)。

六、核心命令对比表

命令 功能描述 典型场景 输出示例
script load 加载脚本,生成 SHA1,存入缓存 预加载脚本,后续通过 EVALSHA 调用以减少网络传输 SHA1 哈希值
script exists 检查脚本是否在缓存中 调用 EVALSHA 前判断是否需要重新加载脚本 1(存在)或 0(不存在)
script flush 清除所有脚本缓存 重置环境、释放内存或脚本更新后强制重新加载 OK
script kill 终止正在执行的脚本(仅非阻塞写脚本) 处理脚本死循环或长时间阻塞 OK(成功终止)或错误提示

七、最佳实践

  1. 预加载脚本
    在服务启动时通过 script load 加载所有需要的脚本,后续用 EVALSHA 调用(比 EVAL 更高效,无需传输完整脚本)。

    # 调用示例:EVALSHA SHA1 键数量 键1 键2 ... 参数1 参数2 ...
    redis-cli evalsha b8059ba43af6ffe8bed3db65bac35d452f8115d8 1 mykey "arg1"
    
  2. 处理 NOSCRIPT 错误
    若 EVALSHA 因脚本未加载报错,捕获错误后重新调用 script load 加载脚本,再重试 EVALSHA

  3. 避免长耗时脚本
    确保脚本逻辑简洁,必要时拆分复杂操作,防止阻塞 Redis 事件循环。

  4. 脚本调试
    先用 redis-cli eval 直接执行脚本调试(如 eval "脚本内容" 0),确认无误后再加载到缓存。

 

三、ACID特性分析        

        A :原子性;事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败; redis
不支持回滚;即使事务队列中的某个命令在执行期间出现了错误,整个事务也会继续执行下去,直
到将事务队列中的所有命令都执行完毕为止。
        C :一致性;事务的前后,所有的数据都保持一个一致的状态,不能违反数据的一致性检测;这里
的一致性是指预期的一致性而不是异常后的一致性;所以 redis 也不满足;这个争议很大: redis
确保事务执行前后的数据的完整约束;但是并不满足业务功能上的一致性;比如转账功能,一个扣
钱一个加钱;可能出现扣钱执行错误,加钱执行正确,那么最终还是会加钱成功;系统凭空多了钱;

        I :隔离性;各个事务之间互相影响的程度; redis 是单线程执行,天然具备隔离性;
        D :持久性; redis 只有在 aof 持久化策略的时候,并且需要在 redis.conf
appendfsync=always 才具备持久性;实际项目中几乎不会使用 aof 持久化策略;
面试时候回答: lua 脚本满足原子性和隔离性;一致性和持久性不满足;

 

四、redis发布订阅

        为了支持消息的多播机制,redis 引入了发布订阅模块;

        消息不一定可达;分布式消息队列; stream 的方式确保一定可达;
# 订阅频道
subscribe 频道
# 订阅模式频道
psubscribe 频道
# 取消订阅频道
unsubscribe 频道
# 取消订阅模式频道
punsubscribe 频道
# 发布具体频道或模式频道的内容
publish 频道 内容
# 客户端收到具体频道内容
message 具体频道 内容
# 客户端收到模式频道内容
pmessage 模式频道 具体频道 内容
应用
发布订阅功能一般要区别命令连接重新开启一个连接;因为命令连接严格遵循请求回应模式;而
pubsub 能收到 redis 主动推送的内容;所以实际项目中如果支持 pubsub 的话,需要 另开一条连接
用于处理发布订阅;
缺点
发布订阅的生产者传递过来一个消息, redis 会直接找到相应的消费者并传递过去;假如没有消费
者,消息直接丢弃;假如开始有 2 个消费者,一个消费者突然挂掉了,另外一个消费者依然能收到
消息,但是如果刚挂掉的消费者重新连上后,在断开连接期间的消息对于该消费者来说彻底丢失
了;
另外, redis 停机重启, pubsub 的消息是不会持久化的,所有的消息被直接丢弃;

五、redis异步连接

redis协议图

 异步连接

        同步连接方案采用阻塞 io 来实现;优点是代码书写是同步的,业务逻辑没有割裂;缺点是阻塞当 前线程,直至 redis 返回结果;通常用多个线程来实现线程池来解决效率问题;

        异步连接方案采用非阻塞 io 来实现;优点是没有阻塞当前线程, redis 没有返回,依然可以往 redis 发送命令;缺点是代码书写是异步的(回调函数),业务逻辑割裂,可以通过协程解决
openresty skynet );配合 redis6.0 以后的 io 多线程(前提是有大量并发请求),异步连接
池,能更好解决应用层的数据访问性能;

实现方案

        详情代码见:https://github.com/jerry123456123456/redis.git 中的redis-master

0voice · GitHub


网站公告

今日签到

点亮在社区的每一天
去签到