既然選擇,就注定風雨兼程!
開始吧!
准備: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學習系列之四(MybaitsPlus+Druid使用)
我的微信公眾號,只寫原創文章!