分布式全局唯一ID方案這么多?
本文轉(zhuǎn)載自微信公眾號「Java極客技術(shù)」,作者鴨血粉絲。轉(zhuǎn)載本文請聯(lián)系Java極客技術(shù)公眾號。
前段時間阿粉想著如何去優(yōu)化我們公司中已經(jīng)存在的分布式中的唯一ID,而提起唯一的ID,相信如果不是從事傳統(tǒng)行業(yè)的人,肯定都有所了解,分布式架構(gòu)下,唯一ID生成方案,是我們在設(shè)計一個系統(tǒng),尤其是數(shù)據(jù)庫使用分庫分表的時候常常會遇見的問題,尤其是當我們進行了分庫分表之后,對這個唯一ID的要求也就越來越高。那么唯一ID方案都有哪些呢?
分布式全局唯一ID
往往一談分布式,總是會 色變,因為在很多面試的時候,都會問你,會不會分布式?你們項目的架構(gòu)是怎么做的,做的如何?你們既然使用了分布式,那么你們的分布式事務是怎么處理的,你們分布式全局唯一 ID 使用的是什么算法來實現(xiàn)的?
往往談到這個的時候,很多面試的朋友就會很尷尬,我都是直接用的,我好像完全沒有注意過。當你意識到這一點的時候,往往接下來的問題,你回答的就會開始磕磕絆絆,于是面試涼了。
并發(fā)越大的系統(tǒng),數(shù)據(jù)就越大,數(shù)據(jù)越大就越需要分布式,而大量的分布式數(shù)據(jù)就越需要唯一標識來識別它們,而這些唯一標識,我們就稱之為分布式全局唯一的ID。
Redis實現(xiàn)全局唯一ID
阿粉的項目說實話,還不是特別的差勁。于是阿粉就開始想著,這分布式的全局唯一ID,為啥生成的時候都是使用 UUID ,要么就是自增主鍵呢?
于是阿粉準備使用 Redis 來生成分布式全局唯一ID。
Redis實現(xiàn)全局唯一ID原理
因為 Redis 的所有命令是單線程的,所以可以利用 Redis 的原子操作 INCR 和 INCRBY,來生成全局唯一的ID。方式一:StringRedisTemplate
- public class Activity {
- private Long id;
- private String name;
- private BigDecimal price;
- }
上面是我們的活動的實體類,馬上就要 618 了,各位做電商的是不是開始準備搞事情了?可以學習一下用一下試試,我們活動中有 id ,活動的名稱 name ,還有對應活動設(shè)置好的價格 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(); 調(diào)用生成
但是看起來是不是總是感覺好像有點 low ,我們是不是就要準備來整的高大上一點,畢竟代碼就像一個程序員的內(nèi)褲,雖然自己看著有洞感覺沒啥,但是別人看到是不是就很不爽了,那就整個他們看起來比較高大上一點的。
方式二:
為什么會有方案二,那是因為我們的 Redis 很多時候都不是只有一個 Redis,都是搭建的集群,既然是集群,我們就要開始合理的利用上集群。
那么我們就要開始考慮到集群方面的知識了,那么我們的思路就有了。于是出現(xiàn)了:集群中每個節(jié)點預生成生成ID;然后與redis的已經(jīng)存在的ID做比較。如果大于,則取節(jié)點生成的ID;小于的話,取Redis中最大ID自增。
這個時候我們還需要一段 lua 腳本來保證我們實現(xiàn)的ID是唯一的,這才是真正的本質(zhì),不然我們實現(xiàn)的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浮點數(shù)類型,會出現(xiàn)不精確情況。
- 注意: 類似"16081817202494579"數(shù)字大小已經(jīng)快超時lua和reids最大數(shù)值,請謹慎的增加seq的位數(shù)
- --]]
- redis.call(\'incrby\', key, incr_amoutt)
- return redis.call(\'get\', key)
- end
- end
- end
- return get_max_seq()
以上的 lua 的腳本來自于Ydoing,一個博客的大佬,我們現(xiàn)在既然會使用他生成全局唯一的ID,那么是不是就得搞清楚為什么會選擇 Redis 來實現(xiàn)分布式全局唯一的ID。
Redis 的所有命令是單線程的
上一段開頭,阿粉就說 Redis 的命令都是單線程的,相信如果你在面試官面前這么說,面試官肯定會問你一句,為什么 Redis 是單線程而不是多線程的呢?
Redis 基于 Reactor 模式開發(fā)了網(wǎng)絡事件處理器,這個處理器被稱為文件事件處理器。它的組成結(jié)構(gòu)為4部分:多個套接字、IO多路復用程序、文件事件分派器、事件處理器。因為文件事件分派器隊列的消費是單線程的,所以 Redis 才叫單線程模型。
當你說到這個 Reactor 模式的時候,如果大家深入研究過 Netty 的模型,就會發(fā)現(xiàn),這個模式在 Netty 中也是有使用的,我們這時候是不是就得需要去官網(wǎng)上去瞅瞅看,為什么這么說。
什么是Reactor模型
Reactor模型實際上都知道,就是一個多路復用I/O模型,主要用于在高并發(fā)、高吞吐量的環(huán)境中進行I/O處理。
而這種多路復用的模型所依賴的永遠都是那么幾個內(nèi)容,事件分發(fā)器,事件處理器,還有調(diào)用的客戶端,
Reactor模型是一個同步的I/O多路復用模型,我們還是先把這個同步的I/O多路復用模型給弄清楚了再看其他的。
這個相信大家肯定不是很熟悉,而阿粉在之前也給大家說了關(guān)于Netty中的Channel,文章地址發(fā)給大家,用Socket編程?我還是選擇了Netty,在文章中,我們已經(jīng)給大家說了關(guān)于Channel,而這種單線程的模型是什么樣子的呢?
圖已經(jīng)給大家畫出來了,丑是丑了點,但是意思還是表達出來了。
這種模型也就是說:Redis 單線程指的是網(wǎng)絡請求模塊使用了一個線程(所以不需考慮并發(fā)安全性),即一個線程處理所有網(wǎng)絡請求,其他模塊仍用了多個線程。
而面試官還會有一種問法,為什么使用 Redis 就會快。
這個相信大家肯定能回答出來,因為 Redis 是一種基于內(nèi)存的存儲數(shù)據(jù),為什么內(nèi)存快?
因為這種快速是針對存儲在磁盤上的數(shù)據(jù)來說的,因為內(nèi)存中的數(shù)據(jù),斷電之后,消失了,你下次來的時候,不還是需要從磁盤讀取出來,然后保存,所以說在Redis速度快。扯遠了,回來繼續(xù)說 Redis的單線程。
我們來看看官網(wǎng)給我們的解釋:
- 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 通常是內(nèi)存或網(wǎng)絡受限的。
其實說白了,官網(wǎng)就是說我們 Redis 就是這么的快,并且正是由于在單線程模式的情況下已經(jīng)很快了,就沒有必要在使用多線程了。這整的是不是就有點惡心了。阿粉也說說自己的見解,畢竟這官網(wǎng)的話有點糊弄人的意思。
其實 Redis 使用單個 CPU 綁定一個內(nèi)存,針對內(nèi)存的處理就是單線程的,而我們使用多個 CPU 模擬出多個線程來,光在多個 CPU 之間的切換,然后再操作 Redis ,實際上就不如直接從內(nèi)存中拿出來,畢竟耗時在這里擺著。
你認為的 Redis 為什么是單線程的?