Skip to content

Commit

Permalink
文档更新
Browse files Browse the repository at this point in the history
文档更新
  • Loading branch information
csy512889371 committed Mar 4, 2020
1 parent dd93a75 commit ba9b145
Show file tree
Hide file tree
Showing 18 changed files with 1,031 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
> 在使用mvn package进行编译、打包时,Maven会执行src/test/java中的JUnit测试用例,有时为了跳过测试,会使用参数-DskipTests和-Dmaven.test.skip=true,这两个参数的主要区别是:
>
>
* -DskipTests,不执行测试用例,但编译测试用例类生成相应的class文件至target/test-classes下。

* -Dmaven.test.skip=true,不执行测试用例,也不编译测试用例类。

258 changes: 258 additions & 0 deletions javadoc2019/k8s/技术文档/分布式ID/分布式ID组件.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
##### 一、普通项目使用说明

###### 两种模式介绍

**Leaf-snowflake模式**

* Leaf-snowflake不同于原始snowflake算法地方,主要是在workId的生成上,Leaf-snowflake依靠Zookeeper生成workId。Leaf中workId是基于ZooKeeper的顺序Id来生成的,每个应用在使用Leaf-snowflake时,启动时都会都在Zookeeper中生成一个顺序Id,相当于一台机器对应一个顺序节点,也就是一个workId。
* 生成的id如下

```
order_id: 1234644838356353109
order_id: 1234644838759006288
order_id: 1234644839161659450
```

* 特点 1、全局唯一性:不能重复 2、信息安全:如何防止用户恶意根据订单号获取数据 3、数据递增: 保证下一个订单号要大于上一个订单号

**Leaf-segment号段模式**
* Leaf-segment号段模式是对直接用数据库自增ID充当分布式ID的一种优化,减少对数据库的频率操作。
* 生成的段号如1、2、3、4 连续
* 主要应用场景微服务下的1、业务编号生成 2、微服务场景下特殊ID规则生成如:年份+业务编号+序号。其中序号部分可用leaf-segment生成。

* 注意:由于提高发号并发效率,有将号码按照step存储在内存中,重启发号器会出现断号(其中一些id 没有被使用),为了减少断号最好是单独部署个发号器微服务。(待优化)

###### 项目整合使用leaf

* 使用发号器可以引用jar和微服务调用的方式,下面先介绍使用**jar方式**

**1、引入pom**

```
<properties>
<loit-build-commom-parent.version>1.0-SNAPSHOT</loit-build-commom-parent.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.timeloit.project</groupId>
<artifactId>loit-build-commom-parent</artifactId>
<version>${loit-build-commom-parent.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
```



```
<dependency>
<groupId>com.timeloit.project</groupId>
<artifactId>loit-keygen-leaf-config</artifactId>
</dependency>
```

**2、配置文件**

指定zookeeper 配置文件地址

```
leaf:
namespace: leaf_${spring.application.name}
zkserver:
# zookeeper 地址
list: 127.0.0.1:2181
segment:
# 初始化Id
id.initial.value: 1
# 步长
step: 50
```



**2、注解使用方式**

如果是实体id 可以使用注解的方式如: @AutoId

```
public class Order implements Serializable {
private static final long serialVersionUID = 661434701950670670L;
@AutoId
private String orderId;
private Integer userId;
private Long addressId;
private String status;
}
```

* @AutoId 可以指定LEAF_SNOWFLAKE、LEAF_SEGMENT。默认是使用LEAF_SNOWFLAKE。如果使用LEAF_SEGMENT 如:**@AutoId(AutoId.IdType.LEAF_SEGMENT)**

* orderId 的数据类型可以是**String**(数据库varchar)和 **Long**(数据库bigint) 如果是leaf_snowflake 长度可以指定 **20**

* mybatis sql配置如下,需包含order_id 字段

```
<insert id="insert" useGeneratedKeys="true" keyProperty="orderId">
INSERT INTO t_order (order_id, user_id, address_id, status) VALUES (#{orderId,jdbcType=VARCHAR}, #{userId,jdbcType=INTEGER}, #{addressId,jdbcType=BIGINT}, #{status,jdbcType=VARCHAR});
</insert>
```



**3、代码调用方式**



LEAF_SEGMENT:

```
@GetMapping("/leafSegment")
public void leafSegment() {
Comparable<?> t_order_id = leafSegmentKeyGeneratorFactory.getKeyGenerator(Order.class.getName()).generateKey();
System.out.println(t_order_id);
}
```



LEAF_SNOWFLAKE:

```
@Autowired
private IDGen idGen;
@GetMapping("/leafSnowflake")
public void leafSnowflake() {
Result result = idGen.get("");
System.out.println(result);
}
```




##### 二、shardingsphere使用分布式ID

* 分表分库中使用分布式id 可参照例子demo: sharding-leaf-mybatis-example


###### 项目整合分布式id

**1、引入pom**

```
<properties>
<loit-build-commom-parent.version>1.0-SNAPSHOT</loit-build-commom-parent.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.timeloit.project</groupId>
<artifactId>loit-build-commom-parent</artifactId>
<version>${loit-build-commom-parent.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
```



```
<dependency>
<groupId>com.timeloit.project</groupId>
<artifactId>sharding-keygen-leaf</artifactId>
</dependency>
```



**1、配置**

LEAF_SNOWFLAKE 配置如下

```
spring.shardingsphere.sharding.tables.t_order.key-generator.type=LEAF_SNOWFLAKE
spring.shardingsphere.sharding.tables.t_order.key-generator.props.leaf.zk.list=localhost:2181
```


LEAF_SEGMENT 配置如下

```
spring.shardingsphere.sharding.tables.t_order.key-generator.type=LEAF_SEGMENT
spring.shardingsphere.sharding.tables.t_order.key-generator.props.leaf.key=t_order_id
spring.shardingsphere.sharding.tables.t_order.key-generator.props.leaf.jdbc.url=jdbc:mysql://39.98.202.173:3306/leaf?serverTimezone=UTC&useSSL=false
spring.shardingsphere.sharding.tables.t_order.key-generator.props.leaf.jdbc.username=root
spring.shardingsphere.sharding.tables.t_order.key-generator.props.leaf.jdbc.password=abcd1234A!
```



备注 全部配置文件如下:

```
spring.shardingsphere.datasource.names=ds_0,ds_1
spring.shardingsphere.datasource.ds_0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds_0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds_0.jdbc-url=jdbc:mysql://39.98.202.173:3306/demo1_ds_0?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
spring.shardingsphere.datasource.ds_0.username=root
spring.shardingsphere.datasource.ds_0.password=abcd1234A!
spring.shardingsphere.datasource.ds_1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds_1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds_1.jdbc-url=jdbc:mysql://39.98.202.173:3306/demo1_ds_1?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
spring.shardingsphere.datasource.ds_1.username=root
spring.shardingsphere.datasource.ds_1.password=abcd1234A!
spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression=ds_$->{user_id % 2}
spring.shardingsphere.sharding.binding-tables=t_order,t_order_item
spring.shardingsphere.sharding.broadcast-tables=t_address
spring.shardingsphere.sharding.tables.t_order.actual-data-nodes=ds_$->{0..1}.t_order
spring.shardingsphere.sharding.tables.t_order.key-generator.column=order_id
# 1、雪花算法
#spring.shardingsphere.sharding.tables.t_order.key-generator.type=SNOWFLAKE
#spring.shardingsphere.sharding.tables.t_order.key-generator.props.worker.id=123
# 2、LEAF_SEGMENT
#spring.shardingsphere.sharding.tables.t_order.key-generator.type=LEAF_SEGMENT
#spring.shardingsphere.sharding.tables.t_order.key-generator.props.leaf.key=t_order_id
#spring.shardingsphere.sharding.tables.t_order.key-generator.props.leaf.jdbc.url=jdbc:mysql://39.98.202.173:3306/leaf?serverTimezone=UTC&useSSL=false
#spring.shardingsphere.sharding.tables.t_order.key-generator.props.leaf.jdbc.username=root
#spring.shardingsphere.sharding.tables.t_order.key-generator.props.leaf.jdbc.password=abcd1234A!
# 2、LEAF_SNOWFLAKE
spring.shardingsphere.sharding.tables.t_order.key-generator.type=LEAF_SNOWFLAKE
spring.shardingsphere.sharding.tables.t_order.key-generator.props.leaf.zk.list=localhost:2181
spring.shardingsphere.sharding.tables.t_order_item.actual-data-nodes=ds_$->{0..1}.t_order_item
spring.shardingsphere.sharding.tables.t_order_item.key-generator.column=order_item_id
spring.shardingsphere.sharding.tables.t_order_item.key-generator.type=SNOWFLAKE
spring.shardingsphere.sharding.tables.t_order_item.key-generator.props.worker.id=123
```








Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
Leaf是美团基础研发平台推出的一个分布式ID生成服务,名字取自德国哲学家、数学家莱布尼茨的一句话:“There are no two identical leaves in the world.”Leaf具备高可靠、低延迟、全局唯一等特点。目前已经广泛应用于美团金融、美团外卖、美团酒旅等多个部门。具体的技术细节,可参考此前美团技术博客的一篇文章:[《Leaf美团分布式ID生成服务》](https://tech.meituan.com/MT_Leaf.html)。近日,Leaf项目已经在Github上开源:https://github.com/Meituan-Dianping/Leaf,希望能和更多的技术同行一起交流、共建。

## Leaf特性

Leaf在设计之初就秉承着几点要求:

1. 全局唯一,绝对不会出现重复的ID,且ID整体趋势递增。
2. 高可用,服务完全基于分布式架构,即使MySQL宕机,也能容忍一段时间的数据库不可用。
3. 高并发低延时,在CentOS 4C8G的虚拟机上,远程调用QPS可达5W+,TP99在1ms内。
4. 接入简单,直接通过公司RPC服务或者HTTP调用即可接入。

## Leaf诞生

Leaf第一个版本采用了预分发的方式生成ID,即可以在DB之上挂N个Server,每个Server启动时,都会去DB拿固定长度的ID List。这样就做到了完全基于分布式的架构,同时因为ID是由内存分发,所以也可以做到很高效。接下来是数据持久化问题,Leaf每次去DB拿固定长度的ID List,然后把最大的ID持久化下来,也就是并非每个ID都做持久化,仅仅持久化一批ID中最大的那一个。这个方式有点像游戏里的定期存档功能,只不过存档的是未来某个时间下发给用户的ID,这样极大地减轻了DB持久化的压力。

整个服务的具体处理过程如下:

![img](https://p1.meituan.net/travelcube/210ca1564c70b228ed46f3b33c9bb9b161120.png)

- Leaf Server 1:从DB加载号段[1,1000]
- Leaf Server 2:从DB加载号段[1001,2000]
- Leaf Server 3:从DB加载号段[2001,3000]

用户通过Round-robin的方式调用Leaf Server的各个服务,所以某一个Client获取到的ID序列可能是:1,1001,2001,2,1002,2002……也可能是:1,2,1001,2001,2002,2003,3,4……当某个Leaf Server号段用完之后,下一次请求就会从DB中加载新的号段,这样保证了每次加载的号段是递增的。

Leaf数据库中的号段表格式如下:

```sql
+-------------+--------------+------+-----+-------------------+-----------------------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+-------------------+-----------------------------+
| biz_tag | varchar(128) | NO | PRI | | |
| max_id | bigint(20) | NO | | 1 | |
| step | int(11) | NO | | NULL | |
| desc | varchar(256) | YES | | NULL | |
| update_time | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+-------------+--------------+------+-----+-------------------+-----------------------------+
```

Leaf Server加载号段的SQL语句如下:

```sql
Begin
UPDATE table SET max_id=max_id+step WHERE biz_tag=xxx
SELECT tag, max_id, step FROM table WHERE biz_tag=xxx
Commit
```

整体上,V1版本实现比较简单,主要是为了尽快解决业务层DB压力的问题,而快速迭代出的一个版本。因而在生产环境中,也发现了些问题。比如:

1. 在更新DB的时候会出现耗时尖刺,系统最大耗时取决于更新DB号段的时间。
2. 当更新DB号段的时候,如果DB宕机或者发生主从切换,会导致一段时间的服务不可用。

## Leaf双Buffer优化

为了解决这两个问题,Leaf采用了异步更新的策略,同时通过双Buffer的方式,保证无论何时DB出现问题,都能有一个Buffer的号段可以正常对外提供服务,只要DB在一个Buffer的下发的周期内恢复,就不会影响整个Leaf的可用性。

![img](https://p1.meituan.net/travelcube/64a44ac6db45e4b7b88b10c85a76614a52159.png)

这个版本代码在线上稳定运行了半年左右,Leaf又遇到了新的问题:

1. 号段长度始终是固定的,假如Leaf本来能在DB不可用的情况下,维持10分钟正常工作,那么如果流量增加10倍就只能维持1分钟正常工作了。
2. 号段长度设置的过长,导致缓存中的号段迟迟消耗不完,进而导致更新DB的新号段与前一次下发的号段ID跨度过大。

## Leaf动态调整Step

假设**服务QPS为Q,号段长度为L,号段更新周期为T**,那么**Q \* T = L**。最开始L长度是固定的,导致随着Q的增长,T会越来越小。但是Leaf本质的需求是**希望T是固定的**。那么如果L可以和Q正相关的话,T就可以趋近一个定值了。所以Leaf每次更新号段的时候,根据上一次更新号段的周期T和号段长度step,来决定下一次的号段长度nextStep:

- T < 15min,nextStep = step * 2
- 15min < T < 30min,nextStep = step
- T > 30min,nextStep = step / 2

至此,满足了号段消耗稳定趋于某个时间区间的需求。当然,面对瞬时流量几十、几百倍的暴增,该种方案仍不能满足可以容忍数据库在一段时间不可用、系统仍能稳定运行的需求。因为本质上来讲,Leaf虽然在DB层做了些容错方案,但是号段方式的ID下发,最终还是需要强依赖DB。

## MySQL高可用

在MySQL这一层,Leaf目前采取了半同步的方式同步数据,通过公司DB中间件Zebra加MHA做的主从切换。未来追求完全的强一致,会考虑切换到[MySQL Group Replication](https://dev.mysql.com/doc/refman/5.7/en/group-replication.html)

现阶段由于公司数据库强一致的特性还在演进中,Leaf采用了一个临时方案来保证机房断网场景下的数据一致性:

- 多机房部署数据库,每个机房一个实例,保证都是跨机房同步数据。
- 半同步超时时间设置到无限大,防止半同步方式退化为异步复制。

## Leaf监控

针对服务自身的监控,Leaf提供了Web层的内存数据映射界面,可以实时看到所有号段的下发状态。比如每个号段双buffer的使用情况,当前ID下发到了哪个位置等信息都可以在Web界面上查看。

![img](https://p1.meituan.net/travelcube/e7cc6df5b167ba9d972e0eb9c66b4446318301.jpg)

## Leaf Snowflake

Snowflake,Twitter开源的一种分布式ID生成算法。基于64位数实现,下图为Snowflake算法的ID构成图。

![img](https://p0.meituan.net/travelcube/96034f8fa0f2cb14c21844a4fa12f50441574.png)

- 第1位置为0。
- 第2-42位是相对时间戳,通过当前时间戳减去一个固定的历史时间戳生成。
- 第43-52位是机器号workerID,每个Server的机器ID不同。
- 第53-64位是自增ID。

这样通过时间+机器号+自增ID的组合来实现了完全分布式的ID下发。

在这里,Leaf提供了Java版本的实现,同时对Zookeeper生成机器号做了弱依赖处理,即使Zookeeper有问题,也不会影响服务。Leaf在第一次从Zookeeper拿取workerID后,会在本机文件系统上缓存一个workerID文件。即使ZooKeeper出现问题,同时恰好机器也在重启,也能保证服务的正常运行。这样做到了对第三方组件的弱依赖,一定程度上提高了SLA。

## 未来规划

- 号段加载优化:Leaf目前重启后的第一次请求还是会同步加载MySQL,之所以这么做而非服务初始化加载号段的原因,主要是MySQL中的Leaf Key并非一定都被这个Leaf服务节点所加载,如果每个Leaf节点都在初始化加载所有的Leaf Key会导致号段的大量浪费。因此,未来会在Leaf服务Shutdown时,备份这个服务节点近一天使用过的Leaf Key列表,这样重启后会预先从MySQL加载Key List中的号段。
- 单调递增:简易的方式,是只要保证同一时间、同一个Leaf Key都从一个Leaf服务节点获取ID,即可保证递增。需要注意的问题是Leaf服务节点切换时,旧Leaf 服务用过的号段需要废弃。路由逻辑,可采用主备的模型或者每个Leaf Key 配置路由表的方式来实现。

## 关于开源

分布式ID生成的方案有很多种,Leaf开源版本提供了两种ID的生成方式:

- 号段模式:低位趋势增长,较少的ID号段浪费,能够容忍MySQL的短时间不可用。
- Snowflake模式:完全分布式,ID有语义。

读者可以按需选择适合自身业务场景的ID下发方式。希望美团的方案能给予大家一些帮助,同时也希望各位能够一起交流、共建。

Leaf项目Github地址:https://github.com/Meituan-Dianping/Leaf

如有任何疑问和问题,欢迎提交至[Github issues](https://github.com/Meituan-Dianping/Leaf/issues)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit ba9b145

Please sign in to comment.