Redis事务
Redis事务介绍
- 首先我们需要知道一点,Redis是弱事务类型的,以下是对它的常见介绍
- 1.Redis的事务是通过MULTI、EXEC、DISCARD和WATCH这四个命令来完成的。
- 2.Redis的单个命令都是原子性的,所以这里需要确保事务性的对象是命令集合。
- 3.Redis将命令集合序列化并确保处于同一事物的命令集合连续切不被打断的执行。
- 4.Redis不支持回滚操作。
事务命令
MULTI
用于标记事务块的开始。
Redis会将后续的命令诸葛放入队列中,然后使用EXEC命令原子化地执行这个命令序列
简单就是创建了一个事务列表,用于处理Redis弱事务的特性,模拟批次原子操作的提交,不过每个原子操作之间是独立的(合在一起的事务列表不支持原子性),可能会执行成功,也可能会执行失败,相互之间不影响。
客户端1,开启multi,但是还未提交
客户端2,查询刚刚输入的值,查询不到值,显示nil
客户端1提交,执行EXEC后,客户端2可以正常查询到
这里显示两个OK表示两条指令都提交成功,也存在其中没有执行成功的情况,这里就体现了redis弱事务不支持原子性的特性。
客户端1提交后,客户端2就正常查询到了
EXEC
在一个事务中执行所有先前放入队列的命令,然后回复正常的连接状态
DISCARD
清除所有先前再一个事务中放入队列的命令,然后恢复正常的连接状态
WATCH
当某个【事务需要按条件执行】时,就需要使用这个命令将给定的【键者之谓监控】的状态
简单来说就是监控某个键的状态,如果该值发生了变化,事务列表里的操作就不能执行了,常用来作为乐观锁
客户端1监听q1,监听时候的值为11,然后执行multi,添加了两个键值对,但是还未执行exec提交
客户端2将q1的值改掉,此时回到客户端1执行exec就会出现下面情况
此时提交会出现nil,这就是watch的效果,常用来做乐观锁
UNWATCH
清除所有先前为一个事务监控的键
就是清除上一个watch的监控效果
事务失败处理
Redis事务失败存在两种情况
-
Redis语法错误
整个事务的命令在列表离都清除
这里sets q1明显存在语法错误,所以后续exec提交的时候,整个队列中都提交失败 -
Redis运行错误
在队列里正确的命令可以执行(弱事务性)
-
Redis为什么不支持事务回滚?
- 大多数事务失败是因为语法错误或者类型错误,这两种错误,在开发阶段都是可以避免的
- Redis为了性能方面就忽略了事务回滚
Redis事务使用场景-----乐观锁
乐观锁基于CAS(Compare And Swap)思想(比较并替换),时不具有胡持续,不会产生锁等待而小号资源,但是需要反复的重试,但也是因为重试的机制,能比较快的响应,英寸我们能可以利用Redis来实现乐观锁,具体思路如下:
- 1、利用redis的watch功能,监控这个redisKey的状态值
- 2、获取redisKey的值
- 3、创建redis事务
- 4、给这个key的值+1
- 5、然后去执行这个事务,如果key的值被修改过则回滚, key不加1
public void watch() {
try {
String watchkeys = "'watchkeys";
//初始值value=1
jedis. set (watchKeys, 1);
//监听key为watchKeys的值
jedis. watch(watchkeys);
//开启事务
Transaction tx = jedis.multi();
//watchkeys自增加一
tx.incr(watchKeys);
//执行事务,如果其他线程对watchkeys中的value进行修改,则该事务将不会执行
//通过redis事务以及watch命令实现乐观锁
List<object> exec = tx.exec();
if (exec == nulll) {
System.out.println("事务未执行");
} else {
system.out.println("事务成功执行,watchkeys的value成功修改");
}
} catch (Exception e) {
e.printstackTrace();
} finally {
jedis.close();
}
}
Redis乐观锁实现秒杀
public static void main(String[] arg) {
String rediskey = "second";
ExecutorService executorservice = Executor.newFixedThreadPoo1(20);
try {
Jedis jedis = new Jedis("127.0.0.1", 6378);
//初始值
jedis.set(rediskey, "0");
jedis.close();
} catch (Exception e) {
e.printstackTrace();
for (int i = 0; i < 1000; i++) {
executorservice.execute(()-> {
Jedis jedis1 = new Jedis("127.0.0.1",6378);
try {
jedi sl.watch(redisKey);
String redisvalue = jedis1.get(rediskey);
int valInteger = Integer.valueof(redisvalue);
String userInfo = UUID.randomUUID().tostring();
//没有秒完
if (valInteger < 20) {
Transaction tx = jedis1.multi();
tx.incr(redisKey);
List list = tx.exec();
//秒成功 失败返回空list而不是空
if (list != nu11 && list.size() > 0) {
System.out.println("用户: " + userInfo + ",秒杀成功!当前成功人数:" + (valInteger + 1));
}
//版本变化,被别人抢了。
else {
System.out.println("用户:"+ userInfo + ",秒杀失败");
}
}
//秒完了
else {
System.out.println("已经有20人秒杀成功,秒杀结束");
}
} catch (Exception e) {
e.printstackTrace();
} finally {
jedis1.close();
}
});
}
executorservice.shutdown();
}
}