java不規則算法_分布式id生成算法 snowflake 詳解

背景

在復雜分布式系統中,往往需要對大量的數據和消息進行唯一標識。如在支付流水號、訂單號等,隨者業務數據日漸增長,對數據分庫分表后需要有一個唯一ID來標識一條數據或消息,數據庫的自增ID顯然不能滿足需求,此時一個能夠生成全局唯一ID的系統是非常必要的。

生成的唯一id需要具備哪些條件

全局唯一性:不能出現重復的ID號,既然是唯一標識,這是最基本的要求。

趨勢遞增:在MySQL InnoDB引擎中使用的是聚集索引,由于多數RDBMS使用B-tree的數據結構來存儲索引數據,在主鍵的選擇上面我們應該盡量使用有序的主鍵保證寫入性能。

單調遞增:保證下一個ID一定大于上一個ID,例如事務版本號、IM增量消息、排序等特殊需求。

信息安全:如果ID是連續的,惡意用戶的扒取工作就非常容易做了,直接按照順序下載指定URL即可;如果是訂單號就更危險了,競對可以直接知道我們一天的單量。所以在一些應用場景下,會需要ID無規則、不規則。

UUID

關于分布式id,很多人會想到使用UUID,UUID在唯一性上確實可以達到這個目的,但它也存在很大的缺陷

優點:

性能非常高:本地生成,沒有網絡消耗。

缺點:

不易于存儲:UUID太長,16字節128位,通常以36長度的字符串表示,很多場景不適用。

信息不安全:基于MAC地址生成UUID的算法可能會造成MAC地址泄露,這個漏洞曾被用于尋找梅麗莎病毒的制作者位置。

ID作為主鍵時在特定的環境會存在一些問題,比如做DB主鍵的場景下,UUID就非常不適用。(MySQL官方有明確的建議主鍵要盡量越短越好,36個字符長度的UUID不符合要求。對MySQL索引不利:如果作為數據庫主鍵,在InnoDB引擎下,UUID的無序性可能會引起數據位置頻繁變動,嚴重影響性能。)

snowflake

"世界上沒有一片雪花是相同的",這大概是snowflake名字的由來吧。

SnowFlake算法是Twitter設計的一個可以在分布式系統中生成唯一的ID的算法,它可以滿足Twitter每秒上萬條消息ID分配的請求,這些消息ID是唯一的且有大致的遞增順序。

snowflake算法的原理

SnowFlake算法產生的ID是一個64位的整型,結構如下(每一部分用“-”符號分隔):

330bb01c2820?from=timeline

snowflake.png

需要注意的是64位是二進制,2的64次方 = 18446744073709551616,這就是能表示的id的范圍,范圍可以通過擴展序列號或則工作機器id來增加id的上限。

1位標識部分

在java中由于long的最高位是符號位,正數是0,負數是1,一般生成的ID為正數,所以為0;

41位時間戳部分

這個是毫秒級的時間,一般實現上不會存儲當前的時間戳,而是時間戳的差值(當前時間-固定的開始時間),這樣可以使產生的ID從更小值開始;41位的時間戳可以使用69年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年;

10位節點部分

Twitter實現中使用前5位作為數據中心標識,后5位作為機器標識,可以部署1024個節點,在Spring Cloud中可以為每一個實例生成唯一的機器識別碼,這樣就能保證每個實例中生成的id都不一樣。

12位序列號部分

支持同一毫秒內同一個節點可以生成4096(2的12次方)個ID,這個同樣可以擴展,但其實每毫秒生成4096個id已經能滿足大部分場景了。

算法的java代碼

/**

* twitter的snowflake算法 -- java實現

*

* @author beyond

* @date 2016/11/26

*/

public class SnowFlake {

/**

* 起始的時間戳(最后的時間 = 當前時間戳 - 起始的時間戳)

*/

private final static long START_STMP = 1480166465631L;

/**

* 每一部分占用的位數

*/

private final static long SEQUENCE_BIT = 12; //序列號占用的位數

private final static long MACHINE_BIT = 5; //機器標識占用的位數

private final static long DATACENTER_BIT = 5;//數據中心占用的位數

/**

* 每一部分的最大值

*/

private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);

private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);

private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);

/**

* 每一部分向左的位移

*/

private final static long MACHINE_LEFT = SEQUENCE_BIT;

private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;

private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;

private long datacenterId; //數據中心

private long machineId; //機器標識

private long sequence = 0L; //序列號

private long lastStmp = -1L;//上一次時間戳

public SnowFlake(long datacenterId, long machineId) {

// 校驗datacenterId長度超過范圍就拋異常

if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {

throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");

}

// 校驗machineId長度超過范圍就拋異常

if (machineId > MAX_MACHINE_NUM || machineId < 0) {

throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");

}

this.datacenterId = datacenterId;

this.machineId = machineId;

}

/**

* 產生下一個ID

*

* @return

*/

public synchronized long nextId() {

long currStmp = getNewstmp();

if (currStmp < lastStmp) {

throw new RuntimeException("Clock moved backwards. Refusing to generate id");

}

if (currStmp == lastStmp) {

//相同毫秒內,序列號自增

sequence = (sequence + 1) & MAX_SEQUENCE;

//同一毫秒的序列數已經達到最大

if (sequence == 0L) {

currStmp = getNextMill();

}

} else {

//不同毫秒內,序列號置為0

sequence = 0L;

}

lastStmp = currStmp;

//使用二進制的"|"運算符將4部分的值整合成我們需要的id

return (currStmp - START_STMP) << TIMESTMP_LEFT //時間戳部分

| datacenterId << DATACENTER_LEFT //數據中心部分

| machineId << MACHINE_LEFT //機器標識部分

| sequence; //序列號部分

}

//如果當前毫秒值下的序列號用完,就循環獲取下個毫秒值,如果沒有獲取到下個毫秒值就

//一直循環下去

private long getNextMill() {

long mill = getNewstmp();

while (mill <= lastStmp) {

mill = getNewstmp();

}

return mill;

}

private long getNewstmp() {

return System.currentTimeMillis();

}

public static void main(String[] args) {

SnowFlake snowFlake = new SnowFlake(2, 3);

for (int i = 0; i < (1 << 12); i++) {

System.out.println(snowFlake.nextId());

}

}

}

總結

10位節點部分在代碼中分成了兩個5位節點,看具體需求,也可以用一個10位節點代替。snowflake更多的是提供一種算法思想,具體的id生成邏輯可以在此基礎上進一步的優化。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/459070.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/459070.shtml
英文地址,請注明出處:http://en.pswp.cn/news/459070.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Android中下載、安裝和卸載(原)

應用場景&#xff1a;在檢查版本更新的時候經常需要從服務器端下載然后安裝到手機中 使用工具&#xff1a; XUtils&#xff0c;這個開源的框架真的是需要花大把時間去閱讀和理解的&#xff0c;十分有用的&#xff0c;on the way &#xff01; fighting&#xff01; 下載&#x…

Android加載圖片OOM錯誤解決方式

前幾天做項目的時候&#xff0c;甲方要求是PAD &#xff08;SAMSUNG P600 10.1寸 2560*1600&#xff09;的PAD上顯示高分辨率的大圖片。 SQLITE採用BOLD方式存儲圖片&#xff0c;這個存取過程就不說了哈&#xff0c;網上一大堆。 可是在加載/讀取/顯示圖片的時候會報OOM錯誤&am…

C# 對Ini文件操作(C# ini文件操作類)

*************************************************** 更多精彩&#xff0c;歡迎進入&#xff1a;http://shop115376623.taobao.com *************************************************** /* C# 對Ini文件操作&#xff08;C# ini文件操作類&#xff09; [IniFiles.cs] 蝶曉…

python對文件進行讀寫操作

2019獨角獸企業重金招聘Python工程師標準>>> python進行文件讀寫的函數是open或file file_handler open(filename,,mode&#xff09; Table mode 模式描述r以讀方式打開文件&#xff0c;可讀取文件信息。w以寫方式打開文件&#xff0c;可向文件寫入信息。如文件存在…

android:contentDescription

android:contentDescription這個屬性相信大家并不陌生&#xff0c;在ImageButton的使用過程中如果不添加這個屬性會有警告信息。 那么android:contentDescription究竟是干什么的呢&#xff1f;今天查了下資料才知道這個屬性的真正作用。 該屬性為視力障礙的用戶提供方便&#x…

c#中bin,obj,properties文件夾的作用

*************************************************** 更多精彩&#xff0c;歡迎進入&#xff1a;http://shop115376623.taobao.com *************************************************** Bin目錄用來存放編譯的結果&#xff0c;bin是二進制binrary的英文縮寫&#xff0c;因為…

getAttribute實例例java_Java ExifInterface.getAttribute方法代碼示例

import android.media.ExifInterface; //導入方法依賴的package包/類public static void copyExif(ExifInterface originalExif, int width, int height, String imageOutputPath) {String[] attributes new String[]{ExifInterface.TAG_APERTURE,ExifInterface.TAG_DATETIME,…

檢測SDWebImage有沒有緩存圖片 IOS 獲取網絡圖片大小

判斷圖片是否緩存NSURL *url [NSURL URLWithString:[model.content objectForKey:"image"]];//請求網絡地址數據的同步方法//因為這個方法在子線程(全局隊列)中執行,所以不需要考慮死線程的問題SDWebImageManager *manager [SDWebImageManager sharedManager];[man…

mac 下 使用 java運行 class 文件 總是提示 “錯誤: 找不到或無法加載主類”的解決方法...

發現問題 切換到mac平臺后&#xff0c;突然想寫點程序運行在mac下&#xff0c;想到mac自帶java&#xff0c;會方便好多。不過在這過程中遇到了麻煩&#xff1a; 總是提示 “錯誤: 找不到或無法加載主類” 工程結構 查了好久&#xff0c;終于找到原型所在&#xff0c;發現網上很…

[轉]VisualStudio如何組織解決方案的目錄結構

*************************************************** 更多精彩&#xff0c;歡迎進入&#xff1a;http://shop115376623.taobao.com *************************************************** 解決方案與項目&#xff1a; 從VC6之后VC系列就使用解決方案&#xff08;Solution&…

java幾種刪除_幾種刪除Linux目錄的方法

在Linux中有很多方法可以刪除目錄&#xff0c;在圖形化界面可以利用文件管理器&#xff0c;或者通過終端刪除。本文將介紹在文本界面使用命令刪除目錄。使用rmdir刪除目錄Rmdir命令間成“remove directory”&#xff0c;用于刪除空目錄的命令。例如&#xff0c;刪除一個名為“M…

php公鑰模數,php – 如何從公共指數和RSA模數生成DER / PEM證書?

眾所周知,公鑰由公共指數和模數組成.我的問題是&#xff1a;如何從公共指數和RSA模數生成DER / PEM證書&#xff1f;非常感謝你提前.解決方法:使用公共指數和模數,你可能希望做的最好的事情是得到這樣的東西&#xff1a;-----BEGIN PUBLIC KEY-----MIGGAoGAfHlcdrcuOK6C02rbGR3…

C# DataTable的詳細用法

*************************************************** 更多精彩&#xff0c;歡迎進入&#xff1a;http://shop115376623.taobao.com *************************************************** DataTable 是一個臨時保存數據的網格虛擬表(表示內存中數據的一個表。)。DataTable是A…

【SpringMVC】SpringMVC系列6之@CookieValue 映射請求Cookie 值

6、CookieValue 映射請求Cookie 值 6.1、示例 CookieValue 可讓處理方法入參綁定某個 Cookie 值&#xff0c;示例如下&#xff1a;

杭電OJ-2104_hide handkerchief超簡潔代碼

#include<iostream> using namespace std; int n, m;; int zz(int a, int b) {return b0 ? a: zz(b, a%b); } int main() {while (cin >> n >> m&&n ! -1 && m ! -1)cout << (nb(n, m) 1 ? "YES" : "POOR Haha"…

php 年月日 中文,轉換中文日期的PHP程序

轉換中文日期的PHP程序本程序將中文日期輸出為2001-12-23&#xff0c;并很好解決了“十”的問題&#xff0c;如“十一”和“二十一”中“十”的處理&#xff01;稍加修改可改為函數。跟隨小編去看看吧&#xff01;希望對大家有所幫助&#xff01;$str"二零○一年十二月二十…

c# Invoke和BeginInvoke

*************************************************** 更多精彩&#xff0c;歡迎進入&#xff1a;http://shop115376623.taobao.com *************************************************** 轉自&#xff1a;http://blog.3snews.net/html/30/34530-27563.html在多線程編程中&am…

Oracle手邊常用70則腳本知識匯總

Oracle手邊常用70則腳本知識匯總 作者&#xff1a;白寧超 時間&#xff1a;2016年3月4日13:58:36 摘要: 日常使用oracle數據庫過程中&#xff0c;常用腳本命令莫不是用戶和密碼、表空間、多表聯合、執行語句等常規操作。另外表的導入導出也很常用&#xff0c;這些腳步命令之前都…

php常見的面試題目

一. 基本知識點1.1 HTTP協議中幾個狀態碼的含義:503 500 401 403 404 200 301 302。。。200 : 請求成功&#xff0c;請求的數據隨之返回。301 : 永久性重定向。302 : 暫時行重定向。401 : 當前請求需要用戶驗證。403 : 服務器拒絕執行請求&#xff0c;即沒有權限。404 : 請求失…

php表示私有變量的是,PHP 訪問私有和受保護的成員變量

示例反射通常用作軟件測試的一部分&#xff0c;例如在運行時創建/實例化模擬對象。這對于在任何給定時間點檢查對象的狀態也非常有用。這是在單元測試中使用Reflection來驗證受保護的類成員是否包含期望值的示例。下面是一個非常基礎的汽車課。它具有受保護的成員變量&#xff…