雪花算法(SnowFlake)
簡介
現(xiàn)在的服務基本是分布式、微服務形式的,而且大數(shù)據(jù)量也導致分庫分表的產(chǎn)生,對于水平分表就需要保證表中 id 的全局唯一性。
對于 MySQL 而言,一個表中的主鍵 id 一般使用自增的方式,但是如果進行水平分表之后,多個表中會生成重復的 id 值。那么如何保證水平分表后的多張表中的 id 是全局唯一性的呢?
如果還是借助數(shù)據(jù)庫主鍵自增的形式,那么可以讓不同表初始化一個不同的初始值,然后按指定的步長進行自增。例如有3張拆分表,初始主鍵值為1,2,3,自增步長為3。
當然也有人使用 UUID 來作為主鍵,但是 UUID 生成的是一個無序的字符串,對于 MySQL 推薦使用增長的數(shù)值類型值作為主鍵來說不適合。
也可以使用 Redis 的自增原子性來生成唯一 id,但是這種方式業(yè)內(nèi)比較少用。
當然還有其他解決方案,不同互聯(lián)網(wǎng)公司也有自己內(nèi)部的實現(xiàn)方案。雪花算法是其中一個用于解決分布式 id 的高效方案,也是許多互聯(lián)網(wǎng)公司在推薦使用的。
SnowFlake 雪花算法
SnowFlake 中文意思為雪花,故稱為雪花算法。最早是 Twitter 公司在其內(nèi)部用于分布式環(huán)境下生成唯一 ID。在2014年開源 scala 語言版本。
雪花算法的原理就是生成一個的 64 位比特位的 long 類型的唯一 id。
- 最高 1 位固定值 0,因為生成的 id 是正整數(shù),如果是 1 就是負數(shù)了。
- 接下來 41 位存儲毫秒級時間戳,2^41/(1000*60*60*24*365)=69,大概可以使用 69 年。
- 再接下 10 位存儲機器碼,包括 5 位 datacenterId 和 5 位 workerId。最多可以部署 2^10=1024 臺機器。
- 最后 12 位存儲序列號。同一毫秒時間戳時,通過這個遞增的序列號來區(qū)分。即對于同一臺機器而言,同一毫秒時間戳下,可以生成 2^12=4096 個不重復 id。
可以將雪花算法作為一個單獨的服務進行部署,然后需要全局唯一 id 的系統(tǒng),請求雪花算法服務獲取 id 即可。
對于每一個雪花算法服務,需要先指定 10 位的機器碼,這個根據(jù)自身業(yè)務進行設定即可。例如機房號+機器號,機器號+服務號,或者是其他可區(qū)別標識的 10 位比特位的整數(shù)值都行。
算法實現(xiàn)
package util;import java.util.Date;/*** @ClassName: SnowFlakeUtil* @Author: jiaoxian* @Date: 2022/4/24 16:34* @Description:*/ public class SnowFlakeUtil {private static SnowFlakeUtil snowFlakeUtil;static {snowFlakeUtil = new SnowFlakeUtil();}// 初始時間戳(紀年),可用雪花算法服務上線時間戳的值// 1650789964886:2022-04-24 16:45:59private static final long INIT_EPOCH = 1650789964886L;// 時間位取&private static final long TIME_BIT = 0b1111111111111111111111111111111111111111110000000000000000000000L;// 記錄最后使用的毫秒時間戳,主要用于判斷是否同一毫秒,以及用于服務器時鐘回撥判斷private long lastTimeMillis = -1L;// dataCenterId占用的位數(shù)private static final long DATA_CENTER_ID_BITS = 5L;// dataCenterId占用5個比特位,最大值31// 0000000000000000000000000000000000000000000000000000000000011111private static final long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_ID_BITS);// dataCenterIdprivate long dataCenterId;// workId占用的位數(shù)private static final long WORKER_ID_BITS = 5L;// workId占用5個比特位,最大值31// 0000000000000000000000000000000000000000000000000000000000011111private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);// workIdprivate long workerId;// 最后12位,代表每毫秒內(nèi)可產(chǎn)生最大序列號,即 2^12 - 1 = 4095private static final long SEQUENCE_BITS = 12L;// 掩碼(最低12位為1,高位都為0),主要用于與自增后的序列號進行位與,如果值為0,則代表自增后的序列號超過了4095// 0000000000000000000000000000000000000000000000000000111111111111private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);// 同一毫秒內(nèi)的最新序號,最大值可為 2^12 - 1 = 4095private long sequence;// workId位需要左移的位數(shù) 12private static final long WORK_ID_SHIFT = SEQUENCE_BITS;// dataCenterId位需要左移的位數(shù) 12+5private static final long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;// 時間戳需要左移的位數(shù) 12+5+5private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS;/*** 無參構(gòu)造*/public SnowFlakeUtil() {this(1, 1);}/*** 有參構(gòu)造* @param dataCenterId* @param workerId*/public SnowFlakeUtil(long dataCenterId, long workerId) {// 檢查dataCenterId的合法值if (dataCenterId < 0 || dataCenterId > MAX_DATA_CENTER_ID) {throw new IllegalArgumentException(String.format("dataCenterId 值必須大于 0 并且小于 %d", MAX_DATA_CENTER_ID));}// 檢查workId的合法值if (workerId < 0 || workerId > MAX_WORKER_ID) {throw new IllegalArgumentException(String.format("workId 值必須大于 0 并且小于 %d", MAX_WORKER_ID));}this.workerId = workerId;this.dataCenterId = dataCenterId;}/*** 獲取唯一ID* @return*/public static Long getSnowFlakeId() {return snowFlakeUtil.nextId();}/*** 通過雪花算法生成下一個id,注意這里使用synchronized同步* @return 唯一id*/public synchronized long nextId() {long currentTimeMillis = System.currentTimeMillis();System.out.println(currentTimeMillis);// 當前時間小于上一次生成id使用的時間,可能出現(xiàn)服務器時鐘回撥問題if (currentTimeMillis < lastTimeMillis) {throw new RuntimeException(String.format("可能出現(xiàn)服務器時鐘回撥問題,請檢查服務器時間。當前服務器時間戳:%d,上一次使用時間戳:%d", currentTimeMillis,lastTimeMillis));}if (currentTimeMillis == lastTimeMillis) {// 還是在同一毫秒內(nèi),則將序列號遞增1,序列號最大值為4095// 序列號的最大值是4095,使用掩碼(最低12位為1,高位都為0)進行位與運行后如果值為0,則自增后的序列號超過了4095// 那么就使用新的時間戳sequence = (sequence + 1) & SEQUENCE_MASK;if (sequence == 0) {currentTimeMillis = getNextMillis(lastTimeMillis);}} else { // 不在同一毫秒內(nèi),則序列號重新從0開始,序列號最大值為4095sequence = 0;}// 記錄最后一次使用的毫秒時間戳lastTimeMillis = currentTimeMillis;// 核心算法,將不同部分的數(shù)值移動到指定的位置,然后進行或運行// <<:左移運算符, 1 << 2 即將二進制的 1 擴大 2^2 倍// |:位或運算符, 是把某兩個數(shù)中, 只要其中一個的某一位為1, 則結(jié)果的該位就為1// 優(yōu)先級:<< > |return// 時間戳部分((currentTimeMillis - INIT_EPOCH) << TIMESTAMP_SHIFT)// 數(shù)據(jù)中心部分| (dataCenterId << DATA_CENTER_ID_SHIFT)// 機器表示部分| (workerId << WORK_ID_SHIFT)// 序列號部分| sequence;}/*** 獲取指定時間戳的接下來的時間戳,也可以說是下一毫秒* @param lastTimeMillis 指定毫秒時間戳* @return 時間戳*/private long getNextMillis(long lastTimeMillis) {long currentTimeMillis = System.currentTimeMillis();while (currentTimeMillis <= lastTimeMillis) {currentTimeMillis = System.currentTimeMillis();}return currentTimeMillis;}/*** 獲取隨機字符串,length=13* @return*/public static String getRandomStr() {return Long.toString(getSnowFlakeId(), Character.MAX_RADIX);}/*** 從ID中獲取時間* @param id 由此類生成的ID* @return*/public static Date getTimeBySnowFlakeId(long id) {return new Date(((TIME_BIT & id) >> 22) + INIT_EPOCH);}public static void main(String[] args) {SnowFlakeUtil snowFlakeUtil = new SnowFlakeUtil();long id = snowFlakeUtil.nextId();System.out.println(id);Date date = SnowFlakeUtil.getTimeBySnowFlakeId(id);System.out.println(date);long time = date.getTime();System.out.println(time);System.out.println(getRandomStr());}}算法優(yōu)缺點
雪花算法有以下幾個優(yōu)點:
- 高并發(fā)分布式環(huán)境下生成不重復 id,每秒可生成百萬個不重復 id。
- 基于時間戳,以及同一時間戳下序列號自增,基本保證 id 有序遞增。
- 不依賴第三方庫或者中間件。
- 算法簡單,在內(nèi)存中進行,效率高。
雪花算法有如下缺點:
- 依賴服務器時間,服務器時鐘回撥時可能會生成重復 id。算法中可通過記錄最后一個生成 id 時的時間戳來解決,每次生成 id 之前比較當前服務器時鐘是否被回撥,避免生成重復 id。
注意事項
其實雪花算法每一部分占用的比特位數(shù)量并不是固定死的。例如你的業(yè)務可能達不到 69 年之久,那么可用減少時間戳占用的位數(shù),雪花算法服務需要部署的節(jié)點超過1024 臺,那么可將減少的位數(shù)補充給機器碼用。
注意,雪花算法中 41 位比特位不是直接用來存儲當前服務器毫秒時間戳的,而是需要當前服務器時間戳減去某一個初始時間戳值,一般可以使用服務上線時間作為初始時間戳值。
對于機器碼,可根據(jù)自身情況做調(diào)整,例如機房號,服務器號,業(yè)務號,機器 IP 等都是可使用的。對于部署的不同雪花算法服務中,最后計算出來的機器碼能區(qū)分開來即可。
本文參考自:SnowFlake 雪花算法詳解與實現(xiàn) - 掘
總結(jié)
以上是生活随笔為你收集整理的雪花算法(SnowFlake)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql的三表查询语句_求三表联合查询
- 下一篇: Big Sur MacOS高清动态壁纸