在服務器系統開發時,爲了適應數據大併發的請求,咱們每每須要對數據進行異步存儲,特別是在作分佈式系統時,這個時候就不能等待插入數據庫返回了取自動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