Redis作为一个数据库,也有事务的概念,本文就从事务的四大特性来分析以下Redis的事务。
1、原子性
原子性是事务的一个非常重要的特性。就是一个事务中的操作要么全部执行,要么全部不执行。
Redis中提供了与原子性有关的命令:
MUlTI:
用于显示开启一个事务,之后的操作都不会立即执行,而是进入一个操作队列
EXEC
依次执行操作队列中的所有操作。
DISCARD
抛弃操作队列中的所有操作,也就是所有操作都不执行
事务执行过程
1、事务开启
通过MULTI命令来显示开启事务
2、命令入队
客户端所有的命令都会被加入一个队列,并不会被立即执行
3、执行事务
通过EXEC将依次执行命令队列中的所有命令
multi
set name jaychou
getname
exec
multi
set name jay
getname
discard
出错
当命令中出现语法错误的时候,命令队列中所有命令都不会执行
但是当命令中出现语义错误的时候,命令队列中的其他正确指令都会执行,这就不满足原子性了。
Watch
当事务开启前可以使用watch命令,watch 的 用法是 watch key1,key2,事务会监视着key1和key2.
当开启事务后,如果对应的key被其他事务修改了,那么当前事务如果再修改对应的key就是失败,命令队列中的所有命令都不会执行。
这是一种乐观锁做法,跟CAS是一个思想。
Watch适用于并发操作,比如银行卡转账等操作。redis的是单线程运行的,虽然不会造成数据的不一致,但是会造成数据的过界现象。
我们以账户余额为例,
两个账户同时执行下面的这两行代码
get balance
decrby balance 10
假如balance的初值是10.也就是账户里只有10块钱。
如果两个客户端先后执行了get balance,发现账户里还有10块钱,就都开始扣10块钱。
两个decrby balance 10先后执行,这时会发现 balance变成了-10,这是不允许的。。。
所以可以通过watch来监视balance这个key,这样的话,当一个key发生变化的时候,事务不会执行
get balance
watch balance
multi
decrby balance 10
exec
Watch的内部实现
我们首先看下redisDb的内部结构
typedef struct redisDb {
dict *dict;
dict *expires;
dict *blocking_keys;
dict *ready_keys;
//这里这里这里
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
int id; /* Database ID */
} redisDb;
其中有一个名字叫watched_keys的dict,watch_keys中存放的是key和其对应的watch监视的客户端链表。
当一个客户端执行exec,想要改变某个key的时候,会首先查看其REDIS_DIRTY_CAS选项是否被打开,如果打开了,说明有其他事务打开了,事务就不会执行。
如果REDIS_DIRTY_CAS选项没有被打开,就可以执行对应的key,并且通过watched_keys获取key对应的监视列表,然后依次将对应客户端的REDIS_DIRTY_CAS选项打开。
当其他客户端发起exec的时候,发现自己的REDIS_DIRTY_CAS被打开了,就会停止事务运行。
当事务调用了exec后,无论事务执行成功还是失败,对应的监视都会被取消,对应的REDIS_DIRTY_CAS也会被关闭。
可以使用unwatch命令来手动关闭所有key的监视。
总结来看,redis并不满足原子性,当发生语义错误或者掉电的时候,事务是不会回滚的。
2、隔离性
因为Redis是单线程来处理请求的,所以事物之间是天然隔离的。
3、一致性
一致性指的是随着事务的执行完毕,数据库必须从一个正确的状态跳转到另一个正确的状态。这一个一致性就需要用户或者系统的定义,比如转账的操作就是所有账户的总额必须保持不变。也就说在转账事务执行完毕后,所有账户的总额必须保持不变,这就是保持了数据库的一致。
但是Redis当发生语义错误或者掉电的时候,事务是不会回滚的,比如转账扣除了A的账户金额,但是这时系统掉电了,而且Redis也不会回滚事务,这就造成了账户金额减少了,不一致了。
所以Redis是不一致的。
4、持久性
Redis可以通过RDB和AOF来将内存中数据保存到硬盘中,满足持久化。