「Redis」事务实现机制

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云原生数据库 PolarDB MySQL 版,通用型 2核4GB 50GB
简介: Redis事务实现机制

关系型数据库一般都支持事务,简单来说,事务允许请求提交的批量执行,且保证全部成功或全部失败。对于Redis来说,它也提供了对事务的简单实现和支持,下面来了解下。


事务实现


Redis通过watch、multi、exec命令来实现事务功能。它实现了一次性、按顺序执行一系列命令,保证在执行期间不受其他变更影响的机制。


事务命令


watch


数据存储


watch监听某个Key状态,对其进行监听标记以确保在事务操作过程中不会被其他操作修改破坏事务完整性。


typedef struct redisDb {

// 省略其他信息

 

   // 正在被 WATCH 命令监视的键

   dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */


} redisDb;


网络异常,图片无法展示
|

如上,关于被监视的Key是被存储在 redisDb watched_keys 字典数组中的,字典中key1,key2是被监视的键,字典中的值则以一个 链表 存储 redisClient 结构引用,通过如此映射来表示哪些客户端在对哪些键Key进行监听。


触发机制


/* "Touch" a key, so that if this key is being WATCHed by some client the

* next EXEC will fail.

*

* “触碰”一个键,如果这个键正在被某个/某些客户端监视着,

* 那么这个/这些客户端在执行 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 REDIS_DIRTY_CAS */

   /* Check if we are already watching for this key */

   // 遍历所有客户端,打开他们的 REDIS_DIRTY_CAS 标识

   listRewind(clients,&li);

   while((ln = listNext(&li))) {

       redisClient *c = listNodeValue(ln);


       c->flags |= REDIS_DIRTY_CAS;

   }

}


如上是touchWatchedKey方法,作用是对数据库中监听键Keywatched_keys进行数据修改标识,具体是通过修改watched_keys链表中对象redisClientflags属性来进行标记,当有被监听键被修改时,会将其客户端标记REDIS_DIRTY_CAS,表示该客户端监听键已被修改。
这里的touchWatchedKey方法是一个旁路方法,当Redis服务端接收到set、lpush、zadd等数据变更命令时会对其进行触发。


事务执行


网络异常,图片无法展示
|

watch 是一个 乐观锁 的实现方式,当多个Client客户端对同一个数据库键Key进行监听时,会将所有监听的客户端和键映射关系进行保存,并不会Client客户端在未提交事务过程中进行变更监听键Key的阻塞或拒绝,而是当Client客户端真正提交时才进行事务状态的检查,当发现数据变更时才拒绝事务提交,否则会执行成功。


multi


multi开启事务,类似关系型数据库中的begin。执行时会将redisClientflags标记为REDIS_MULTI,表明客户端已开启事务。


discard


discard取消事务,类似关系型数据库中的rollback。操作会清空事务队列中所有入队命令,并取消客户端事务状态。


exec


exec执行事务,类似关系型数据库中的commit。操作会提交事务队列中所有入队命令,并取消客户端事务状态。


执行流程


网络异常,图片无法展示
|

事务执行分为三个阶段: 事务开始、命令入队、事务执行(事务丢弃)


  • 事务开始 标记客户端已开启事务状态
  • 命令入队 当开启事务状态后,所有后续执行命令会被放入到一个FIFO队列中,此时命令均不会被执行,等待事务执行命令发起后按序执行
  • 事务执行 当发起事务执行时,Redis会将之前放入命令队列中的命令取出,按照存入顺序依次执行
  • 事务丢弃 当发起事务丢弃时,Redis会清空命令队列,将客户端事务标记取消
    网络异常,图片无法展示
    |
  • 当Redis服务端收到客户端标记为事务状态开启时,会立即执行multi、watch、discard、exec等事务命令
  • 当Redis服务端收到客户端标记为事务状态开启时,命令是非以上事务命令则会将命令放入事务命令队列中,返回客户端QUEUED表明事务命令已入队
  • 当不是以上情况,则不是事务状态,正常执行命令即可。


数据构成


typedef struct redisClient {

   // 这里省略大部分其他信息


   // 客户端状态标志

   int flags;              /* REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI ... */


   // 事务状态

   multiState mstate;      /* MULTI/EXEC state */


   // 被监视的键

   list *watched_keys;     /* Keys WATCHED for MULTI/EXEC CAS */


} redisClient;


如上,redisClient是客户端数据结构,Redis会为每一个客户端保存诸多信息。


  • flags 记录客户端状态标志,若包含REDIS_MULTI 标记则表明客户端在事务状态下
  • mstate 记录事务状态是exec执行还是multi开启
  • watched_keys 是watch操作后被监视的键


/*

* 事务状态

*/

typedef struct multiState {


   // 事务队列,FIFO 顺序

   multiCmd *commands;     /* Array of MULTI commands */


   // 已入队命令计数

   int count;              /* Total number of MULTI commands */

} multiState;


  • commands存储的是事务状态下入队的命令集合,这里是一个FIFO顺序的数组,先入队会被优先执行。
  • count 记录入队的命令数量


/*

* 事务命令

*/

typedef struct multiCmd {


   // 参数

   robj **argv;


   // 参数数量

   int argc;


   // 命令指针

   struct redisCommand *cmd;


} multiCmd;


multCmd存储了事务命令的具体描述,argv是一个以字符串对象存储的命令数组,argc是参数数量,cmd指向命令执行。

网络异常,图片无法展示
|


异常处理


在事务执行过程中,一般会有入队错误执行错误两种错误存在。


  • 入队错误 Redis会对入队命令进行检查,一旦有异常则返回提示,当事务提交时则拒绝队列命令执行,所有命令都不会提交
  • 执行错误 对于逻辑错误的命令,如键Key是列表对象,而对其进行字符串对象操作,这种多是出现在应用程序运行时,而Redis本着Keep it simple的设计理念,并不会在命令入队阶段对数据类型进行检查,这完全可以在应用程序中进行避免。与入队错误不同的时,执行错误只会对错误的执行提交失败,其余正常命令无论顺序如何均会被正常提交。


入队错误


当事务未执行、命令入队阶段出现命令错误,则会进行提示,当事务提交时会直接拒绝,命令队列中的命令均不会生效。如下:

网络异常,图片无法展示
|


执行错误


网络异常,图片无法展示
|


ACID探讨


通过以上对Redis事务机制实现的剖析,下面对Redis中事务ACID特性进行分别探讨和总结。


原子性(Atomic)


Redis可以multi标记客户端事务状态,通过命令队列来暂存所有事务开启期间的命令,使用watch命令实现了对特定数据基于乐观锁实现的保护,在事务提交阶段检查客户端事务状态、检查监听数据变更情况从而来确保事务执行和提交过程中的事务完整性,由于Redis是工作在单线程环境下的,所有操作都可以确保顺序性,它的单线程设计确保了它无需考虑因竞态出现的数据不一致问题。


对于执行错误,只能确保正确命令可以被正确执行,而错误命令会被忽略,这需要上层应用使用时进行逻辑校验和容错


隔离性(Isolation)


Redis与如MySQL这种关系型数据库不同的一点是,无法支持多种维度的会话间的数据可见性。当在事务状态未提交,命令都会暂存命令队列,而此时事务会话中的变更对于其他事务会话是既不可见也未提交,只有在执行exec才会真正进行事务提交。watch命令提供了数据库键Key的监听功能,它相当于给不同的多个事务会话提供了一种监听通信的能力来察觉数据变更,但是它仅能监测已提交数据变更来保护事务完整性,却不能向MySQL那样提供不同隔离级别来察觉其他事务会话未提交或已提交数据变化。


持久性(Durability)


Redis中事务的持久性依赖于自身的AOFRDB的开启和持久化机制,即使出现极端情况下的异常,只要将AOFRDB文件持久化刷盘到磁盘上,那么事务操作便具有持久性了,在重启后也会自动加载到内存中进行还原。


一致性(Consistency)


数据一致性可以参考持久性部分。


总结Redis的ACID特性的话,相比MySQL这种关系型数据库的事务保证来说,还是要单薄和简单一些,毕竟Redis的定位和使用场景不同,设计复杂度也便不同,事务实现并不是它的第一要义。但是这并不影响我们了解Redis的事务实现机制和细节,透过内部实现来因场景来使用做事务取舍。


参考

《Redis设计与实现》

https://github.com/huangz1990/redis-3.0-annotated

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
5月前
|
NoSQL Java Redis
实现基于Redis的分布式锁机制
实现基于Redis的分布式锁机制
|
2月前
|
缓存 NoSQL Redis
Redis 事务
10月更文挑战第18天
34 1
|
4月前
|
负载均衡 NoSQL 算法
一天五道Java面试题----第十天(简述Redis事务实现--------->负载均衡算法、类型)
这篇文章是关于Java面试中Redis相关问题的笔记,包括Redis事务实现、集群方案、主从复制原理、CAP和BASE理论以及负载均衡算法和类型。
一天五道Java面试题----第十天(简述Redis事务实现--------->负载均衡算法、类型)
|
4天前
|
NoSQL Redis
Redis事务长什么样?一文带你全面了解
Redis事务是一组命令的有序队列,通过MULTI、EXEC、WATCH和DISCARD等命令实现原子性操作。事务中的命令在EXEC执行前不会实际运行,而是先进入队列,确保所有命令要么全部成功,要么全部失败。此外,Redis还支持Lua脚本实现类似事务的操作,通常更简单高效。事务适用于购物车结算、秒杀活动、排行榜更新等需要保证数据一致性的场景。
18 0
|
2月前
|
存储 缓存 NoSQL
大数据-45 Redis 持久化概念 RDB AOF机制 持久化原因和对比
大数据-45 Redis 持久化概念 RDB AOF机制 持久化原因和对比
53 2
大数据-45 Redis 持久化概念 RDB AOF机制 持久化原因和对比
|
2月前
|
设计模式 NoSQL 网络协议
大数据-48 Redis 通信协议原理RESP 事件处理机制原理 文件事件 时间事件 Reactor多路复用
大数据-48 Redis 通信协议原理RESP 事件处理机制原理 文件事件 时间事件 Reactor多路复用
44 2
|
2月前
|
SQL 分布式计算 NoSQL
大数据-42 Redis 功能扩展 发布/订阅模式 事务相关的内容 Redis弱事务
大数据-42 Redis 功能扩展 发布/订阅模式 事务相关的内容 Redis弱事务
30 2
|
2月前
|
NoSQL 关系型数据库 MySQL
Redis 事务特性、原理、具体命令操作全方位诠释 —— 零基础可学习
本文全面阐述了Redis事务的特性、原理、具体命令操作,指出Redis事务具有原子性但不保证一致性、持久性和隔离性,并解释了Redis事务的适用场景和WATCH命令的乐观锁机制。
406 0
Redis 事务特性、原理、具体命令操作全方位诠释 —— 零基础可学习
|
4月前
|
NoSQL 关系型数据库 Redis
Redis6入门到实战------ 九、10. Redis_事务_锁机制_秒杀
这篇文章深入探讨了Redis事务的概念、命令使用、错误处理机制以及乐观锁和悲观锁的应用,并通过WATCH/UNWATCH命令展示了事务中的锁机制。
Redis6入门到实战------ 九、10. Redis_事务_锁机制_秒杀