Redis事务的分析及改进
本文内容纲要:
-Redis事务的分析及改进
-Redis的事务特性
-原子性
-持久性
-隔离性和一致性
-redis事务的缺陷
-更新操作中的查询实现
-优化网络特性
-参考
Redis事务的分析及改进
Redis的事务特性
数据ACID特性满足了几条?
为了保持简单,redis事务保证了其中的一致性和隔离性;
不满足原子性和持久性;
原子性
redis事务在执行的中途遇到错误,不会回滚,而是继续执行后续命令;(违反原子性)
事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作;
中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做;
比如:
redis127.0.0.1:7000>multi
OK
redis127.0.0.1:7000>setaaaa
QUEUED
redis127.0.0.1:7000>setbbbb
QUEUED
redis127.0.0.1:7000>setcccc
QUEUED
redis127.0.0.1:7000>exec
1)OK
2)OK
3)OK
如果在setbbbb处失败,seta已成功不会回滚,setc还会继续执行;
持久性
事务不过是用队列包裹起了一组Redis命令,并没有提供任何额外的持久性功能,所以事务的持久性由Redis所使用的持久化模式决定:
- 在单纯的内存模式下,事务肯定是不持久的。
- 在RDB模式下,服务器可能在事务执行之后、RDB文件更新之前的这段时间失败,所以RDB模式下的Redis事务也是不持久的。
- 在AOF的“总是SYNC”模式下,事务的每条命令在执行成功之后,都会立即调用fsync或fdatasync将事务数据写入到AOF文件。但是,这种保存是由后台线程进行的,主线程不会阻塞直到保存成功,所以从命令执行成功到数据保存到硬盘之间,还是有一段非常小的间隔,所以这种模式下的事务也是不持久的。
- 其他AOF模式也和“总是SYNC”模式类似,所以它们都是不持久的。
隔离性和一致性
redis事务在执行的过程中,不会处理其它命令,而是等所有命令都执行完后,再处理其它命令(满足隔离性)
redis事务在执行过程中发生错误或进程被终结,都能保证数据的一致性;(详见参考资料1)
redis事务的缺陷
除了不保证原子性和持久性,在实际使用中还有以下问题:
1)遇到有查询的情况穿插在事务中间,不会返回结果;
设置事务开始标志后,所有的命令都是queued,即使是查询指令;
如果后续的更新操作需要依赖于前面的查询指令,那redis事务就无法有效的完成任务;
例如:
redis127.0.0.1:7000>multi
OK
redis127.0.0.1:7000>setaaaa
QUEUED
redis127.0.0.1:7000>getb
QUEUED
业务逻辑...
redis127.0.0.1:7000>setcccc
QUEUED
redis127.0.0.1:7000>exec
1)OK
2)bbb
3)OK
第二步geta返回的是queued,并不是a的查询结果,
如果后续的set操作依赖于get的结果(存在依赖业务逻辑),就不能将get操作放在事务操作中;
2)事务中的每条命令都与redis服务器进行了一次网络交互;
redis事务指定开始后,执行一个事务返回的都是queued,那这个入队操作是在客户端实现,还是在服务器端实现的?
查看源码,很容易发现是在服务器端实现;
在Redis.c中有这么一段:
intprocessCommand(redisClient*c){
...
/*Execthecommand*/
if(c->flags&REDIS_MULTI&&
c->cmd->proc!=execCommand&&c->cmd->proc!=discardCommand&&
c->cmd->proc!=multiCommand&&c->cmd->proc!=watchCommand)
{
queueMultiCommand(c);//将事务中的命令都放入到队列中,然后返回"QUEUED"
addReply(c,shared.queued);
}else{
if(server.vm_enabled&&server.vm_max_threads>0&&
blockClientOnSwappedKeys(c))returnREDIS_ERR;
//调用该命令函数来处理命令
call(c);
}
returnREDIS_OK;
}
这里就涉及到客户端与服务器端的多次交互,明明是需要一次批量执行的n条命令,还需要通过多次网络交互,有些浪费;
更新操作中的查询实现
如果有这样的需求:在事务开始后,中间穿插有查询逻辑;
那么使用redis事务(单库),无法满足这个要求;
可能的解决方案:
-
可以考虑使用多个库,读写分离,查询库只用来查询,更新库用来开事务做写操作;
-
不再使用redis的事务指令,自己在客户端将待执行的命令批量打包,决定是否回滚还是全部执行;这样可以在更新的间隙执行查询逻辑;而不需要将查询逻辑提前到事务指令multi之前;
-
将查询业务逻辑提前;严格规范代码编写要求,所有的redis查询逻辑都放在事务之外:
redis127.0.0.1:7000>getb bbb 业务逻辑... redis127.0.0.1:7000>multi OK redis127.0.0.1:7000>setaaaa QUEUED redis127.0.0.1:7000>setcccc QUEUED redis127.0.0.1:7000>exec 1)OK 2)OK
优化网络特性
将多个命令打包批量发送到redis服务器执行,减少网络交互,优化性能,可能的解决方案:
- 对于所有的get/set操作,可使用现有的mget/mset指令;
- 对于各种不同类型的更新操作,可使用lua脚本将命令打包后,发送到服务器端一次执行;
参考
http://redisbook.readthedocs.org/en/latest/feature/transaction.html
Postedby:大CC|10MAR,2015
博客:blog.me115.com[订阅]
微博:新浪微博
本文内容总结:Redis事务的分析及改进,Redis的事务特性,原子性,持久性,隔离性和一致性,redis事务的缺陷,更新操作中的查询实现,优化网络特性,参考,
原文链接:https://www.cnblogs.com/me115/p/4326375.html