歡迎光臨
每天分享高質量文章

Redis 分佈式鎖:樂觀鎖的實現,以秒殺系統為例

(點擊上方公眾號,可快速關註)


來源:Evankaka

blog.csdn.net/Evankaka/article/details/70570200

樂觀鎖


大多數是基於資料版本(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 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 result = transaction.exec();  
                   if (result == null || result.isEmpty()) {  
                       System.out.println("悲劇了,顧客:" + clientName + "沒有搶到商品");// 可能是watch-key被外部修改,或者是資料操作被駁回  
                   } 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);  
           }  
 
       }  
   }  
 
}



和上文的使用悲觀鎖相比,樂觀鎖的實現更加的簡單,併發性能也會更好。


本文原始碼請在這裡下載:

https://github.com/appleappleapple/DistributeLearning

看完本文有收穫?請轉發分享給更多人

關註「資料分析與開發」,提升資料技能

赞(0)

分享創造快樂