「Redis」事务实现机制

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
简介: 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
相关文章
|
1月前
|
NoSQL Redis 数据库
10- 你们用过Redis的事务吗 ? 事务的命令有哪些 ?
```markdown Redis事务包括MULTI、EXEC、DISCARD、WATCH四个命令。虽具备事务功能,但在实际开发中使用较少。 ```
50 7
|
1月前
|
NoSQL Redis 数据库
什么是Redis的事务?
Redis事务提供原子性和顺序性,确保命令按顺序执行且不被打断。核心概念包括原子性、顺序性、隔离性和持久性。关键指令有MULTI、EXEC、DISCARD和WATCH,用于事务的开始、执行、取消和监视。这保障了命令的完整性,防止并发操作导致的数据不一致。
21 2
|
1月前
|
存储 NoSQL Redis
保障数据安全,提升性能:探秘Redis AOF持久化机制在在线购物网站的应用
保障数据安全,提升性能:探秘Redis AOF持久化机制在在线购物网站的应用
|
1月前
|
NoSQL Redis
Redis事务:保证数据操作的一致性和可靠性
Redis事务:保证数据操作的一致性和可靠性
|
2天前
|
存储 缓存 JSON
Redis-持久化-淘汰机制-IO策略
Redis-持久化-淘汰机制-IO策略
|
8天前
|
存储 NoSQL 算法
Redis分片机制
Redis分片机制
|
1月前
|
NoSQL 关系型数据库 MySQL
Redis(事务)
Redis(事务)
30 2
|
1月前
|
NoSQL Redis 数据库
Redis实现数据持久性主要依赖两种机制
【5月更文挑战第15天】Redis持久化包括RDB快照和AOF日志。RDB通过定时内存数据快照生成文件,恢复速度快但可能丢失部分数据;AOF记录每次写操作,实时性好但文件大、恢复慢。混合持久化兼顾两者优点,提供数据安全与性能平衡。用户可按需选择或组合使用策略。
23 2
|
1月前
|
缓存 NoSQL Java
【Redis系列笔记】Redis事务
Redis事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
125 3
|
1月前
|
NoSQL 算法 Java
【redis源码学习】持久化机制,java程序员面试算法宝典pdf
【redis源码学习】持久化机制,java程序员面试算法宝典pdf