概述系统唯一ID是我们在设计系统时经常会遇到的问题,也经常为这个问题纠结。ID的生成方式有很多种,适应不同的场景、需求和性能要求。因此,一些比较复杂的系统会有多种ID生成策略。分布式ID的特点唯一性:确保生成的ID在全网唯一。有序递增:保证生成的ID对于某个用户或业务按一定数量递增。高可用:确保ID可以随时正确生成。带时间:ID中包含时间,一眼就能知道是哪天的交易。下面介绍几种分布式ID生成方案。1、数据库自增长序列或字段最常见的方式。使用数据库,整个数据库是唯一的。优点:1)代码简单方便,性能尚可。2)数字身份证是自然排序的,这对于分页或者需要排序的结果很有帮助。缺点:1)不同数据库的语法和实现不同,在数据库迁移或支持多个数据库版本时需要处理。2)在单库或者读写分离或者一主多从的情况下,只能生成一个主库。存在单点故障的风险。3)性能达不到要求时,难以扩展。4)如果遇到多个系统需要合并或者涉及到数据迁移,那将是相当痛苦的。5)分表和分库的时候会有麻烦。优化方案:对于主库单点,如果有多个主库,每个主库设置的起始数不同,步长相同,可以是主库个数。例如:Master1生成1、4、7、10,Master2生成2、5、8、11,Master3生成3、6、9、12。这样可以高效生成集群内唯一ID,负载ID生成数据库操作的次数也可以大大减少。2、UUID的常用方式。它可以使用数据库或程序生成,并且通常在世界上是唯一的。优点:1)编码简单方便。2)生成ID的性能非常好,基本不会出现性能问题。3)全球唯一,可轻松应对数据迁移、系统数据合并或数据库变更。缺点:1)没有排序,趋势不能保证上升。2)UUID往往使用字符串存储,查询效率比较低。3)存储空间比较大。如果是海量数据库,还需要考虑存储容量。4)传输数据量大5)不可读。3.批量生成ID根据需要一次批量生成多个ID。每一代都需要访问数据库,修改数据库为最大ID值,并在内存中记录当前值和最大值。优点:避免每次生成ID都需要访问数据库带来压力,提高性能缺点:属于本地生成策略,存在单点故障,服务重启导致ID不连续4.Redis生成使用数据库生成ID时的ID。在需要时尝试使用Redis生成ID。这个主要是依赖Redis是单线程的,所以也可以用来生成全局唯一的ID。可以用Redis原子操作INCR和INCRBY来实现。RedisCluster可用于更高的吞吐量。假设一个集群中有5个Redis。每个Redis可以初始化的值分别为1、2、3、4、5,然后步长为5。每个Redis生成的ID为:A:1、6、11、16、21B:2,7,12,17,22C:3,8,13,18,23D:4,9,14,19,24E:5,10,15,20,25,决定加载到哪台机器就好了,将来很难做出改变。但是3-5台服务器基本可以满足服务器的需求,而且都可以获得不同的ID。但是步长和初始值必须事先需要。使用Redis集群还可以解决单点故障问题。另外,每天用Redis生成从0开始的序号比较合适。比如订单号=日期+当天的自增号。每天可以在Redis中生成一个Key,使用INCR累积。优点:1)不依赖于数据库,灵活方便,性能优于数据库。2)数字身份证是自然排序的,这对于分页或者需要排序的结果很有帮助。缺点:1)如果系统中没有Redis,需要引入新的组件,增加系统的复杂度。2)编码和配置的工作量比较大。5.Twitter的snowflake算法(目前我们正在使用)snowflake是Twitter开源的分布式ID生成算法,结果是一个长ID。雪花算法会生成不高于19位的有序Long整数,多用于分布式环境中的数据主键。核心思想是:用41bit作为毫秒数,10bit作为机器的ID(5bit为数据中心,5bit为机器ID),12bit作为毫秒内的序号(意思是每个节点可以generate4096IDs),末尾有个符号位,一直为0。雪花算法可以根据自己项目的需要修改。比如预估未来数据中心的数量,每个数据中心的机器数量,可能的毫秒并发数,调整算法需要的比特数。优点:1)不依赖于数据库,灵活方便,性能优于数据库。2)ID在单机上是按时间递增的。缺点:在单机上是增量的,但是由于涉及分布式环境,每台机器上的时钟不能完全同步,有时可能会出现不是全局增量的情况。6.使用zookeeper生成唯一ID。Zookeeper主要通过其znode数据版本生成序号。它可以生成32位和64位数据版本号。客户端可以使用这个版本号作为唯一的序列号。Zookeeper很少用于生成唯一ID。主要是需要依赖zookeeper,需要分步调用API。如果竞争比较多,就需要考虑使用分布式锁。因此在高并发的分布式环境下性能并不理想。7、MongoDB的ObjectIdMongoDB的ObjectId类似于雪花算法。它被设计成轻量级的,不同的机器可以很容易地用全球唯一的相同方法生成它。MongoDB从一开始就被设计成一个分布式数据库,处理多个节点是一个核心需求。使其更容易在分片环境中生成。在MongoDB中,我们经常会接触到一个自动生成的字段:“_id”,其类型为ObjectId。之前我们在使用MySQL等关系型数据库时,主键设置为自增。但是在分布式环境下,这种方式是行不通的,会产生冲突。为此,mongodb使用一种称为ObjectId的类型作为主键。ObjectId是一个12字节的BSON类型字符串。按字节顺序,一次代表:4个字节:UNIX时间戳3个字节:表示运行MongoDB的机器2个字节:表示生成这个_id的进程3个字节:一个以随机数开头的计数器生成的值确保生成的ObjectId同一台机器上的多个并发进程是唯一的,接下来的两个字节来自生成ObjectId的进程标识符(PID)。ObjetId的12个字节中的前9个字节保证同一秒内不同机器和进程生成的ObjectId是唯一的。最后3个字节是自增计数器,保证同一秒同一进程生成的ObjectId也不同。每个进程在同一秒内最多允许有2563(16777216)个不同的ObjectId。
