第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

1. 前言

對于一個在線運行的系統(tǒng),如果需要修改數(shù)據(jù)庫已有數(shù)據(jù),需要先讀取舊數(shù)據(jù),再寫入新數(shù)據(jù)。因為讀數(shù)據(jù)和寫數(shù)據(jù)不是原子操作,所以在高并發(fā)的場景下,關(guān)注的數(shù)據(jù)可能會修改失敗,需要使用鎖控制。

2. 分布式場景

2.1 分布式鎖場景

面試官提問: 為什么要使用分布式鎖?分布式鎖解決了什么問題?

題目解析:

首先分析鎖的應(yīng)用場景,我們對于已有數(shù)據(jù)的修改可以歸納為兩個動作:

(1)讀舊數(shù)據(jù);
(2)寫新數(shù)據(jù)。

然后分析并發(fā)操作導(dǎo)致臟數(shù)據(jù)的過程:

圖片描述

(并發(fā)場景問題)

對于并發(fā)執(zhí)行的兩次請求,兩個請求同時讀到舊數(shù)據(jù)值為 10,第一個請求執(zhí)行操作后新值為 30,第二個請求執(zhí)行操作后新值為 40,最終只有第二次請求成功寫入數(shù)據(jù)實體,導(dǎo)致第一次請求失效。

在單機(jī)部署的系統(tǒng)中,我們可以直接使用本地的鎖(例如 Java 的 Object 對象鎖)解決上述的并發(fā)沖突問題,但是當(dāng)服務(wù)器分布式部署時,單機(jī)的鎖并不能跨網(wǎng)絡(luò)調(diào)用,所以需要使用分布式鎖解決問題。

2.2 Redis 分布式鎖

面試官提問: 既然談到了分布式鎖的應(yīng)用場景,在實戰(zhàn)環(huán)境是如何實現(xiàn)分布式鎖的呢?

題目解析:

目前分布式鎖最主要有三種實現(xiàn)方式:

(1)基于 Redis 集群的模式;
(2)基于 Zookeeper 集群的模式;
(3)基于 DB 數(shù)據(jù)庫的模式

本章節(jié)只關(guān)注 Redis 的部分,核心思路是通過 setnx 指令,實例:

    public static void wrongWayLock(Jedis jedis, String prefix_key, String id, int expire_time) {
        // 加鎖
        Long result = jedis.setnx(prefix_key, id);
        if (result==1){
            // 如果加鎖成功,設(shè)置過期時間   
            jedis.expire(prefix_key,expire_time);
        }
    }

加鎖步驟主要分為兩步:

(1)通過 setnx 指令加鎖,setnx 的含義是 set if not exist,即如果 redis 不存在已有的 prefix_key ,則寫入 prefix_key ,設(shè)置對應(yīng) value=id,并且調(diào)用返回為 1,如果已有 prefix_key ,則不寫入并且返回非 1.

(2)通過 expire 指令,設(shè)置過期時間,如果 prefix_key 代表的鎖一直沒有刪除,則在定時后自動失效,防止產(chǎn)生死鎖的情況。

上述代碼并不完美,其中 setnx()expire() 函數(shù)并不是原子操作,如果執(zhí)行 setnx() 指令之后,redis 集群出現(xiàn)網(wǎng)絡(luò)抖動或者在線服務(wù)本身異常,導(dǎo)致后續(xù) expire() 指令并沒有執(zhí)行,prefix_key 代表的鎖并沒有被加上過期時間,還是有產(chǎn)生死鎖的可能性,我們對上述代碼進(jìn)行改造,實例:

    public static boolean setLock(Jedis jedis, String prefix_key, String id, int expire_time) {
        if(jedis.set(prefix_key, id, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expire_time) == 1) {
            return true; //加鎖成功
        }
        return false; //加鎖失敗
    }

這種方案是將加鎖和設(shè)置過期時間合并為一個步驟,一次 set,是原子操作。另外還有諸多開源代碼解決這個問題,例如通過開源 lua 腳本,基于 redis 集群進(jìn)行改造。

既然有加鎖的過程,就有操作執(zhí)行結(jié)束之后釋放鎖的過程,實例:

    public static void unLock(Jedis jedis, String prefix_key, String id){
      	//如果在集群中存在prefix_key的值,并且和之前配置的id相同
        if(id.equals(jedis.get(prefix_key))){
          	//刪除prefix_key鍵值對
            jedis.del(prefix_key);
        }
    }

使用分布式鎖都是為了應(yīng)對高并發(fā)的場景,高并發(fā)場景下,上述代碼存在嚴(yán)重的并發(fā)執(zhí)行問題。

例如第一行 if 判斷完成之后,其他線程已經(jīng)提前進(jìn)入條件判斷并且執(zhí)行了 del 操作,當(dāng)前線程再執(zhí)行 del 操作就不合理。

還是出現(xiàn)了沒有保證操作原子性的問題,通用的解決方案是通過 lua 腳本的 eval() 函數(shù),首先獲取鎖對應(yīng)的 value(即我們的 id ),如果相等

才刪除鎖,lua 腳本能保證原子性,實例:

    public  boolean unlock(String prefix_key,String request){
        //lua腳本
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Long result = jedis.eval(script, Collections.singletonList(prefix_key), Collections.singletonList(id));
        if (result == 1){
            return true ;
        }
        return false;
    }

3. 小結(jié)

本章節(jié)介紹了使用 Redis 實現(xiàn)最基礎(chǔ)的分布式鎖問題,給出了滿足原子性的加鎖和解鎖操作,需要候選人能夠給面試官清晰解釋兩步操作的關(guān)注點。另外,本章節(jié)對于一些可能存在的問題沒有給出具體解決方案,例如 prefix_key 經(jīng)過超時時間后自動過期,但是業(yè)務(wù)還沒有執(zhí)行完成,以及 Redis 集群的主從同步可能發(fā)生的宕機(jī)問題。