一、redis的事务
Redis目前对事务的支持相对简单。Redis只能保证一个client发起的事务中的命令可以连续的执行,而中间不会插入其他的client命令。当一个client在一个链接中发出multi命令时,这个链接会进入一个事务上下文,该连接后续的命令不会立即执行,而是先放到一个队列中,当执行exec命令时,redis会顺序的执行队列中的所有命令。注意redis的事务和乐观锁只针对于redis单机实例,redis的集群没有事务相关的概念。
redis事务设置的命令有 MULTI (multi)(开启事务), exec(执行队列中的redis命令),discard(取消)Redis支持简单的事务
1、Redis与 mysql事务的对比
| Mysql | Redis |
开启 | start transaction | muitl |
语句 | 普通sql | 普通命令 |
失败 | rollback 回滚 | discard 取消 |
成功 | commit | exec |
注: rollback与discard 的区别
如果已经成功执行了2条语句, 第3条语句出错.
Rollback后,前2条的语句影响消失.
Discard只是结束本次事务,前2条语句(在exec命令之前已经成功的命令)造成的影响仍然还在
2、客户端命令:
#先设置金额为10000
127.0.0.1:6379> set money 10000
OK
#获取金额
127.0.0.1:6379> get money
"10000"
#执行事务操作
127.0.0.1:6379> MULTI
OK
#将所有的redis命令都存在到队列中,在执行discard(取消执行队列中的命令)
#或者exec操作(执行存放在队列中的redis命令)
127.0.0.1:6379> set money 9000
QUEUED
127.0.0.1:6379> get money
QUEUED
#执行操作
127.0.0.1:6379> exec
3、java代码实现
/**
* redis的事务操作
*/
@Test
public void testTransaction(){
ValueOperations<String,String> stringOper = redisTemplate.opsForValue();
stringOper.set("张三","10000");
stringOper.set("李四","10000");
//开启redis的事务支持
redisTemplate.setEnableTransactionSupport(true);
//开启事务
redisTemplate.multi();
//执行转账操作 张三 减去500 李四加上500
stringOper.increment("张三",-500);
stringOper.increment("李四",500);
//取消操作 则上面的转账命令不会执行,则两人的金额不会发生改变
//redisTemplate.discard();
//执行操作,转账成功
redisTemplate.exec();
String zhangsanMoney = stringOper.get("张三");
String lisimoney = stringOper.get("李四");
log.info("redis discard取消(没有发生变量) 转账后张三的资金:{},李四的资金:{}",zhangsanMoney,lisimoney);
}
4、注意
在mutil后面的语句中, 语句出错可能有2种情况
1: 语法就有问题,
这种,exec时,报错, 所有语句得不到执行
2: 语法本身没错,但适用对象有问题. 比如 zadd 操作list对象
Exec之后,会执行正确的语句,并跳过有不适当的语句.
(如果zadd操作list这种事怎么避免? 这一点,由程序员负责)
二、redis的乐观锁
1、redis的乐观锁
乐观锁:redis大多数是基于数据版本(version)的记录机制实现的。即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表添加一个version字段来实现。在读取数据时,将此版本号一同读出,之后更新时对此版本号加1。此时,将提交数据的版本号与数据库表对应记录的当前版本号进行对比,如果提交的数据版本号大于数据库当前版本号,则予以更新,否则认为是过期数据。
watch监控:watch命令会监控给定的key,当exec时如果监视的key从调用watch后发生过变化,则整个事务会失败。也可以调用watch多次监视多个key,这样就对指定事务key加乐观锁了。注意watch的key是对整个链接有效的,事务也一样。如果链接断开,监视和事务都会被自动清除。当然exex、discard、unwatch命令都会自动清除链接中的所有监视。
2、客户端命令
2.1 只事务实现两个人抢一张票
client 1
#开启事务
127.0.0.1:6379> MULTI
OK
#票数自减
127.0.0.1:6379> DECR ticket
QUEUED
#等待客户端2操作 在执行该操作
#客户端2操作完成后,再执行则票数在0的基础上-1 则票数为-1 不合理
127.0.0.1:6379> exec
1) (integer) -1
127.0.0.1:6379> get ticket
"-1"
client 2
#客户端2操作
127.0.0.1:6379> MULTI
OK
#票数自减
127.0.0.1:6379> decr ticket
QUEUED
执行 票数为0
127.0.0.1:6379> exec
1) (integer) 0
出现 票数为负数的情况,则无法确保业务正确性。
2、使用乐观锁配合事务完成抢票操作
client 1
#使用watch命令来监听ticket 如果在针对该key操作的时候 发现key已经被操作过
#则这次事务所涉及的redis操作都被取消
127.0.0.1:6379> watch ticket
OK
127.0.0.1:6379> MULTI
OK
#在票数自减之前,client2已经对ticket进行了操作,则该次事务取消
127.0.0.1:6379> decr ticket
QUEUED
#操作为 nil 说明别的客户端已经操作过,无法进行相关的操作
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get ticket
"0"
client 2
#客户端2操作
127.0.0.1:6379> MULTI
OK
#票数自减
127.0.0.1:6379> decr ticket
QUEUED
执行 票数为0
127.0.0.1:6379> exec
1) (integer) 0
则不会出现业务超售情况,确保了事务,watch命令在一次key的修改事务完成后失效,如果需要继续监听,则重新运行watch命令。
有关redis的事务和乐观锁的知识先写到这里,以后有什么新的理解,在重新补充,第一次学习redis,如果有什么错误的地方,希望大家指出,手打不易,看完请点赞,您的点赞是我继续更新的动力。