Redis学习笔记(二十一) 事务

文章开始啰嗦两句,写到这里共21篇关于redis的琐碎知识,没有过多的写编程过程中redis的应用,着重写的是redis命令、客户端、服务器以及生产环境搭建用到的主从、哨兵、集群实现原理,如果你真的能看的进去,相信对你在以后用到redis时会有一定的帮助。

写到现在,redis相关的内容暂时告一段落了,以后可能更着重的去介绍c#相关的知识,包括用到IL、.net core底层、微服务等知识。

哎呀,写着写着突然又想啰嗦几句,最近几年c#在国内式微java如日中天,好多微软系的小伙伴或者各路大神都不看好c#,网上相关的帖子更是多如牛毛,另外一个现象就是从职位到薪资被java、go等语言甩了n条街。

但我觉得抛开这些外界因素,单从技术或者说是语言上讲c#其实非常优秀,虽然近几年.net  core 有些不伦不类、各种借鉴(抄袭)其他语言的语法糖、各个大版本之间不兼容等问题一堆,但是这并不影响他的优秀,毕竟成长确实要走很多弯路,所以希望有兴趣继续搞搞.net 的童鞋可以持续关注c#。


 

Redis通过MULTI、EXEC、WATCH等命令来实现事务,事务提供了一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制,并且在事务执行期间,服务器不会中断事务而改去执行其他客户端命令请求,他会将事务中的所有命令执行完毕,然后采取处理其他客户端的命令请求。

一个事务从开始到结束通常会经历以下三个阶段:事务开始、命令入队、事务执行。

1)事务开始

MULTI命令的执行标志是事务的开始,MULTI命令可以将执行该命令的客户端的从非事务状态切换至事务状态,这一切是通过在客户端状态的flags属性中打开REDIS_MULTI标识来完成的。

2)命令入队

如果客户端发送的命令为EXEC、DISCARD、WATCH、MULTI四个命令的其中一个,那么服务器立即执行这个命令,相反,服务器并不会立即执行这个命令,而是将命令放入到一个事务队列中,而后向客户端返回QUEUED回复。

每个Redis客户端都有自己的事务状态,这个事务状态保存在客户端状态的mstate属性里面:

typedef struct redisClient{
    //事务状态
    multiState mstate;
} redisClient;

事务状态包含一个事务队列,以及一个已入队命令的计数器:

typedef struct multiState{
    //事务队列,FIFO顺序
    multiCmd *commands;
    ///已入队列计数
    int count;
} multiState;
typedef struct multiCmd{
    //参数
    robj **argv;
    //参数数量
    int argc;
    //命令指针
    struct redisCommand *cmd;
} multiCmd;

事务队列以先进先出的方式 保存入队的命令。

3)执行事务

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

WATCH命令

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

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

typedef struct redisDb{
    //正在被watch命令监视的键
    dict *watched_keys;
} redisDb;

监视机制的触发:所有对数据库进行修改的命令,在执行之后都会调用touchWatchKey函数对watched_keys字典进行检查,查看是否在客户端正在监视刚刚被命令修改过的数据库键,如果有的话,那么touchWatchKey函数会将监视被修改键的客户端的REDIS_DIRTY_CAS标识打开,标识该客户端的事务安全性已经被破坏。

 

判断事务是否安全

当服务器接收到一个客户端发来的EXEC命令时,服务器会根据客户端是否打开REDIS_DIRTY_CAS标识来决定是否执行事务:如果客户端的REDIS_DIRTY_CAS标识已经被打开,那么说明客户端所监视的键当中,至少有一个键已经被修改过了,在这种情况下,客户端提交的事务已经不再安全,所以服务器会拒绝执行客户端提交的事务;如果客户端REDIS_DIRTY_CAS标识没有被打开,那么说明客户端监视的所有键都没有被修改过,事务仍然是安全的,服务器将执行客户端提交的这个事务。

 

事务的ACID性质

原子性:Redis的事务和传统的关系型数据库事务最大的区别在于,Redis不支持事务回滚机制,事务队列中的某个命令在执行期间出现错误,整个事务也会继续执行下去,直到将事务队列中的所有命令都执行完毕为止。

一致性:

1、入队错误,如果一个事务在入队命令的过程中,出现了命令不存在,或者命令的格式不正确的情况那么Redis将拒绝执行这个事务

2、执行错误:执行过程中发生的错误都是一些不能再入队时被服务器发现的错误,这些错误只会在命令实际执行时被触发;即使在事务的执行过程中发生了错误,服务器也不会中断事务的执行,他会继续执行事务中余下的其他命令,并且一致性的命令不会被出错的命令影响。

3、服务器停机:如果服务器运行在无持久化的内存模式中,那么重启之后数据库将是一片空白的,因此数据总是一致性的;如果服务器运行了RDB模式下,那么事务中途停机不会导致不一致性,因为服务器可以根据现有的RDB文件来恢复数据,从而将数据库还原到一个一致性的状态。如果服务器运行在APF模式下,那么事务中途停机不会导致不一致性,因为服务器可以根据现有的APF文件来恢复数据,从而将数据库还原至一致的状态。

隔离性:Redis使用单线程的方式执行事务,并且服务器保证,在执行事务期间不会对事务中断,因此,redis的事务总是以串行的方式运行,并且事务也总是具有隔离性的。

耐久性:Redis事务的耐久性由Redis所使用的持久化模式决定的,(不论Redis在什么模式下运行,在一个事务的最后加上SAVE命令总可以保证事务的耐久性。)

 


每天学一点,总会有收获。

 

说明:尊重作者知识产权,文中内容参考《Redis设计与实现》,仅在此做学习与大家分享。

 


 

 

posted @ 2020-06-13 23:03  温暖如太阳  阅读(223)  评论(0编辑  收藏  举报