1. UUID
UUID是可以生成時間、空間上都獨一無二的值,其本質是隨機+規則組合而成的。即使在兩個獨立的服務器上生成UUID,其預期值也是不同的。以MySQL為例,說明下UUID。
格式
在MySQL中,UUID值是一個128位的數字,表示為以下格式的十六進制數字的utf8字符串:aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee。其得到的隨機值由5個部分組成,且分隔符位為:中劃線。其各部分含義如下:
- 前三組值是時間戳換算過來的;?
- 第四組值是暫時性保持時間戳的唯一性。例如,使用夏令時;
- 第五組值是一個IEE 802的節點標識值,它是空間上唯一的。若后者不可用,則用一個隨機數字替換。假如主機沒有網卡,或者我們不知道如何在某系統下獲得機器地址,則空間唯一性就不能得到保證,即使這樣出現重復值的幾率還是非常小的。
在MySQL環境中多次調用或執行得到的后兩組值相同,若把mysqld服務器關閉,重新啟動之后,會發現第四組的組與未重啟前的值發生變化,然后一直不變化,只要重新啟動mysqld服務就會發生變化。另外,對于同一臺機器,第五組值始終不會發生變化。
優點
使用UUID作為主鍵具有以下優點:
- UUID值在表,數據庫甚至在服務器上都是唯一的,允許您從不同數據庫合并行或跨服務器分發數據庫。
- UUID值不會公開有關數據的信息,因此在URL中使用更安全。
- 可以在避免往返數據庫服務器的任何地方生成UUID值。它也簡化了應用程序中的邏輯。
缺點
除了優勢之外,UUID值也存在一些缺點:
- 存儲UUID值(16字節)比整數(4字節)或甚至大整數(8字節)占用更多的存儲空間。
- 調試似乎更加困難,想象一下WHERE id ='9d6212cf-72fc-11e7-bdf0-f0def1e6646c'和WHERE id = 10哪個舒服一點?
- 使用UUID值可能會導致性能問題,因為它們的大小和沒有被排序。
數據庫案例:MySQL
在MySQL中,就內置了對UUID的支持。在使用上需注意若干問題。
- 作為主鍵問題
UUID()函數產生的值,并不適合作為InnoDB引擎表的主鍵。因為格式無序,作為索引組織表存儲會帶來管理上的不小開銷。
- 格式問題
在MySQL中,可以使用UUID()來生成主鍵,但是用MySQL的UUID()函數 ,生成的UUID是36位的,其中包含32個字符以及4個分隔符(-),往往這個分隔符對我們來說是沒有用的,可以使用MySQL自帶的REPLACE函數去掉分隔符。
- 內置函數
支持在MySQL中,可以以緊湊格式(BINARY)存儲UUID值,并通過以下功能顯示人機可讀格式(VARCHAR):UUID_TO_BIN、BIN_TO_UUID、IS_UUID。需要注意,UUID_TO_BIN(),BIN_TO_UUID()和IS_UUID()函數僅在MySQL 8.0或更高版本中可用。- UUID_TO_BIN()函數將UUID從人類可讀格式(VARCHAR)轉換成用于存儲的緊湊格式(BINARY)格式- BIN_TO_UUID()函數將UUID從緊湊格式(BINARY)轉換為人類可讀格式(VARCHAR)- IS_UUID()函數則可用來判斷參數是有效的字符串格式UUID。
2. NanoID
UUID 是軟件開發中最常用的通用標識符之一。然而,在過去的幾年里,其他的競品挑戰了它的存在。其中,NanoID 是 UUID 的主要競爭對手之一。但是,這兩者之間的主要區別很簡單。它歸結為鍵所使用的字母表。由于 NanoID 使用比 UUID 更大的字母表,因此較短的 ID 可以用于與較長的 UUID 相同的目的。
優點
- 更小
NanoID 只有 108 個字節那么大。與 UUID 不同,NanoID 的大小要小 4.5 倍,并且沒有任何依賴關系。此外,大小限制已用于將大小從另外 35% 減小。大小減少直接影響數據的大小。例如,使用 NanoID 的對象小而緊湊,能夠用于數據傳輸和存儲。隨著應用程序的增長,這些數字變得明顯起來。
- 更安全
在大多數隨機生成器中,它們使用不安全的 Math.random()。但是,NanoID 使用 crypto module 和 Web Crypto API,意味著 NanoID 更安全。此外,NanoID 在 ID 生成器的實現過程中使用了自己的算法,稱為 統一算法,而不是使用“隨機 % 字母表” random % alphabet。
- 更快
NanoID既快速又緊湊,NanoID 比 UUID 快 60%。與 UUID 字母表中的 36 個字符不同,NanoID 只有 21 個字符。
- 更多語言
NanoID 支持 14 種不同的編程語言,它們分別是:C#、C++、Clojure 和 ClojureScript、Crystal、Dart & Flutter、Deno、Go、Elixir、Haskell、Janet、Java、Nim、Perl、PHP、帶字典的 Python、Ruby、Rust、Swift。
- 更好兼容性
它還支持 PouchDB、CouchDB WebWorkers、Rollup 以及 React 和 Reach-Native 等庫。我們可以使用 npx nanoid 在終端中獲得唯一 ID。在 JavaScript 中使用 NanoID 唯一的要求是要先安裝 NodeJS。
- 自定義字母
NanoID 的另一個現有功能是它允許開發人員使用自定義字母表。我們可以更改文字或 id 的大小。在下面的示例中,我將自定義字母表定義為 ABCDEF1234567890,并將 Id 的大小定義為 12。
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('ABCDEF1234567890', 12);
model.id = nanoid();
- 沒有第三方依賴
由于 NanoID 不依賴任何第三方依賴,隨著時間的推移,它能夠變得更加穩定自治。從長遠來看,這有利于優化包的大小,并使其不太容易出現依賴項帶來的問題。
數據庫案例-ShardingSphere
原生數據庫產品,大多沒有支持NanoID,但可通過外部方式引用進來。例如在開源項目 Apache ShardingSphere 中可通過規則的配置,在其分片表中使用 NanoID作為主鍵生成器。參考如下配置:
CREATE SHARDING TABLE RULE t_order(
RESOURCES(ds_3307,ds_3308),
SHARDING_COLUMN=order_id,TYPE(NAME=hash_mod,PROPERTIES("sharding-count"=4)),
KEY_GENERATE_STRATEGY(COLUMN=order_id,TYPE(NAME=NanoID,PROPERTIES("worker-id"=123)))
);
CREATE TABLE t_order (
user_id int NOT NULL,
status varchar(50) DEFAULT NULL,
PRIMARY KEY (order_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3. SnowFlake
分布式系統中ID生成方案,比較簡單的是UUID(Universally Unique Identifier,通用唯一識別碼),但是其存在兩個明顯的弊端:一、UUID是128位的,長度過長;二、UUID是完全隨機的,無法生成遞增有序的UUID。而現在流行的基于 Snowflake 雪花算法的ID生成方案就可以很好地解決了UUID存在的這兩個問題。
原理
Snowflake 雪花算法,由Twitter提出并開源,可在分布式環境下用于生成唯一ID的算法。該算法生成的是一個64位的ID。在同一個進程中,它首先是通過時間位保證不重復,如果時間相同則是通過序列位保證。同時由于時間位是單調遞增的,且各個服務器如果大體做了時間同步,那么生成的主鍵在分布式環境可以認為是總體有序的,這就保證了對索引字段的插入的高效性。例如 MySQL 的 Innodb 存儲引擎的主鍵。
格式
使用雪花算法生成的主鍵,二進制表示形式包含 4 部分,從高位到低位分表為:1bit 符號位、41bit 時間戳位、10bit 工作進程位以及 12bit 序列號位。
- 符號位(1bit)
預留的符號位,恒為零。
- 時間戳位(41bit)
41 位的時間戳可以容納的毫秒數是 2 的 41 次冪,一年所使用的毫秒數是:365 * 24 * 60 * 60 * 1000。通過計算可知:Math.pow(2, 41) / (365 * 24 * 60 * 60 * 1000L); 結果約等于 69.73 年。Apache ShardingSphere 的雪花算法的時間紀元從 2016 年 11 月 1 日零點開 始,可以使用到 2086 年,相信能滿足絕大部分系統的要求。
- 工作進程位(10bit)
該標志在 Java 進程內是唯一的,如果是分布式應用部署應保證每個工作進程的 id 是不同的。該值默認為0,可通過屬性設置。
- 序列號位(12bit)
該序列是用來在同一個毫秒內生成不同的 ID。如果在這個毫秒內生成的數量超過 4096 (2 的 12 次冪),那么生成器會等待到下個毫秒繼續生成。
優點
使用SnowFlake的優點是其空間占用更小,且具備一定有序性,這對于類似MySQL數據庫是比較友好的。
缺點
因為其生成策略需參考當前時間,當服務器時鐘回撥會導致產生重復序列,因此默認分布式主鍵生成器提供了一個最大容忍的時鐘回撥毫秒數。如果時鐘回撥的時間超過最大容忍的毫秒數閾值,則程序報錯;如果在可容忍的范圍內,默認分布式主鍵生成器會等待時鐘同步到最后一次主鍵生成的時間后再繼續工作。最大容忍的時鐘回撥毫秒數的默認值為 0,可通過屬性設置。
數據庫案例-ShardingSphere
原生數據庫產品,大多沒有支持SnowFlake,但可通過外部方式引用進來。例如在開源項目 Apache ShardingSphere 中可通過規則的配置,在其分片表中使用 SnowFlake作為主鍵生成器。參考如下配置
CREATE SHARDING TABLE RULE t_order(
RESOURCES(ds_3307,ds_3308),
SHARDING_COLUMN=order_id,TYPE(NAME=hash_mod,PROPERTIES("sharding-count"=4)),
KEY_GENERATE_STRATEGY(COLUMN=order_id,TYPE(NAME= Snowflake,PROPERTIES("worker-id"=123)))
);
CREATE TABLE t_order (
order_id varchar(50) NOT NULL,
user_id int NOT NULL,
status varchar(50) DEFAULT NULL,
PRIMARY KEY (order_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
作者介紹
韓鋒,51CTO社區編輯,CCIA(中國計算機協會)常務理事,前Oracle?ACE,騰訊TVP,阿里云MVP,dbaplus等多家社群創始人或專家團成員。有著豐富的一線數據庫架構、軟件研發、產品設計、團隊管理經驗。曾擔任多家公司首席DBA、數據庫架構師等職。在云、電商、金融、互聯網等行業均有涉獵,精通多種關系型數據庫,對NoSQL及大數據相關技術也有涉足,實踐經驗豐富。曾著有數據庫相關著作《SQL優化最佳實踐》、《數據庫高效優化》。