分布式全局唯一ID方案, 使用Redis


提起唯一的ID,相信如果不是從事傳統行業的人,肯定都有所了解,分布式架構下,唯一ID生成方案,是我們在設計一個系統,

尤其是數據庫使用分庫分表的時候常常會遇見的問題,尤其是當我們進行了分庫分表之后,對這個唯一ID的要求也就越來越高。

那么唯一ID方案都有哪些呢?

分布式全局唯一ID

往往一談分布式,總是會色變,因為在很多面試的時候,都會問你,會不會分布式?你們項目的架構是怎么做的,做的如何?

你們既然使用了分布式,那么你們的分布式事務是怎么處理的,你們分布式全局唯一 ID 使用的是什么算法來實現的?

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

並發越大的系統,數據就越大,數據越大就越需要分布式,而大量的分布式數據就越需要唯一標識來識別它們,而這些唯一標識,我們就稱之為分布式全局唯一的ID。

Redis實現全局唯一ID

阿粉的項目說實話,還不是特別的差勁。於是阿粉就開始想着,這分布式的全局唯一ID,為啥生成的時候都是使用 UUID ,要么就是自增主鍵呢?

於是阿粉准備使用 Redis 來生成分布式全局唯一ID。

1. Redis實現全局唯一ID原理

    因為 Redis 的所有命令是單線程的,所以可以利用 Redis 的原子操作 INCR 和 INCRBY,來生成全局唯一的ID。

    方式一:StringRedisTemplate

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

上面是我們的活動的實體類,我們活動中有 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()

2. Redis 的所有命令是單線程的

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

Redis 基於 Reactor 模式開發了網絡事件處理器,這個處理器被稱為文件事件處理器。

它的組成結構為4部分:多個套接字、IO多路復用程序、文件事件分派器、事件處理器。

因為文件事件分派器隊列的消費是單線程的,所以 Redis 才叫單線程模型。

當你說到這個 Reactor 模式的時候,如果大家深入研究過 Netty 的模型,就會發現,

這個模式在 Netty 中也是有使用的,我們這時候是不是就得需要去官網上去瞅瞅看,為什么這么說。

什么是Reactor模型

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

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

image

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

這個相信大家肯定不是很熟悉,而阿粉在之前也給大家說了關於Netty中的Channel,文章地址發給大家,

用Socket編程?我還是選擇了Netty,在文章中,我們已經給大家說了關於Channel,而這種單線程的模型是什么樣子的呢?

image

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

這種模型也就是說: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 ,實際上就不如直接從內存中拿出來,畢竟耗時在這里擺着。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM