Redis分布式鎖----樂觀鎖的實現(xiàn),以秒殺系統(tǒng)為例

樂觀鎖

大多數(shù)是基于數(shù)據(jù)版本(version)的記錄機制實現(xiàn)的。即為數(shù)據(jù)增加一個版本標識,在基于數(shù)據(jù)庫表的版本解決方案中,一般是通過為數(shù)據(jù)庫表增加一個”version”字段來實現(xiàn)讀取出數(shù)據(jù)時,將此版本號一同讀出,之后更新時,對此版本號加1。此時,將提交數(shù)據(jù)的版本號與數(shù)據(jù)庫表對應記錄的當前版本號進行比對,如果提交的數(shù)據(jù)版本號大于數(shù)據(jù)庫當前版本號,則予以更新,否則認為是過期數(shù)據(jù)。redis中可以使用watch命令會監(jiān)視給定的key,當exec時候如果監(jiān)視的key從調用watch后發(fā)生過變化,則整個事務會失敗。也可以調用watch多次監(jiān)視多個key。這樣就可以對指定的key加樂觀鎖了。注意watch的key是對整個連接有效的,事務也一樣。如果連接斷開,監(jiān)視和事務都會被自動清除。當然了exec,discard,unwatch命令都會清除連接中的所有監(jiān)視。

Redis事務

Redis中的事務(transaction)是一組命令的集合。事務同命令一樣都是Redis最小的執(zhí)行單位,一個事務中的命令要么都執(zhí)行,要么都不執(zhí)行。Redis事務的實現(xiàn)需要用到 MULTI 和 EXEC 兩個命令,事務開始的時候先向Redis服務器發(fā)送 MULTI 命令,然后依次發(fā)送需要在本次事務中處理的命令,最后再發(fā)送 EXEC 命令表示事務命令結束。Redis的事務是下面4個命令來實現(xiàn)

1.multi,開啟Redis的事務,置客戶端為事務態(tài)。

2.exec,提交事務,執(zhí)行從multi到此命令前的命令隊列,置客戶端為非事務態(tài)。

3.discard,取消事務,置客戶端為非事務態(tài)。

4.watch,監(jiān)視鍵值對,作用時如果事務提交exec時發(fā)現(xiàn)監(jiān)視的監(jiān)視對發(fā)生變化,事務將被取消。

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;// 模擬客戶數(shù)目
        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();
                }    
            }  
    }

    /**
     * 初始化商品個數(shù)
     */
    public static void initPrduct() {
        int prdNum = 100;// 商品個數(shù)
        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));// 當前商品個數(shù)
                if (prdNum > 0) {
                    Transaction transaction = jedis.multi();
                    transaction.set(key, String.valueOf(prdNum - 1));
                    List<Object> result = transaction.exec();
                    if (result == null || result.isEmpty()) {
                        System.out.println("悲劇了,顧客:" + clientName + "沒有搶到商品");// 可能是watch-key被外部修改,或者是數(shù)據(jù)操作被駁回
                    } 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);
            }

        }
    }

}

和上文的使用悲觀鎖相比,樂觀鎖的實現(xiàn)更加的簡單,并發(fā)性能也會更好。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容