【实践】k8s部署美团 Leaf 应用
Leaf
Leaf——美团点评分布式ID生成系统
美团开源的分布式 ID 生成系统,支持号段模式(Segment)和雪花模式(Snowflake)。
号段模式
号段模式基于数据库的“号段”思想来生成 ID。
号段模式 - 核心思想
从数据库中批量获取一个“号段”(例如 1-1000),缓存在本地内存中。
每次生成 ID 时,直接从内存中递增返回,避免频繁访问数据库。
当本地号段快用完时(比如剩余 10%),异步地向数据库申请下一个号段,保证连续性。
数据库中维护一张表 leaf_alloc,记录每个业务的当前最大 ID 和步长。
服务启动时,通过 UPDATE … SET max_id = max_id + step WHERE biz_tag = ‘order’ 获取一个号段 [max_id + 1, max_id + step]。
本地使用原子计数器在这个区间内递增生成 ID。
号段模式 - 特点
- 优点:
性能高:大部分操作在内存中完成,数据库压力小。
ID 有序:生成的 ID 是递增的,有利于数据库索引性能。
容灾能力强:即使数据库暂时不可用,本地缓存的号段仍可继续使用一段时间。 - 缺点:
强依赖数据库:初始号段获取依赖 DB,DB 宕机可能导致无法获取新号段(但有缓存缓冲)。
存在“跳跃”:每次换号段时 ID 会有跳跃(如从 999 跳到 2000),不完全连续。
需要管理多个业务的号段分配。
号段模式 - 适用场景
对 ID 有序性有要求的场景(如 MySQL 主键、时间序列数据)。
高并发、低延迟要求的业务(如订单号、支付流水号)。
可接受 ID 不完全连续,但希望整体趋势递增。
雪花模式
雪花模式 - 原理
雪花模式源自 Twitter 的 Snowflake 算法,是一种基于时间戳和机器标识的分布式 ID 生成算法。
一个 64 位的 Long 型 ID 被划分为几个部分:
| 1 bit | 41 bits | 10 bits | 12 bits |
| 符号位 | 时间戳 | 机器ID | 序列号 |
符号位:固定为 0,保证 ID 为正数。
时间戳(41位):毫秒级时间戳,可使用约 69 年(从自定义纪元开始)。
机器ID(10位):支持最多 1024 个节点(可拆分为数据中心 ID + 机器 ID)。
序列号(12位):同一毫秒内的自增序列,支持每毫秒最多 4096 个 ID。
示例:5920134567890123456
雪花模式 - 特点
- 优点:
完全去中心化:无需依赖数据库,纯内存计算。
高性能:每秒可生成数十万 ID。
趋势递增:时间维度上递增,有利于索引。 - 缺点:
依赖系统时钟:如果服务器时间回拨(NTP 同步或手动调整),可能导致 ID 冲突。
ID 过长且“不友好”:看起来像随机数,不利于排查。
机器 ID 需要配置或注册中心分配。
雪花模式 - 适用场景
完全去中心化的微服务架构。
不希望依赖数据库的轻量级部署。
对性能要求极高,且能容忍时钟回拨风险的场景。
日志 trace ID、消息 ID、缓存 key 等。
雪花模式 - 解决时钟回拨问题
启动时解决 - 双重校验机制
- 持久化节点对比:服务启动时,首先检查在 ZooKeeper 的 leaf_forever节点下是否注册过持久顺序节点。如果存在,则比较节点中记录的上次上报时间和当前系统时间,如果当前时间更小,那么证明发生了回拨,服务会启动失败并触发报警。
- 集群时间校准:经过持久化节点通过第一步校验的节点,会获取 leaf_temporary 节点的所有临时节点,通过RPC查询节点的系统时间,计算平均时间作为集群时间,如果自身时间与平均值超过设定与之,则认定为时钟漂移,服务会启动失败。
运行时解决 - 三级回拨策略
- 轻度回拨(< 5ms):采用等待策略,让当前线程睡眠两倍的回拨时间差后再次检查,如果仍旧存在问题则抛出异常。
- 中度回拨(> 5ms 且 < 100ms):采用随机序号策略。直接使用随机数重置序列号,利用12位序列号的随机性来降低重复ID的概率,保证服务可用。
- 严重回拨(> 100ms):服务直接拒绝生成ID并抛出异常,同事触发高级别告警。
依赖ZooKeeper问题
在本地文件系统缓存WorkerID,当ZooKeeper不可用时,通过本地缓存启动服务。
安装MySQL数据库
创建数据库和用户
-- 创建数据库
CREATE DATABASE IF NOT EXISTS leaf_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 创建用户并授权(允许从任意主机连接)
CREATE USER 'leaf_user'@'%' IDENTIFIED BY 'BE1Mw1&@&7J3@p';
GRANT ALL PRIVILEGES ON leaf_db.* TO 'leaf_user'@'%';
FLUSH PRIVILEGES;
初始化 Leaf 表结构
USE leaf_db;
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(1024) 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('user_id', 1, 1000, '用户ID');
INSERT INTO leaf_alloc(biz_tag, max_id, step, description) VALUES('order_id', 1, 1000, '订单ID');
配置防火墙
sudo firewall-cmd --permanent --add-port=3306/tcp
sudo firewall-cmd --reload
构建 Leaf 的 Docker 镜像
git clone https://github.com/Meituan-Dianping/Leaf.git
安装git
sudo dnf install git -y
安装 ZooKeeper
配置对应的目录
# 创建用于 ZooKeeper 数据存储的目录
sudo mkdir -p /opt/zookeeper/data-{0..2}
# 设置权限(ZooKeeper 容器以非 root 用户运行,UID 通常为 1000)
sudo chown -R 1000:1000 /opt/zookeeper
sudo chmod -R 755 /opt/zookeeper
使用PV挂载到对应目录上
ggVG"+y
apiVersion: v1
kind: PersistentVolume
metadata:
name: zk-data-0-pv
labels:
app: zookeeper
ordinal: "0"
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
hostPath:
path: /opt/zookeeper/data-0
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k8s-node01 # 替换为实际节点名,如 k8s-node1
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: zk-data-1-pv
labels:
app: zookeeper
ordinal: "1"
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
hostPath:
path: /opt/zookeeper/data-1
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k8s-node02 # 同上
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: zk-data-2-pv
labels:
app: zookeeper
ordinal: "2"
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
hostPath:
path: /opt/zookeeper/data-2
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k8s-node01 # 同上
更多推荐
所有评论(0)