一 Redis事务
redis开启事务后,会把接下来的所有命令缓存到一个单独的队列中,在提交事务时,使这些命令不可被分割的一起执行完成。
如果使用了watch命令监视某一个key,如果在开启事务之后,提交事务之前,有其他连接修改了这个key,那么这个事务在被提交的时候是无法执行的,会返回nil。
以下是redis与事务相关的命令:
#MULTI 开启事务,事务执行过程中,单个命令是入队列操作,直到调用 EXEC 才会一起执行;
MULTI
EXEC #提交事务
DISCARD #取消事务
WATCH #检测 key 的变动,若在事务执行中,key 变动则取消事务;在事务开启前调用,乐观锁实现
#(cas);若被取消则事务返回 nil ;
watch:
监视某些键, 如果这些键被改变, 就会导致事务被取消. EXEC, DISCARD, UNWATCH都能取消watch对键的监视. 如果不使用watch, 可能会导致自己对键的修改, 覆盖掉别人的修改.
如果在输入watch和输入MULTI之间, 被监视的键被修改, 也会导致事务失败.
lua脚本
lua 脚本实现原子性;redis中加载了一个 lua 虚拟机;用来执行 redis lua 脚本;redis lua 脚本的执行是原子性的;当某个脚本正在执行的时候,不会有其他命令或者脚本被执行;lua 脚本当中的命令会直接修改数据状态;
注意:如果项目中使用了 lua 脚本,不需要使用上面的事务命令;
二 Redis Pipeline
Redis Pipeline是一种通过将多个命令打包成单个请求发送给Redis服务器,以减少客户端和服务器之间的通信次数,从而提高Redis性能的技术。它允许客户端在发送多个命令之前将它们缓冲在本地,然后一次性将它们发送到Redis服务器。Redis服务器按照请求的顺序依次处理每个命令,并将所有命令的响应一次性返回给客户端。
pipeline不具备事务性。
三 Redis事务的ACID特性分析
- A 原子性;事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败;为了保持简单且快速, redis不支持回滚;即使事务队列中的某个命令在执行期间出现了错误,整个事务也会继续执行下去,直到将事务队列中的所有命令都执行完毕为止。
- C 一致性;事务使数据库从一个一致性状态到另外一个一致性状态;这里的一致性是指预期的一致性而不是异常后的一致性;所以redis也不满足;这个争议很大:redis 能确保事务执行前后的数据的完整约束;但是并不满足传统意义上的一致性;比如转账功能,一个扣钱一个加钱;可能出现扣钱执行错误,加钱执行正确,那么最终还是会加钱成功;系统凭空多了钱;
- I 隔离性;事务的操作不被其他用户操作所打断;redis 是单线程执行,天然具备隔离性;
- D 持久性;redis只有在 aof 持久化策略的时候,并且需要在 redis.conf 中appendfsync=always 才具备持久性;实际项目中几乎不会使用 aof 持久化策略;
因此, Redis的事务, 不具备原子性, 一致性, 持久性, 只满足隔离性;
四 Redis网络层
连接Redis的协议是Redis协议, 其运输层协议是TCP协议.
Redis是一个单线程reactor模型,它的主要逻辑处理是单线程的,但是io操作能够做到多线程处理。所以它可以同时有多个连接。多个连接并发执行。它的多线程模型如下:
Redis的所有命令都是原子性的。
五 异步连接Redis
协议实现的第一步需要知道如何界定数据包。常用界定数据包有两种方式:
- 长度 + 二进制流
- 二进制流 + 特殊分隔符
redis混合了这两种界定数据包的方式。server返回数据的时候,返回的数据头是*n\r\n开头,这个n就表明了返回的数据长度是多少。
异步连接
同步连接方案采用阻塞io来实现;优点是代码书写是同步的,业务逻辑没有割裂;缺点是阻塞当前线程,直至redis返回结果。通常用多个线程来实现线程池来解决效率问题。
异步连接方案采用非阻塞io来实现,优点是没有阻塞当前线程,redis 没有返回,依然可以往redis发送命令;缺点是代码书写是异步的(回调函数),业务逻辑割裂,可以通过协程解决(openresty,skynet);配合redis6.0以后的io多线程(前提是有大量并发请求),异步连接池,能更好解决应用层的数据访问性能。
redis6.0 io多线程
redis6.0版本后添加的 io多线程主要解决redis协议的压缩以及解压缩的耗时问题,一般项目中不需要开启。如果有大量并发请求,且返回数据包一般比较大的场景才有它的用武之地。
原理:
int n = read(fd, buff, size);// redis io-threads
msg = decode(buff, size);
data = do_command(msg);
bin = encode(data, sz);// io-threads
send(fd, bin, sz1);
第一行代码表示读取发送给redis的命令,第二行代码表示解析命令,第三行代码表示redis处理命令的逻辑,第四行代码表示对redis返回结果的加密,第五行代码表示将加密好的结果返回给客户端。
可以将第一二四五行代码逻辑交给io线程池处理,以提高效率。
Redis的IO多线程, 可以在配置文件中开启, 同时页可以配置IO线程数量