遊戲服務器生成全局惟一ID的幾種方法

在服務器系統開發時,爲了適應數據大併發的請求,咱們每每須要對數據進行異步存儲,特別是在作分佈式系統時,這個時候就不能等待插入數據庫返回了取自動id了,而是須要在插入數據庫以前生成一個全局的惟一id,使用全局的惟一id,在遊戲服務器中,全局惟一的id能夠用於未來合服方便,不會出現鍵衝突。也能夠未來在業務增加的狀況下,實現分庫分表,好比某一個用戶的物品要放在同一個分片內,而這個分片斷多是根據用戶id的範圍值來肯定的,好比用戶id大於1000小於100000的用戶在一個分片內。目前經常使用的有如下幾種:html

1,Java 自帶的UUID.redis

UUID.randomUUID().toString(),能夠經過服務程序本地產生,ID的生成不依賴數據庫的實現。算法

優點:數據庫

   本地生成ID,不須要進行遠程調用。api

   全局惟一不重複。服務器

   水平擴展能力很是好。併發

 

劣勢:less

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

   生成的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

相關文章
相關標籤/搜索