Redis事务和秒杀业务的实现

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个人能抢到。

发布了178 篇原创文章 · 获赞 185 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/Sophia_0331/article/details/104792656