事务模式
同时存在多个客户端时,一个客户端批量发送的命令和另外一个客户端的命令执行顺序会存在交叉。redis
当须要将批量执行的语句原子化时,须要引入Redis的事务模式。服务器
一个事务中的多条命令以原子化的方式执行,不一样事务的命令相互时序再也不交叉。微信
入队/执行分离的事务原子性
客户端和Redis服务器经过两阶段交互作到批量命令原子化执行:并发
入队阶段:客户端将请求发送到服务器端,服务器端将其暂存在链接对象对应的请求队列中app
执行阶段:发送完一个批次的全部请求后,Redis服务器依次执行链接对象队列中的全部请求。单个实例的Redis单线程执行全部请求,一个链接的请求在执行批量请求的过程当中,不会执行其余客户端的请求。flex
批量执行命令:EXECthis
由MULTI开启事务,随后发送的请求都暂存在服务器端的链接上,最后经过EXEC一次性批量执行,并将全部执行结果做为一个响应,以array类型的协议数据返回客户端。spa
事务的一致性
入队阶段若是发生语法错误,不会后续执行EXEC。.net
EXEC过程当中若是有一条请求命令执行失败,后续请求会继续执行,只是在返回客户端的array数据中标记这条命令失败。线程
严格讲,Redis事务并非一致的。
事务的只读操做
批量请求须要应用程序一开始在入队阶段肯定每次写操做的值,每一个请求的参数取值不能依赖上一次请求的执行。
入队的请求应该全是写操做,只读操做需放到MULTI语句前执行。
事务在执行期间,若是穿插其余客户端的其余语句,会形成数据不一致问题。经过Redis的WATHC机制能够解决一致性问题。
乐观锁的可串行化事务隔离
Redis经过WATCH机制实现乐观锁解决一致性问题:
将本次事务涉及的全部key注册为观察模式,假设此时逻辑时间为tstart。
执行只读操做
根据只读操做的结果组装写操做命令并发送到服务器端入队
发送原子化的批量执行命令EXEC试图执行链接的请求队列中的命令,假设此时逻辑时间为tcommit。
执行时有两种状况:
假设前面注册为观察模式的key有一个或多个,在tstart和tcommit之间被修改过,那么EXEC将直接失败,拒绝执行
不然顺序执行请求队列中的全部请求
事务实现
事务的状态保存在redisClient中,经过两个属性控制:
typedef struct redisClient{ ... int flags; multiState mstate; ...} redisClient;
flags包含多个bit,其中两个bit分别标记了:当前链接处于MULTI和EXEC之间、当前链接WATCH以后到如今它所观察的key是否被修改过
typedef struct mlutiState{ multiCmd *commands; int count; ...} multiState;
count用来标记MULTI和EXEC之间总共有多少个待执行命令,同时commands就是该链接的请求队列。
watch机制经过维护在redisDb中的全局map来实现:
typedef struct redisDb{ dict *dict; /* The keyspace for this DB */ dict *expires; /* Timeout of keys with a timeout set */ dict *blocking_keys;/* Keys with clients waiting for data (BLPOP) */ dict *ready_keys; /* Blocked keys that received a PUSH */ dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */ struct evictionPoolEntry * eviction_pool; /* Eviction pool of keys */ int id; /* Database ID */ long long avg_ttl; /* Average TTL, just for stats */} redisDb;
map的键是被watch的key,值是watch这些key的redisClient指针的链表。
每当redis执行一个写命令时,同时会对执行命令的这个key在watchedkeys中找到对应的client并将后者的flag对应位置为REDIS_DIRTY_CAS,后续这个client在执行EXEC以前若是看到flag有REDIS_DIRTY_CAS标记,则拒绝执行。
事务的结束或者显示UNWATCH都会重置redisClient中的REDIS_DIRTY_CAS标记并从redisDb对应watched_keys链表中删除本身。
事务交互模式
客户端发送四类请求:监听相关(WATCH、UNWATCH)、只读请求、写请求的批量执行或放弃执行请求(EXEC/DISCARD)、写请求的入队(MULTI和EXEC/DISCARD之间)
交互时序为:开启对读写主键的监听、只读操做、MULTI请求、根据前面只读操做的结果编排/参数赋值/入队写操做、一次性批量执行队列中的写请求。
本文分享自微信公众号 - shysh95(shysh95)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。