游戏服务器生成全局唯一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的随机数。(本代码参考:http://www.jianshu.com/p/61817cf48cc3)
上面说的这几种方式我们可以根据自己的需要去选择。在游戏服务器开发中,根据自己的游戏类型选择,比如手机游戏,可以使用简单的redis方式,简单不容易出错,由于这种游戏单服并发新建id量并不太大,完全可以满足需要。而对于大型的世界游戏服务器,它本身就是以分布式为主的,所以可以使用snowflake的方式,上面的snowflake代码只是一个例子,需要自己根据自己的需求去定制,所以有额外的开发量,而且要注意上述所说的注意事项。转载请注明来自游戏技术网,本文地址:http://www.youxijishu.com/h-nd-147-2_323.html
游戏服务器生成全局唯一ID的几种方法的更多相关文章
- 如何在高并发分布式系统中生成全局唯一Id
月整理出来,有兴趣的园友可以关注下我的博客. 分享原由,最近公司用到,并且在找最合适的方案,希望大家多参与讨论和提出新方案.我和我的小伙伴们也讨论了这个主题,我受益匪浅啊…… 博文示例: 1. ...
- 如何在高并发分布式系统中生成全局唯一Id(转)
http://www.cnblogs.com/heyuquan/p/global-guid-identity-maxId.html 又一个多月没冒泡了,其实最近学了些东西,但是没有安排时间整理成博文, ...
- (转)如何在高并发分布式系统中生成全局唯一Id
又一个多月没冒泡了,其实最近学了些东西,但是没有安排时间整理成博文,后续再奉上.最近还写了一个发邮件的组件以及性能测试请看 <NET开发邮件发送功能的全面教程(含邮件组件源码)> ,还弄了 ...
- 生成全局唯一ID
在实际业务处理中,有时需要生成全局唯一ID来区别同类型的不同事物,介绍一下几种方式及其C++实现 //获取全局唯一ID //server_id为服务的id,因当同一个服务部署在多个服务器上时,需要区别 ...
- 常见的生成全局唯一id有哪些?他们各有什么优缺点?
分布式系统中全局唯一id是我们经常用到的,生成全局id方法由很多,我们选择的时候也比较纠结.每种方式都有各自的使用场景,如果我们熟悉各种方式及优缺点,使用的时候才会更方便.下面我们就一起来看一下常见的 ...
- 面试官:如何在分布式场景下生成全局唯一 ID?
在分布式系统中,有一些场景需要使用全局唯一 ID ,可以和业务场景有关,比如支付流水号,也可以和业务场景无关,比如分库分表后需要有一个全局唯一 ID,或者用作事务版本号.分布式链路追踪等等,好的全局唯 ...
- 高并发分布式系统中生成全局唯一Id汇总
数据在分片时,典型的是分库分表,就有一个全局ID生成的问题.单纯的生成全局ID并不是什么难题,但是生成的ID通常要满足分片的一些要求: 1 不能有单点故障. 2 以时间为序,或者ID里包含时间 ...
- SnowFlake 生成全局唯一id
public class SnowFlakeUtil { private long workerId; private long datacenterId; private long sequence ...
- 雪花算法生成全局唯一ID
系统中某些场景少不了全局唯一ID的使用,来保证数据的唯一性.除了通过数据库自带的自增id来保证 id 的唯一性,通常为了保证的数据的可移植性会选择通过程序生成全局唯一 id.百度了不少php相关的生成 ...
随机推荐
- 【shell】变量的配置文件
(1)/etc/profile 登录时,会执行.全局(公有)配置,不管是哪个用户,登录时都会读取该文件.(2)/ect/bashrc Ubuntu没有此文件,与之对应的是/ect/bash.b ...
- 51nod1258 序列求和V4
T(n) = n^k,S(n) = T(1) + T(2) + ...... T(n).给出n和k,求S(n). 例如k = 2,n = 5,S(n) = 1^2 + 2^2 + 3^2 + 4^ ...
- FastReport使用总结三——条码简介
FastReport Desinger中支持的Barcode类型如下图所示: 设置其Barcode属性可以实现支持不同的条码类型. 注意: 支持的条码类型说明如下: 总结: 1.通过设置Barcode ...
- (ORA-12899) 10g数据库导入到11g数据库时报错
问题: 10g数据库导入到11g数据库时,部分表的字段会出现ORA-12899的报错,如下: IMP-00019: 由于 ORACLE 错误 12899 而拒绝行 IMP-00003: 遇 ...
- cent os下面的基本配置操作
二,修改Linux分辨率命令行 在root用户模式下,输入$ vi /boot/grub/grub.conf(路径可能会不一样,也可以是 /etc/grub.conf),打开grub.conf文件 我 ...
- PLSQL_Oracle Table Lock表级锁的处理(案例)
20150506 Created By BaoXinjian
- HDU 2087 剪花布条(KMP,不可重叠重复子串)
给KMP传的数组一定要从0开始!! 显然,我们要先把模式串放到前面,之后主串放后面,中间隔开,这样就可以根据前缀数组的性质来求了. 这题和我上一篇博客类似,只不过不可重叠,我看了数据范围不大,所以就开 ...
- CF534A Exam 构造
An exam for n students will take place in a long and narrow room, so the students will sit in a line ...
- 类(class)、构造函数(constructor)、原型(prototype)
类 Class 类的概念应该是面向对象语言的一个特色,但是JavaScript并不像Java,C++等高级语言那样拥有正式的类,而是多数通过构造器以及原型方式来仿造实现.在讨论构造器和原型方法前,我可 ...
- Windows2008防火墙封ip
http://www.bitscn.com/os/windows/201411/406212.html