亚洲一区精品自拍_2021年国内精品久久_男同十八禁gv在线观看_免费观看a级性爱黄片

Article / 文章中心

6 種常見分布式唯一ID生成策略及它們的優(yōu)缺點對比

發(fā)布時間:2022-01-28 點擊數(shù):968


大局僅有的 ID 幾乎是一切體系都會遇到的剛需。這個 id 在查找, 存儲數(shù)據(jù), 加快檢索速度 等等很多方面都有著重要的意義。有多種戰(zhàn)略來獲取這個大局僅有的id,針對常見的幾種場景,我在這兒進行簡略的總結(jié)和對比。

簡略剖析一下需求

所謂大局僅有的 id 其實往往對應(yīng)是生成僅有記載標識的事務(wù)需求 

這個 id 常常是數(shù)據(jù)庫的主鍵,數(shù)據(jù)庫上會樹立集合索引(cluster index),即在物理存儲上以這個字段排序。這個記載標識上的查詢,往往又有分頁或許排序的事務(wù)需求。所以往往要有一個time字段,而且在time字段上樹立一般索引(non-cluster index)。

一般索引存儲的是實踐記載的指針,其拜訪效率會比集合索引慢,假如記載標識在生成時能夠基本依照時刻有序,則能夠省去這個time字段的索引查詢。

這就引出了記載標識生成的兩大中心需求:

  • 大局僅有
  • 趨勢有序

常見生成戰(zhàn)略的優(yōu)缺陷對比

辦法一: 用數(shù)據(jù)庫的 auto_increment 來生成

長處:

  • 此辦法運用數(shù)據(jù)庫原有的功能,所以相對簡略
  • 能夠確保僅有性
  • 能夠確保遞加性
  • id 之間的步長是固定且可自定義的

缺陷:

  • 可用性難以確保:數(shù)據(jù)庫常見架構(gòu)是 一主多從 + 讀寫分離,生成自增ID是寫請求 主庫掛了就玩不轉(zhuǎn)了
  • 擴展性差,功能有上限:因為寫入是單點,數(shù)據(jù)庫主庫的寫功能決定ID的生成功能上限,而且 難以擴展

改善計劃:

  • 冗余主庫,防止寫入單點
  • 數(shù)據(jù)水平切分,確保各主庫生成的ID不重復(fù)

image.png

如上圖所述,由1個寫庫變成3個寫庫,每個寫庫設(shè)置不同的 auto_increment 初始值,以及相同的增長步長,以確保每個數(shù)據(jù)庫生成的ID是不同的(上圖中DB 01生成0,3,6,9…,DB 02生成1,4,7,10,DB 03生成2,5,8,11…)

改善后的架構(gòu)確保了可用性,但缺陷是

  • 喪失了ID生成的“肯定遞加性”:先拜訪DB 01生成0,3,再拜訪DB 02生成1,可能導(dǎo)致在十分短的時刻內(nèi),ID生成不是肯定遞加的(這個問題不大,目標是趨勢遞加,不是肯定遞加
  • 數(shù)據(jù)庫的寫壓力仍然很大,每次生成ID都要拜訪數(shù)據(jù)庫

為了處理這些問題,引出了以下辦法:

辦法二:單點批量ID生成服務(wù)

分布式體系之所以難,很重要的原因之一是“沒有一個大局時鐘,難以確??隙ǖ臅r序”,要想確??隙ǖ臅r序,仍是只能運用單點服務(wù),用本地時鐘確?!翱隙〞r序”。

數(shù)據(jù)庫寫壓力大,是因為每次生成ID都拜訪了數(shù)據(jù)庫,能夠運用批量的方式下降數(shù)據(jù)庫寫壓力。

image.png

如上圖所述,數(shù)據(jù)庫運用雙master確??捎眯裕瑪?shù)據(jù)庫中只存儲當(dāng)時ID的最大值,例如4。

ID生成服務(wù)假定每次批量拉取5個ID,服務(wù)拜訪數(shù)據(jù)庫,將當(dāng)時ID的最大值修改為4,這樣運用拜訪ID生成服務(wù)索要ID,ID生成服務(wù)不需求每次拜訪數(shù)據(jù)庫,就能依次派發(fā)0,1,2,3,4這些ID了。

當(dāng)ID發(fā)完后,再將ID的最大值修改為11,就能再次派發(fā)6,7,8,9,10,11這些ID了,于是數(shù)據(jù)庫的壓力就下降到原來的1/6。

長處:

  • 確保了ID生成的肯定遞加有序
  • 大大的下降了數(shù)據(jù)庫的壓力,ID生成能夠做到每秒生成幾萬幾十萬個

缺陷:

  • 服務(wù)仍然是單點
  • 假如服務(wù)掛了,服務(wù)重啟起來之后,繼續(xù)生成ID可能會不連續(xù),中心出現(xiàn)空泛(服務(wù)內(nèi)存是保存著0,1,2,3,4,數(shù)據(jù)庫中max-id是4,分配到3時,服務(wù)重啟了,下次會從5開端分配,3和4就成了空泛,不過這個問題也不大)
  • 雖然每秒能夠生成幾萬幾十萬個ID,但畢竟仍是有功能上限,無法進行水平擴展

改善計劃

  • 單點服務(wù)的常用高可用優(yōu)化計劃是“備用服務(wù)”,也叫“影子服務(wù)”,所以咱們能用以下辦法優(yōu)化上述缺陷:

image.png

如上圖,對外供給的服務(wù)是主服務(wù),有一個影子服務(wù)時刻處于備用狀況,當(dāng)主服務(wù)掛了的時候影子服務(wù)頂上。這個切換的進程對調(diào)用方是透明的,能夠主動完成,常用的技術(shù)是 vip+keepalived。另外,id generate service 也能夠進行水平擴展,以處理上述缺陷,但會引發(fā)一致性問題。

辦法三:uuid / guid

不管是經(jīng)過數(shù)據(jù)庫,仍是經(jīng)過服務(wù)來生成ID,事務(wù)方Application都需求進行一次長途調(diào)用,比較耗時。uuid是一種常見的本地生成ID的辦法。

UUID uuid = UUID.randomUUID();

長處:

  • 本地生成ID,不需求進行長途調(diào)用,時延低
  • 擴展性好,基本能夠以為沒有功能上限

缺陷:

  • 無法確保趨勢遞加
  • uuid過長,往往用字符串表明,作為主鍵樹立索引查詢效率低,常見優(yōu)化計劃為“轉(zhuǎn)化為兩個uint64整數(shù)存儲”或許“減半存儲”(減半后不能確保僅有性)

辦法四:取當(dāng)時毫秒數(shù)

uuid是一個本地算法,生成功能高,但無法確保趨勢遞加,且作為字符串ID檢索效率低,有沒有一種能確保遞加的本地算法呢?- 取當(dāng)時毫秒數(shù)是一種常見計劃。

長處:

  • 本地生成ID,不需求進行長途調(diào)用,時延低
  • 生成的ID趨勢遞加
  • 生成的ID是整數(shù),樹立索引后查詢效率高

缺陷:

  • 假如并發(fā)量超越1000,會生成重復(fù)的ID
  • 這個缺陷要了命了,不能確保ID的僅有性。當(dāng)然,運用微秒能夠下降沖突概率,但每秒最多只能生成1000000個ID,再多的話就一定會沖突了,所以運用微秒并不從根本上處理問題。

辦法五:運用 Redis 來生成 id

當(dāng)運用數(shù)據(jù)庫來生成ID功能不行要求的時候,咱們能夠嘗試運用Redis來生成ID。這主要依賴于Redis是單線程的,所以也能夠用生成大局僅有的ID。能夠用Redis的原子操作 INCR 和 INCRBY 來完成。

長處:

  • 依賴于數(shù)據(jù)庫,靈活方便,且功能優(yōu)于數(shù)據(jù)庫。
  • 數(shù)字ID天然排序,對分頁或許需求排序的結(jié)果很有協(xié)助。

缺陷:

  • 假如體系中沒有Redis,還需求引進新的組件,增加體系復(fù)雜度。
  • 需求編碼和配置的作業(yè)量比較大。

辦法六:Twitter 開源的 Snowflake 算法

snowflake 是 twitter 開源的分布式ID生成算法,其中心思想為,一個long型的ID:

  • 41 bit 作為毫秒數(shù) - 41位的長度能夠運用69年
  • 10 bit 作為機器編號 (5個bit是數(shù)據(jù)中心,5個bit的機器ID) - 10位的長度最多支撐布置1024個節(jié)點
  • 12 bit 作為毫秒內(nèi)序列號 - 12位的計數(shù)順序號支撐每個節(jié)點每毫秒發(fā)生4096個ID序號

image.png

算法單機每秒內(nèi)理論上最多能夠生成1000*(2^12),也就是400W的ID,完全能滿足事務(wù)的需求。

該算法 java 版本的完成代碼如下:

package com; public class SnowflakeIdGenerator {  //================================================Algorithm's Parameter=============================================  // 體系開端時刻截 (UTC 2017-06-28 00:00:00)  private final long startTime = 1498608000000L;  // 機器id所占的位數(shù)  private final long workerIdBits = 5L;  // 數(shù)據(jù)標識id所占的位數(shù)  private final long dataCenterIdBits = 5L;  // 支撐的最大機器id(十進制),結(jié)果是31 (這個移位算法能夠很快的計算出幾位二進制數(shù)所能表明的最大十進制數(shù))  // -1L 左移 5位 (worker id 所占位數(shù)) 即 5位二進制所能取得的最大十進制數(shù) - 31  private final long maxWorkerId = -1L ^ (-1L << workerIdBits);  // 支撐的最大數(shù)據(jù)標識id - 31  private final long maxDataCenterId = -1L ^ (-1L << dataCenterIdBits);  // 序列在id中占的位數(shù)  private final long sequenceBits = 12L;  // 機器ID 左移位數(shù) - 12 (即末 sequence 所占用的位數(shù))  private final long workerIdMoveBits = sequenceBits;  // 數(shù)據(jù)標識id 左移位數(shù) - 17(12+5)  private final long dataCenterIdMoveBits = sequenceBits + workerIdBits;  // 時刻截向 左移位數(shù) - 22(5+5+12)  private final long timestampMoveBits = sequenceBits + workerIdBits + dataCenterIdBits;  // 生成序列的掩碼(12位所對應(yīng)的最大整數(shù)值),這兒為4095 (0b111111111111=0xfff=4095)  private final long sequenceMask = -1L ^ (-1L << sequenceBits);  //=================================================Works's Parameter================================================  /**  * 作業(yè)機器ID(0~31)  */  private long workerId;  /**  * 數(shù)據(jù)中心ID(0~31)  */  private long dataCenterId;  /**  * 毫秒內(nèi)序列(0~4095)  */  private long sequence = 0L;  /**  * 前次生成ID的時刻截  */  private long lastTimestamp = -1L;  //===============================================Constructors=======================================================  /**  * 構(gòu)造函數(shù)  *  * @param workerId     作業(yè)ID (0~31)  * @param dataCenterId 數(shù)據(jù)中心ID (0~31)  */  public SnowflakeIdGenerator(long workerId, long dataCenterId) {  if (workerId > maxWorkerId || workerId < 0) {  throw new IllegalArgumentException(String.format("Worker Id can't be greater than %d or less than 0", maxWorkerId));  }  if (dataCenterId > maxDataCenterId || dataCenterId < 0) {  throw new IllegalArgumentException(String.format("DataCenter Id can't be greater than %d or less than 0", maxDataCenterId));  }  this.workerId = workerId;  this.dataCenterId = dataCenterId;  }  // ==================================================Methods========================================================  // 線程安全的取得下一個 ID 的辦法  public synchronized long nextId() {  long timestamp = currentTime();  //假如當(dāng)時時刻小于上一次ID生成的時刻戳: 闡明體系時鐘回退過 - 這個時候應(yīng)當(dāng)拋出反常  if (timestamp < lastTimestamp) {  throw new RuntimeException(  String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));  }  //假如是同一時刻生成的,則進行毫秒內(nèi)序列  if (lastTimestamp == timestamp) {  sequence = (sequence + 1) & sequenceMask;  //毫秒內(nèi)序列溢出 即 序列 > 4095  if (sequence == 0) {  //阻塞到下一個毫秒,取得新的時刻戳  timestamp = blockTillNextMillis(lastTimestamp);  }  }  //時刻戳改動,毫秒內(nèi)序列重置  else {  sequence = 0L;  }  //前次生成ID的時刻截  lastTimestamp = timestamp;  //移位并經(jīng)過或運算拼到一起組成64位的ID  return ((timestamp - startTime) << timestampMoveBits) //  | (dataCenterId << dataCenterIdMoveBits) //  | (workerId << workerIdMoveBits) //  | sequence;  }  // 阻塞到下一個毫秒 即 直到取得新的時刻戳  protected long blockTillNextMillis(long lastTimestamp) {  long timestamp = currentTime();  while (timestamp <= lastTimestamp) {  timestamp = currentTime();  }  return timestamp;  }  // 取得以毫秒為單位的當(dāng)時時刻  protected long currentTime() {  return System.currentTimeMillis();  }  //====================================================Test Case=====================================================  public static void main(String[] args) {  SnowflakeIdGenerator idWorker = new SnowflakeIdGenerator(0, 0);  for (int i = 0; i < 100; i++) {  long id = idWorker.nextId();  //System.out.println(Long.toBinaryString(id));  System.out.println(id);  }  } }