龙空技术网

高并发订单全局ID如何生成

小王子1680 258

前言:

而今朋友们对“物品id生成算法有哪些”大概比较关心,我们都需要了解一些“物品id生成算法有哪些”的相关内容。那么小编也在网络上收集了一些对于“物品id生成算法有哪些””的相关资讯,希望小伙伴们能喜欢,看官们一起来学习一下吧!

全局唯一id介绍

系统唯一id是我们在设计阶段常常遇到的问题。在复杂的分布式系统中,几乎都需要对大量的数据和消息进行唯一标识。在设计初期,我们需要考虑日后数据量的级别,如果可能会对数据进行分库分表,那么就需要有一个全局唯一id来标识一条数据或记录。生成唯一id的策略有多种,但是每种策略都有它的适用场景、优点以及局限性。

全局唯一id特点:

全局唯一性:不能出现重复的ID号,既然是唯一标识,这是最基本的要求;趋势递增:在MySQL InnoDB引擎中使用的是聚集索引,由于多数RDBMS使用B-tree的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能;单调递增:保证下一个ID一定大于上一个ID,例如事务版本号、IM增量消息、排序等特殊需求;信息安全:如果ID是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定URL即可;如果是订单号就更危险了,绝对可以直接知道我们一天的单量。所以在一些应用场景下,会需要ID无规则、不规则;高可用性:同时除了对ID号码自身的要求,业务还对ID号生成系统的可用性要求极高,想象一下,如果ID生成系统瘫痪,这就会带来一场灾难。所以不能有单点故障;分片支持:可以控制ShardingId。比如某一个用户的文章要放在同一个分片内,这样查询效率高,修改也容易;长度适中。基于数据库自增或者序列生成订单号

最常见的一种生成id方式。利用数据库本身来进行设置,在全数据库内保持唯一。

【优点】

非常简单。利用现有数据库系统的功能实现,成本小,代码简单,性能可以接受。ID号单调递增。可以实现一些对ID有特殊要求的业务,比如对分页或者排序结果这类需求有帮助。

【缺点】

强依赖DB。不同数据库语法和实现不同,数据库迁移的时候、多数据库版本支持的时候、或分表分库的时候需要处理,会比较麻烦。当DB异常时整个系统不可用,属于致命问题。单点故障。在单个数据库或读写分离或一主多从的情况下,只有一个主库可以生成。有单点故障的风险。数据一致性问题。配置主从复制可以尽可能的增加可用性,但是数据一致性在特殊情况下难以保证。主从切换时的不一致可能会导致重复发号。难于扩展。在性能达不到要求的情况下,比较难于扩展。ID发号性能瓶颈限制在单台MySQL的读写性能。

【部分优化方案】数据库集群如何考虑数据库自增唯一性

注意:在数据库集群环境下,默认自增方式存在问题,因为都是从1开始自增,可能会存在重复,应该设置每台不同数据库自增的间隔方式不同。

MySQL+MyCat+ZooKeeper

如果大家分库分表工具恰好使用的是 MyCat,那么结合 Zookeeper 也能很好的实现主键全局自增。

MyCat 作为一个分布式数据库中间,屏蔽了数据库集群的操作,让我们操作数据库集群就像操作单机版数据库一样,对于主键自增,它有自己的方案:

通过本地文件实现通过数据库实现通过本地时间戳实现通过分布式 ZK ID 生成器实现通过 ZK 递增方式实现

这里我们主要来看方案 4。

配置步骤如下:

首先修改主键自增方式为 4 ,4 表示使用 zookeeper 实现主键自增。

server.xml

配置表自增,并且设置主键

schema.xml

设置主键自增,并且设置主键为 id 。

配置 zookeeper 的信息

在 myid.properties 中配置 zookeeper 信息:

配置要自增的表

sequence_conf.properties

注意,这里表名字要大写。

TABLE.MINID 某线程当前区间内最小值TABLE.MAXID 某线程当前区间内最大值TABLE.CURID 某线程当前区间内当前值文件配置的MAXID以及MINID决定每次取得区间,这个对于每个线程或者进程都有效文件中的这三个属性配置只对第一个进程的第一个线程有效,其他线程和进程会动态读取 ZK重启 MyCat 测试

最后重启 MyCat ,删掉之前创建的表,然后创建新表进行测试即可。

这种方式就比较省事一些,而且可扩展性也比较强,如果选择了 MyCat 作为分库分表工具,那么这种不失为一种最佳方案。

前面介绍这两种都是在数据库或者数据库中间件层面来处理主键自增,我们 Java 代码并不需要额外工作。

接下来我们再来看几种需要在 Java 代码中进行处理的方案。

利用全球全球唯一UUID生成订单号

UUID 的标准型式包含 32 个 16 进制数字,以连字号分为五段,形式为 8-4-4-4-12 的 36 个字符,这个是 Java 自带的,用着也简单,最大的优势就是本地生成,没有网络消耗,但是但凡在公司做开发的小伙伴都知道这个东西在公司项目中使用并不多。原因如下:

字符串太长,对于 MySQL 而言,不利于索引。UUID 的随机性对于 I/O 密集型的应用非常不友好!它会使得聚簇索引的插入变得完全随机,使得数据没有任何聚集特性。信息不安全:基于 MAC 地址生成 UUID 的算法可能会造成 MAC 地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。

因此,UUID 并非最佳方案。

【部分优化方案】

为了解决UUID不可读, 可以使用UUID to Int64的方法 。为了解决UUID无序的问题, NHibernate在其主键生成方式中提供了Comb算法(combined guid/timestamp)。保留GUID的10个字节,用另6个字节表示GUID生成的时间(DateTime)。基于Redis生成生成全局ID策略Twitter的Snowflake算法生成全局ID

【优点】

稳定性高,不依赖于数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的。灵活方便,可以根据自身业务特性分配bit位。单机上ID单调自增,毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。

【缺点】

强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。ID可能不是全局递增。在单机上是递增的,但是由于涉及到分布式环境,每台机器上的时钟不可能完全同步,也许有时候也会出现不是全局递增的情况。基于Zookeeper生成全局ID

zookeeper主要通过其znode数据版本来生成序列号,可以生成32位和64位的数据版本号,客户端可以使用这个版本号来作为唯一的序列号。

很少会使用zookeeper来生成唯一ID。主要是由于需要依赖zookeeper,并且是多步调用API,如果在竞争较大的情况下,需要考虑使用分布式锁。因此,性能在高并发的分布式环境下,也不甚理想。

LEAF

Leaf 是美团开源的分布式 ID 生成系统,最早期需求是各个业务线的订单 ID 生成需求。在美团早期,有的业务直接通过 DB 自增的方式生成 ID,有的业务通过 Redis 缓存来生成 ID,也有的业务直接用 UUID 这种方式来生成 ID。以上的方式各自有各自的问题,因此美团决定实现一套分布式 ID 生成服务来满足需求目前 Leaf 覆盖了美团点评公司内部金融、餐饮、外卖、酒店旅游、猫眼电影等众多业务线。在4C8G VM 基础上,通过公司 RPC 方式调用,QPS 压测结果近 5w/s,TP999 1ms(TP=Top Percentile,Top 百分数,是一个统计学里的术语,与平均数、中位数都是一类。TP50、TP90 和 TP99 等指标常用于系统性能监控场景,指高于 50%、90%、99% 等百分线的情况)。

目前 LEAF 的使用有两种不同的思路,号段模式和 SNOWFLAKE 模式,你可以同时开启两种方式,也可以指定开启某种方式(默认两种方式为关闭状态)。

我们从 GitHub 上 Clone LEAF 之后,它的配置文件在 leaf-server/src/main/resources/leaf.properties 中,各项配置的含义如下:

可以看到,如果使用号段模式,需要数据库支持;如果使用 SNOWFLAKE 模式,需要 Zookeeper 支持。

6.1 号段模式

号段模式还是基于数据库,但是思路有些变化,如下:

利用 proxy server 从数据库中批量获取 id,每次获取一个 segment (step 决定其大小) 号段的值,用完之后再去数据库获取新的号段,可以大大的减轻数据库的压力。各个业务不同的发号需求用 biz_tag 字段来区分,每个 biz-tag 的 ID 获取相互隔离,互不影响。如果有新的业务需要扩区 ID,只需要增加表记录即可。

如果使用号段模式,我们首先需要创建一张数据表,脚本如下:

CREATE DATABASE leaf CREATE TABLE `leaf_alloc` ( `biz_tag` varchar(128) NOT NULL DEFAULT '', `max_id` bigint(20) NOT NULL DEFAULT '1', `step` int(11) NOT NULL, `description` varchar(256) DEFAULT NULL, `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`biz_tag`) ) ENGINE=InnoDB; insert into leaf_alloc(biz_tag, max_id, step, description) values('leaf-segment-test', 1, 2000, 'Test leaf Segment Mode Get Id')

这张表中各项字段的含义如下:

biz_tag:业务标记(不同业务可以有不同的号段序列)max_id:当前号段下的最大 idstep:每次取号段的步长description:描述信息update_time:更新时间

配置完成后,启动项目,访问 路径(路径最后面的 leaf-segment-test 是业务标记),即可拿到 ID。

可以通过如下地址访问到号段模式的监控页面

号段模式优缺点:

优点

Leaf 服务可以很方便的线性扩展,性能完全能够支撑大多数业务场景。ID 号码是趋势递增的 8byte 的 64 位数字,满足上述数据库存储的主键要求。容灾性高:Leaf 服务内部有号段缓存,即使 DB 宕机,短时间内 Leaf 仍能正常对外提供服务。可以自定义 max_id 的大小,非常方便业务从原有的 ID 方式上迁移过来。

缺点

ID 号码不够随机,能够泄露发号数量的信息,不太安全。DB 宕机会造成整个系统不可用。

6.2 SNOWFLAKE 模式

SNOWFLAKE 模式需要配合 Zookeeper 一起,不过 SNOWFLAKE 对 Zookeeper 的依赖是弱依赖,把 Zookeeper 启动之后,我们可以在 SNOWFLAKE 中配置 Zookeeper 信息,如下:

leaf.snowflake.enable=true leaf.snowflake.zk.address=192.168.91.130 leaf.snowflake.port=2183

然后重新启动项目,启动成功后,通过如下地址可以访问到 ID:

小结

综上,如果项目中恰好使用了 MyCat,那么可以使用 MyCat+Zookeeper,否则建议使用 LEAF,两种模式皆可。

标签: #物品id生成算法有哪些