ZooKeeper 實現分布式 ID
1. 前言
在我們使用數據庫進行數據存儲時,會給數據加上唯一標識,也就是我們常說的 ID,通過唯一的 ID, 我們可以精確的定位到每一條數據,設置 ID 常用的方式有 MySQL 的主鍵自增,UUID,雪花算法等。
除了數據庫的數據需要進行唯一標識的設置之外,在分布式環境中,集群中的每一個服務或者分布式服務的地址列表,我們都可以為它們設置唯一標識 ID,通過這個 ID 我們可以更方便的獲取它們的服務提供者的信息和地址列表等數據。
本節我們就來學習如何使用 Zookeeper 來生成分布式的全局唯一 ID。
2. 分布式 ID
在使用 Zookeeper 生成分布式的全局唯一 ID 之前,我們先來了解什么是分布式 ID,為什么要使用分布式 ID ,以及分布式 ID 的實現方式有哪些。
分布式 ID,也就是在分布式的環境下,全局的唯一的 ID 。那么我們為什么要使用分布式 ID 呢?
在單體結構的應用中,我們可以使用 MySQL 數據庫的主鍵自增來為我們的數據設置唯一標識 ID,但是在分布式環境中,單個數據庫的吞吐量成為整個應用的性能瓶頸,我們就可以搭建數據庫集群來提升數據庫的性能,此時如果還使用 MySQL 的主鍵自增來設置數據 ID 的話,就會出現重復的 ID,這樣就會出現主鍵沖突的情況。
如果使用分布式的全局唯一 ID 就不用擔心會出現這個問題了。那么分布式 ID 的實現方式有哪些呢?接下來我們就對分布式 ID 的實現方式進行介紹。
2.1 分布式 ID 的實現方式
本小節我們來簡單介紹一下常用的分布式 ID 的實現方式,例如:UUID,Redis,雪花算法等。
-
UUID
Universally Unique Identifier 通用唯一標識符,由 32 個字符組成,采用 16 進制進行編碼,定義了在時間和空間都完全唯一的系統信息。
在 Java 中可以使用 java.util.UUID 的 randomUUID() 方法來獲得:
java.util.UUID.randomUUID().toString();
UUID 可以在本地生成,生成速度快,不依賴網絡和其它服務,但是 UUID 沒有可以識別的特點,也沒有順序性。
-
Redis 實現分布式 ID
我們都知道 Redis 的性能非常高,而且還可以搭建集群。我們可以使用 Redis 的 Incr 命令來把 <key,value> 中 key 的數值加 1 并返回,如果這個 key 不存在,則 key 值會被初始化為 0,再執行 Incr 命令來進行加 1 操作。
// 使用 incr(key) 來讓 key 加 1 long id = jedis.incr("id");
使用 Redis 的方式生成分布式 ID 需要依賴 Redis 服務,還要保證 Redis 的高可用,否則 Redis 服務宕機會影響整個應用。
-
雪花算法
SnowFlake 雪花算法是 Twitter 公司推出的?個?于?成分布式 ID 的策略,基于這個算法可以生成 64 位 Long 型的 ID,它是由 1 位符號位,41 位的時間戳毫秒數,10 位的機器 ID,12 位的序列號這 4 種元素來組成的。理論上,雪花算法每秒可以生成 400 多萬個 ID,完全可以支撐住分布式環境下高并發的場景。
一些公司在雪花算法的基礎上實現了自己的分布式 ID 的算法,比如:滴滴的 Tinyid,百度的 UidGenerator,美團的 Leaf 等。
簡單介紹了一些分布式 ID 的實現方式,接下來我們就使用 Zookeeper 來實現分布式 ID 。
3. Zookeeper 實現分布式 ID
在 Zookeeper 中,我們可以使用 Zookeeper 的 順序節點來完成分布式 ID 的生成。這里我們來回顧一下順序節點的特性。
順序節點,在 Zookeeper 客戶端創建順序節點時,Zookeeper 會根據創建的時間順序,在節點名稱后添加 10 位的順序編號。
這里我們使用在 Zookeeper Curator 一節創建的 Spring Boot 測試項目來進行測試:
@SpringBootTest
class CuratorDemoApplicationTests {
@Autowired
private CuratorService curatorService;
@Test
void contextLoads() throws Exception {
// 獲取客戶端
CuratorFramework client = curatorService.getCuratorClient();
// 開啟會話
client.start();
// 第一次創建 /wiki-
String s = client.create().
creatingParentsIfNeeded().
withMode(CreateMode.PERSISTENT_SEQUENTIAL).
forPath("/wiki-");
// 輸出第一次創建的 /wiki-
System.out.println(s);
// 第二次創建 /wiki-
String s1 = client.create().
creatingParentsIfNeeded().
withMode(CreateMode.PERSISTENT_SEQUENTIAL).
forPath("/wiki-");
// 輸出第二次創建的 /wiki-
System.out.println(s1);
// 第三次創建 /wiki-
String s2 = client.create().
creatingParentsIfNeeded().
withMode(CreateMode.PERSISTENT_SEQUENTIAL).
forPath("/wiki-");
// 輸出第三次創建的 /wiki-
System.out.println(s2);
// 關閉客戶端
client.close();
}
}
執行測試方法??刂婆_輸出:
/wiki-0000000000
/wiki-0000000001
/wiki-0000000002
我們可以觀察到,每個 /wiki-
節點后都增加了 10 位的順序編號,而且都是按照添加順序來進行編號的。
再加上我們在創建 Curator 客戶端時設置的命名空間 imooc
,就可以形成完整的節點路徑 /imooc/wiki-0000000000
,這個完整的路徑就可以當作我們的分布式 ID 了,而且這樣我們也能根據命名空間來區別不同的業務模塊,還可以添加不同的命名空間來擴展不同的業務模塊。
4. 總結
在本節內容中,我們學習了什么是分布式 ID ,在分布式環境下為什么要使用分布式 ID,我們還介紹了幾種常用的分布式 ID 實現方式,以及它們的優缺點,最后我們回顧了 Zookeeper 順序節點, 并使用 Zookeeper 的順序節點的特性實現了分布式 ID 的生成。以下是本節內容的總結:
- 為什么要使用分布式 ID 。
- 分布式 ID 實現方式。
- 使用 Zookeeper 的順序節點實現分布式 ID。