基於Redis的分佈式鎖兩種實現方式

最近有一個競拍的項目會用到分佈式鎖,網上查到的結果是有三種途徑能夠實現。1.數據庫鎖機制,2.redis的鎖,3.zookeeper。考慮到使用mysql實現會在性能這一塊會受影響,zookeeper又是不怎麼會。因此使用redis來實現了。

第一種:使用redis的watch命令進行實現html


如上圖所示:session1在執行修改以前使用watch命令監視了age,而後又在session2更新了age以後,session1在執行exec,在該命令執行的時候應該會檢查age值是否更改,如今是已經發生了改變,因此返回執行失敗。java

基於上述圖示寫了一段java代碼mysql

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

import java.util.List;


public class RedisWatchTest extends  Thread {

    private String auctionCode;
    public RedisWatchTest
(String auctionCode) {
        super(auctionCode);
        this.auctionCode = auctionCode;
    }
    private static int bidPrice = 100;

    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName() + "主線程運行開始!");
        //更改key爲a的值
        Jedis jedis=new Jedis("127.0.0.1",6379);
        jedis.set("goodsprice","0");
        System.out.println("輸出初始化值:"+jedis.get("goodsprice"));
        jedis.close();
        RedisWatchTest thread1 = new RedisWatchTest("A001");
        RedisWatchTest thread2  = new RedisWatchTest("B001");
        thread1.start();
        thread2.start();
        try{
            thread1.join();
            thread2.join();
       }catch(InterruptedException e){
           e.printStackTrace();
       }
        System.out.println(Thread.currentThread().getName() + "主線程運行結束!");
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "線程運行開始 ");
        Jedis jedis=new Jedis("127.0.0.1",6379);
        try {
            if(Thread.currentThread().getName()=="B001"){
                sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //監視KEY
        jedis.watch("goodsprice");
        //A先進
        String v =  jedis.get("goodsprice");
        Integer iv = Integer.valueOf(v);
        //條件都給過
        if(bidPrice > iv){
            Transaction tx = jedis.multi();// 開啓事務
            Integer bp = iv + 100;
            //出價成功,事務未提交
            tx.set("goodsprice",String.valueOf(bp));
            System.out.println("子線程" + auctionCode + "set成功");
            try {
                if(Thread.currentThread().getName()=="A001"){
                    sleep(2000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            List<Object> list = tx.exec();
            if (list == null ||list.size()==0) {
                System.out.println("子線程" + auctionCode + ",出價失敗");
            }else{
                System.out.println("子線程"+this.auctionCode +", 出價:"+ jedis.get("goodsprice") +",出價時間:"
+ System.nanoTime()); } }else{ System.out.println("出價低於現有價格!"); } jedis.close(); System.out.println(Thread.currentThread().getName() + "線程運行結束"); } }

執行結果:
main主線程運行開始!
輸出初始化值:0
B001線程運行開始
A001線程運行開始
子線程A001set成功
子線程B001set成功
子線程B001, 出價:100,出價時間:76269463819581
B001線程運行結束
子線程A001,出價失敗
A001線程運行結束
main主線程運行結束!redis

上述代碼是在主線程裏面開了兩個子線程,首先讓B001先等待1s時間,讓A001先watch最高價,而後在A001事務exec以前讓他等待2s時間。這個時候B001已經出價成功了,因此最後應當返回A001出價失敗。

第二種:使用redis的setnx命令進行實現

關於setnx的詳解參考的是下面這個文章,拿了他的兩個加鎖和解鎖的正確實現方式。
http://www.javashuo.com/article/p-ejxnzdni-ht.html下面是經過setnx實現的相關代碼sql

import redis.clients.jedis.Jedis;

import java.util.Collections;

/**
 * @author chen
 * @date 2018/4/30 16:09
 */
    public class RedisSetNXTest extends  Thread{

    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";

    private String auctionCode;
    public RedisSetNXTest
            (String auctionCode) {
        super(auctionCode);
        this.auctionCode = auctionCode;
    }
    private static int bidPrice = 100;

    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName() + "主線程運行開始!");
        //更改key爲a的值
        Jedis jedis=new Jedis("127.0.0.1",6379);
        jedis.set("goodsprice","0");
        System.out.println("輸出初始化值:"+jedis.get("goodsprice"));
        jedis.close();
        RedisSetNXTest thread1 = new RedisSetNXTest("A001");
        RedisSetNXTest thread2  = new RedisSetNXTest("B001");
        thread1.start();
        thread2.start();
        try{
            thread1.join();
            thread2.join();
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "主線程運行結束!");
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "線程運行開始 ");
        Jedis jedis=new Jedis("127.0.0.1",6379);
        try {
            if(Thread.currentThread().getName()=="B001"){
                sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //讓A先拿到鎖
        boolean isOk=  tryGetDistributedLock(jedis, "goods_lock", Thread.currentThread().getName() , 10000);

        try {
            if(Thread.currentThread().getName()=="A001"){
                sleep(2000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        if(isOk) {
            System.out.println("子線程"+this.auctionCode +"拿到鎖");
            String v =  jedis.get("goodsprice");
            Integer iv = Integer.valueOf(v);
            //條件都給過
            if(bidPrice > iv){

                Integer bp = iv + 100;
                //出價成功,事務未提交
                jedis.set("goodsprice",String.valueOf(bp));
                System.out.println("子線程"+this.auctionCode +", 出價:"+ jedis.get("goodsprice") +",出價時間:"
+ System.nanoTime()); }else{ System.out.println("出價低於現有價格!"); } boolean isOk1= releaseDistributedLock(jedis, "goods_lock", Thread.currentThread().getName()); if(isOk1){ System.out.println("子線程"+this.auctionCode +"釋放鎖"); } }else{ System.out.println("子線程" + auctionCode + "未拿到鎖"); } jedis.close(); System.out.println(Thread.currentThread().getName() + "線程運行結束"); } /** * 嘗試獲取分佈式鎖 * @param jedis Redis客戶端 * @param lockKey 鎖 * @param requestId 請求標識 * @param expireTime 超期時間 * @return 是否獲取成功 */ public boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) { String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); if (LOCK_SUCCESS.equals(result)) { return true; } return false; } private static final Long RELEASE_SUCCESS = 1L; /** * 釋放分佈式鎖 * @param jedis Redis客戶端 * @param lockKey 鎖 * @param requestId 請求標識 * @return 是否釋放成功 */ public boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) { String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1])
else return 0 end"; Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections
.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) { return true; } return false; } }

執行結果:
main主線程運行開始!
輸出初始化值:0
A001線程運行開始
B001線程運行開始
子線程B001未拿到鎖
B001線程運行結束
子線程A001拿到鎖
子線程A001, 出價:100,出價時間:77389730033100
子線程A001釋放鎖
A001線程運行結束
main主線程運行結束!

一樣代碼是在主線程裏面開了兩個子線程,先讓B001等待,讓A001先去拿到鎖。而後讓B001在沒有拿到鎖的狀況下去操做redis,代碼作出判斷該現場未拿到鎖,後執行的A001由於拿到了鎖,因此能夠進行出價。

這兩種方式實現應該都是屬於樂觀鎖吧,上述實現可能暫時不適應什麼秒殺之類的併發環境。總之具體問題仍是得具體分析吧。

數據庫

相關文章
相關標籤/搜索