公司网站 建设,企业网站建设在国内现状,设计制作费税率,北京出大大事了Redis 事务可以一次执行多个命令#xff0c; 并且带有以下三个重要的保证#xff1a;
批量操作在发送 EXEC 命令前被放入队列缓存。收到 EXEC 命令后进入事务执行#xff0c;事务中任意命令执行失败#xff0c;其余的命令依然被执行。在事务执行过程#xff0c;其他客户端…Redis 事务可以一次执行多个命令 并且带有以下三个重要的保证
批量操作在发送 EXEC 命令前被放入队列缓存。收到 EXEC 命令后进入事务执行事务中任意命令执行失败其余的命令依然被执行。在事务执行过程其他客户端提交的命令请求不会插入到事务执行命令序列中。
一个事务从开始到执行会经历以下三个阶段
开始事务。命令入队。执行事务。
以下是一个事务的例子 它先以 MULTI 开始一个事务 然后将多个命令入队到事务中 最后由 EXEC 命令触发事务 一并执行事务中的所有命令
redis 127.0.0.1:6379 MULTI
OKredis 127.0.0.1:6379 SET book-name Mastering C in 21 days
QUEUEDredis 127.0.0.1:6379 GET book-name
QUEUEDredis 127.0.0.1:6379 SADD tag C Programming Mastering Series
QUEUEDredis 127.0.0.1:6379 SMEMBERS tag
QUEUEDredis 127.0.0.1:6379 EXEC
1) OK
2) Mastering C in 21 days
3) (integer) 3
4) 1) Mastering Series2) C3) Programming
详细介绍
事务开始
MULTI 命令的执行标志着事务的开始
redis MULTI
OKMULTI 命令可以将执行该命令的客户端从非事务状态切换至事务状态 这一切换是通过在客户端状态的 flags 属性中打开 REDIS_MULTI 标识来完成的 MULTI 命令的实现可以用以下伪代码来表示
def MULTI():# 打开事务标识client.flags | REDIS_MULTI# 返回 OK 回复replyOK()
命令入队
当一个客户端处于非事务状态时 这个客户端发送的命令会立即被服务器执行
redis SET name Practical Common Lisp
OKredis GET name
Practical Common Lispredis SET author Peter Seibel
OKredis GET author
Peter Seibel
与此不同的是 当一个客户端切换到事务状态之后 服务器会根据这个客户端发来的不同命令执行不同的操作
如果客户端发送的命令为 EXEC 、 DISCARD 、 WATCH 、 MULTI 四个命令的其中一个 那么服务器立即执行这个命令。与此相反 如果客户端发送的命令是 EXEC 、 DISCARD 、 WATCH 、 MULTI 四个命令以外的其他命令 那么服务器并不立即执行这个命令 而是将这个命令放入一个事务队列里面 然后向客户端返回 QUEUED 回复。
事务队列
每个 Redis 客户端都有自己的事务状态 这个事务状态保存在客户端状态的 mstate 属性里面
typedef struct redisClient {// ...// 事务状态multiState mstate; /* MULTI/EXEC state */// ...} redisClient;
事务状态包含一个事务队列 以及一个已入队命令的计数器 也可以说是事务队列的长度
typedef struct multiState {// 事务队列FIFO 顺序multiCmd *commands;// 已入队命令计数int count;} multiState;
事务队列是一个 multiCmd 类型的数组 数组中的每个 multiCmd 结构都保存了一个已入队命令的相关信息 包括指向命令实现函数的指针 命令的参数 以及参数的数量
typedef struct multiCmd {// 参数robj **argv;// 参数数量int argc;// 命令指针struct redisCommand *cmd;} multiCmd;
事务队列以先进先出FIFO的方式保存入队的命令 较先入队的命令会被放到数组的前面 而较后入队的命令则会被放到数组的后面。
举个例子 如果客户端执行以下命令
redis MULTI
OKredis SET name Practical Common Lisp
QUEUEDredis GET name
QUEUEDredis SET author Peter Seibel
QUEUEDredis GET author
QUEUED那么服务器将为客户端创建事务状态
最先入队的 SET 命令被放在了事务队列的索引 0 位置上。第二入队的 GET 命令被放在了事务队列的索引 1 位置上。第三入队的另一个 SET 命令被放在了事务队列的索引 2 位置上。最后入队的另一个 GET 命令被放在了事务队列的索引 3 位置上。
执行事务
当一个处于事务状态的客户端向服务器发送 EXEC 命令时 这个 EXEC 命令将立即被服务器执行 服务器会遍历这个客户端的事务队列 执行队列中保存的所有命令 最后将执行命令所得的结果全部返回给客户端。
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 0release_transaction_queue(client.mstate.commands)# 将事务的执行结果返回给客户端send_reply_to_client(client, reply_queue)
WATCH命令的实现
WATCH命令是一个乐观锁它可以在EXEC命令执行之前监视任意数量的数据库键并在EXEC执行后检查被监视的键是否至少有一个被修改如果是服务器拒绝执行事务并向客户端返回代表事务执行失败的回复。
/* Redis database representation. There are multiple databases identified* by integers from 0 (the default database) up to the max configured* database. The database number is the id field in the structure. */
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 已经准备好数据的阻塞状态的key*/dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS 事物模块用于保存被WATCH命令所监控的键*/// 当内存不足时Redis会根据LRU算法回收一部分键所占的空间而该eviction_pool是一个长为16数组保存可能被回收的键// eviction_pool中所有键按照idle空转时间从小到大排序每次回收空转时间最长的键struct evictionPoolEntry *eviction_pool; /* Eviction pool of keys */// 数据库IDint id; /* Database ID */// 键的平均过期时间long long avg_ttl; /* Average TTL, just for stats */
} redisDb;
在每个代表数据库的 server.h/redisDb 结构类型中 都保存了一个 watched_keys 字典 字典的键是这个数据库被监视的键 而字典的值则是一个链表 链表中保存了所有监视这个键的客户端。比如说以下字典就展示了一个 watched_keys 字典的例子 每个key后挂着监视自己的客户端。
监控的触发
在任何对数据库键空间key space进行修改的命令成功执行之后 比如 FLUSHDB 、 SET 、 DEL 、 LPUSH 、 SADD 、 ZREM 诸如此类 multi.c/touchWatchedKey 函数都会被调用 修改命令会调用signalModifiedKey()函数来处理数据库中的键被修改的情况该函数直接调用touchWatchedKey()函数—— 它检查数据库的 watched_keys 字典 看是否有客户端在监视已经被命令修改的键 如果有的话 程序将所有监视这个/这些被修改键的客户端的 REDIS_DIRTY_CAS 选项打开
/* Touch a key, so that if this key is being WATCHed by some client the* next EXEC will fail. */
// Touch 一个 key如果该key正在被监视那么客户端会执行EXEC失败
void touchWatchedKey(redisDb *db, robj *key) {list *clients;listIter li;listNode *ln;// 字典为空没有任何键被监视if (dictSize(db-watched_keys) 0) return;// 获取所有监视这个键的客户端 clients dictFetchValue(db-watched_keys, key);// 没找到返回if (!clients) return;/* Mark all the clients watching this key as CLIENT_DIRTY_CAS *//* Check if we are already watching for this key */// 遍历所有客户端打开他们的 REDIS_DIRTY_CAS 标识listRewind(clients,li);while((ln listNext(li))) {client *c listNodeValue(ln);// 设置CLIENT_DIRTY_CAS标识c-flags | CLIENT_DIRTY_CAS;}
}
事务的ACID性质 在传统的关系式数据库中常常用 ACID 性质来检验事务功能的安全性。
redis事物总是具有前三个性质。
a原子性atomicityredis事务保证事务中的命令要么全部执行要不全部不执行。
但是redis不同于传统关系型数据库不支持回滚即使出现了错误事务也会继续执行下去。
因为redis作者认为这种复杂的机制和redis追求的简单高效不符。并且redis事务错误通常是编程错误只会出现在开发环境中而不会出现在实际生产环境中所以没必要支持回滚。
b一致性consistencyredis事务可以保证命令失败的情况下得以回滚数据能恢复到没有执行之前的样子是保证一致性的除非redis进程意外终结。
Redis 的一致性问题可以分为三部分来讨论入队错误、执行错误、Redis 进程被终结。
入队错误
在命令入队的过程中如果客户端向服务器发送了错误的命令比如命令的参数数量不对等等 那么服务器将向客户端返回一个出错信息 并且将客户端的事务状态设为 REDIS_DIRTY_EXEC 。 因此带有不正确入队命令的事务不会被执行也不会影响数据库的一致性。
执行错误
如果命令在事务执行的过程中发生错误比如说对一个不同类型的 key 执行了错误的操作 那么 Redis 只会将错误包含在事务的结果中 这不会引起事务中断或整个失败不会影响已执行事务命令的结果也不会影响后面要执行的事务命令 所以它对事务的一致性也没有影响。
Redis 进程被终结
如果 Redis 服务器进程在执行事务的过程中被其他进程终结或者被管理员强制杀死那么根据 Redis 所使用的持久化模式可能有以下情况出现
内存模式如果 Redis 没有采取任何持久化机制那么重启之后的数据库总是空白的所以数据总是一致的。
RDB 模式在执行事务时Redis 不会中断事务去执行保存 RDB 的工作只有在事务执行之后保存 RDB 的工作才有可能开始。所以当 RDB 模式下的 Redis 服务器进程在事务中途被杀死时事务内执行的命令不管成功了多少都不会被保存到 RDB 文件里。恢复数据库需要使用现有的 RDB 文件而这个 RDB 文件的数据保存的是最近一次的数据库快照snapshot所以它的数据可能不是最新的但只要 RDB 文件本身没有因为其他问题而出错那么还原后的数据库就是一致的。
AOF 模式因为保存 AOF 文件的工作在后台线程进行所以即使是在事务执行的中途保存 AOF 文件的工作也可以继续进行因此根据事务语句是否被写入并保存到 AOF 文件有以下两种情况发生
1如果事务语句未写入到 AOF 文件或 AOF 未被 SYNC 调用保存到磁盘那么当进程被杀死之后Redis 可以根据最近一次成功保存到磁盘的 AOF 文件来还原数据库只要 AOF 文件本身没有因为其他问题而出错那么还原后的数据库总是一致的但其中的数据不一定是最新的。
2如果事务的部分语句被写入到 AOF 文件并且 AOF 文件被成功保存那么不完整的事务执行信息就会遗留在 AOF 文件里当重启 Redis 时程序会检测到 AOF 文件并不完整Redis 会退出并报告错误。需要使用 redis-check-aof 工具将部分成功的事务命令移除之后才能再次启动服务器。还原之后的数据总是一致的而且数据也是最新的直到事务执行之前为止。
c隔离性Isolationredis事务是严格遵守隔离性的原因是redis是单进程单线程模式可以保证命令执行过程中不会被其他客户端命令打断。
因为redis使用单线程执行事务并且保证不会中断所以肯定有隔离性。
d持久性Durability持久性是指当一个事务执行完毕结果已经保存在永久介质里比如硬盘所以即使服务器后来停机了结果也不会丢失
redis事务是不保证持久性的这是因为redis持久化策略中不管是RDB还是AOF都是异步执行的不保证持久性是出于对性能的考虑。 重点提炼
事务提供了一种将多个命令打包 然后一次性、有序地执行的机制。多个命令会被入队到事务队列中 然后按先进先出FIFO的顺序执行。事务在执行过程中不会被中断 当事务队列中的所有命令都被执行完毕之后 事务才会结束。带有 WATCH 命令的事务会将客户端和被监视的键在数据库的 watched_keys 字典中进行关联 当键被修改时 程序会将所有监视被修改键的客户端的 REDIS_DIRTY_CAS 标志打开。只有在客户端的 REDIS_DIRTY_CAS 标志未被打开时 服务器才会执行客户端提交的事务 否则的话 服务器将拒绝执行客户端提交的事务。Redis 的事务总是保证 ACID 中的原子性、一致性和隔离性 当服务器运行在 AOF 持久化模式下 并且 appendfsync 选项的值为 always 时 事务也具有耐久性。