redis学习笔记(19)---事务
来源:互联网 发布:sql查询替换部分字符串 编辑:程序博客网 时间:2024/06/07 09:00
事务的性质
ACID
一般数据库的事务需要满足ACID四条性质:
redis事务
为了保持简单,redis事务保证了其中的一致性和隔离性;
不满足原子性和持久性;
1)原子性:redis事务在执行的中途遇到错误,不会回滚,而是继续执行后续命令;(违反原子性)
2)持久性:事务不过是用队列包裹起了一组 Redis 命令,并没有提供任何额外的持久性功能
Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:
1)事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
2)事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
命令
下表列出了 redis 事务的相关命令:
取消事务,放弃执行事务块内的所有命令2EXEC
执行所有事务块内的命令3MULTI
标记一个事务块的开始4UNWATCH
取消 WATCH 命令对所有 key 的监视5WATCH key [key …]
监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断
实现
上述事务相关命令的实现都在文件multi.c中
一个事务从开始到结束通常可以分为3个阶段:
1)事务开始
2)命令入队
3)事务执行
事务开始(multi)
multi命令标志着事务的开始 ,通过将标志位置为REDIS_MULTI来标志正处于事务阶段
void multiCommand(redisClient *c) { if (c->flags & REDIS_MULTI) { //已经处于事务阶段 return; } c->flags |= REDIS_MULTI; addReply(c,shared.ok);}
命令入队
在server每次收到一个client的请求,对请求进行处理时,
1)首先会判断是否需要将命令入队
2)如果是,则调用queueMultiCommand将命令加入到队列中
3)否则调用call处理命令
int processCommand(redisClient *c) { /* ...... */ if (c->flags & REDIS_MULTI && c->cmd->proc != execCommand && c->cmd->proc != discardCommand && c->cmd->proc != multiCommand && c->cmd->proc != watchCommand) { //判断条件 queueMultiCommand(c); //将命令入队 addReply(c,shared.queued); } else { call(c,REDIS_CALL_FULL); //处理命令 c->woff = server.master_repl_offset; if (listLength(server.ready_keys)) handleClientsBlockedOnLists(); } return REDIS_OK;}
命令入队的实现如下:
void queueMultiCommand(redisClient *c) { multiCmd *mc; int j; c->mstate.commands = zrealloc(c->mstate.commands, sizeof(multiCmd)*(c->mstate.count+1)); //重新分配内存 mc = c->mstate.commands+c->mstate.count; //队列尾部元素 //将命令copy到c->mstate.commands数组中 mc->cmd = c->cmd; mc->argc = c->argc; mc->argv = zmalloc(sizeof(robj*)*c->argc); memcpy(mc->argv,c->argv,sizeof(robj*)*c->argc); for (j = 0; j < c->argc; j++) incrRefCount(mc->argv[j]); c->mstate.count++; //增加队列长度}
所有命令都是按照从前到后的顺序,保存在c->mstate.commands这个数组中的。
执行事务
当server收到exec命令时,就开始执行事务
void execCommand(redisClient *c) { //如果有watch的key发生变化,或者在命令入队过程中发生错误,则取消事务 if (c->flags & (REDIS_DIRTY_CAS|REDIS_DIRTY_EXEC)) { addReply(c, c->flags & REDIS_DIRTY_EXEC ? shared.execaborterr : shared.nullmultibulk); discardTransaction(c); goto handle_monitor; } /* 首先unwatch所有监视的key */ unwatchAllKeys(c); /* Unwatch ASAP otherwise we'll waste CPU cycles */ orig_argv = c->argv; orig_argc = c->argc; orig_cmd = c->cmd; addReplyMultiBulkLen(c,c->mstate.count); //遍历队列,依次取出每条命令 for (j = 0; j < c->mstate.count; j++) { c->argc = c->mstate.commands[j].argc; c->argv = c->mstate.commands[j].argv; c->cmd = c->mstate.commands[j].cmd; call(c,REDIS_CALL_FULL); //依次执行每个命令 c->mstate.commands[j].argc = c->argc; c->mstate.commands[j].argv = c->argv; c->mstate.commands[j].cmd = c->cmd; } discardTransaction(c); //事务执行完成,释放事务}
取消事务
通过discard命令取消事务
void discardCommand(redisClient *c) { if (!(c->flags & REDIS_MULTI)) { //没有事务要执行,返回 addReplyError(c,"DISCARD without MULTI"); return; } discardTransaction(c); //执行discard操作 addReply(c,shared.ok);}void discardTransaction(redisClient *c) { freeClientMultiState(c); //释放队列中的所有命令 initClientMultiState(c); //将事务状态恢复成初始状态 c->flags &= ~(REDIS_MULTI|REDIS_DIRTY_CAS|REDIS_DIRTY_EXEC); //设置标志位 unwatchAllKeys(c); //unwatch所有监视的key}
watch & unwatch
对于监视的命令,是通过数据库中的字典c->db->watched_keys
实现的。
将watch的命令加入到该字典中,并从该字典中移除unwatch的命令
本文所引用的源码全部来自Redis3.0.7版本
redis学习参考资料:
https://github.com/huangz1990/redis-3.0-annotated
Redis 设计与实现(第二版)
- redis学习笔记(19)---事务
- Redis 事务学习笔记
- Redis学习笔记:事务
- [Redis学习笔记]-Redis 事务
- Redis学习笔记(三)--事务
- 【Redis学习笔记(七)】 Redis中的事务
- Redis学习笔记(3)-Redis事务,过期时间,队列
- 《Redis源码学习笔记》事务
- Redis学习笔记八、事务
- 【学习笔记】Redis(4)-事务
- Redis学习笔记1 事务
- Redis学习笔记6--Redis事务
- Redis学习笔记6--Redis事务
- Redis学习笔记6--Redis事务
- redis学习笔记5(redis事务)
- Redis学习笔记(七)进阶之事务
- Redis学习笔记(八)——事务入门
- Redis学习笔记(九)——事务进阶
- 自定义圆图片
- python 查看32位还是64位的
- eclipse工程导入Android studio 有些包无效的解决
- Android接口回调深入理解
- Android之注解IOC(二)
- redis学习笔记(19)---事务
- html和htm、html5对比
- Ubuntu SSH 私匙和公匙的产生原理阐述
- ADTS和LATM的AAC格式
- 局域网实现VLAN实例
- 跨平台开发的两种方法及其对比
- java.math.BigDecimal cannot be cast to java.lang.String解决方法
- HUD3488 Tour(二分图的最小权值和)
- SQL 性能调优日常积累