当前位置:   article > 正文

Redis中的事务(二)

Redis中的事务(二)

事务

事务的实现

执行事务

当一个处于事务状态的客户端向服务器发送EXEC命令时,这个EXEC命令将立即被服务器执行,服务器会遍历这个客户端的事务队列,执行队列中保存的所有命令,最后将执行命令所得的结果全部返回给客户端。

例子

在这里插入图片描述

  • 举个例子。对于如图所示的事务队列来说,服务器会先执行命令:
SET "name" "Practical Common Lisp"
  • 1

之后执行命令:

GET "name"
  • 1

在之后执行命令:

GET "author"
  • 1

最后,服务器会将执行这四个命令所得的回复返回给客户端:

127.0.0.1:6379> EXEC
1) OK
2) "Practical Common Lisp"
3) OK
4) "Peter Seibel"
  • 1
  • 2
  • 3
  • 4
  • 5
EXEC命令的实现原理

可以用以下伪代码来描述:

def EXEC():
# 创建空白的回复队列
reply_queue =[]
# 遍历事务队列中的每个项
# 读取命令的参数,参数的个数,以及要执行的命令
for argv, argc, cmd in client.mstate.commands:
# 执行命令,并取得命令的返回值
reply = execute_command(cmd, argv, argc)

# 将返回值追加到回复队列末尾
reply_queue.append(reply)

# 移除REDIS_MULTI标识,让客户端回到非事务状态
client.flags &= ~REDIS_MULTI

# 清空客户端的事务状态,包括:
# 1.清零入队命令计数器
# 2.释放事务队列
client.mstate.count = 0
release_transaction_queue(client.mstate.commands)

# 将事务的执行结果返回给客户端
send_reply_to_client(client, reply_queue)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

WATCH命令的实现

WATCH命令是一个乐观锁(optimistic locking),它可以在EXEC命令执行之前,监视任意数量的数据库键,并在EXEC命令执行时,检查被监视的键是否至少有一个已经被修改过了,如果是的话,服务器将拒绝执行事务,并向客户端返回代表事务执行失败的空回复。

例子

  • 举个例子。
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> WATCH "name"
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET "name" "vinpink"
QUEUED
127.0.0.1:6379> EXEC
(nil)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

如表展示了上述的事务是如何失败的。在时间T4,客户端B修改了"name"键的值,当客户端A在T5执行EXEC命令时,服务器会发现WATCH监视的键"name"已经被修改,因此服务器拒绝执行客户端A的事务,并向客户端A返回空回复
在这里插入图片描述

使用WATCH命令监视数据库键。

在这里插入图片描述

每个Redis数据库都保存着一个watched_keys字典,这个字典的键是某个被WATCH命令监视的数据库键,而字典的值则是一个链表,链表中记录了所有监视相应数据库键的客户端:

typedef struct redisDb {
// ...

// 正在被WATCH命令监视的键
dict *watched_keys;

// ...
}redisDb;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

通过watched_keys字典,服务器可以清楚地知道哪些数据库键正在被监视,以及哪些客户端正在监视这些数据库键。

  • 1.客户端c1和c2正在监视键"name"
  • 2.客户端c3正在监视键"age"
  • 3.客户端c2和c4正在监视键"address"
    通过执行WATCH命令,客户端可以在watched_keys字典中与被监视的键进行关联
例子
  • 举个例子。如果当前客户端为c10086,那么客户端执行以下WATCH命令之后:
redis>WATCH "name" "age"
OK
  • 1
  • 2

上图展示的wathced_keys字典将被更新为如图所示的状态,其中用虚线包围的两个c10086节点就是由刚刚执行的WATCH命令添加到字典中的。
在这里插入图片描述

监视机制的触发

所有对数据库进行修改的命令,比如SET、LPUSH、SADD、ZREM、DEL、FLUSHDB等等,在执行之后都会调用multi.c/touchWatchKey
函数对watched_keys字典进行检查,查看是否有客户端正在监视刚刚被命令修改过的数据库键,如果有的话,那么touchWatchKey函数
会将监视被修改键的客户端的REDIS_DIRTY_CAS标识打开,标识客户端的事务安全性已经被破坏。touchWatchKey函数的定义额可以用以下伪代码来描述

def touchWatchKey(db, key) :
# 如果键key存在于数据库的watched_keys字典中
# 那么说明至少有一个客户端在监视这个key
if key in db.watched_keys:
# 遍历所有监视键key的客户端
for client in db.watched_keys[key]:
# 打开标识
client.flags |= REDIS_DIRTY_CAS
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
例子
  • 举个例子。如图所示的watched_keys字典来说:
    1.如果键"name"被修改,那么c1、c2、c10086三个客户端的REDIS_DIRTY_CAS标识将被打开
    2.如果键"age"被修改,那么c3和c10086两个客户端的REDIS_DIRTY_CAS标识将被打开
    3.如果键"address"被修改,那么c2和c4两个客户端的REDIS_DIRTY_CAS标识将被打开
    在这里插入图片描述

判断事务是否安全

当服务器接收到一个客户端发来的EXEC命令时,服务器会根据这个客户端是否打开了REDIS_DIRTY_CAS标识
来决定是否执行事务:

  • 1.入股客户端的REDIS_DIRTY_CAS标识已经被打开,那么说明客户端所监视的键当中,至少有一个键已经被修改过了,在这种情况下,客户端提交的事务已经不再安全,所以服务器会拒绝执行客户端提交的事务
  • 2.如果客户端的REDIS_DIRTY_CAS标识没有被打开,那么说明客户端监视的所有键都没有被修改过(或者客户端没有监视任何键),事务仍然是安全的,服务器将执行客户端提交的这个事务。

这个判断是否执行事务的过程可以用流程图来描述
在这里插入图片描述

例子
  • 举个例子。对于上图的watched_keys字典来说,如果某个客户端对"name"进行了修改(比如执行SET “name” “cover”),
    那么c1、c2、c10086三个客户端的REDIS_DIRTY_CAS标识将被打开,当这三个客户端向服务器发送EXEC命令的时候,
    服务器会拒绝执行它们提交的事务,以此来保证事务的安全性
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/477785
推荐阅读
相关标签
  

闽ICP备14008679号