(轉(zhuǎn))關(guān)于redis中使用鎖機制,( 實現(xiàn)分布式鎖和任務(wù)隊列)

場景:
電商網(wǎng)站上有很多秒殺活動,會迎來一個用戶請求的高峰期,可能會有幾十萬幾百萬的并發(fā)量,來搶這個手機,在高并發(fā)的情形下會對數(shù)據(jù)庫服務(wù)器或者是文件服務(wù)器應(yīng)用服務(wù)器造成巨大的壓力,嚴重時說不定就宕機了;
另一個問題是,秒殺的東西都是有量的,一款手機只有10臺的量秒殺,在高并發(fā)的情況下,成千上萬條數(shù)據(jù)更新數(shù)據(jù)庫(例如10臺的量被人搶一臺就會在數(shù)據(jù)集某些記錄下 減1),那次這個時候的先后順序是很亂的,很容易出現(xiàn)10臺的量,搶到的人就不止10個這種嚴重的問題。

對于redis的并發(fā)的處理:

a)Redis為單進程單線程模式,Redis本身沒有鎖的概念,Redis對于多個客戶端連接并不存在競爭,
但是在Jedis客戶端對Redis進行并發(fā)訪問時會發(fā)生 連接超時、連接阻塞、客戶端關(guān)閉連接等問題,對于這些問題對此有2種解決方法:

1.客戶端角度,為保證每個客戶端間正常有序與Redis進行通信,對連接進行池化,同時對客戶端讀寫Redis操作采用內(nèi)部鎖synchronized。

2..服務(wù)器角度,利用setnx實現(xiàn)鎖。

Redis分布式鎖的實現(xiàn):
思路很簡單,主要用到的redis函數(shù)是setnx(),首先是將某一任務(wù)標識名(這里用Lock:order作為標識名的例子)作為鍵存到redis里,并為其設(shè)個過期時間。
對于再次Lock:order請求過來,先是通過setnx()看看是否能將Lock:order插入到redis里,可以的話就返回true,不可以就返回false。當然,在我的代碼里會比這個思路復(fù)雜一些,我會在分析代碼時進一步說明。

Redis實現(xiàn)任務(wù)隊列:
實現(xiàn)會用到上面的Redis分布式的鎖機制,主要是用到了Redis里的有序集合這一數(shù)據(jù)結(jié)構(gòu),例如入隊時,通過zset的add()函數(shù)進行入隊,而出對時,可以用到zset的getScore()函數(shù)。另外還可以彈出頂部的幾個任務(wù)。

Redis實現(xiàn)分布式鎖代碼:需要注意的問題
1:為避免特殊原因?qū)е骆i無法釋放,在加鎖成功后,鎖會被賦予一個生存時間(通過lock方法的參數(shù)設(shè)置或者使用默認值),超出生存時間鎖會被自動釋放鎖;如果需要長時間加鎖,可以通過expire方法延長鎖的生存時間。
2:系統(tǒng)級的鎖在進程無論何種原因時出現(xiàn)崩潰時,操作系統(tǒng)會自己回收鎖,所以不會出現(xiàn)資源丟失;但是分布式鎖則不同,如果設(shè)置的鎖生成時間過長,一旦由于某個原因出現(xiàn)系統(tǒng)崩潰的時候,其他進程就會獲取不到鎖, 這個鎖就會變成垃圾鎖,其他進程也用不到這個鎖,進不到加鎖區(qū)。
3:加鎖代碼中主要的兩個參數(shù),一個是timeout,這個是循環(huán)獲取鎖的等待時間,在這個時間內(nèi)會一直嘗試獲取鎖知道超時,如果為0,則表示獲取鎖失敗后直接返回而不再等待;另一個重要參數(shù)的expire,這個參數(shù)指當前鎖的最大生存時間,以秒為單位的,它必須大于0,如果超過生存時間鎖仍未被釋放,則系統(tǒng)會自動強制釋放
代碼實現(xiàn)過程:先取得當前時間,然后再獲取到鎖失敗時的等待超時的時刻(是個時間戳),再獲取到鎖的最大生存時間。
key用這種格式:”Lock:鎖的標識名”,進入循環(huán)了,先是插入數(shù)據(jù)到redis里,使用setnx()函數(shù),key鍵不存在則插入數(shù)據(jù),如果插入成功,則對該鍵進行失效時間的設(shè)置,并將該key鍵放在$lockedName數(shù)組里,返回true,也就是上鎖成功 如果該key鍵存在,則不會插入操作了。

public class RedisBillLockHandler implements IBatchBillLockHandler {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(RedisBillLockHandler.class);
 
    private static final int DEFAULT_SINGLE_EXPIRE_TIME = 3;
    
    private static final int DEFAULT_BATCH_EXPIRE_TIME = 6;
 
    private final JedisPool jedisPool;
    
   
    public RedisBillLockHandler(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }
 
    /**
     * 獲取鎖  如果鎖可用   立即返回true,  否則返回false
     * @see com.fx.platform.components.lock.IBillLockHandler#tryLock(com.fx.platform.components.lock.IBillIdentify)
     * @param billIdentify
     * @return
     */
    public boolean tryLock(IBillIdentify billIdentify) {
        return tryLock(billIdentify, 0L, null);
    }
 
    /**
     * 鎖在給定的等待時間內(nèi)空閑,則獲取鎖成功 返回true, 否則返回false
     * @param billIdentify
     * @param timeout
     * @param unit
     * @return
     */
    public boolean tryLock(IBillIdentify billIdentify, long timeout, TimeUnit unit) {
        String key = (String) billIdentify.uniqueIdentify();
        Jedis jedis = null;
        try {
            jedis = getResource();
            long nano = System.nanoTime();
            do {
                LOGGER.debug("try lock key: " + key);
                Long i = jedis.setnx(key, key);
                if (i == 1) { 
                    jedis.expire(key, DEFAULT_SINGLE_EXPIRE_TIME);
                    LOGGER.debug("get lock, key: " + key + " , expire in " + DEFAULT_SINGLE_EXPIRE_TIME + " seconds.");
                    return Boolean.TRUE;
                } else { // 存在鎖
                    if (LOGGER.isDebugEnabled()) {
                        String desc = jedis.get(key);
                        LOGGER.debug("key: " + key + " locked by another business:" + desc);
                    }
                }
                if (timeout == 0) {
                    break;
                }
                Thread.sleep(300);
            } while ((System.nanoTime() - nano) < unit.toNanos(timeout));
            return Boolean.FALSE;
        } catch (JedisConnectionException je) {
            LOGGER.error(je.getMessage(), je);
            returnBrokenResource(jedis);
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
        } finally {
            returnResource(jedis);
        }
        return Boolean.FALSE;
    }
 
    /**
     * 如果鎖空閑立即返回   獲取失敗 一直等待
     * @param billIdentify
     */
    public void lock(IBillIdentify billIdentify) {
        String key = (String) billIdentify.uniqueIdentify();
        Jedis jedis = null;
        try {
            jedis = getResource();
            do {
                LOGGER.debug("lock key: " + key);
                Long i = jedis.setnx(key, key);
                if (i == 1) { 
                    jedis.expire(key, DEFAULT_SINGLE_EXPIRE_TIME);
                    LOGGER.debug("get lock, key: " + key + " , expire in " + DEFAULT_SINGLE_EXPIRE_TIME + " seconds.");
                    return;
                } else {
                    if (LOGGER.isDebugEnabled()) {
                        String desc = jedis.get(key);
                        LOGGER.debug("key: " + key + " locked by another business:" + desc);
                    }
                }
                Thread.sleep(300); 
            } while (true);
        } catch (JedisConnectionException je) {
            LOGGER.error(je.getMessage(), je);
            returnBrokenResource(jedis);
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
        } finally {
            returnResource(jedis);
        }
    }
 
    /**
     * 釋放鎖
     * @param billIdentify
     */
    public void unLock(IBillIdentify billIdentify) {
        List<IBillIdentify> list = new ArrayList<IBillIdentify>();
        list.add(billIdentify);
        unLock(list);
    }
 
    /**
     * 批量獲取鎖  如果全部獲取   立即返回true, 部分獲取失敗 返回false
     * @param billIdentifyList
     * @return
     */
    public boolean tryLock(List<IBillIdentify> billIdentifyList) {
        return tryLock(billIdentifyList, 0L, null);
    }
    
    /**
     * 鎖在給定的等待時間內(nèi)空閑,則獲取鎖成功 返回true, 否則返回false
     * @param billIdentifyList
     * @param timeout
     * @param unit
     * @return
     */
    public boolean tryLock(List<IBillIdentify> billIdentifyList, long timeout, TimeUnit unit) {
        Jedis jedis = null;
        try {
            List<String> needLocking = new CopyOnWriteArrayList<String>();    
            List<String> locked = new CopyOnWriteArrayList<String>();    
            jedis = getResource();
            long nano = System.nanoTime();
            do {
                // 構(gòu)建pipeline,批量提交
                Pipeline pipeline = jedis.pipelined();
                for (IBillIdentify identify : billIdentifyList) {
                    String key = (String) identify.uniqueIdentify();
                    needLocking.add(key);
                    pipeline.setnx(key, key);
                }
                LOGGER.debug("try lock keys: " + needLocking);
                // 提交redis執(zhí)行計數(shù)
                List<Object> results = pipeline.syncAndReturnAll();
                for (int i = 0; i < results.size(); ++i) {
                    Long result = (Long) results.get(i);
                    String key = needLocking.get(i);
                    if (result == 1) {    // setnx成功,獲得鎖
                        jedis.expire(key, DEFAULT_BATCH_EXPIRE_TIME);
                        locked.add(key);
                    } 
                }
                needLocking.removeAll(locked);    // 已鎖定資源去除
                
                if (CollectionUtils.isEmpty(needLocking)) {
                    return true;
                } else {    
                    // 部分資源未能鎖住
                    LOGGER.debug("keys: " + needLocking + " locked by another business:");
                }
                
                if (timeout == 0) {    
                    break;
                }
                Thread.sleep(500);    
            } while ((System.nanoTime() - nano) < unit.toNanos(timeout));
 
            // 得不到鎖,釋放鎖定的部分對象,并返回失敗
            if (!CollectionUtils.isEmpty(locked)) {
                jedis.del(locked.toArray(new String[0]));
            }
            return false;
        } catch (JedisConnectionException je) {
            LOGGER.error(je.getMessage(), je);
            returnBrokenResource(jedis);
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
        } finally {
            returnResource(jedis);
        }
        return true;
    }
 
    /**
     * 批量釋放鎖
     * @param billIdentifyList
     */
    public void unLock(List<IBillIdentify> billIdentifyList) {
        List<String> keys = new CopyOnWriteArrayList<String>();
        for (IBillIdentify identify : billIdentifyList) {
            String key = (String) identify.uniqueIdentify();
            keys.add(key);
        }
        Jedis jedis = null;
        try {
            jedis = getResource();
            jedis.del(keys.toArray(new String[0]));
            LOGGER.debug("release lock, keys :" + keys);
        } catch (JedisConnectionException je) {
            LOGGER.error(je.getMessage(), je);
            returnBrokenResource(jedis);
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
        } finally {
            returnResource(jedis);
        }
    }
    
    /**
     * @return
     */
    private Jedis getResource() {
        return jedisPool.getResource();
    }
    
    /**
     * 銷毀連接
     * @author http://blog.csdn.net/java2000_wl
     * @param jedis
     */
    private void returnBrokenResource(Jedis jedis) {
        if (jedis == null) {
            return;
        }
        try {
            //容錯
            jedisPool.returnBrokenResource(jedis);
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
        }
    }
    
    /**
     * @param jedis
     */
    private void returnResource(Jedis jedis) {
        if (jedis == null) {
            return;
        }
        try {
            jedisPool.returnResource(jedis);
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
        }
    }

轉(zhuǎn)自:https://blog.csdn.net/wanghang88/article/details/53028009

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

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容