基於Redis的分佈式鎖實現方案

一.Redis分佈式鎖實現分析
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
存在的問題:
  如果某個線程執行的太慢,導致在有效期內還沒有執行完,那麼因爲設置了鎖超時自動釋放機制,此時鎖被自動釋放,另一個線程進來拿到鎖開始執行代碼,就會出現同一時間有兩個線程在執行互斥資源代碼,可能出現數據不一致。
如何解決:
設置合理的超時時間 + 監控代碼執行情況
自動續期,起一個定時任務,週期性掃描超距離時時間還剩多少時仍沒有執行完的線程,自動延長時間。
在這裏插入圖片描述
註定只能使用其他機制解決的難題
在這裏插入圖片描述
二.分佈式鎖實現方案
在這裏插入圖片描述

Redis鎖開源實現:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

三.自己實現一個基於Redis的分佈式鎖

package com.hong.redis;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.types.Expiration;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author wanghong
 **/
public class RedisLockImpl implements Lock {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 你要鎖的資源
     */
    private String resourceName;

    int timeout;

    public RedisLockImpl(StringRedisTemplate stringRedisTemplate, String resourceName, int timeout) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.resourceName = "lock_" + resourceName;
        this.timeout = timeout;
    }

    private Lock lock = new ReentrantLock();

    /**
     * 一直要等到搶到鎖爲止
     */
    @Override
    public void lock() {
      
        // 限制同一個JVM進程內的資源競爭,分佈式鎖(JVM之間的競爭) + JVM鎖
        lock.lock();
        try {
            while (!tryLock()) {
                // 訂閱指定的Redis主題,接收釋放鎖的信號
                stringRedisTemplate.execute(new RedisCallback<Long>() {
                    @Override
                    public Long doInRedis(RedisConnection redisConnection) throws DataAccessException {
                        try {
                            CountDownLatch latch = new CountDownLatch(1);
                            // subscribe立馬返回,是否訂閱完畢,異步觸發
                            redisConnection.subscribe((message, pattern) -> {
                                // 收到消息,不管結果,立刻再次搶鎖
                                latch.countDown();
                            }, ("release_lock_" + resourceName).getBytes());
                            //等待有通知,才繼續循環
                            latch.await(timeout, TimeUnit.SECONDS);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        return 0L;
                    }
                });
            }
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void lockInterruptibly() throws InterruptedException { }

    @Override
    public boolean tryLock() {
        // set命令,往redis存放鎖的標記
        Boolean lockResult = stringRedisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection redisConnection) throws DataAccessException {
                String value = "";
                Boolean result = redisConnection.set(resourceName.getBytes(),
                        value.getBytes(),
                        Expiration.seconds(timeout),
                        RedisStringCommands.SetOption.SET_IF_ABSENT
                );
                return result;
            }
        });
        return lockResult;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public void unlock() {
        stringRedisTemplate.delete(resourceName);
        //通過Redis發佈訂閱機制,發送一個通知給其他等待的請求
        stringRedisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection redisConnection) throws DataAccessException {
                return redisConnection.publish(("release_lock_" + resourceName).getBytes(), resourceName.getBytes());
            }
        });
    }

    @Override
    public Condition newCondition() {
        return null;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章