赞
踩
1、业务
1)全局唯一性:不能出现重复的ID号,既然是唯一标识,这是最基本的要求
2)趋势递增:在MySQL InnoDB引擎中使用的是聚集索引,由于多数RDBMS使用B-tree的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能
3)单调递增:保证下一个ID一定大于上一个ID,例如事务版本号、IM增量消息、排序等特殊需求
4)信息安全:如果ID是连续的, 竞对在两天中午12点分别下单,通过订单id号相减就能大致计算出公司一天的订单量 。所以在一些应用场景下,会需要ID无规则
2、可靠性
1、定义
36个字符,示例:550e8400-e29b-41d4-a716-446655440000
public class IdUtil {
/*
* 返回使用ThreadLocalRandom的UUID,比默认的UUID性能更优
*/
public static UUID fastUUID() {
ThreadLocalRandom random = ThreadLocalRandom.current();
return new UUID(random.nextLong(), random.nextLong());
}
}
2、缺点
1、结构
Long型64位的整数,如图1所示
41-bit的时间戳
可以表示(1L<<41)/(1000L360024*365)=69年的时间
10-bit 数据中心ID和机器
5bit数据中心id,5bit机器id,一般使用ZK分配
12个自增序列号
在同一毫秒内生成2^12个唯一的ID
理论上snowflake方案的QPS约为409.6w/s,可以保证在任何一个IDC、任何一台机器、在任意毫秒内、生成的ID都是不同的
2、问题
41-bit时间戳部分,强依赖机器时钟,如果机器上时钟回拨,会导致发号重复
使用参考:Leaf生成单据号
1、适合场景:生成的ID需要无规则
2、解决机器时钟回拨问题
1)要求当前时间戳,必须 > 机器创建时间
2)同时
阈值 = 5ms
因为理论上5ms内无法,完全使用完成后12个自增序列号,所以不会重复
否则直接报错自动摘除本身节点并报警
1、适合场景:生成的ID单调递增
2、实现:基于MySQL的自增主键
1、获取ID方式
使用下列SQL读写MySQL得到ID号
begin;
REPLACE INTO Tickets64 (stub) VALUES ('a');
SELECT LAST_INSERT_ID();
commit;
实现方式类似:
useGeneratedKeys=“true“ keyProperty=“id“
2、存在问题
因为每次都是都需要写,读MySQL才能获取ID值,单台MySQL的读写性能是瓶颈
3、解决-集群
在分布式系统中多部署几台机器
每台机器设置不同的初始值,且步长和机器数相等
比如有两台机器。设置步长step为2,TicketServer1的初始值为1(1,3,5,7,9,11…)
TicketServer2的初始值为2(2,4,6,8,10…)
假设部署N台机器,步长需设置为N,每台的初始值依次为0,1,2…N-1 ,则整个Leaf架构如图2
4、存在问题
5、解决- 批量分段(segment)获取
1、实现,如图3所示
1)db表设计
+-------------+--------------+------+-----+-------------------+-------------------------
| Field | Type | Null | Key | Default | Extra
+-------------+--------------+------+-----+-------------------+--------------------------
| biz_tag | varchar(128) | NO | PRI | |
| max_id | bigint(20) | NO | | 1 |
| step | int(11) | NO | | NULL |
| desc | varchar(256) | YES | | NULL |
| update_time | timestamp | NO | | CURRENT_TIMESTAMP | on update
UPDATE table SET max_id=max_id+step WHERE biz_tag=xxx
2)系统架构
3) ID值趋势递增
eg:test_tag业务
用户通过Round-robin的方式调用Leaf Server的各个服务,通过CAS获取ID,所以某一个Client获取到的ID序列
可能是:1,1001,2001,2,1002,2002……
也可能是:1,2,1001,2001,2002,2003,3,4…
当某个Leaf Server号段用完之后,下一次请求就会从DB中加载新的号段,这样保证了每次加载的号段是递增
2、解决-读写性能
原来获取一个ID值,都需要读写一次数据库
现在只需要把step设置得足够大,比如1000。那么只有当1000个号被消耗完了之后才会去重新读写一次
读写数据库的频率从1减小到了1/step
test_tag业务,在第一台Leaf机器上是1~1000的号段,当这个号段用完时
会去加载另一个长度为step=1000的号段
假设另外两台号段都没有更新,这个时候第一台机器新加载的号段就应该是3001~4000
同时数据库对应的biz_tag = test_tag 这条数据的max_id会从3000被更新成4000
3、 解决-扩容操作
只需要对biz_tag分库分表
4、问题
在号段消耗完的时候进行取号段时,还是会夯在更新数据库的I/O上
假如取DB的时候网络发生抖动,或者DB发生慢查询就会导致整个系统的响应时间变慢
5、解决-双buffer
1、解决-双buffer
2、 容灾
分库分表
4、问题
在号段消耗完的时候进行取号段时,还是会夯在更新数据库的I/O上
假如取DB的时候网络发生抖动,或者DB发生慢查询就会导致整个系统的响应时间变慢
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。