基於Java代碼實現遊戲伺服器產生全域唯一ID的方法匯總_java

來源:互聯網
上載者:User

在伺服器系統開發時,為了適應資料大並發的請求,我們往往需要對資料進行非同步儲存,特別是在做分布式系統時,這個時候就不能等待插入資料庫返回了取自動id了,而是需要在插入資料庫之前產生一個全域的唯一id,使用全域的唯一id,在遊戲伺服器中,全域唯一的id可以用於將來合服方便,不會出現鍵衝突。也可以將來在業務增長的情況下,實現分庫分表,比如某一個使用者的物品要放在同一個分區內,而這個分區段可能是根據使用者id的範圍值來確定的,比如使用者id大於1000小於100000的使用者在一個分區內。目前常用的有以下幾種:

1,Java 內建的UUID.

UUID.randomUUID().toString(),可以通過服務程式本地產生,ID的產生不依賴資料庫的實現。

優勢:

本地產生ID,不需要進行遠程調用。

全域唯一不重複。

水平擴充能力非常好。

劣勢:

ID有128 bits,佔用的空間較大,需要存成字串類型,索引效率極低。

產生的ID中沒有帶Timestamp,無法保證趨勢遞增,資料庫分庫分表時不好依賴

2,基於Redis的incr方法

Redis本身是單線程操作的,而incr更保證了一種原子遞增的操作。而且支援設定遞增步長。

優勢:

部署方便,使用簡單,只需要調用一個redis的api即可。

可以多個伺服器共用一個redis服務,減少共用資料的開發時間。

Redis可以群集部署,解決單點故障的問題。

劣勢:

如果系統太龐大的話,n多個服務同時向redis請求,會造成效能瓶頸。

3,來自Flicker的解決方案

這個解決方案是基於資料庫自增id的,它使用一個單獨的資料庫專門用於產生id。詳細的大家可以網上找找,個人覺得使用挺麻煩的,不建議使用。

4,Twitter Snowflake

snowflake是twitter開源的分布式ID產生演算法,其核心思想是:產生一個long型的ID,使用其中41bit作為毫秒數,10bit作為機器編號,12bit作為毫秒內序號。這個演算法單機每秒內理論上最多可以產生1000*(2^12)個,也就是大約400W的ID,完全能滿足業務的需求。

根據snowflake演算法的思想,我們可以根據自己的業務情境,產生自己的全域唯一ID。因為Java中long類型的長度是64bits,所以我們設計的ID需要控制在64bits。

優點:高效能,低延遲;獨立的應用;按時間有序。

缺點:需要獨立的開發和部署。

比如我們設計的ID包含以下資訊:

| 41 bits: Timestamp | 3 bits: 地區 | 10 bits: 機器編號 | 10 bits: 序號 |

產生唯一ID的Java代碼:

/*** 自訂 ID 產生器* ID 建置規則: ID長達 64 bits** | 41 bits: Timestamp (毫秒) | 3 bits: 地區(機房) | 10 bits: 機器編號 | 10 bits: 序號 |*/public class GameUUID{// 基準時間private long twepoch = 1288834974657L; //Thu, 04 Nov 2010 01:42:54 GMT// 地區標誌位元private final static long regionIdBits = 3L;// 機器標識位元private final static long workerIdBits = 10L;// 序號識位元private final static long sequenceBits = 10L;// 地區標誌ID最大值private final static long maxRegionId = -1L ^ (-1L << regionIdBits);// 機器ID最大值private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);// 序號ID最大值private final static long sequenceMask = -1L ^ (-1L << sequenceBits);// 機器ID偏左移10位private final static long workerIdShift = sequenceBits;// 業務ID偏左移20位private final static long regionIdShift = sequenceBits + workerIdBits;// 時間毫秒左移23位private final static long timestampLeftShift = sequenceBits + workerIdBits + regionIdBits;private static long lastTimestamp = -1L;private long sequence = 0L;private final long workerId;private final long regionId;public GameUUID(long workerId, long regionId) {// 如果超出範圍就拋出異常if (workerId > maxWorkerId || workerId < 0) {throw new IllegalArgumentException("worker Id can't be greater than %d or less than 0");}if (regionId > maxRegionId || regionId < 0) {throw new IllegalArgumentException("datacenter Id can't be greater than %d or less than 0");}this.workerId = workerId;this.regionId = regionId;}public GameUUID(long workerId) {// 如果超出範圍就拋出異常if (workerId > maxWorkerId || workerId < 0) {throw new IllegalArgumentException("worker Id can't be greater than %d or less than 0");}this.workerId = workerId;this.regionId = 0;}public long generate() {return this.nextId(false, 0);}/*** 實際產生代碼的** @param isPadding* @param busId* @return*/private synchronized long nextId(boolean isPadding, long busId) {long timestamp = timeGen();long paddingnum = regionId;if (isPadding) {paddingnum = busId;}if (timestamp < lastTimestamp) {try {throw new Exception("Clock moved backwards. Refusing to generate id for " + (lastTimestamp - timestamp) + " milliseconds");} catch (Exception e) {e.printStackTrace();}}//如果上次產生時間和目前時間相同,在同一毫秒內if (lastTimestamp == timestamp) {//sequence自增,因為sequence只有10bit,所以和sequenceMask相與一下,去掉高位sequence = (sequence + 1) & sequenceMask;//判斷是否溢出,也就是每毫秒內超過1024,當為1024時,與sequenceMask相與,sequence就等於0if (sequence == 0) {//自旋等待到下一毫秒timestamp = tailNextMillis(lastTimestamp);}} else {// 如果和上次產生時間不同,重設sequence,就是下一毫秒開始,sequence計數重新從0開始累加,// 為了保證尾數隨機性更大一些,最後一位設定一個隨機數sequence = new SecureRandom().nextInt(10);}lastTimestamp = timestamp;return ((timestamp - twepoch) << timestampLeftShift) | (paddingnum << regionIdShift) | (workerId << workerIdShift) | sequence;}// 防止產生的時間比之前的時間還要小(由於NTP回撥等問題),保持增量的趨勢.private long tailNextMillis(final long lastTimestamp) {long timestamp = this.timeGen();while (timestamp <= lastTimestamp) {timestamp = this.timeGen();}return timestamp;}// 擷取當前的時間戳記protected long timeGen() {return System.currentTimeMillis();}}

使用自訂的這種方法需要注意的幾點:

為了保持增長的趨勢,要避免有些伺服器的時間早,有些伺服器的時間晚,需要控制好所有伺服器的時間,而且要避免NTP時間伺服器回撥伺服器的時間;在跨毫秒時,序號總是歸0,會使得序號為0的ID比較多,導致產生的ID模數後不均勻,所以序號不是每次都歸0,而是歸一個0到9的隨機數。

上面說的這幾種方式我們可以根據自己的需要去選擇。在遊戲伺服器開發中,根據自己的遊戲類型選擇,比如手機遊戲,可以使用簡單的redis方式,簡單不容易出錯,由於這種遊戲單服並發建立id量並不太大,完全可以滿足需要。而對於大型的世界遊戲伺服器,它本身就是以分布式為主的,所以可以使用snowflake的方式,上面的snowflake代碼只是一個例子,需要自己根據自己的需求去定製,所以有額外的開發量,而且要注意上述所說的注意事項。

以上所述是小編給大家介紹的基於Java代碼實現遊戲伺服器產生全域唯一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.