一背景
傳統生成id方式可以靠數據庫的自增來實現,但是在分佈式環境下不太適應。依賴數據庫容易造成單點。
爲什麼不用UUID的,網上看別人介紹的時候,從兩個方面去分析:
1 大併發的情況下,UUID會出現重複。
2.UUID是隨即的,含義不明。從業務角度去考慮,如果用作訂單,用戶查詢訂單在數據分片的情況下很可能分散在多個庫,查詢困難。
全局唯一id的要求比較高:
不能有單點故障。
性能好,毫秒級返回。
能順序便於DB存儲及劃分。
二 使用zookeeper生成全局唯一id.
2.1 利用Zookeeper的znode數據版本生成序列號
客戶端採用:zkClient (https://github.com/adyliu/zkclient)
<dependency> <groupId>com.github.adyliu</groupId> <artifactId>zkclient</artifactId> <version>2.1.1</version> </dependency>
java 代碼實現
public class ZKSeqTest { //提前創建好存儲Seq的"/createSeq"結點 CreateMode.PERSISTENT public static final String SEQ_ZNODE = "/seq"; //通過znode數據版本實現分佈式seq生成 public static class Task1 implements Runnable { private final String taskName; public Task1(String taskName) { this.taskName = taskName; } @Override public void run() { ZkClient zkClient = new ZkClient("192.168.190.36:2181", 3000, 50000); Stat stat =zkClient.writeData(SEQ_ZNODE, new byte[0], -1); int versionAsSeq = stat.getVersion(); System.out.println(taskName + " obtain seq=" +versionAsSeq ); zkClient.close(); } } public static void main(String[] args) { // TODO Auto-generated method stub //main final ExecutorService service = Executors.newFixedThreadPool(20); for (int i = 0; i < 10; i++) { service.execute(new Task1("[Concurrent-" + i + "]")); } } }
public class ZKLock { //提前創建好鎖對象的結點"/lock" CreateMode.PERSISTENT public static final String LOCK_ZNODE = "/lock"; //分佈式鎖實現分佈式seq生成 public static class Task2 implements Runnable, IZkChildListener { private final String taskName; private final ZkClient zkClient; private final String lockPrefix = "/loc"; private final String selfZnode; public Task2(String taskName) { this.taskName = taskName; zkClient = new ZkClient("192.168.190.36:2181", 30000, 50000); selfZnode = zkClient.createEphemeralSequential(LOCK_ZNODE + lockPrefix, new byte[0]); } @Override public void run() { createSeq(); } private void createSeq() { Stat stat = new Stat(); byte[] oldData = zkClient.readData(LOCK_ZNODE, stat); byte[] newData = update(oldData); zkClient.writeData(LOCK_ZNODE, newData); System.out.println(taskName + selfZnode + " obtain seq=" + new String(newData)); } private byte[] update(byte[] currentData) { String s = new String(currentData); int d = Integer.parseInt(s); d = d + 1; s = String.valueOf(d); return s.getBytes(); } @Override public void handleChildChange(String parentPath, List<String> currentChildren) throws Exception { // TODO Auto-generated method stub } } public static void main(String[] args) { final ExecutorService service = Executors.newFixedThreadPool(20); for (int i = 0; i < 10; i++) { service.execute(new Task2("[Concurrent-" + i + "]")); } service.shutdown(); } }
利用帶序列號的znode實現
[java] view plain copy
後端看日誌就是運行期間有臨時節點,會話結束後自動刪除。
三 開源方案
網上還有開源的更好的開源實現方案值得借鑑。
3.1Flikr
基於int/bigint的自增
優:開發成本低
劣:如果需要高性能,需要專門一套MySQL集羣只用於生成自增ID。可用性也不強
3.2 Snowflake
twitter利用zookeeper實現了一個全局ID生成的服務snowflake,https://github.com/twitter/snowflake,可以生成全局唯一的64bit ID。
生成的ID的構成:
時間--用前面41 bit來表示時間,精確到毫秒,可以表示69年的數據 機器ID--用10 bit來表示,也就是說可以部署1024臺機器 序列數--用12 bit來表示,意味着每臺機器,每毫秒最多可以生成4096個ID
優:可用性強,速度快,id保存信息多。
劣:需要引入zookeeper 和獨立的snowflake專用服務器
3.3instagram
instagram參考了flickr的方案,再結合twitter的經驗,利用Postgres數據庫的特性,實現了一個更簡單可靠的ID生成服務。
使用41 bit來存放時間,精確到毫秒,可以使用41年。
使用13 bit來存放邏輯分片ID。
使用10 bit來存放自增長ID,意味着每臺機器,每毫秒最多可以生成1024個ID
優: 開發成本低
劣: 基於postgreSQL的存儲過程,通用性差
還有基於redis的全局id生成方案:http://blog.csdn.net/hengyunabc/article/details/44244951
參考:
http://blog.csdn.net/bohu83/article/details/51457961