一. 概述
传统方案在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识。如在点评的金融,支付,餐饮,酒店,猫眼电影等产品。对数据分库分表后需要一个唯一ID来标识一条数据或消息
思维导图
二. 开源组件
- 只支持雪花算法,组件无人维护
- 只支持数据库号段,多DB,高可用
- 提供号段模式,雪花算法模式
三. 特点
- 全局唯一
- 高并发
- 高可用
四. 方案
1. 时间戳
时间戳不能用来解决分布式ID的原因:
有可能在同一毫秒的时候,会有多个请求打进来,那么就可能在该毫秒生成两条数据,并且ID是一致的
2. UUID
可以考虑使用它来作为分布式ID
缺点:
- (生成的字符)空间占用较多
- 不能生成递增有序的数字,索引效率下降
3. 数据库主键自增
缺点:
- 并发性能不高,受限于数据库性能
- 分库分表,需改造,较复杂
- 自增:数据量泄露
4. Redis自增
缺点
- 若Redis服务器挂了,可能会造成数据丢失
- 并且还可能暴露数据量泄露
5. 雪花算法
五. 总结
主键自增与号段模式区别
场景:将一个应用部署到一台服务器,
- 主键自增来保证ID唯一,那么应用每发一次请求就会向数据库请求一个ID
- 号段模式相当于就是在应用第一次请求的时候数据库直接返回一个段(100),那么当这个应用再次需要ID的时候不会去请求数据库了,而是直接把1-100中的数字拿来用,当1-100的数字使用完后,才会再去从数据库请求新的(101-200)的段,可见号段模式相较于主键自增方式提升了性能
雪花算法
一般组成:1bit符号位+41bit时间戳位+10bit工作进程位+12bit序列号位
- 10bit工作进程位:一般将它分为5+5:前5位用来标识区域,后五位指服务器标识
- 在一台服务器中,每毫秒生成的ID数为4096个,对于大部分公司已经够用了,如果公司业务特别庞大,那么可以考虑修改该算法,增加工作进程位数
六. SpringBoot整合Leaf
步骤
- 由于美团的Leaf组件开发团队并没有将Leaf的jar包上传至maven中心仓库,因此需要我们自己下载打包
- 在本地打好包之后,再在项目引入依赖
<dependency>
<artifactId>leaf-boot-starter</artifactId>
<groupId>com.sankuai.inf.leaf</groupId>
<version>1.0.1-RELEASE</version>
</dependency>
- 创建数据库可参看官方教程
- 写配置:根据自己需求时使用号段模式还是雪花算法,也可以两种都用
leaf.name=com.sankuai.leaf.opensource.test
leaf.segment.enable=true
leaf.segment.url=xxxxxx
leaf.segment.username=xxxx
leaf.segment.password=xxxxx
leaf.snowflake.enable=true
leaf.snowflake.address=xxxxx
leaf.snowflake.port=2181
- 启动类加@EnableLeafServer
@RestController
public class IdController {
@Autowired
private SegmentService segmentService;
@Autowired
private SnowflakeService snowflakeService;
// 号段模式
@GetMapping("/segment")
public Result segment() {
return segmentService.getId("leaf-segment-test");
}
// 雪花模式
@GetMapping("/snowflake")
public Result snowflake() {
return snowflakeService.getId("ray");
}
}
问题
若两个程序运行在同一服务器:可能会产生相同的ID
原因:1bit符号位+41时间戳位+10bit工作进程位+12bit序列号位
由于是同一台服务器,如果在同一时间2个请求分别打在了两太服务器上,由于可能会出现10bit工作进程位相同,12bit序列号位相同,因此可能产生相同的Id
解决方案:如果确实想部署在同一台服务器上,可考虑将程序运行在docker里,同一集群docker容器IP唯一