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();
        }
    }

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章