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