您的位置:

美团 leaf:分布式 ID 解决方案

一、背景介绍

在分布式系统中,很多场景都需要生成唯一的 ID,比如订单号、用户 ID 等。由于多个节点并发生成 ID,因此需要保证生成的 ID 具有全局唯一性。传统的解决方式是使用数据库生成自增 ID 或者使用 Redis 生成类似自增 ID 的方式,但这种方式在高并发场景下性能会下降。

美团 Leaf 是一个开源的分布式 ID 生成系统,由美团点评公司推出,其默认使用 Snowflake 算法生成唯一 ID,而且提供了多种 ID 生成方式满足各种场景需求。同时,Leaf 支持多语言、跨语言调用,并提供了简单易用的接口。

二、ID 生成算法

美团 Leaf 默认使用 Snowflake 算法生成唯一 ID。这种方式的具体实现是:

  1. 使用 64 位长的二进制数据结构存放生成的 ID,其中第 1 个 bit 为不采用,剩下的 63 个 bit 中,第 2-41 个 bit 存放毫秒级时间戳,第 42-51 个 bit 存放数据中心 ID,第 52-63 个 bit 存放机器 ID。
  2. 为每个数据中心和机器分配唯一的 ID。
  3. 使用机器 ID 和数据中心 ID 拼接 10 位长度的二进制数据,将这个数据转换为 10 位长度的整型数据,作为 Leaf 服务器在一个数据中心内的唯一标识。
  4. 每次生成 ID 前,根据设定初始时间戳(由管理员配置)计算出当前时间戳,并与上一次计算出来的时间戳之间的时间差(毫秒级)。
  5. 每个 Leaf 服务器在一个数据中心内,可以保证每个时间戳下生成的 ID 不重复。

三、ID 的生成方式

美团 Leaf 提供了多种 ID 生成方式,可以根据业务需求选择适合的方式。

1. Snowflake ID 生成方式

这是默认的 ID 生成方式,它使用 Snowflake 算法生成 ID。

(1)代码示例:

@IdLeaf("order_leaf")
public class OrderService {
    @Autowired
    private IdGenerator idGenerator;

    public long saveOrder(Order order) {
        long orderId = idGenerator.getId();
        order.setOrderId(orderId);
        orderMapper.save(order);
        return orderId;
    }
}

(2)代码解释:

@IdLeaf("order_leaf") 标识了当前业务生成 ID 时使用的 Leaf 实例名为 "order_leaf"。这个实例名在 Leaf 服务器中需要做单独配置。

idGenerator.getId() 调用 IdGenerator 接口的方法生成唯一 ID。

2. Snowflake ID 单机版生成方式

这种方式不需要远程调用 Leaf 服务器,适合单机使用,首先需要在本地启动一个 Leaf 服务。在工程中加入 Leaf 的 client 和 protocol 两个 Jar 包,并添加以下配置:

leaf:
  zkAddress: localhost:2181
  port: 20010

(1)代码示例:

@Bean
public IdGenerator idGenerator() throws Exception {
    IdGenerator idGenerator = new LeafSegmentIdGenerator();
    Properties properties = new Properties();
    properties.setProperty("leaf.name", "order");
    idGenerator.init(properties);
    return idGenerator;
}

public long saveOrder(Order order) throws Exception {
    long orderId = idGenerator.getId();
    order.setOrderId(orderId);
    orderMapper.save(order);
    return orderId;
}

(2)代码解释:

使用了 LeafSegmentIdGenerator 类,这种方式不需要发起远程请求,可以提高性能。需要指定 Leaf 实例的 name,这个 name 在该 Leaf 服务中需要额外配置。

3. Snowflake ID 跨语言生成方式

由于 Leaf 使用 Thrift 作为 RPC 框架,因此支持多语言调用,同时使用 Thrift 也保证了 Leaf 的高性能。

(1)代码示例:

Leaf 提供了多种语言的客户端 API,下面是 Java 和 Python 的客户端 API 代码示例。

Java 客户端:

TTransport transport = new TSocket("localhost", 8080);
transport.open();

TProtocol protocol = new TBinaryProtocol(transport);
SegmentService.Client client = new SegmentService.Client(protocol);

SegmentIdRequest request = new SegmentIdRequest();
request.setBizType("test");
request.setSize(100);
List<SegmentIdResult> resList = client.getId(request);

System.out.println(resList);

Python 客户端:

from leaf import SegmentIdReq, SegmentService
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol

transport = TSocket.TSocket("localhost", 8080)
transport = TTransport.TBufferedTransport(transport)
protocol = TBinaryProtocol.TBinaryProtocol(transport)
client = SegmentService.Client(protocol)

transport.open()

req = SegmentIdReq(bizType="order", size=5)
result = client.getId(req)

print(result)

(2)代码解释:

客户端通过 RPC 方式请求 Leaf 服务器,获取 ID。

4. 派发全局唯一 ID 的方式

除了使用 Snowflake 算法之外,美团 Leaf 也支持其他方式生成全局唯一 ID,如基于 Redis 实现的全局唯一 ID 生成竞赛方案。下面是使用 Redis 方式生成全局唯一 ID 的代码示例。

(1)代码示例:

@Autowired
private IdGenerator idGenerator;

public long saveOrder(Order order) {
    Jedis jedis = null;
    try {
        jedis = jedisPool.getResource();
        String key = "order_id";
        long orderId = jedis.incr(key);
        order.setOrderId(orderId);
        orderMapper.save(order);
        return orderId;
    } finally {
        if (jedis != null) {
            jedis.close();
        }
    }
}

(2)代码解释:

通过 Redis 原子性操作 incr 自增来实现全局唯一 ID 的生成。

四、使用场景

美团 Leaf 适用于分布式系统中需要生成唯一 ID 的场景。例如:

  • 订单系统,生成唯一的订单号
  • 用户系统,生成唯一的用户 ID
  • 消息系统,生成唯一的消息 ID

五、总结

美团 Leaf 分布式 ID 解决方案使用简单,性能高,适用于分布式系统中唯一 ID 的生成,是一种不错的解决方案。