程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> 游戲服務器生成全局唯一ID的幾種方法,全局幾種方法

游戲服務器生成全局唯一ID的幾種方法,全局幾種方法

編輯:JAVA綜合教程

游戲服務器生成全局唯一ID的幾種方法,全局幾種方法


在服務器系統開發時,為了適應數據大並發的請求,我們往往需要對數據進行異步存儲,特別是在做分布式系統時,這個時候就不能等待插入數據庫返回了取自動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就等於0

           if (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的隨機數。(本代碼參考:http://www.jianshu.com/p/61817cf48cc3)

 

上面說的這幾種方式我們可以根據自己的需要去選擇。在游戲服務器開發中,根據自己的游戲類型選擇,比如手機游戲,可以使用簡單的redis方式,簡單不容易出錯,由於這種游戲單服並發新建id量並不太大,完全可以滿足需要。而對於大型的世界游戲服務器,它本身就是以分布式為主的,所以可以使用snowflake的方式,上面的snowflake代碼只是一個例子,需要自己根據自己的需求去定制,所以有額外的開發量,而且要注意上述所說的注意事項。參考文章:http://www.youxijishu.com/h-nd-147-2_323.html

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved