雪花算法生成实例

it2024-06-30  41

雪花算法生成实例

一、集群高并发情况下如何保证分布式唯一全局id生成?1.1 为什么需要分布式全局唯一ID以及分布式ID的业务需求1.2 ID生成规则部分硬性要求1.3 ID号生成系统的可用性要求 二、一般通用方案2.1 UUID2.2 数据库自增主键2.3 基于Redis生成全局id策略2.4 SnowFlake 三、Hutool工具包代码实现


一、集群高并发情况下如何保证分布式唯一全局id生成?


1.1 为什么需要分布式全局唯一ID以及分布式ID的业务需求

对分库分表后需要有一个唯一ID来标识一条数据或消息; 特别一点的如订单、骑手、优惠券也都需要有唯一ID做标识

1.2 ID生成规则部分硬性要求

① 全局唯一:不能出现重复的ID号,作为唯一标识的基本要求 ② 趋势递增:MySQL的InnoDB引擎中使用的是聚簇索引,多数索引使用B+树的数据结构来存储索引数据,在主键选择上应尽量使用有序的主键保证写入性能 ③ 单调递增:保证下一个ID一定大于上一个ID,新增数据对于索引结构的影响也最小 ④ 信息安全:ID信息安全 ⑤ 含时间戳:快速了解分布式id的生成时间

1.3 ID号生成系统的可用性要求

① 高可用 ② 低延迟 ③ 高QPS

二、一般通用方案


2.1 UUID

UUID:(Universally Unique Identifier)的标准型式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的36个字符,示例:abcd1234-ef56-2356-elid-23fges458965 性能非常高:本地生成,没有网络消耗 缺点: 无序,每一次UUID数据的插入都会对主键的B+树进行很大的修改,容易引起B+树索引的分裂

2.2 数据库自增主键

数据库自增主键:分布式里,数据库的自增ID机制的主要原理是:数据库自增ID和mysql数据库的replace into实现的(replace into首先尝试插入数据列表中,如果发现表中已经有此行数据(根据主键或唯一索引判断)则先删除,再插入) 缺点: 系统水平扩展比较困难,比如定义好了步长和机器台数之后,如果要添加机器该怎么做? 每次获取ID都得读写一次数据库,非常影响性能,不符合分布式ID里面的延迟低和高QPS的规则(在高并发下,如果都去数据库里面获取id,那是非常影响性能的)

2.3 基于Redis生成全局id策略

基于Redis生成全局id策略:因为Redis是单线程的,天生保证原子性,可以使用原子操作INCR和INCRBY来实现 集群分布式 在Redis集群情况下,可以设置不同的增长步长,同事key一定要设置有效期 缺点: 配置麻烦,维护Redis集群

2.4 SnowFlake

Twitter的分布式自增ID算法: ①ID能够按照时间有序生成 ②生成id的结果是一个64bit大小的整数,为一个Long型(转换成字符串后长度最多19) ③分布式系统内不会产生ID碰撞(由datacenter和workId作区分)并且效率较高 号段解析: 1bit-不用 因为二进制中最高位是符号位,1表示负数,0表示正数 生成的id一般都是用整数,所以最高位固定为0

41bit-时间戳,毫秒级 -41位可以表示241-1 个数字, -如果只用来标识正整数(计算机中正数包含0),可以表示的数值范围是:0至241-1,减1是因为可表示的数值范围是从0开始算的,而不是1(可以使用69年,到2039年)

10bit-工作机器id,用来记录工作机器id -可以部署在210 = 1024个节点,包括5位datacenter和5位workId -5位(bit)可以表示的最大正整数是25-1 = 31,即可以用0、1、2、3、…31这32个数字,来表示不同的datacenterId和workId

12bit-序列号,用来记录同毫秒内产生的不同id -12位(bit)可以表示的最大正整数是212-1 = 4095,即可以用0、1、2、3、…4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号

三、Hutool工具包代码实现


Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 引入依赖:

<!--包含雪花算法的Hutool工具包--> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.4.4</version> </dependency>

Controller层:

/** * @Author: Ron * @Create: 2020 10:41 */ @RestController public class SnowFlakeController { @Autowired private OrderService orderService; @RequestMapping("/snowflake") public String index() { return orderService.getIDBySnowFlake(); } }

Service层:

/** * @Author: Ron * @Create: 2020 10:43 */ public interface OrderService { String getIDBySnowFlake(); }

Impl:

/** * @Author: Ron * @Create: 2020 10:43 */ @Service public class OrderServiceImpl implements OrderService { @Autowired private IdGeneratorSnowflake idGenerator; @Override public String getIDBySnowFlake() { ExecutorService threadPool = Executors.newFixedThreadPool(5); for (int i = 0; i < 20; i++) { threadPool.submit(() -> { System.out.println(idGenerator.snowflakeId()); }); } threadPool.shutdown(); return "Hello SnowFlake!!!"; } }

Mapper层:

/** * @Author: Ron * @Create: 2020 10:46 */ @Repository @Slf4j public class IdGeneratorSnowflake { private long workId = 0; private long datacenterId = 1; private Snowflake snowflake = IdUtil.createSnowflake(workId, 1); @PostConstruct public void init() { workId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr()); log.info("当前机器id: {}", workId); } public synchronized long snowflakeId() { return snowflake.nextId(); } // 范围为0~31 public synchronized long snowflakeId(long workId, long datacenterId) { Snowflake snowflake = IdUtil.createSnowflake(workId, datacenterId); return snowflake.nextId(); } public static void main(String[] args) { System.out.println(new IdGeneratorSnowflake().snowflakeId()); } }

启动类:

@SpringBootApplication public class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } }

application.yml配置文件

server: port: 8888

页面访问: 控制台打印结果:

缺点: 依赖机器时钟,如果机器时钟回拨,会导致重复ID生成 在单机上是递增的,但是由于设计到分布式环境,每台机器的时钟不可能完全同步,有时候会出现不是全局递增的情况(此缺点可以认为无所谓,一般分布式ID只要求趋势递增,并不会严格要求递增,90%的需求都只要求趋势递增)

最新回复(0)