前言:
而今姐妹们对“java生成唯一的id”大致比较关注,咱们都想要分析一些“java生成唯一的id”的相关知识。那么小编同时在网摘上收集了一些关于“java生成唯一的id””的相关知识,希望看官们能喜欢,同学们一起来了解一下吧!在分布式系统中,生成全局唯一ID是一个很基础的需求,相信每个项目都会结合自身实际情况,有自己的一套ID生成算法。本文收集了几种常见的ID生成方式,希望可以给大家一些参考。
利用数据库自增ID实现
利用数据库字段本身的auto_increment功能生成自增ID是最常见的做法之一。
这种做法的优势是实现简单,成本低,生成的ID是自增且步长固定的。
当然,在实际游戏中,数据库往往是要分库分表的,在实际应用中的一种常见的做法是使用逻辑服务器ID*偏移量作为自增初始值,例如1服的玩家ID从10000000开始自增,2服的玩家ID从20000000开始自增……这样在分库的同时也保证了ID的唯一性,方便日后服务器的合并。
当然,这种方式的缺陷也很明显,那就是数据库的IO效率往往是比较低的,如果业务过于依赖数据库的话,容易产生瓶颈。因此,这种方式通常不是孤立存在的,而是和各种缓存技术结合使用。
利用单点来统一生成全局ID
分布式系统最大的问题在于程序跑在各个独立的进程上,无法保证不同进程各自生成ID的唯一性。因此,一个顺理成章的思路就是由统一的ID生成模块来专门进行全局的ID生成。而且,这个模块最好是单线程的,这样可以达到最高的执行效率。
满足这个条件的现成组件很容易想到,那就是redis。
Redis的incr命令是原子自增,可以实现该需求。在实际业务中,整个分布式系统可以使用一个公共redis来实现唯一自增ID需求,而且,现在的redis直接支持主从复制,数据的安全性也有一定保证。这种方式的好处就是实现成本低,部署一个redis即可,redis的吞吐量也是经得起考验的。
不过,incr命令的一个问题是不够灵活,只能自增加1,当业务有特殊需求例如手动控制步长时,incr命令无法直接满足需求。如果自己实现全局ID管理模块的话,工作量较大,还需要考虑各种安全性问题,例如单点服务挂掉之后如何处理、脏数据如何处理,如果考虑不周的话很容易出bug或者成为瓶颈,实现起来成本会略高一些。
使用UUID生成全局唯一ID
UUID是Universally Unique ID的缩写。是一种用于分布式系统的全局ID生成思想,最常见的UUID实现方式之一是微软的GUID规则。
UUID的基本思想是由以下几个部分经过算法组合之后生成一个ID:
当前时间时钟序列机器识别号(通常是mac地址)与当前进程相关的一个序列
从组成UUID的四个部分就可以看出,在分布式系统中,哪怕是不同进程部署在同一台机器上,也可以保证每个进程生成的UUID是唯一的。
UUID有不同的实现方式,也有很多现成的API可以使用,成本低,安全性和稳定性都有保证。而且,UUID是直接在本地生成的,生成时无需像前两种方式一样需要远程调用,在速度上是最快的。
不过UUID最大的问题在于,它太长了,甚至超过了64位长整型的上限。如果不经过二次处理的话,它在入库的时候是一个字符串,字符串在进行插入和查询时的效率都远低于数字,而且占用的磁盘空间也更大,不够完美。
使用UUID的变体
对比下上面的几种方式,我们可以看出综合考虑IO效率、稳定性、实现成本这几个方面的话,UUID是最好的思路,如果能解决长度问题,那就不失为一种非常完美的生成算法了。
为了解决这个问题,行业内产生了很多UUID的变体实现。例如Twitter曾经采用的一种方案,将所有信息压缩到一个64位长整形内,他们还给方案起了一个很好听的名字叫做snowflake算法。
Snowflake的思路是使用一个64位long型的数据来生成ID。64位的每一位使用方式如上图所示:
第1位是符号位,固定是0,无法用到(因为这玩意最初是用java写的,java里面没有unsigned数据类型)。
2~42位记录当前时间戳,精确到毫秒级。用41位毫秒级来记录时间戳的话,可以保证69.73年内ID不会重复,这个时长对九成九的产品来说是够用了。实际上, 我觉得在游戏业务中还可以进一步缩减,39位20年就足够绝大多数游戏项目用到公司倒闭了。
接下来10位是节点ID。Twitter是一款互联网产品,只有分布式的不同数据节点,没有服务器概念。在游戏产品中可以把这10位作为逻辑服务器的ID,10位数最多支持1024个服。如果觉得不够,再加3位,8192个服,王者荣耀都没这么多服,管够用了。
最后12位是节点内的ID,在各个进程内部自己循环累加即可。
从上面的分析可以看出,snowflake策略具备UUID的所有优点,实现成本也不高,只要掌握一些位运算基础即可,最多可以支持每毫秒4096个新增,相信还没有哪个游戏能达到这个新增量级的。
不过,对当代手游来说,snowflake还存在两个难以搞定的问题:
首先,需要考虑时钟往回调的问题。当各个节点的时钟是各自向前走的时候,该问题是不会出现的,但是在分布式系统中,为了保证各个硬件的时钟同步,势必会用到时钟校准,在校准时就可能会出现时钟回调,可能因此导致ID重复。处理时钟回调也不难,以游戏的注册量级,一般来说最多重试个两三次即可
第二个问题就比较难搞定了,那就是客户端的数字精度问题。现代手游一般采用脚本语言来做客户端逻辑,而有些脚本语言是不支持64位整数的。例如在JS中,没有int、long、float、double这些类型,所有的数字都统一是number类型,而这个number的上限,是53位,原始的snowflake肯定是没法用的,需要用snowflake的思路进一步将ID压缩到53位以内。
本文介绍了几种分布式系统中常用的方法,对游戏业务来说,这几种方法各有利弊。在现实中,很多时候就是这样,没有完美的方案,只有最适合当前情况的方案。在做项目的过程中,不要盲目迷信,选择最适合自己项目的才是最好的(顺便吐个槽,游戏业盲目迷信spring和protocol buffer的太多了)。
标签: #java生成唯一的id