游戏服务器中的ID生成策略
来源:互联网 发布:mac itunes铃声 编辑:程序博客网 时间:2024/04/30 03:25
原文出处:http://bafeimao.net/2013/06/17/generate-unique-id-in-distributed-system/
常见的游戏架构和部署中,很多采用分服的一服一库的部署模式,而且运营过程中经常会有合服要求。
所谓合服就是随着游戏单服活跃用户数不断的下降,为节省资源和提高用户活跃度,在运营过程中将两个或多个服务数据合并到一起的做法。
合服时经常碰到的一个最大的问题就是:相同表中的记录不能有ID重复的情况发生。如果有重复ID的情况,那么合服时就要写特定的脚本去处理重复ID的问题,这会比较麻烦。
要解决不能有重复ID的情况,最好的办法是让每台服务器上出生的ID都是全局唯一的。
1. 使用UUID
首先想到的是UUID,它天生就是全局唯一的并运用于分布式计算中,UUID是16字节128位长的数字,通常以36字节的字符串表示:
UUID: 21e58ba8-5c7e-4524-ae8f-079faf987b3b
由于它太长而且可读性差,因此不用这货。
2. Flickr的Ticket Server方案
Flickr的实现方式比较简单巧妙,它是利用两台Ticket Server按指定的步长递增生成ID。将两台服务器的初始偏移值设置不一样,会让两台服务器交替生成ID而不会产生冲突,其配置如下:
1234567
TicketServer1:auto-increment-increment = 2auto-increment-offset = 1TicketServer2:auto-increment-increment = 2auto-increment-offset = 2
这样,TicketServer1将一直生成”奇数ID”,TicketServer2生成“偶数ID”
两台Ticket Server做负载均衡,使用Round-Robin策略轮询访问,避免了单点故障,如果两台不够也可以扩展至更多台。
关于该技术方案的细节请见官方博客原文(要翻墙),也可见这篇译文
该方案的缺点:
TicketServer会成为性能瓶颈,当负载较重时数据库写压力较大,对于Flickr这种读频繁写较少的应用来说也许够用,但是对于游戏服务器来说,会有短期内密集生成ID的需求,该方案似乎应付这样的需求会显得有点吃力。
生成的ID不带时间信息,IDC机房信息,或机器ID等信息
3. MongoDB的ObjectId方案
ObjectId使用了12个字节来存储,每个字节用16进制字符串来表示,所以的表示形式是由24个字符组成,如:507f1f77bcf86cd799439011
ObjectId组成部分有:
- 4字节:UNIX时间戳
- 3字节:表示运行MongoDB的机器
- 2字节:表示生成此_id的进程
- 3字节:由一个随机数开始的计数器生成的值
该方案的好处:
ObjectId是可按时间排序的并且带机器ID的,根据ObjectId可以反推得到时间戳和机器ID信息,而且比UUID短。
但是,我们希望最好能够用长整形来表示ID,因为那样既短小不占空间又容易识别。
4. Twitter的Snowflake算法
Snowflake是twitter为解决在分布式系统中高效生成全局ID所提出的解决方案,该算法使用Scale实现的并且已开源,地址在:https://github.com/twitter/snowflake
其原理是用一个64位的长整形来储存ID,其内部位的表示如下:
毫秒时间(41bits)+ datacenterId(5bits) + workerId(5bits) + sequence(12bits)
该方案的优点明显:
- 生成的ID能够按时间排序,并且可带机器ID,IDC等信息
- 用64位表示的,储存空间小,并且易读
- 每台机器可独立生成ID,无需中心节点,无单点故障风险
- 生成ID高效并且速度快,如果用12位表示sequence,那么每毫秒将可以生成1024个ID
在游戏应用中,我们可将内部位调整为如下表现形式:
毫秒时间(37bits)+ serverId (20bits) + sequence (16bits)
说明:
- 37bits的毫秒时间可供我们使用约93年
- serverId最大可以表示为2^20(1048576)个服务器
- 每毫秒最多可以生成2^16(65535)个ID
总结
在分布式应用或游戏服务器中,Snowflake作为全局唯一ID的生成算法具有生产ID迅速高效,无单点故障,ID带丰富的信息,并且可按时间排序等诸多特性,是可靠高效的方案。
附录
Twitter是用Scale实现该算法并开源的,其中Java版本如下:
* @author zhujuan * From: https://github.com/twitter/snowflake * An object that generates IDs. * This is broken into a separate class in case * we ever want to support multiple worker threads * per process */public class IdWorker { protected static final Logger LOG = LoggerFactory.getLogger(IdWorker.class); private long workerId; private long datacenterId; private long sequence = 0L; private long twepoch = 1288834974657L; private long workerIdBits = 5L; private long datacenterIdBits = 5L; private long maxWorkerId = -1L ^ (-1L << workerIdBits); private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); private long sequenceBits = 12L; private long workerIdShift = sequenceBits; private long datacenterIdShift = sequenceBits + workerIdBits; private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; private long sequenceMask = -1L ^ (-1L << sequenceBits); private long lastTimestamp = -1L; public IdWorker(long workerId, long datacenterId) { // sanity check for workerId if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); } if (datacenterId > maxDatacenterId || datacenterId < 0) { throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId)); } this.workerId = workerId; this.datacenterId = datacenterId; LOG.info(String.format("worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d", timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId)); } public synchronized long nextId() { long timestamp = timeGen(); if (timestamp < lastTimestamp) { LOG.error(String.format("clock is moving backwards. Rejecting requests until %d.", lastTimestamp)); throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); } if (lastTimestamp == timestamp) { sequence = (sequence + 1) & sequenceMask; if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); } } else { sequence = 0L; } lastTimestamp = timestamp; return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence; } protected long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } protected long timeGen() { return System.currentTimeMillis(); }}
- 游戏服务器中的ID生成策略
- 服务器开发之用户ID生成策略
- 分布式系统中的ID生成策略
- JPA ID生成策略
- JPA ID生成策略
- JPA ID生成策略
- Hibernate ID 生成策略
- hibernate id 生成策略
- hibernate ID生成策略
- ID生成策略
- Hibernate ID 生成策略
- Hibernate id生成策略
- ID生成策略
- JPA ID生成策略
- JPA ID生成策略
- 生成ID策略
- hibernate---ID生成策略
- ID生成策略
- Android-architecture之MVC、MVP、MVVM、Data-Binding
- Android 获取手机本地图片所在的位置
- failed to obtain a cell from its dataSource 解决方案
- “58信息定时发布器” 编写纪要
- [从头学声学] 第213节 声调的频谱
- 游戏服务器中的ID生成策略
- iOS自定义NavigationBar
- 试试
- ABAP源代码保护
- 将链表中的数据保存到二进制文件和读出数据
- conrestoneSVN的登录,使用自行解决,嘻嘻
- SUNDIALS-CLN:An Arbitrary Precision Extension of the CVODE Library
- Mac OS中配置环境变量及vim的简单使用
- SuperGarden mySQL常用命令