Redis事务
Redis事务可以一次执行多个命令,(按顺序地串行化执行,执行中不会被其他命令插入,不许加塞)允许在一次单独的步骤中执行一组命令,并且带有以下两个重要保证:
1、Redis会将一个事务中的所有命令序列化,然后按顺序执行
2、执行中不会被其他命令插入,不许出现加塞行为
批量操作在发送 EXEC 命令前被放入队列缓存。
收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。
一个事务从开始到执行会经历以下三个阶段
(1)开始事务
(2)命令入队
(3)执行事务
Redis事务涉及到的五个命令:
- multi: 启动事务,事务开始的命令是MULTI, 该命令返回OK提示信息. Redis不支持事务嵌套,执行多次MULTI命令和执行一次是相同的效果.嵌套执行MULTI命令时,Redis只是返回错误提示信息.
- exec:执行事务,EXEC是事务的提交命令,事务中的命令序列将被执行(或者不被执行,比如乐观锁失败等).该命令将返回响应数组,其内容对应事务中的命令执行结果.
- watch:WATCH命令是开始执行乐观锁,该命令的参数是key(可以有多个), Redis将执行WATCH命令的客户端对象和key进行关联,如果其他客户端修改了这些key,则执行WATCH命令的客户端将被设置乐观锁失败的标志.该命令必须在事务开始前执行,即在执行MULTI命令前执行WATCH命令,否则执行无效,并返回错误提示信息.
- unwatch:UNWATCH命令将取消当前客户端对象的乐观锁key,该客户端对象的事务提交将变成无条件执行.
- discard:DISCARD命令将结束事务,并且会丢弃全部的命令序列.
秒杀功能的设计
那么用Redis事务如何去实现秒杀功能呢?答案是使用watch,watch能监控某一个key的变化,在事务执行时,如果其他的client改变了这个可以所对应的值,将会导致当前client的事务不执行,即类似于乐观锁机制。
示例代码如下:
首先没有watch的代码:
示例为有10张优惠券,有多人来抢,需要提供秒杀功能。
首先在redis中设置一个key为num值为0:
命令为:set num 0
MyThread类:
import com.itheima.RedisPoolUtil;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import java.util.List;
import java.util.UUID;
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++){
Jedis jedis = RedisPoolUtil.getJedis();
String num = jedis.get("num");
int n = Integer.parseInt(num);
if (n < 10){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
jedis.incr("num");
String name = UUID.randomUUID().toString().replaceAll("-", "");
System.out.println(name + "抢到一张优惠券");
}
}
}
}
主函数:
public class TestMain {
public static void main(String[] args) {
for (int i = 0; i < 5; i++){
MyThread thread = new MyThread();
thread.start();
}
}
}
RedisPoolUtil类:
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class RedisPoolUtil {
private static JedisPool pool;
static {
//1、连接池redis pool 基本配置信息
JedisPoolConfig poolConfig=new JedisPoolConfig();
poolConfig.setMaxTotal(5);
poolConfig.setMaxIdle(1);
//其他配置
//2、连接池
String host="192.168.18.132";
int port=6379;
pool=new JedisPool(poolConfig,host,port);
}
public static Jedis getJedis(){
Jedis jedis=pool.getResource();
jedis.auth("root");
return jedis;
}
//关闭连接
public static void close(Jedis jedis){
jedis.close();
}
}
当执行上述代码时,发现会有超过10个人抢到优惠券。
代码优化,使用事务:
加入watch时,代码如下,修改Mythred类:
import com.itheima.RedisPoolUtil;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import java.util.List;
import java.util.UUID;
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++){
// Jedis jedis = RedisConnection.getJedis();
Jedis jedis = RedisPoolUtil.getJedis();
String watch = jedis.watch("num");
String num = jedis.get("num");
int n = Integer.parseInt(num);
if (n < 10){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
Transaction transaction = jedis.multi();
transaction.incr("num");
List<Object> list = transaction.exec();
String name = UUID.randomUUID().toString().replaceAll("-", "");
if (list == null || list.size() == 0){
System.out.println(watch + "==="+list+"----" + name + "手慢了,抢票失败");
}else {
System.out.println(watch + "==="+list+"----" + name + "抢到一张优惠券");
}
}
}
}
}
测试结果,你会发现,虽然一样会有多人去抢,但是始终只有10个人能抢到。