当前位置:网站首页>分布式ID生成服务,真的有必要搞一个
分布式ID生成服务,真的有必要搞一个
2020-11-06 01:31:00 【尹吉欢】
目录
不吹嘘,不夸张,项目中用到ID生成的场景确实挺多。比如业务要做幂等的时候,如果没有合适的业务字段去做唯一标识,那就需要单独生成一个唯一的标识,这个场景相信大家不陌生。
很多时候为了涂方便可能就是写一个简单的ID生成工具类,直接开用。做的好点的可能单独出一个Jar包让其他项目依赖,做的不好的很有可能就是Copy了N份一样的代码。
单独搞一个独立的ID生成服务非常有必要,当然我们也没必要自己做造轮子,有现成开源的直接用就是了。如果人手够,不差钱,自研也可以。
今天为大家介绍一款美团开源的ID生成框架Leaf,在Leaf的基础上稍微扩展下,增加RPC服务的暴露和调用,提高ID获取的性能。
Leaf介绍
Leaf 最早期需求是各个业务线的订单ID生成需求。在美团早期,有的业务直接通过DB自增的方式生成ID,有的业务通过redis缓存来生成ID,也有的业务直接用UUID这种方式来生成ID。以上的方式各自有各自的问题,因此我们决定实现一套分布式ID生成服务来满足需求。
具体Leaf 设计文档见:https://tech.meituan.com/2017/04/21/mt-leaf.html
目前Leaf覆盖了美团点评公司内部金融、餐饮、外卖、酒店旅游、猫眼电影等众多业务线。在4C8G VM基础上,通过公司RPC方式调用,QPS压测结果近5w/s,TP999 1ms。
snowflake模式
snowflake是Twitter开源的分布式ID生成算法,被广泛应用于各种生成ID的场景。Leaf中也支持这种方式去生成ID。
使用步骤如下:
修改配置leaf.snowflake.enable=true开启snowflake模式。
修改配置leaf.snowflake.zk.address和leaf.snowflake.port为你自己的Zookeeper地址和端口。
想必大家很好奇,为什么这里依赖了Zookeeper呢?
那是因为snowflake的ID组成中有10bit的workerId,如下图:
一般如果服务数量不多的话手动设置也没问题,还有一些框架中会采用约定基于配置的方式,比如基于IP生成wokerID,基于hostname最后几位生成wokerID,手动在机器上配置,手动在程序启动时传入等等方式。
Leaf中为了简化wokerID的配置,所以采用了Zookeeper来生成wokerID。就是用了Zookeeper持久顺序节点的特性自动对snowflake节点配置wokerID。
如果你公司没有用Zookeeper,又不想因为Leaf去单独部署Zookeeper的话,你可以将源码中这块的逻辑改掉,比如自己提供一个生成顺序ID的服务来替代Zookeeper。
segment模式
segment是Leaf基于数据库实现的ID生成方案,如果调用量不大,完全可以用Mysql的自增ID来实现ID的递增。
Leaf虽然也是基于Mysql,但是做了很多的优化,下面简单的介绍下segment模式的原理。
首先我们需要在数据库中新增一张表用于存储ID相关的信息。
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;
biz_tag用于区分业务类型,比如下单,支付等。如果以后有性能需求需要对数据库扩容,只需要对biz_tag分库分表就行。
max_id表示该biz_tag目前所被分配的ID号段的最大值。
step表示每次分配的号段长度。
下图是segment的架构图:
从上图我们可以看出,当多个服务同时对Leaf进行ID获取时,会传入对应的biz_tag,biz_tag之间是相互隔离的,互不影响。
比如Leaf有三个节点,当test_tag第一次请求到Leaf1的时候,此时Leaf1的ID范围就是1~1000。
当test_tag第二次请求到Leaf2的时候,此时Leaf2的ID范围就是1001~2000。
当test_tag第三次请求到Leaf3的时候,此时Leaf3的ID范围就是2001~3000。
比如Leaf1已经知道自己的test_tag的ID范围是1~1000,那么后续请求过来获取test_tag对应ID时候,就会从1开始依次递增,这个过程是在内存中进行的,性能高。不用每次获取ID都去访问一次数据库。
问题一
这个时候又有人说了,如果并发量很大的话,1000的号段长度一下就被用完了啊,此时就得去申请下一个范围,这期间进来的请求也会因为DB号段没有取回来,导致线程阻塞。
放心,Leaf中已经对这种情况做了优化,不会等到ID消耗完了才去重新申请,会在还没用完之前就去申请下一个范围段。并发量大的问题你可以直接将step调大即可。
问题二
这个时候又有人说了,如果Leaf服务挂掉某个节点会不会有影响呢?
首先Leaf服务是集群部署,一般都会注册到注册中心让其他服务发现。挂掉一个没关系,还有其他的N个服务。问题是对ID的获取有问题吗? 会不会出现重复的ID呢?
答案是没问题的,如果Leaf1挂了的话,它的范围是1~1000,假如它当前正获取到了100这个阶段,然后服务挂了。服务重启后,就会去申请下一个范围段了,不会再使用1~1000。所以不会有重复ID出现。
Leaf改造支持RPC
如果你们的调用量很大,为了追求更高的性能,可以自己扩展一下,将Leaf改造成Rpc协议暴露出去。
首先将Leaf的Spring版本升级到5.1.8.RELEASE,修改父pom.xml即可。
<spring.version>5.1.8.RELEASE</spring.version>
然后将Spring Boot的版本升级到2.1.6.RELEASE,修改leaf-server的pom.xml。
<spring-boot-dependencies.version>2.1.6.RELEASE</spring-boot-dependencies.version>
还需要在leaf-server的pom中增加nacos相关的依赖,因为我们kitty-cloud是用的nacos。同时还需要依赖dubbo,才可以暴露rpc服务。
<dependency>
<groupId>com.cxytiandi</groupId>
<artifactId>kitty-spring-cloud-starter-nacos</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.cxytiandi</groupId>
<artifactId>kitty-spring-cloud-starter-dubbo</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
</dependency>
在resource下创建bootstrap.properties文件,增加nacos相关的配置信息。
spring.application.name=LeafSnowflake
dubbo.scan.base-packages=com.sankuai.inf.leaf.server.controller
dubbo.protocol.name=dubbo
dubbo.protocol.port=20086
dubbo.registry.address=spring-cloud://localhost
spring.cloud.nacos.discovery.server-addr=47.105.66.210:8848
spring.cloud.nacos.config.server-addr=${spring.cloud.nacos.discovery.server-addr}
Leaf默认暴露的Rest服务是LeafController中,现在的需求是既要暴露Rest又要暴露RPC服务,所以我们抽出两个接口。一个是Segment模式,一个是Snowflake模式。
Segment模式调用客户端
/**
* 分布式ID服务客户端-Segment模式
*
* @作者 尹吉欢
* @个人微信 jihuan900
* @微信公众号 猿天地
* @GitHub https://github.com/yinjihuan
* @作者介绍 http://cxytiandi.com/about
* @时间 2020-04-06 16:20
*/
@FeignClient("${kitty.id.segment.name:LeafSegment}")
public interface DistributedIdLeafSegmentRemoteService {
@RequestMapping(value = "/api/segment/get/{key}")
String getSegmentId(@PathVariable("key") String key);
}
Snowflake模式调用客户端
/**
* 分布式ID服务客户端-Snowflake模式
*
* @作者 尹吉欢
* @个人微信 jihuan900
* @微信公众号 猿天地
* @GitHub https://github.com/yinjihuan
* @作者介绍 http://cxytiandi.com/about
* @时间 2020-04-06 16:20
*/
@FeignClient("${kitty.id.snowflake.name:LeafSnowflake}")
public interface DistributedIdLeafSnowflakeRemoteService {
@RequestMapping(value = "/api/snowflake/get/{key}")
String getSnowflakeId(@PathVariable("key") String key);
}
使用方可以根据使用场景来决定用RPC还是Http进行调用,如果用RPC就@Reference注入Client,如果要用Http就用@Autowired注入Client。
最后改造LeafController同时暴露两种协议即可。
@Service(version = "1.0.0", group = "default")
@RestController
public class LeafController implements DistributedIdLeafSnowflakeRemoteService, DistributedIdLeafSegmentRemoteService {
private Logger logger = LoggerFactory.getLogger(LeafController.class);
@Autowired
private SegmentService segmentService;
@Autowired
private SnowflakeService snowflakeService;
@Override
public String getSegmentId(@PathVariable("key") String key) {
return get(key, segmentService.getId(key));
}
@Override
public String getSnowflakeId(@PathVariable("key") String key) {
return get(key, snowflakeService.getId(key));
}
private String get(@PathVariable("key") String key, Result id) {
Result result;
if (key == null || key.isEmpty()) {
throw new NoKeyException();
}
result = id;
if (result.getStatus().equals(Status.EXCEPTION)) {
throw new LeafServerException(result.toString());
}
return String.valueOf(result.getId());
}
}
扩展后的源码参考:https://github.com/yinjihuan/Leaf/tree/rpc_support
感兴趣的Star下呗:https://github.com/yinjihuan/kitty
关于作者:尹吉欢,简单的技术爱好者,《Spring Cloud微服务-全栈技术与案例解析》, 《Spring Cloud微服务 入门 实战与进阶》作者, 公众号 猿天地 发起人。个人微信 jihuan900,欢迎勾搭。
我整理了一份很全的学习资料,感兴趣的可以微信搜索 「猿天地」,回复关键字 「学习资料」获取我整理好了的Spring Cloud,Spring Cloud Alibaba,Sharding-JDBC分库分表,任务调度框架XXL-JOB,MongoDB,爬虫等相关资料。
版权声明
本文为[尹吉欢]所创,转载请带上原文链接,感谢
http://cxytiandi.com/blog/detail/36494
边栏推荐
- 梯度下降算法在机器学习中的工作原理
- Working principle of gradient descent algorithm in machine learning
- Big data real-time calculation of baichenghui Hangzhou station
- 对pandas 数据进行数据打乱并选取训练机与测试机集
- TensorFlow2.0 问世,Pytorch还能否撼动老大哥地位?
- 为了省钱,我用1天时间把PHP学了!
- Skywalking系列博客1-安装单机版 Skywalking
- Clean架构能够解决哪些问题? - jbogard
- 【jmeter】實現介面關聯的兩種方式:正則表示式提取器和json提取器
- API 测试利器 WireMock
猜你喜欢
随机推荐
不能再被问住了!ReentrantLock 源码、画图一起看一看!
6.8 multipartresolver file upload parser (in-depth analysis of SSM and project practice)
JVM内存区域与垃圾回收
8.1.3 handling global exceptions through exceptionhandler (Global exception handling) - SSM in depth analysis and project practice
解決pl/sql developer中資料庫插入資料亂碼問題
基于深度学习的推荐系统
天天说要做性能优化,到底在优化什么?
面经手册 · 第12篇《面试官,ThreadLocal 你要这么问,我就挂了!》
网络安全工程师演示:原来***是这样获取你的计算机管理员权限的!【***】
使用ES5实现ES6的Class
Using tensorflow to forecast the rental price of airbnb in New York City
leetcode之赎金信
Using class weight to improve class imbalance
Anomaly detection method based on SVM
适合时间序列数据的计算脚本
6.7 theme resolver theme style parser (in-depth analysis of SSM and project practice)
什么是无副作用的函数方法?如何取名? - Mario
深入了解JS数组的常用方法
Jumpserver高可用集群部署:(六)SSH代理模块koko部署并实现系统服务管理
普通算法面试已经Out啦!机器学习算法面试出炉 - kdnuggets