1. 程式人生 > >Dubbo學習系列之七(分散式訂單ID方案)

Dubbo學習系列之七(分散式訂單ID方案)

既然選擇,就註定風雨兼程!

開始吧!

準備:Idea201902/JDK11/ZK3.5.5/Gradle5.4.1/RabbitMQ3.7.13/Mysql8.0.11/Lombok0.26/Erlang21.2/postman7.5.0

難度:新手--戰士--老兵--大師

目標:1,使用“雪花演算法”生成訂單ID  2,使用集中式Redis生成訂單明細ID,3.Logback+slf4j列印日誌

步驟:1.專案架構及程式碼基礎設施,見往期文章。2.整體思路:其實ID的生成有很多種方案,如UUID,DB自增id,那分散式環境下有何方案呢?UUID也可以,但無規律;DB生成法,間隔初始值加步長,水平擴充套件差;雪花演算法,伺服器間時間同步是個問題,Redis集中式生成,容易單點瓶頸。總結而言,各有千秋,一是獨立式,每個使用者自己生成,二是中心式,ID集中產生再分發。今天我們來看看典型代表: 雪花演算法和Redis集中式。3.先說"雪花演算法",理解也簡單,“世界上沒有一片雪花是相同的”顧名思義,雪花演算法即是多維度組合,生成一個ID值,且這個值“趨勢遞增”,其核心構成如下圖:

 

64bit,第一位0固定,二進位制符號位,41bit時間戳,注意是當前與初始值相減的值,10bit工作機器id,12bit序列號,毫秒內的計數,其中工作機器id可預先人工指定,最後64位剛好轉成Long型別即可。

4.演算法生成類,放公共模組,com.biao.mall.common.util.SnowFlake

先來個整體的程式碼,包含了幾個內部方法:

 

 

再來分析一下,

 

 

 

重點看下最大值,使用對-1左移位演算法,(二進位制右邊補0),再按位取反,舉例:最多2位,

~(-1L<<2)-->11111111-->11111100-->00000011-->3,實際上這裡,就是(2的n次方-1),因移位演算法是效率最高的,因此採用。

private final static long MAX_DATAC_ENTER_ID = ~(-1L << DATACENTER_BIT);
private final static long MAX_WORK_ID= ~(-1L << MACHINE_BIT);
private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);

 

建構函式中判斷下資料中心id和主機id是否合法,

 

5.演算法核心部分,nextId()方法,注意這是個同步方法,執行緒安全的:

 

A.先取得當前主機的毫秒,判斷下是否小於上個時間戳,這個很重要!因為如果時間回撥,會導致可能的ID重複,因此當遇到時間回撥的情況,可以考慮使用備份的主機ID,這裡是直接拋異常了。

B.如果取ID併發非常高,導致時間間隔很小,在同一毫秒內,則序列號自增並和最大序列號"按位與",這樣的目的就是如果序列號達到最大,+1再按位與,就會等於0,(假設2位最大值加1:011+001=100,再100 & 011=000,然後只有2位就成0了)

C. 如果同一毫秒內序列號達到最大歸為0,就使用getNextMill(),無限迴圈直到獲得下個毫秒數

D.最後生成id就是時間戳差值,移位再按位或其他部分的數值,

6.再看Redis方案,這個主要是利用Redis速度快的特點,並可以使用Redis叢集,就可確保速度和可用性,集中式的好處即統一生產,易控制,但分發受網路波動影響。具體位置在:com.biao.mall.common.util.RedisUitl7.分析重點:類裡寫了兩個生成方式,第一個doGetId()是個簡單生成法,直接使用對一個key做自增操作,每次遞增一個,但壞處明顯,容易暴露一天的訂單量!數鴨子的遊戲。

 

這裡使用的是redisAPI的redisTemplate,其有操作如下,即對redis的五種資料型別都有對應的封裝方法。

 

還有一個思路是先快取,再取值,過程是:先生成一定量的順序數,打亂順序,再分成若干段,迴圈存入redis,取值時,根據全域性計數器進行檢索取值,

 

以下為分段儲存,每個段都有個keyId。這裡為啥,做分段?主要是存取效率問題,顯然從一小段中檢索資料要快於直接從一個大的List中檢索快。

 

取亂序值方法getShuffleId:因為這個是個開箱即用的靜態方法,必須保證首次使用時,初始化計數器,並執行分段儲存操作,而應用重啟,不重複這個操作,故可以先判斷計數器是否已存在。取值是先找到分段的list的key,再找到偏移量index:

 

7.測試一下,我寫了個controller:com.biao.mall.business.controller.MsgProducerController

 

啟動Redis-->zk-->business:

可以看到,前面兩圖就是順序的,而後面的兩圖則是亂序的。

 

8.專案地址:day10

https://github.com/xiexiaobiao/dubbo-project.git

 

後記:

1.關於服務間通訊:內部服務間通訊宜使用私有通訊,如RPC,Netty等,效率高,對外使用Restful,相容性好,因此,專案程式碼編號day09成為了一個臨時版,其實現了MQ獨立部署,Restful對內通訊的模式,但技術上不可取。

2.redis生產中不會使用單點,常用的是叢集式,如三主三從叢集,但從使用者角度,有JedisCluster,如操作redisTemplate類似,邏輯上是操作一個,不必考慮後臺實現與具體的物理分庫儲存。

3.看程式碼,多思考,才有收穫,此文中有關於二進位制的操作,試問(1L<<-1)可否?為啥使用二進位制操作呢?

4.分散式Id方案,有很多,各有特點,生搬硬套行不通,必須根據業務特點取捨和改造。

5.之前在做MQ解析時遇到,一定小心!注意jsonString傳輸:以下String將發生JSON.parse()錯誤。"{\"orderId\":\"362951082266333184\",\"URL\":\"http://localhost:8085/delivery/one\"}",去掉json中的轉義字元,可使用StringEscapeUtils.unescapeJava(jsonString)

 

往期文章導航:

Dubbo學習系列之六(微服務架構實戰)

Dubbo學習系列之五(MQ實現微服務間通訊)

Dubbo學習系列之四(MybaitsPlus+Druid使用)

Dubbo學習系列之三(Gradle打包可執行jar)

Dubbo學習系列之二(Nacos做配置中心)

Dubbo學習系列之一(消費者生產者模式搭建)