Redis實現樂觀鎖
樂觀鎖
大多數是基於資料版本(version)的記錄機制實現的。即為資料增加一個版本標識,在基於資料庫表的版本解決方案中,一般是通過為資料庫表增加一個”version”欄位來實現讀取出資料時,將此版本號一同讀出,之後更新時,對此版本號加1。此時,將提交資料的版本號與資料庫表對應記錄的當前版本號進行比對,如果提交的資料版本號大於資料庫當前版本號,則予以更新,否則認為是過期資料。redis中可以使用watch命令會監視給定的key,當exec時候如果監視的key從呼叫watch後發生過變化,則整個事務會失敗。也可以呼叫watch多次監視多個key。這樣就可以對指定的key加樂觀鎖了。注意watch的key是對整個連線有效的,事務也一樣。如果連線斷開,監視和事務都會被自動清除。當然了exec,discard,unwatch命令都會清除連線中的所有監視。
Redis事務
Redis中的事務(transaction)是一組命令的集合。事務同命令一樣都是Redis最小的執行單位,一個事務中的命令要麼都執行,要麼都不執行。Redis事務的實現需要用到 MULTI 和 EXEC 兩個命令,事務開始的時候先向Redis伺服器傳送 MULTI 命令,然後依次傳送需要在本次事務中處理的命令,最後再發送 EXEC 命令表示事務命令結束。Redis的事務是下面4個命令來實現
1.multi,開啟Redis的事務,置客戶端為事務態。
2.exec,提交事務,執行從multi到此命令前的命令佇列,置客戶端為非事務態。
3.discard,取消事務,置客戶端為非事務態。
4.watch,監視鍵值對,作用時如果事務提交exec時發現監視的監視對發生變化,事務將被取消。
下面筆者簡單實現一個用redis樂觀鎖實現的秒殺系統
package com.github.distribute.lock.redis; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import redis.clients.jedis.Jedis; import redis.clients.jedis.Transaction; /** * redis樂觀鎖例項 * @author linbingwen * */ public class OptimisticLockTest { public static void main(String[] args) throws InterruptedException { long starTime=System.currentTimeMillis(); initPrduct(); initClient(); printResult(); long endTime=System.currentTimeMillis(); long Time=endTime-starTime; System.out.println("程式執行時間: "+Time+"ms"); } /** * 輸出結果 */ public static void printResult() { Jedis jedis = RedisUtil.getInstance().getJedis(); Set<String> set = jedis.smembers("clientList"); int i = 1; for (String value : set) { System.out.println("第" + i++ + "個搶到商品,"+value + " "); } RedisUtil.returnResource(jedis); } /* * 初始化顧客開始搶商品 */ public static void initClient() { ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); int clientNum = 10000;// 模擬客戶數目 for (int i = 0; i < clientNum; i++) { cachedThreadPool.execute(new ClientThread(i)); } cachedThreadPool.shutdown(); while(true){ if(cachedThreadPool.isTerminated()){ System.out.println("所有的執行緒都結束了!"); break; } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * 初始化商品個數 */ public static void initPrduct() { int prdNum = 100;// 商品個數 String key = "prdNum"; String clientList = "clientList";// 搶購到商品的顧客列表 Jedis jedis = RedisUtil.getInstance().getJedis(); if (jedis.exists(key)) { jedis.del(key); } if (jedis.exists(clientList)) { jedis.del(clientList); } jedis.set(key, String.valueOf(prdNum));// 初始化 RedisUtil.returnResource(jedis); } } /** * 顧客執行緒 * * @author linbingwen * */ class ClientThread implements Runnable { Jedis jedis = null; String key = "prdNum";// 商品主鍵 String clientList = "clientList";//// 搶購到商品的顧客列表主鍵 String clientName; public ClientThread(int num) { clientName = "編號=" + num; } public void run() { try { Thread.sleep((int)(Math.random()*5000));// 隨機睡眠一下 } catch (InterruptedException e1) { } while (true) { System.out.println("顧客:" + clientName + "開始搶商品"); jedis = RedisUtil.getInstance().getJedis(); try { jedis.watch(key); //當前商品個數 int prdNum = Integer.parseInt(jedis.get(key)); if (prdNum > 0){ Transaction transaction = jedis.multi(); transaction.set(key, String.valueOf(prdNum - 1)); List<Object> result = transaction.exec(); if (result == null || result.isEmpty()) { // 可能是watch-key被外部修改,或者是資料操作被駁回 System.out.println("悲劇了,顧客:" + clientName + "沒有搶到商品"); } else { jedis.sadd(clientList, clientName);// 搶到商品記錄一下 System.out.println("好高興,顧客:" + clientName + "搶到商品"); break; } }else{ System.out.println("庫存為0,顧客:" + clientName + "沒有搶到商品"); break; } } catch (Exception e) { e.printStackTrace(); } finally { jedis.unwatch(); RedisUtil.returnResource(jedis); } } } }