基于Java代码实现游戏服务器生成全局唯一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的方法汇总,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • Javascript生成全局唯一标识符(GUID,UUID)的方法

    全局唯一标识符(GUID,Globally Unique Identifier)也称作 UUID(Universally Unique IDentifier) . GUID是一种由算法生成的二进制长度为128位的数字标识符.GUID 的格式为"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",其中的 x 是 0-9 或 a-f 范围内的一个32位十六进制数.在理想情况下,任何计算机和计算机集群都不会生成两个相同的GUID. GUID 的总数达到了2^128(3

  • java实现随机生成UUID

    java实现随机生成UUID public class IDGenerator { private static long num=0; /** * 随机生成UUID * @return */ public static synchronized String getUUID(){ UUID uuid=UUID.randomUUID(); String str = uuid.toString(); String uuidStr=str.replace("-", ""

  • Java4Android开发教程(五)java的基本数据类型特征

    java的数据类型分为基本数据类型和引用数据类型.   基本数据类型分为数值型.字符型(char).布尔型(boolean)   数值型变量   1.整数型 类型 占用存储空间 表示范围 byte 1字节Byte -128~127 short 2字节 -215~215-1 int 4字节 -231~231-1 long 8字节 -263~263-1 Java的整型常量的三种表示形式: -   十进制整数,如12,-31   - 八进制整数,以0开头,如012   - 十六进制数,以0x或0X开头

  • Java4Android开发教程(一)JDK安装与配置

    1.JDK:Java Development Kit,java开发工具包.http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 2.安装JDK,一路Next.默认安装路径:C:\Program Files\Java 3.配置环境变量path和classpath( windows中) path:计算机→属性→高级系统设置→高级→环境变量→找到path,点编辑→移动到最后,添加英文的分号

  • java自动生成ID号的方法

    本文实例讲述了java自动生成ID号的方法.分享给大家供大家参考.具体实现方法如下: import java.util.UUID; public class SystemAttribute { public static String getUUID() { return UUID.randomUUID().toString().replace("-",""); } } 希望本文所述对大家的java程序设计有所帮助.

  • PHP-Java-Bridge使用笔记

    /**============================================================ * @author  ken(695093513@qq.com) * @date    2014-09-09 * ==============================================================**/ /**============================================================

  • android工程下不能运行java main程序的解决方法

    右击有main方法的类 ===> Run as ===> Run Configurations ===>双击java application ===> 单击有main方法的类 ===>选中classpath选项卡 ===> remove掉Bootstrap Entries下的android.jar ===> 然后点击advanced ===> Add Library ===>JRE System Library ===>next ===>最

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

    在服务器系统开发时,为了适应数据大并发的请求,我们往往需要对数据进行异步存储,特别是在做分布式系统时,这个时候就不能等待插入数据库返回了取自动id了,而是需要在插入数据库之前生成一个全局的唯一id,使用全局的唯一id,在游戏服务器中,全局唯一的id可以用于将来合服方便,不会出现键冲突.也可以将来在业务增长的情况下,实现分库分表,比如某一个用户的物品要放在同一个分片内,而这个分片段可能是根据用户id的范围值来确定的,比如用户id大于1000小于100000的用户在一个分片内.目前常用的有以下几种:

  • 一种简单的ID生成策略: Mysql表生成全局唯一ID的实现

    生成全局ID的方法很多, 这里记录下一种简单的方案: 利用mysql的自增id生成全局唯一ID. 1. 创建一张只需要两个字段的表: CREATE TABLE `guid` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `stub` char(1) NOT NULL DEFAULT '' COMMENT '桩字段,占坑的', PRIMARY KEY (`id`), UNIQUE KEY `uk_stub` (`stub`) -- 将 st

  • Redis生成全局唯一ID的实现方法

    目录 简介: 特性: 生成规则: ID生成类: 测试类: 简介: 全局唯一ID生成器是一种在分布式系统下用来生成全局唯一ID的工具 特性: 唯一性 高性能 安全性 高可用 递增性 生成规则: 有时为了增加ID的安全性,我们可以不直接使用Redis自增的数值,而是拼接一些其他信息 ID组成部分: 符号位:1bit,永远为0 时间戳:31bit,以秒为单位,可以使用69年 序列号:32bit,秒内的计数器,支持每秒产生2^32个不同ID ID生成类: package com.example.util

  • PHP实现Snowflake生成分布式唯一ID的方法示例

    前言 Twitter 的 snowflake 在分布式生成唯一 UUID 应用还是蛮广泛的,基于 snowflake 的一些变种的算法网上也有不少.使用 snowflake 生成 UUID 很多都是在分布式场景下使用,我看了下网上有其中有几篇 PHP 实现的都没有考虑到线程安全.现在 PHP 有了 Swoole 的锁和协程的加持,对于我们开发线程安全和高并发模拟还是很方便的,这里用 PHP 结合 Swoole 来学习下实现最简单的 snowflake. 先来看以下 snowflake 的结构:

  • Java几种分布式全局唯一ID生成方案

    目录 缘起 常见方案 UUID 数据库自增键 TDDL Sequence Leaf-segment 类雪花算法 时间回拨问题 Leaf-snowflake Seata UUID 总结 缘起 在分布式微服务系统架构下,有非常多的情况我们需要生成一个全局唯一的 ID 来做标识,比如: 需要分库分表的情况下,分库或分表会导致表本事的自增键不具备唯一性. 较长的业务链路涉及到多个微服务之间的调用,需要一个唯一 ID 来标识比如订单 ID.消息 ID.优惠券 ID.分布式事务全局事务 ID. 对于全局唯一

  • 基于Java代码实现支付充值的通用流程

    废话不多说了,直接给大家贴java代码了. 具体代码如下所示: /*支付流程*/ /****Controller.java 代码如下:*/ @RequestMapping(value = "/paySubmit.htm", method = RequestMethod.POST) public ModelAndView paySubmit(HttpServletRequest request, HttpServletResponse response, @RequestParam Ma

  • 基于Java代码配置MyBatis Generator

    使用MyBatis Generator生成器时,有时候没办法使用xml形式的配置文件,比如将Maven项目设置成pom打包方式(<packaging>pom</packaging>)!由于Maven的工作机制对于打包方式为pom的项目是不会输出jar包或war包和resources内容,所以放在resources目录下或放在源码目录下的xml文件就没法读取了,就算你在pom.xml文件中明确有如下配置也没有用的: <build> <resources> &l

  • Redis生成分布式系统全局唯一ID的实现

    目录 分布式系统全局唯一ID 基于Redis INCR 命令生成分布式全局唯一ID 采用Redis生成商品全局唯一ID 分布式系统全局唯一ID 在互联网系统中,并发越大的系统,数据就越大,数据越大就越需要分布式,而大量的分布式数据就越需要唯一标识来识别它们. 例如淘宝的商品系统有千亿级别商品,订单系统有万亿级别的订单数据,这些数据都是日渐增长,传统的单库单表是无法支撑这种级别的数据,必须对其进行分库分表:一旦分库分表,表的自增ID就失去了意义:故需要一个全局唯一的ID来标识每一条数据(商品.订单

  • springboot集成redis并使用redis生成全局唯一索引ID

    部署redis Windows下搭建Reids本地集群,可参考https://www.jb51.net/article/242520.htm springboot集成 redis pom文件 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency&

  • Java中生成唯一ID的方法示例

    有时我们不依赖于数据库中自动递增的字段产生唯一ID,比如多表同一字段需要统一一个唯一ID,这时就需要用程序来生成一个唯一的全局ID. UUID 从Java 5开始, UUID 类提供了一种生成唯一ID的简单方法.UUID是通用唯一识别码 (Universally Unique Identifier)的缩写,UUID来源于OSF(Open Software Foundation,开源软件基金会)的DCE(Distributed Computing Environment,分布式计算环境)规范.UU

随机推荐