欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

基于Java代码实现游戏服务器生成全局唯一ID的方法汇总

程序员文章站 2024-03-11 22:03:07
在服务器系统开发时,为了适应数据大并发的请求,我们往往需要对数据进行异步存储,特别是在做分布式系统时,这个时候就不能等待插入数据库返回了取自动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的随机数。

上面说的这几种方式我们可以根据自己的需要去选择。在游戏服务器开发中,根据自己的游戏类型选择,比如手机游戏,可以使用简单的redis方式,简单不容易出错,由于这种游戏单服并发新建id量并不太大,完全可以满足需要。而对于大型的世界游戏服务器,它本身就是以分布式为主的,所以可以使用snowflake的方式,上面的snowflake代码只是一个例子,需要自己根据自己的需求去定制,所以有额外的开发量,而且要注意上述所说的注意事项。

以上所述是小编给大家介绍的基于java代码实现游戏服务器生成全局唯一id的方法汇总,希望对大家有所帮助