基于Java代碼實(shí)現(xiàn)游戲服務(wù)器生成全局唯一ID的方法匯總
在服務(wù)器系統(tǒng)開(kāi)發(fā)時(shí),為了適應(yīng)數(shù)據(jù)大并發(fā)的請(qǐng)求,我們往往需要對(duì)數(shù)據(jù)進(jìn)行異步存儲(chǔ),特別是在做分布式系統(tǒng)時(shí),這個(gè)時(shí)候就不能等待插入數(shù)據(jù)庫(kù)返回了取自動(dòng)id了,而是需要在插入數(shù)據(jù)庫(kù)之前生成一個(gè)全局的唯一id,使用全局的唯一id,在游戲服務(wù)器中,全局唯一的id可以用于將來(lái)合服方便,不會(huì)出現(xiàn)鍵沖突。也可以將來(lái)在業(yè)務(wù)增長(zhǎng)的情況下,實(shí)現(xiàn)分庫(kù)分表,比如某一個(gè)用戶的物品要放在同一個(gè)分片內(nèi),而這個(gè)分片段可能是根據(jù)用戶id的范圍值來(lái)確定的,比如用戶id大于1000小于100000的用戶在一個(gè)分片內(nèi)。目前常用的有以下幾種:
1,Java 自帶的UUID.
UUID.randomUUID().toString(),可以通過(guò)服務(wù)程序本地產(chǎn)生,ID的生成不依賴數(shù)據(jù)庫(kù)的實(shí)現(xiàn)。
優(yōu)勢(shì):
本地生成ID,不需要進(jìn)行遠(yuǎn)程調(diào)用。
全局唯一不重復(fù)。
水平擴(kuò)展能力非常好。
劣勢(shì):
ID有128 bits,占用的空間較大,需要存成字符串類(lèi)型,索引效率極低。
生成的ID中沒(méi)有帶Timestamp,無(wú)法保證趨勢(shì)遞增,數(shù)據(jù)庫(kù)分庫(kù)分表時(shí)不好依賴
2,基于Redis的incr方法
Redis本身是單線程操作的,而incr更保證了一種原子遞增的操作。而且支持設(shè)置遞增步長(zhǎng)。
優(yōu)勢(shì):
部署方便,使用簡(jiǎn)單,只需要調(diào)用一個(gè)redis的api即可。
可以多個(gè)服務(wù)器共享一個(gè)redis服務(wù),減少共享數(shù)據(jù)的開(kāi)發(fā)時(shí)間。
Redis可以群集部署,解決單點(diǎn)故障的問(wèn)題。
劣勢(shì):
如果系統(tǒng)太龐大的話,n多個(gè)服務(wù)同時(shí)向redis請(qǐng)求,會(huì)造成性能瓶頸。
3,來(lái)自Flicker的解決方案
這個(gè)解決方法是基于數(shù)據(jù)庫(kù)自增id的,它使用一個(gè)單獨(dú)的數(shù)據(jù)庫(kù)專(zhuān)門(mén)用于生成id。詳細(xì)的大家可以網(wǎng)上找找,個(gè)人覺(jué)得使用挺麻煩的,不建議使用。
4,Twitter Snowflake
snowflake是twitter開(kāi)源的分布式ID生成算法,其核心思想是:產(chǎn)生一個(gè)long型的ID,使用其中41bit作為毫秒數(shù),10bit作為機(jī)器編號(hào),12bit作為毫秒內(nèi)序列號(hào)。這個(gè)算法單機(jī)每秒內(nèi)理論上最多可以生成1000*(2^12)個(gè),也就是大約400W的ID,完全能滿足業(yè)務(wù)的需求。
根據(jù)snowflake算法的思想,我們可以根據(jù)自己的業(yè)務(wù)場(chǎng)景,產(chǎn)生自己的全局唯一ID。因?yàn)镴ava中l(wèi)ong類(lèi)型的長(zhǎng)度是64bits,所以我們?cè)O(shè)計(jì)的ID需要控制在64bits。
優(yōu)點(diǎn):高性能,低延遲;獨(dú)立的應(yīng)用;按時(shí)間有序。
缺點(diǎn):需要獨(dú)立的開(kāi)發(fā)和部署。
比如我們?cè)O(shè)計(jì)的ID包含以下信息:
| 41 bits: Timestamp | 3 bits: 區(qū)域 | 10 bits: 機(jī)器編號(hào) | 10 bits: 序列號(hào) |
產(chǎn)生唯一ID的Java代碼:
/**
* 自定義 ID 生成器
* ID 生成規(guī)則: ID長(zhǎng)達(dá) 64 bits
*
* | 41 bits: Timestamp (毫秒) | 3 bits: 區(qū)域(機(jī)房) | 10 bits: 機(jī)器編號(hào) | 10 bits: 序列號(hào) |
*/
public class GameUUID{
// 基準(zhǔn)時(shí)間
private long twepoch = 1288834974657L; //Thu, 04 Nov 2010 01:42:54 GMT
// 區(qū)域標(biāo)志位數(shù)
private final static long regionIdBits = 3L;
// 機(jī)器標(biāo)識(shí)位數(shù)
private final static long workerIdBits = 10L;
// 序列號(hào)識(shí)位數(shù)
private final static long sequenceBits = 10L;
// 區(qū)域標(biāo)志ID最大值
private final static long maxRegionId = -1L ^ (-1L << regionIdBits);
// 機(jī)器ID最大值
private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
// 序列號(hào)ID最大值
private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
// 機(jī)器ID偏左移10位
private final static long workerIdShift = sequenceBits;
// 業(yè)務(wù)ID偏左移20位
private final static long regionIdShift = sequenceBits + workerIdBits;
// 時(shí)間毫秒左移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);
}
/**
* 實(shí)際產(chǎn)生代碼的
*
* @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();
}
}
//如果上次生成時(shí)間和當(dāng)前時(shí)間相同,在同一毫秒內(nèi)
if (lastTimestamp == timestamp) {
//sequence自增,因?yàn)閟equence只有10bit,所以和sequenceMask相與一下,去掉高位
sequence = (sequence + 1) & sequenceMask;
//判斷是否溢出,也就是每毫秒內(nèi)超過(guò)1024,當(dāng)為1024時(shí),與sequenceMask相與,sequence就等于0
if (sequence == 0) {
//自旋等待到下一毫秒
timestamp = tailNextMillis(lastTimestamp);
}
} else {
// 如果和上次生成時(shí)間不同,重置sequence,就是下一毫秒開(kāi)始,sequence計(jì)數(shù)重新從0開(kāi)始累加,
// 為了保證尾數(shù)隨機(jī)性更大一些,最后一位設(shè)置一個(gè)隨機(jī)數(shù)
sequence = new SecureRandom().nextInt(10);
}
lastTimestamp = timestamp;
return ((timestamp - twepoch) << timestampLeftShift) | (paddingnum << regionIdShift) | (workerId << workerIdShift) | sequence;
}
// 防止產(chǎn)生的時(shí)間比之前的時(shí)間還要小(由于NTP回?fù)艿葐?wèn)題),保持增量的趨勢(shì).
private long tailNextMillis(final long lastTimestamp) {
long timestamp = this.timeGen();
while (timestamp <= lastTimestamp) {
timestamp = this.timeGen();
}
return timestamp;
}
// 獲取當(dāng)前的時(shí)間戳
protected long timeGen() {
return System.currentTimeMillis();
}
}
使用自定義的這種方法需要注意的幾點(diǎn):
為了保持增長(zhǎng)的趨勢(shì),要避免有些服務(wù)器的時(shí)間早,有些服務(wù)器的時(shí)間晚,需要控制好所有服務(wù)器的時(shí)間,而且要避免NTP時(shí)間服務(wù)器回?fù)芊?wù)器的時(shí)間;在跨毫秒時(shí),序列號(hào)總是歸0,會(huì)使得序列號(hào)為0的ID比較多,導(dǎo)致生成的ID取模后不均勻,所以序列號(hào)不是每次都?xì)w0,而是歸一個(gè)0到9的隨機(jī)數(shù)。
上面說(shuō)的這幾種方式我們可以根據(jù)自己的需要去選擇。在游戲服務(wù)器開(kāi)發(fā)中,根據(jù)自己的游戲類(lèi)型選擇,比如手機(jī)游戲,可以使用簡(jiǎn)單的redis方式,簡(jiǎn)單不容易出錯(cuò),由于這種游戲單服并發(fā)新建id量并不太大,完全可以滿足需要。而對(duì)于大型的世界游戲服務(wù)器,它本身就是以分布式為主的,所以可以使用snowflake的方式,上面的snowflake代碼只是一個(gè)例子,需要自己根據(jù)自己的需求去定制,所以有額外的開(kāi)發(fā)量,而且要注意上述所說(shuō)的注意事項(xiàng)。
以上所述是小編給大家介紹的基于Java代碼實(shí)現(xiàn)游戲服務(wù)器生成全局唯一ID的方法匯總,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
SpringBoot獲取HttpServletRequest的3種方式總結(jié)
這篇文章主要給大家介紹了關(guān)于SpringBoot獲取HttpServletRequest的3種方式,在Spring boot項(xiàng)目中經(jīng)常要用到Servlet的常用對(duì)象如HttpServletRequest request,HttpServletResponse response,HttpSession session,需要的朋友可以參考下2023-08-08
深入理解JAVA中的聚集和組合的區(qū)別與聯(lián)系
下面小編就為大家?guī)?lái)一篇深入理解JAVA中的聚集和組合的區(qū)別與聯(lián)系。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考,一起跟隨小編過(guò)來(lái)看看吧2016-05-05
一文深入理解Java中的java.lang.reflect.InvocationTargetException錯(cuò)誤
這篇文章主要給大家介紹了關(guān)于Java中java.lang.reflect.InvocationTargetException錯(cuò)誤的相關(guān)資料,java.lang.reflect.InvocationTargetException是Java中的一個(gè)異常類(lèi),它通常是由反射調(diào)用方法時(shí)拋出的異常,需要的朋友可以參考下2024-03-03
Java中LambdaQueryWrapper的常用方法詳解
這篇文章主要給大家介紹了關(guān)于Java中LambdaQueryWrapper常用方法的相關(guān)資料,lambdaquerywrapper是一個(gè)Java庫(kù),用于構(gòu)建類(lèi)型安全的Lambda表達(dá)式查詢,需要的朋友可以參考下2023-11-11

