在服务器系统开发时,为了适应数据大并发的请求,咱们每每须要对数据进行异步存储,特别是在作分布式系统时,这个时候就不能等待插入数据库返回了取自动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