分布式ID设计

分布式ID生成方案概述

  1. UUID

    优点:实现简单,无性能问题,全球唯一。

    缺点:ID无序、查询效率慢、存储空间大、不可读。

    适用场景:适合生成token令牌等场景,不适合要求趋势递增的ID场景。

  2. MySQL主键自增

    优点:数字化、ID递增、查询效率高、有一定业务可读性。

    缺点:存在单点问题,数据库压力大,难以应对高并发。

  3. MySQL多实例主键自增

    优点:解决了单点问题。步长可以设置成节点个数,比如步长设置为4,DB节点1当前为1,则取5,9,13等,DB节点2当前为2,则取6,10,14等,DB3、DB4同理

    缺点:步长固定后无法扩容,单个数据库压力仍然较大。

    适用场景:适用于不需要扩容的数据场景。

  4. 雪花snowflake算法

    优点:每秒能产生大量ID,性能快,ID是趋势递增的,灵活度高。

    缺点:依赖机器时钟,服务器时钟回拨可能导致重复ID生成。

  5. Redis生成方案

    优点:有序递增,可读性强。

    缺点:每次请求都需要与Redis通信,占用带宽。

Redis搭配数据库

我当前项目正在使用的方案
创建一张序列记录表,包含业务类型,序列号,用来记录每个业务场景的最新序列号
创建一个 BusinessCodeService 服务的 getCode(String businessType) 方法
核心实现是通过Jedis获取连接后,获取指定key,addLong实现序列号自增后更新到数据库
项目现在的瓶颈是大批量获取序列的时候,频繁连接redis导致获取很慢

数据库主键自增优化方案

思路:通过请求数据库获得一个ID区间段,而非每次都去请求单个ID。例如,服务A请求到【max_id + 1, max_id + step】区间的ID,保存在JVM中,依次使用。

解决了数据库压力:减少了频繁访问数据库的需求。

方便扩容:可以通过调整step来适应不同规模的服务需求。

数据库宕机影响:即使数据库宕机,系统也能维持一段时间的正常运行。

实际步骤:

  1. 【用户服务】在注册一个用户时,需要一个用户ID;会请求【生成ID服务(是独立的应用)】的接口

  2. 【生成ID服务】会去查询数据库,找到user_tag的id,现在的max_id为0,step=1000

  3. 【生成ID服务】把max_id和step返回给【用户服务】;并且把max_id更新为max_id = max_id + step,即更新为1000

  4. 【用户服务】获得max_id=0,step=1000;

  5. 这个用户服务可以用ID=【max_id + 1,max_id+step】区间的ID,即为【1,1000】

  6. 【用户服务】会把这个区间保存到jvm中

  7. 【用户服务】需要用到ID的时候,在区间【1,1000】中依次获取id,可采用AtomicLong中的getAndIncrement方法。

  8. 如果把区间的值用完了,再去请求【生产ID服务】接口,获取到max_id为1000,即可以用【max_id + 1,max_id+step】区间的ID,即为【1001,2000】

解决竞争问题

使用分布式锁或数据库行锁确保同一时刻只有一个用户服务获取max_id,避免ID重复。

解决突发阻塞问题

双buffer方案

当一个buffer中的ID使用达到一定比例时,提前请求新的ID区间填充另一个buffer,两个buffer之间自行切换使用,从而减少因多个服务同时请求新ID而造成的阻塞。

双buffer方案示意图

Leaf:美团分布式ID生成服务开源 - 美团技术团队 (meituan.com)

posted @ 2025-05-05 19:36  一只盐桔鸡  阅读(27)  评论(0)    收藏  举报