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

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

開始吧!github

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

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

目標:1,使用「雪花算法」生成訂單ID  2,使用集中式Redis生成訂單明細ID,3.Logback+slf4j打印日誌sql

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

 

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學習系列之一(消費者生產者模式搭建)

相關文章
相關標籤/搜索