Golang分布式ID產生服務

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

周末花了一晚上的時間,用Go寫了一個ID產生服務,Github地址:go-id-alloc。

分布式ID產生,就我來看主要是2個流派,各有利弊,沒有完美的實現。

1,snowflake流派。

它用於twitter的微博ID,因為是timeline按發布時間排序,所以這個演算法是用毫秒時間戳記作為ID的左半部,從而可以實現按時間有序。

像新浪微博也是在使用類似的ID產生演算法,snowflake的好處是去中心化,但是依賴時鐘的準確性,最差的情況是時鐘發生了回退,那麼ID就會重複;而如果不開啟NTP同步時鐘,那麼不同節點分配的時間不同,也會影響feed流的排序,所以在我看來只能說基本可用,一旦時鐘回退比較大的區間,服務是完全停用。美團在這方面做了一些工作,主要還是在發現回退以及警示方面的事情,可以參考:Leaf — 美團點評分布式ID產生系統。

2,mysql流派。

該流派使用廣泛,基本原理就是mysql的自增主鍵。最初為了擴充性能,會通過部署多台mysql,為每個mysql設定不同的起始id,從而實現橫向擴充性。

mysql支援自訂表格的auto_increment屬性,可以用於控制起始ID:

Transact-SQLCREATE TABLE `partition_1` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `meanless` tinyint(4) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `meanless` (`meanless`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;ALTER TABLE `partition_1` auto_increment=1;
12345678 CREATE TABLE `partition_1` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `meanless` tinyint(4) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `meanless` (`meanless`)) ENGINE=InnoDB DEFAULT CHARSET=utf8; ALTER TABLE `partition_1` auto_increment=1;

ALTER修改了parition_1表的起始ID=1,不同的表可以設定不同的起始ID,例如給partition_2設定auto_increment=2。

僅僅這樣是不夠的,因為預設partition_1的下一個自增ID=2,會和partition_2表分配的ID重複。

mysql提供了另外一個配置,叫做auto_increment_increment,可以在mysql session級設定(set auto_increment_increment=xxx;),也可以設定給整個mysql執行個體。該配置用於控制步長,在上述例子中我會設定auto_increment_increment=2,那麼2個partition表的ID分配情況如下:

  • 1,3,5,7,9…
  • 2,4,6,8,10…

你會發現,步長設定為分區的個數,就可以避免ID衝突,整體向更大的ID共同增長了。

那麼如何分配下一個ID呢?一般insert新紀錄會產生新的ID,而這樣會導致資料規模增長,更好的方法是使用replace命令:

Shellreplace into partition_0(`meanless`) values(0)
1 replace into partition_0(`meanless`) values(0)

因為meanless唯一鍵的原因,id欄位會自增,同時最多隻會產生一條記錄。

在我的go-id-alloc項目中,就是採用了這樣的方式實現ID自增,但是僅僅這樣還是不夠的。資料庫更新畢竟存在一個效能的瓶頸,在請求壓力更大的業務情境下終將成為一個瓶頸。

新的方案基於mysql自增原理實現,通過”號段”批量擷取的方式,將資料庫的寫入壓力降低為忽略不計,下面說明其原理。

仍舊以上面的分配布局為例,兩個mysql分別產生如下的ID序列:

  • 1,3,5,7,9…
  • 2,4,6,8,10…

現在我假設一個號段長度10000,那麼當replace產生了ID=1的時候,表示分配得到了[0, 10000)這個號段。同樣的,當再次replace時產生ID=3,那麼表示分配得到了[20000, 30000)這個號段。

將基於號段的序列重新整理,就會像下面這樣:

  • [0, 10000),[20000, 30000),[40000,50000),[60000,70000),[80000,90000)
  • [10000,20000),[30000,40000),[50000,60000),[70000,80000),[90000,100000)

觀察出規律了嗎?若分配得到的ID是N,那麼號段就是[(N – 1) * SIZE, N * SIZE)。

有了號段,我們只需要寫一個服務,每次向mysql分配一個ID,也就得到了一個獨佔的號段,接下來的ID分配請求可以直接從記憶體中的號段擷取。另外,應當在記憶體裡的號段消耗殆盡之前,向mysql擷取新的號段。

通過提升號段的SIZE,我們可以減少訪問資料庫的頻率,從而提升整個ID分配的服務能力。

mysql故障

因為ID序列儲存在mysql,因此Mysql遺失資料就變得不可容忍。一般我們有mysql的master-slave模式來即時備份資料,但是畢竟主從存在延遲,主庫宕機可能導致最新的更新沒有同步給從庫,那麼就會導致再次分配ID產生重複。

沒錯,這是mysql方案的劣勢,就像snowflake有其自身的劣勢一樣。但是,通常這個問題可以避免,通過設定更大的號段SIZE,我們可以確保在有限的主從延遲時間內(比如1分鐘的延遲),根據業務的請求量,最多隻會丟失N次replace產生的ID自增。在這種假設下,我們可以令從庫的起始ID跳過一定數量的步長,確保它不會重複。

使用情境

snowflake方案適合時間有序的情境,並且外界無法猜測一天的ID分配總量,從而無法猜測某個公司的業務量;缺點是時間回退服務就會不可用。

mysql方案適合內部業務,對ID有更多的控制能力(比如定義起始ID),擴充性很強,能滿足任何體量的業務規模;缺點是依賴mysql,另外ID存在規律,容易暴露公司業務量。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.