分散式全域性唯一ID

大雄45發表於2021-06-21
導讀 如何去最佳化我們公司中已經存在的分散式中的唯一ID,而提起唯一的ID,相信如果不是從事傳統行業的人,肯定都有所瞭解,分散式架構下,唯一ID生成方案,是我們在設計一個系統,尤其是資料庫使用分庫分表的時候常常會遇見的問題,尤其是當我們進行了分庫分表之後,對這個唯一ID的要求也就越來越高。那麼唯一ID方案都有哪些呢?

分散式全域性唯一ID分散式全域性唯一ID

分散式全域性唯一ID

往往一談分散式,總是會 色變,因為在很多面試的時候,都會問你,會不會分散式?你們專案的架構是怎麼做的,做的如何?你們既然使用了分散式,那麼你們的分散式事務是怎麼處理的,你們分散式全域性唯一 ID 使用的是什麼演算法來實現的?

往往談到這個的時候,很多面試的朋友就會很尷尬,我都是直接用的,我好像完全沒有注意過。當你意識到這一點的時候,往往接下來的問題,你回答的就會開始磕磕絆絆,於是面試涼了。

併發越大的系統,資料就越大,資料越大就越需要分散式,而大量的分散式資料就越需要唯一標識來識別它們,而這些唯一標識,我們就稱之為分散式全域性唯一的ID。

Redis實現全域性唯一ID

阿粉的專案說實話,還不是特別的差勁。於是阿粉就開始想著,這分散式的全域性唯一ID,為啥生成的時候都是使用 UUID ,要麼就是自增主鍵呢?

於是阿粉準備使用 Redis 來生成分散式全域性唯一ID。

Redis實現全域性唯一ID原理

因為 Redis 的所有 是單執行緒的,所以可以利用 Redis 的原子操作 INCR 和 INCRBY,來生成全域性唯一的ID。方式一:StringRedisTemplate

public class Activity { 
    private Long id; 
    private String name; 
    private BigDecimal price; 
}

上面是我們的活動的實體類,馬上就要 618 了,各位做電商的是不是開始準備搞事情了?可以學習一下用一下試試,我們活動中有 id ,活動的名稱 name ,還有對應活動設定好的價格 price 等等,欄位可能還會有很多,我們需要的暫時就列出這麼多。

public class IdGeneratorService { 
    @Autowired 
    private StringRedisTemplate stringRedisTemplate; 
 
    private static final String ID_KEY = "id:generator:activity"; 
 
    public Long incrementId() { 
        return stringRedisTemplate.opsForValue().increment(ID_KEY); 
    }
long id = idGeneratorService.incrementId(); 呼叫生成

但是看起來是不是總是感覺好像有點 low ,我們是不是就要準備來整的高大上一點,畢竟程式碼就像一個程式設計師的內褲,雖然自己看著有洞感覺沒啥,但是別人看到是不是就很不爽了,那就整個他們看起來比較高大上一點的。

方式二:

為什麼會有方案二,那是因為我們的 Redis 很多時候都不是隻有一個 Redis,都是搭建的叢集,既然是叢集,我們就要開始合理的利用上叢集。

那麼我們就要開始考慮到叢集方面的知識了,那麼我們的思路就有了。於是出現了:叢集中每個節點預生成生成ID;然後與redis的已經存在的ID做比較。如果大於,則取節點生成的ID;小於的話,取Redis中最大ID自增。

這個時候我們還需要一段 lua  來保證我們實現的ID是唯一的,這才是真正的本質,不然我們實現的ID在高階,不唯一,有個錘子用

核心 :

local function get_max_seq() 
    local key = tostring(KEYS[1]) 
    local incr_amoutt = tonumber(KEYS[2]) 
    local seq = tostring(KEYS[3]) 
    local month_in_seconds = 24 * 60 * 60 * 30 
    if (1 == redis.call(\'setnx\', key, seq)) 
    then 
        redis.call(\'expire\', key, month_in_seconds) 
        return seq 
    else 
        local prev_seq = redis.call(\'get\', key) 
        if (prev_seq < seq) 
        then 
            redis.call(\'set\', key, seq) 
            return seq 
        else 
        --[[ 
            不能直接返回redis.call(\'incr\', key),因為返回的是number浮點數型別,會出現不精確情況。 
            注意: 類似"16081817202494579"數字大小已經快超時lua和reids最大數值,請謹慎的增加seq的位數 
        --]] 
            redis.call(\'incrby\', key, incr_amoutt) 
            return redis.call(\'get\', key) 
        end 
    end 
end 
return get_max_seq()

以上的 lua 的指令碼來自於Ydoing,一個部落格的大佬,我們現在既然會使用他生成全域性唯一的ID,那麼是不是就得搞清楚為什麼會選擇 Redis 來實現分散式全域性唯一的ID。

Redis 的所有 是單執行緒的

上一段開頭,阿粉就說 Redis 的命令都是單執行緒的,相信如果你在面試官面前這麼說,面試官肯定會問你一句,為什麼 Redis 是單執行緒而不是多執行緒的呢?

Redis 基於 Reactor 模式開發了網路事件處理器,這個處理器被稱為檔案事件處理器。它的組成結構為4部分:多個套接字、IO多路複用程式、檔案事件分派器、事件處理器。因為檔案事件分派器佇列的消費是單執行緒的,所以 Redis 才叫單執行緒模型。

當你說到這個 Reactor 模式的時候,如果大家深入研究過 Netty 的模型,就會發現,這個模式在 Netty 中也是有使用的,我們這時候是不是就得需要去官網上去瞅瞅看,為什麼這麼說。

什麼是Reactor模型

Reactor模型實際上都知道,就是一個多路複用I/O模型,主要用於在高併發、高吞吐量的環境中進行I/O處理。

而這種多路複用的模型所依賴的永遠都是那麼幾個內容,事件分發器,事件處理器,還有呼叫的客戶端

分散式全域性唯一ID分散式全域性唯一ID

Reactor模型是一個同步的I/O多路複用模型,我們還是先把這個同步的I/O多路複用模型給弄清楚了再看其他的。

這個相信大家肯定不是很熟悉,而阿粉在之前也給大家說了關於Netty中的Channel,文章地址發給大家,用Socket程式設計?我還是選擇了Netty,在文章中,我們已經給大家說了關於Channel,而這種單執行緒的模型是什麼樣子的呢?

分散式全域性唯一ID分散式全域性唯一ID

圖已經給大家畫出來了,醜是醜了點,但是意思還是表達出來了。

這種模型也就是說:Redis 單執行緒指的是網路請求模組使用了一個執行緒(所以不需考慮併發安全性),即一個執行緒處理所有網路請求,其他模組仍用了多個執行緒。

而面試官還會有一種問法,為什麼使用 Redis 就會快。

這個相信大家肯定能回答出來,因為 Redis 是一種基於記憶體的儲存資料,為什麼記憶體快?

因為這種快速是針對儲存在磁碟上的資料來說的,因為記憶體中的資料,斷電之後,消失了,你下次來的時候,不還是需要從磁碟讀取出來,然後儲存,所以說在Redis速度快。扯遠了,回來繼續說 Redis的單執行緒。

我們來看看官網給我們的解釋:

Redis is single threaded. How can I exploit multiple CPU / cores? 
It's not very frequent that CPU becomes your bottleneck with Redis, as usually Redis is either memory or network bound. For instance, using pipelining Redis running on an average Linux system can deliver even 1 million requests per second, so if your application mainly uses O(N) or O(log(N)) commands, it is hardly going to use too much CPU. 
 
However, to maximize CPU usage you can start multiple instances of Redis in the same box and treat them as different servers. At some point a single box may not be enough anyway, so if you want to use multiple CPUs you can start thinking of some way to shard earlier. 
 
You can find more information about using multiple Redis instances in the Partitioning page. 
 
However with Redis 4.0 we started to make Redis more threaded. For now this is limited to deleting objects in the background, and to blocking commands implemented via Redis modules. For future releases, the plan is to make Redis more and more threaded.

其實翻譯過來大致就是說,當我們使用 Redis 的時候,CPU 成為瓶頸的情況並不常見,因為 Redis 通常是記憶體或網路受限的。

其實說白了,官網就是說我們 Redis 就是這麼的快,並且正是由於在單執行緒模式的情況下已經很快了,就沒有必要在使用多執行緒了。這整的是不是就有點噁心了。阿粉也說說自己的見解,畢竟這官網的話有點糊弄人的意思。

其實 Redis 使用單個 CPU 繫結一個記憶體,針對記憶體的處理就是單執行緒的,而我們使用多個 CPU 模擬出多個執行緒來,光在多個 CPU 之間的切換,然後再操作 Redis ,實際上就不如直接從記憶體中拿出來,畢竟耗時在這裡擺著。

原文來自:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2777685/,如需轉載,請註明出處,否則將追究法律責任。

相關文章