redis利于watch、multi、exec命令,实现秒杀功能

1、注意点:redis watch 命令用于监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。监控一直持续到EXEC命令(事务中的命令是在EXEC之后才执行的,所以在MULTI命令后可以修改WATCH监控的键值)

2:代码如下:

public class JedisRunnable implements Runnable {

    private Jedis jedis = JedisUtil.getJedis();
    private String userId;

    public JedisRunnable(String userId) {
        this.userId = userId;
    }

    public void run() {

        try {
            // 事务状态,如果监控的key没有发生改变,那么应该返回OK,事务也可以正常执行。
            jedis.watch(RedisSecKiller.WATCH_KEY);
            // 获取剩余商品数
            int leftGoodsNum = Integer.valueOf(jedis.get(RedisSecKiller.WATCH_KEY));
            // 当剩余商品数大于0时,才进行剩余商品数减1的事务操作。
            if (leftGoodsNum > 0) {
                // 开启jedis事务
                Transaction tx = jedis.multi();
                // 方法一:在事务中对键Goods对应的值做减1操作,此时tx.exec()的返回值的第一个元素是Goods对应的当前值。
                tx.decrBy(RedisSecKiller.WATCH_KEY, 1);
                // 方法二:在事务中设置Goods的值为原值减1,此时tx.exec()的返回值的第一个元素是"OK"。
//                tx.set(RedisSecKiller.WATCH_KEY, String.valueOf(leftGoodsNum - 1));
                // 执行事务,得到返回值。
                List<Object> results = tx.exec();
                // leftGoodsNum比键Goods对应的值大1,因为事务中刚执行了减1操作。
                // 由此可知,在当前事务中,leftGoodsNum与Goods对应的值(真实剩余商品数量)并不同步。
//                System.out.println("剩余商品数量:" + leftGoodsNum);
//                System.out.println("真实剩余商品数量:" + results);
                // results为null或空时,表示并发情况下用户没能抢购到商品,秒杀失败。
                if (results == null || results.isEmpty()) {
                    String failUserInfo = "fail---" + userId;
                    // 此时无法通过results.get(0)获取真实剩余商品数量。
                    String failMsg = "用户" + failUserInfo + ",抢购失败,剩余商品数量:" + leftGoodsNum +
                            ",但此时无法获取真实剩余商品数量。";
                    System.out.println(failMsg);
                    // 将秒杀失败的用户信息存入Redis。
                    jedis.setnx(failUserInfo, failMsg);
                } else { // 此时tx.exec()事务执行成功,会自动提交事务。
                    for (Object succ : results) {
                        String succUserInfo = "succ" + succ.toString() + "---" + userId;
                        String succMsg = "用户" + succUserInfo + ",抢购成功,当前抢购成功人数:" +
                                (10 - Integer.parseInt(results.get(0).toString())) +
                                ",真实剩余商品数量:" + Integer.parseInt(results.get(0).toString());
                        System.out.println(succMsg);
                        // 将秒杀成功的用户信息存入Redis。
                        jedis.setnx(succUserInfo, succMsg);
                    }
                }
            } else { // 此时库存为0,秒杀活动结束。
                String overUserInfo = "over---" + userId;
                String overMsg = "用户" + overUserInfo + ",商品被抢购完毕,剩余商品数量:" + leftGoodsNum;
                System.out.println(overMsg);
                // 将秒杀活动结束后还在访问秒杀系统的用户信息存入Redis。
                jedis.setnx(overUserInfo, overMsg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JedisUtil.returnResource(jedis);
        }

    }
}
public class RedisSecKiller {

    // 模拟用户抢购最大并发数
    private static final int N_THREADS = 5;
    // jedis通过watch方法监控WATCH_KEY,一旦发生改变,事务将失败。
    public static final String WATCH_KEY = "Goods";
    // 商品总数
    private static final int GOODS_NUM = 1000;
    // 用户数量
    private static final int USER_NUM = 100;

    public static void main(String[] args) {
        // 创建线程池,模拟N_THREADS位用户同时抢购的过程。
        ExecutorService executorService = Executors.newFixedThreadPool(N_THREADS);
        Jedis jedis = JedisUtil.getJedis();
        // 设置商品总数为10
        jedis.set(WATCH_KEY, String.valueOf(GOODS_NUM));
        jedis.close();
        // 模拟USER_NUM位用户在抢购商品
        for (int i = 0; i < USER_NUM; i++) {
            executorService.execute(new JedisRunnable(UUID.randomUUID().toString()));
//            System.out.println("==============循环分割线===============");
        }
        executorService.shutdown();
    }
}

 

public class JedisUtil {

    private static final String ADDR = "192.168.222.130";
    private static final int PORT = 6379;
    private static final boolean TEST_ON_BORROW = true;
    private static final int MAX_IDLE = 200;
    private static JedisPool jedisPool = null;

    static {
        try {
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxIdle(MAX_IDLE);
            config.setTestOnBorrow(TEST_ON_BORROW);
            jedisPool = new JedisPool(config, ADDR, PORT);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public synchronized static Jedis getJedis() {
        try {
            if (jedisPool != null) {
                Jedis resource = jedisPool.getResource();
                return resource;
            }
            return null;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public static void returnResource(final Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }

}

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Redis可以通过使用事务或Lua脚本来实现高并发下的抢购/秒杀功能。 1. 使用事务:抢购/秒杀的过程可以看做是一个先检查库存是否充足,再扣减库存的过程。使用Redis的事务可以保证这个过程是原子性的,即要么全部成功,要么全部失败。 具体实现方法如下: - 使用MULTI命令开启一个事务。 - 使用WATCH命令对库存进行监视(即设置监视器)。 - 使用GET命令获取当前库存。 - 判断库存是否充足,如果充足,则使用DECRBY命令扣减库存。 - 使用EXEC命令提交事务,如果提交成功,则说明扣减成功,否则说明被其他线程抢先扣减了库存。 示例代码如下: ```python def decrease_stock(redis_conn, stock_key): with redis_conn.pipeline() as pipeline: while True: try: pipeline.watch(stock_key) stock = int(pipeline.get(stock_key)) if stock > 0: pipeline.multi() pipeline.decr(stock_key) pipeline.execute() return True else: return False except WatchError: continue ``` 2. 使用Lua脚本:Lua脚本可以在Redis端原子性地执行多个命令,可以减少网络开销和锁竞争的问题。 具体实现方法如下: - 编写一个Lua脚本,该脚本首先使用GET命令获取当前库存,如果库存充足,则使用DECRBY命令扣减库存,否则返回0。 - 在Python中使用Redis的EVAL命令执行该Lua脚本。 示例代码如下: ```python def decrease_stock(redis_conn, stock_key): script = """ local stock = tonumber(redis.call('GET', KEYS[1])) if stock > 0 then redis.call('DECRBY', KEYS[1], 1) return 1 else return 0 end """ result = redis_conn.eval(script, 1, stock_key) return bool(result) ``` 以上两种方法都可以实现高并发下的抢购/秒杀功能,但是使用Lua脚本的方法效率更高,因为在Redis端执行命令可以减少网络开销和锁竞争的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

敏成

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值