Redis协议与异步方式

Rredis Pipeline

redis pipeline 是一个客户端提供的机制,而不是服务端提供的;
pipeline 不具备事务性;
目的:节约网络传输时间
通过一次发送多次请求命令,从而减少网络传输的时间。

Redis发布订阅

为了支持消息的多播机制,redis 引入了发布订阅模块;
消息不一定可达;分布式消息队列; stream 的方式确保一定可
达;

服务器和Redis建立多少链接?

  1. 使用5种基本数据结构处理,可以使用连接池(更快)
  2. 阻塞命令 brpop
  3. 监听发布者模式 sub/pub

应用:

发布订阅功能一般要区别命令连接重新开启一个连接;

因为命令连接严格遵循请求回应模式;而 pubsub 能收到 redis 主动推送的内容;

所以实际项目中如果支持 pubsub 的话,需要另开一条连接用于处理发布订阅;

缺点: 不确保消息到达,不会持久化 使用stream, kafka分布式消息队列

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

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

redis 事务: 只有一条连接服务器时,不需要考虑事务,因为串行执行

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

MULTI 开启事务,事务执行过程中,单个命令是入队列操作,直到调用 EXEC 才会一起执行;

乐观锁实现,所以失败需要重试,增加业务逻辑的复杂度;

  • MULTI:开启事务 begin / start transaction
  • EXEC:提交事务 commit multiexec中的Redis指令将作为一个整体执行
  • DISCARD: 取消事务 rollback
  • WATCH:监测key变动,若在事务中执行,key变动则取消事务;在事务开启前调用,乐观锁实现(CAS);若被取消事务则返回nil

以上操作在业务中不会使用,一般都使用lua脚本

ACID特性分析(理解性背诵)

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

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

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

D 持久性;Redis 只有在 AOF 持久化策略的时候,并且需要在redis.confappendfsync=always 才具备持久性;实际项目中几乎不会使用 AOF 持久化策略;

面试时候回答:lua 脚本满足原子性和隔离性一致性和持久性不满足;

Lua脚本

Lua 脚本实现原子性;

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

Lua 脚本当中的命令会直接修改数据状态;

Lua 脚本 MySQL 存储区别:MySQL存储过程不具备事务性,所以也不具备原子性;

注意:如果项目中使用了 Lua 脚本,不需要使用上面的事务命令;

EVAL
# 测试使用
script numkeys key [key ...] arg [arg ...]
EVAL
# 线上使用
EVALSHA sha1 numkeys key [key ...] arg [arg ...]

在这里插入图片描述

在这里插入图片描述

异步连接:

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

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

异步连接性能测试:

在这里插入图片描述

同步连接性能测试:

在这里插入图片描述

怎么实现异步连接?
  1. 另起线程
  2. reactor
    • Redis建立连接 connect(fd, &addr, &len) 使用非阻塞IO
      • 创建socket,设置fd为非阻塞
      • connect -1, errno = EINPROGRESS
      • fd注册到epoll, 写事件
      • 如果连接建立成功,fd的写事件进行响应
    • Redis发送数据,使用Redis协议加密,通过tcp发送过去
      • 直接write, int n = write(fd, buf, sz); if (n < sz && n != -1)部分数据没有发送,说明fd对应的缓冲区已满
      • 注册写事件,如果写事件出发,继续发送未完成的数据;如果全部发送,注销写事件
      • 注册读事件
    • 读取Redis的返回,通过tcp接收数据,分割数据包(粘包处理),Redis协议解密
      • 读事件触发,int n = read(fd, buf, sz)
      • 根据Redis协议,分割数据包
  3. proactor

最后给大家推荐一个LinuxC/C++高级架构系统教程的学习资源与课程,可以帮助你有方向、更细致地学习C/C++后端开发,具体内容请见 https://xxetb.xetslk.com/s/1o04uB

上一篇:网络安全的重要性及人才需求


下一篇:springboot整合mybatis配置多数据源(mysql/oracle)