1.背景
分布式id(Distributed ID)可以用来唯一标识分布式系统中的各个实体、任务或请求。它可以用于多种用途,包括以下几个方面:
1. 数据库主键:在分布式数据库中,每个数据记录需要一个唯一的主键标识。分布式id可以用作主键,确保数据记录的唯一性。
2. 分布式锁:在分布式系统中,多个节点之间需要共享锁来实现同步和互斥。分布式id可以作为锁的名称,确保每个节点使用的锁是唯一的。
3. 分布式消息队列:在消息队列系统中,分布式id可以用来标识消息的发送和接收状态,以便进行消息的可靠传输和处理。
4. 分布式事务:在分布式事务中,分布式id可以用来标识事务的唯一性,确保事务的一致性和隔离性。
5. 分布式任务调度:在分布式任务调度系统中,分布式id可以用来标识任务的唯一性,确保任务的正确执行和结果的正确处理。
总之,分布式id可以在分布式系统中实现唯一标识、数据一致性、任务调度等功能,提高系统的可靠性和性能。
2.号段模式
官方很懒,到现在还没有上传maven仓库,需要手动git下来。
git clone git@github.com:Meituan-Dianping/Leaf.git
git checkout feature/spring-boot-starter
cd Leaf
mvn clean install -Dmaven.test.skip=true
2.1依赖引入
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<artifactId>leaf-boot-starter</artifactId>
<groupId>com.sankuai.inf.leaf</groupId>
<version>1.0.1-RELEASE</version>
</dependency>
</dependencies>
2.2sql执行
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')
2.3 使用
1.在resources文件下创建一个leaf.properties
leaf.name=com.sankuai.leaf.opensource.test
leaf.segment.enable=true
leaf.segment.url=jdbc:mysql://localhost:3306/leaf
leaf.segment.username=root
leaf.segment.password=123456
2.主函数引入@EnableLeafServer注解
package com.mikey.distributed;
import com.sankuai.inf.leaf.plugin.annotation.EnableLeafServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author mikey
* @create 2024-03-03 21:21
*/
@SpringBootApplication
@EnableLeafServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}
3.实际应用
@RestController
public class IdController {
@Autowired
private SegmentService segmentService;
@GetMapping("/segment")
public Result getId1() {
return segmentService.getId("leaf-segment-test");
}
}
2.4总结
Leaf 最早期需求是各个业务线的订单ID生成需求。在美团早期,有的业务直接通过DB自增的方式生成ID,有的业务通过redis缓存来生成ID,也有的业务直接用UUID这种方式来生成ID。以上的方式各自有各自的问题,因此我们决定实现一套分布式ID生成服务来满足需求。(来自官网)
我们会发现在获取id的时候,需要一个key,这个key实际就是数据库的biz_tag这个字段,代表着业务的标签。max_id就是这次请求拿到的id,step就是步长。
假设此时数据库是 max_id = 1 , step = 20。
那么这次请求拿到的id就是1,然后他会缓存这1-20的id,下一次请求而不会请求数据库,而是从缓存里面拿,这里就是他性能高的原因。你会发现当你请求多几次的时候,而id还没到20,数据库的max_id更新到了21,这是因为他会提前去申请id空间,防止id不够,而提前做了数据库更新。
3.雪花算法
雪花算法(Snowflake Algorithm)是一种用于生成分布式系统中唯一 ID 的算法。
-
64 位二进制格式:Snowflake 算法生成的唯一 ID 是一个 64 位的整数,其结构一般是:1位符号位(不使用)、41位的时间戳、10位的机器标识(或工作机器 ID)、12位的序列号。
-
时间戳部分:占据了整个 64 位中的高位 41 位,可以精确到毫秒级别的时间,可以保证在相同的时间戳内生成的 ID 唯一。
-
机器标识部分:占据了中间的 10 位,用于区分不同的机器。这部分的信息可以手动配置或者从机器的某些特征中提取,例如数据中心 ID 和机器 ID 的组合。
-
序列号部分:占据了低位的 12 位,用于解决在同一毫秒内产生大量请求时的并发冲突问题。在同一毫秒内,序列号会从 0 开始递增,最多可以生成 4096(2^12)个不同的 ID。
-
生成 ID 的规则:Snowflake 算法根据当前时间戳、机器标识和序列号生成唯一的 ID。当某个节点生成 ID 时,它会根据当前时间戳计算出时间戳部分,将机器标识和序列号部分组合成 ID。
实际上不同的公司都会根据自己的场景从而去改造雪花算法,可以从时间戳部分或者机器表示部分或者序列号部分去优化,可以参考一下百度UidGenerator。
3.1依赖引入
这里需要引入zk的相关依赖,可以理解成zk在这里做了数据库这样的角色,存储了机器的标识符(ip)。这里不展开zk的安装,自行查看相关资料。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<artifactId>leaf-boot-starter</artifactId>
<groupId>com.sankuai.inf.leaf</groupId>
<version>1.0.1-RELEASE</version>
</dependency>
<!--zk-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.6.0</version>
<exclusions>
<exclusion>
<artifactId>log4j</artifactId>
<groupId>log4j</groupId>
</exclusion>
</exclusions>
</dependency>
3.2使用
1.在resources文件下创建一个leaf.properties
leaf.name=com.sankuai.leaf.opensource.test
leaf.snowflake.enable=false
leaf.snowflake.address=ip
leaf.snowflake.port=port
2.使用
@Autowired
private SnowflakeService snowflakeService;
@GetMapping("/snowflake")
public Result getId2() {
return snowflakeService.getId("111");
}
我们可以发现雪花算法这里也要获取一个key,实际上这里的key是没有意义的。看看github上的解释。