Redis协议与异步方式

)

01.慢查询

​ 慢查询日志就是Redis的"行车记录仪"。建议线上环境至少保留100条记录,阈值按业务调整。它的原理很简单:系统会记录每条命令的执行时间,一旦超过设定的阈值,就把这条命令的相关信息(eg:发生时间、耗时、具体操作内容)记下来。Redis 也支持类似的功能,方便排查那些"拖后腿"的慢操作。

  1. 发送命令
  2. 命令排队
  3. 命令执行
  4. 返回结果

​ Redis的慢查询只统计命令在Redis内部执行的时间(就是步骤3),不包含网络传输和排队等待的时间。所以即使慢查询日志干干净净,客户端照样可能超时。

复制代码
为了解决预设阀值怎么设置以及慢查询记录存放在哪的问题,可以使用一下配置进行解决。
  1. slowlog-log-slower-than

    • 设定"慢"的门槛,单位是微秒(1秒=100万微秒)
    • 默认10000微秒(10毫秒)------超过这个时间的命令才会被记录
    • 设成0:记录所有命令(慎用!)
    • 设成负数:直接关闭慢查询功能
  2. slowlog-max-len

    • 控制慢查询日志最多存多少条(本质是个内存列表)
    • 新记录进来时,老记录自动弹出(先进先出)
    • 比如设成100,第101条进来时,最早那条就被踢掉

02.Redis pipeline

​ 已知传统模式是请求---效应,任意两个请求之间没有关联。Pipeline 是 Redis 的批处理技术,一次发送多个命令,Redis 按序执行并按序返回响应。减少网络往返时间(RTT),提升批量操作吞吐量。

​ Redis 客户端执行一条命令的四个阶段(发送 → 排队 → 执行 → 返回)

​ Pipeline纯客户端行为,Redis 服务端无感知。类似 HTTP/1.1 的"请求管道化"(Pipelining),解决"请求-响应串行阻塞"问题。不保证原子性和事务语义,仅用于性能优化。


03.事务与Lua

3.1事务

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

​ Redis 命令遵循 请求-响应 模型------每个命令需等待服务器确认后才能发下一个。而事务通过 MULTI/EXEC 将多次网络往返压缩为两次交互 (入队 + 执行),大幅提升效率。 事务存在并发连接 时,需保证一组操作的逻辑完整性。eg:对 counter 执行 "读取 → 计算翻倍 → 写回",避免中间被其他客户端干扰。

​ 在下面的例子中,开启事务执行后未提交事务,后被另外一个连接对数据进行了修改,造成事务内部的数据key变动,事务被取消。

Redis 事务通过 队列 + 原子执行 实现:

  1. MULTI 开启事务 :后续命令(如多个 SET)不会立即执行,而是入队缓存;
  2. EXEC 触发执行:一次性原子化执行队列中所有命令(单线程特性保证执行过程不被其他请求打断);
  3. DISCARD取消事务:清空已入队的命令,不执行任何操作。

WATCH机制:

  • 通过 WATCH 监控 key ,若事务入队后、EXEC 前该 key 被其他客户端修改,则 EXEC 会自动丢弃整个队列,避免脏写。

下面每一条指令都是请求-响应的双向连接。一来一回的。


3.1ACID

原子性

  • 事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败;redis不支持回滚;即使事务队列中的某个命令在执行期间出现了错误,整个事务也会继续执行下去,直到将事务队列中的所有命令都执行完毕为止。

一致性

  • 事务的前后,所有的数据都保持一个一致的状态,不能违反数据的一致性检测;这里的一致性是指预期的一致性而不是异常后的一致性;所以redis 也不满足;这个争议很大:redis 能确保事务执行前后的数据的完整约束;但是并不满足业务功能上的一致性;比如转账功能,一个扣钱一个加钱;可能出现扣钱执行错误,加钱执行正确,那么最终还是会加钱成功;系统凭空多了钱;
  • 完整约束的一致
  • 用户逻辑的一致
    • 是指我操作之间不能被其他操作影响

隔离性

  • 各个事务之间互相影响的程度;redis是单线程执行,天然具备隔离性;

持久性

  • redis 只有在laof持久化策略的时候,并且需要在redis.conf 中appendfsync=always才具备持久性;实际项目中几乎不会使用aof持久化策略;
3.2Lua脚本

​ Redis 内置 Lua 虚拟机,脚本执行具有原子性:一旦开始运行,整个脚本会独占 Redis 单线程,期间不会插入任何其他命令或脚本,所有操作连续完成。MySQL 不具备事务原子性。lua脚本满足原子性和隔离性;一致性和持久性不满足;

  • 脚本中的命令直接修改数据状态 ,无需额外事务(使用 Lua 后,无需 MULTI/EXEC 等事务命令)
  • 通过 EVAL 或 EVALSHA 触发执行,避免网络往返开销
bash 复制代码
# 从文件加载脚本(生成 SHA1 校验码)
cat test.lua | redis-cli --eval /dev/stdin

# 手动加载脚本字符串
> SCRIPT LOAD 'return KEYS[1]'
"b8059ba43af6ffe8bed3db65bac35d452f8115d8"

# 检查脚本是否已缓存
> SCRIPT EXISTS "b8059ba43af6ffe8bed3db65bac35d452f8115d8"
1) (integer) 1  # 1=存在,0=不存在

# 清理所有缓存脚本(重启 Redis 也会自动清空)
> SCRIPT FLUSH
OK

# 终止卡死的脚本(如死循环)
> SCRIPT KILL
(error) NOBUSY  # 无运行中脚本时返回此提示

​ 通过第一次发送的脚本生成的hash值,这个值就是未来执行lua脚本,通过hash值找到脚本索引找到脚本进行执行。

shell 复制代码
# 1. 开发测试:直接执行脚本
> EVAL "return {KEYS[1], ARGV[1]}" 1 user:1001 "profile"

# 2. 生产环境:先加载脚本获取 SHA1
> SCRIPT LOAD "return redis.call('GET', KEYS[1])"
"767e31a4d5a0f9a3d3d3d3d3d3d3d3d3d3d3d3d3"

04.发布订阅

​ Redis提供了基于"发布/订阅"模式的消息机制,此种模式下,消息发布者(可以是web服务器也可以是普通服务器?)和订阅者不进行直接通信,发布者客户端向指定的频道(channel)发布消息,订阅该频道的每个客户端都可以收到该消息。

shell 复制代码
#订阅频道
subscribe频道
#订阅模式频道
psubscrihe 频道人
#取消订阅频道
unsubscribe 频道
#取消订阅模式频道
punsubscribe 频道
#发布具体频道或模式频道的内容
publish频道 内容
#客户端收到具体频道内容
message 具体频道 内容
#客户端收到模式频道内容
pmessage模式频道具体频道内容

​ 发布订阅模式基于 TCP 长连接实现,应用此功能一般要区别命令连接重新开启一个连接;因为命令连接严格遵循请求回应模式;而pub/sub能收到redis主动推送的内容;所以实际项目中如果支持pub/sub的话,需要另开一条连接用于处理发布订阅。

​ 发布订阅的生产者传递过来一个消息,redis会直接找到相应的消费者并传递过去;假如没有消费者,消息直接丢弃;假如开始有2个消费者,一个消费者突然挂掉了,另外一个消费者依然能收到消息,但是如果刚挂掉的消费者重新连上后,在断开连接期间的消息对于该消费者来说彻底丢失了;

另外,redlis停机重启,pub/sub的消息是不会持久化的,所有的消息被直接丢弃;

适用场景:适用于容忍消息丢失的实时广播场景:

  • 实时通知(如在线状态变更)

  • 日志收集(非关键日志)

  • 配置热更新广播

  • 不适用于 需要可靠投递、消息追溯或强一致性的业务(如订单、支付、审计日志),此类场景应考虑 Redis Stream 或专业消息队列(如 Kafka、RabbitMQ)。


05.Reactor异步连接拆解
1. 与Redis建立连接
socket(fd,&addr,&len) 非阻塞I/O

​ 1.创建socket,设置文件描述符为非阻塞模式

​ 2.调用connect,当返回-1且errno为EINPROGRESS时表示连接正在进行中

​ 3.将文件描述符注册到epoll的写事件

​ 4.当连接建立成功后,epoll会触发写事件,此时注销写事件

2. 向Redis发送数据
使用Redis协议对命令进行编码
  1. 通过TCP发送数据

    • 调用int n = write(fd, buf, sz),若返回n < sz&&n != -1; n = -1且errno = EWOULDBLOCK,说明fd对应的发送缓冲区已满

    • 此时注册写事件,当写事件触发时调用write(fd, buf, sz),继续发送剩余数据

    • 数据全部发送完毕后,注销写事件并注册读事件

3. 读取Redis的返回
  1. 通过tcp接收数据分割数据包
  2. 使用redis协议解密
  • 当读事件触发时,int n = read(fd , buf , sz ),通过TCP接收数据
  • 根据Redis协议格式分割 数据包
  • 对协议数据进行解码,获取最终响应结果

c语言通过typedef,C++通过继承获取evenr_t和ctx两个对象

相关推荐
前端 贾公子19 分钟前
LangGraph == 图的状态(State)管理 (下)
运维·服务器·数据库
zlwool28 分钟前
机械行业ERP与进销存的合规选型边界
数据库·erp
梦想的颜色34 分钟前
【AI速览】RAG 向量数据库选型:Milvus、Chroma、Qdrant、PGVector 全维度横向对比
数据库·milvus·chroma·rag·pgvector·向量库·qdrant
小溪学编程1 小时前
向量数据库 Milvus 基础实践:从入门到上手
数据库·milvus
数据最前线1 小时前
雪上加霜,Oracle向蓝色巨人最后的阵地发起挑战
数据库·oracle
于眠牧北1 小时前
Charles配置指定域名抓取和雷电模拟器中抓取APP接口配置
数据库·redis·缓存
福娃筱欢2 小时前
57014 canceling statement due to user request(金仓 KingbaseES)
数据库·kingbase·金仓数据库
ClouGence2 小时前
SAP HANA 数据同步遇到长事务,如何兼顾实时性与完整性?
数据库·后端
数据库小学妹2 小时前
数据库管理工具有哪些?2026主流工具分类与选型对照
数据库·数据库管理工具·数据库工具·信创数据库·数据库客户端
bingHHB2 小时前
销售订单同步实战:MES生产领料/补料→金蝶材料出库
数据库·集成测试