游戏服务器生成全局唯一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: 序列号 |
import java.security.SecureRandom;/*** 自定义 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就等于0if (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);
游戏服务器生成全局唯一ID的几种方法相关推荐
- 全局唯一递增的id_生成全局唯一id的几种方式
生成全局唯一id的几种方式: 1.uuid生成全球唯一id,生成方式简单粗暴,本地生成,没有网络开销,效率高:缺点长度较长,没有递增趋势性,不易维护,常用于生成token令牌. 2.mysql自带自增 ...
- 高并发分布式系统中生成全局唯一Id汇总
全局唯一ID <高并发分布式系统中生成全局唯一Id汇总> Twitter 方案(Snowflake 算法):41位时间戳+10位机器标识(比如IP,服务器名称等)+12位序列号(本地计数器 ...
- 如何在高并发分布式系统中生成全局唯一Id
又一个多月没冒泡了,其实最近学了些东西,但是没有安排时间整理成博文,后续再奉上.最近还写了一个发邮件的组件以及性能测试请看 <NET开发邮件发送功能的全面教程(含邮件组件源码)> ,还弄了 ...
- 如何在分布式场景下生成全局唯一 ID ?
作者 l 会点代码的大叔(CodeDaShu) 在分布式系统中,有一些场景需要使用全局唯一 ID ,可以和业务场景有关,比如支付流水号,也可以和业务场景无关,比如分库分表后需要有一个全局唯一 ID,或 ...
- Redis生成全局唯一ID
简介: 全局唯一ID生成器是一种在分布式系统下用来生成全局唯一ID的工具 特性: 唯一性 高性能 安全性 高可用 递增性 生成规则: 有时为了增加ID的安全性,我们可以不直接使用Redis自增的数值, ...
- 生成唯一id的几种方法
生成唯一id的几种方法 生成唯一id的方式有很多,UUID,自动增长列,雪花算法,redis等等. 生成id的要求: 全局唯一 趋势递增 效率高(生成.使用.索引) 控制并发 1.雪花算法(twitt ...
- snowflake mysql_SnowFlake 生成全局唯一id
public classSnowFlakeUtil {private longworkerId;private longdatacenterId;private long sequence = 0L; ...
- 雪花算法及运用PHP,雪花算法生成全局唯一ID,参考了下网上雪花算法生成规则,机器ID和序列号自动获取 理论上毫秒可生成 1024*4096个唯一ID
任务要求毫秒生成10000个唯一ID 研究了下twitter/snowflake的算法思想: 参考了下网上雪花算法生成规则,把数据中心和机器编号整合一起,变成10位机器ID, 机器ID和序列号自动获取 ...
- php给留言分配id_如何使用php生成唯一ID的4种方法
php生成唯一ID的应用场景非常普遍,如临时缓存文件名称,临时变量,临时安全码等,uniqid()函数基于以微秒计的当前时间,生成一个唯一的 ID.由于生成唯一ID与微秒时间关联,因此ID的唯一性非常 ...
最新文章
- jq处理 php数组,jQuery数组处理方法汇总_jquery
- 在ECS上使用Windows “跨区卷”、“条带卷”讨论以及扩容操作
- RedHat开机启动流程
- 仿Twitter的公告效果
- 网站内容收录除了原创性和质量其他因素也少不了
- 从杂技表演到日剧BGM(r12笔记第23天)
- 你或许还未听说过的一些ASP.NET 2.0要诀 [转]
- 一旦有辞职念头就干不长了吗_每天都有辞职不想上班的冲动,你有吗?
- flask框架基本使用(2)(响应与重定向)
- c语言中调试时go的作用,C语言调用GO
- Sentinel配置规则持久化
- 《深入体验Java Web开发内幕——核心基础》目录
- java 定义方法_java如何定义方法
- 每个人都可以创业成功,不要急于求成,慢慢来
- 为什么不能完全相信自动驾驶?
- Python 用下划线作为变量前缀和后缀指定特殊变量
- 启动mongodb时发现错误libcrypto.so.10
- CDA数据分析师-LEVEL I考试-分享
- kernel更改开机画面
- Codeforces 106 Buns【多重背包】