美团Leaf源码——号段模式源码解析

本文深入解析美团开源的分布式ID生成服务Leaf的号段模式源码。从导入项目、测试号段模式到源码解析,详细介绍了SegmentService的创建过程,包括配置文件解析、数据源创建、ID生成器初始化等步骤,以及如何获取ID和号段模式的工作原理。
摘要由CSDN通过智能技术生成

前言

分布式ID生成策略基本要求就是全局不重复,最好还能递增,长度较短,性能高,可用性强。关于相关的实现方案有很多,本文着重使用美团开源的分布式ID生成解决方案——Leaf。

关于Leaf,美团官方的介绍文档主要如下,强烈建议阅读文章大致了解Leaf的工作流程与原理,这对本文后续的源码解析有很大的帮助。

  1. Leaf:美团分布式ID生成服务开源
  2. Leaf——美团点评分布式ID生成系统

本系列Leaf源码解析部分按照使用的方式也分为号段模式和snowflake模式两篇文章,本文就来着重研究号段模式的源码实现。

本文的Leaf源码注释地址:https://github.com/MrSorrow/Leaf

I. 导入项目

Leaf由Maven构建,源码地址:https://github.com/Meituan-Dianping/Leaf
首先先Fork官方仓库到自己的仓库,我的源码注释版:https://github.com/MrSorrow/Leaf
下载源码,导入IDEA,导入成功依赖下载完成后项目结构大致如下:
项目框架

II. 测试号段模式

「创建数据库表」
DROP TABLE IF EXISTS `leaf_alloc`;

CREATE TABLE `leaf_alloc` (
  `biz_tag` varchar(128)  NOT NULL DEFAULT '' COMMENT '业务key',
  `max_id` bigint(20) NOT NULL DEFAULT '1' COMMENT '当前已经分配了的最大id',
  `step` int(11) NOT NULL COMMENT '初始步长,也是动态调整的最小步长',
  `description` varchar(256)  DEFAULT NULL COMMENT '业务key的描述',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '数据库维护的更新时间',
  PRIMARY KEY (`biz_tag`)
) ENGINE=InnoDB;
「开启号段模式」
leaf.name=com.sankuai.leaf.opensource.test
leaf.segment.enable=true
leaf.jdbc.url=jdbc:mysql://localhost:3306/leaf_test?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
leaf.jdbc.username=root
leaf.jdbc.password=1234

leaf.snowflake.enable=false
#leaf.snowflake.zk.address=
#leaf.snowflake.port=
「测试号段模式」

开启号段模式并配置好数据库连接后,点击启动 leaf-server 模块的 LeafServerApplication,将服务跑起来。

浏览器输入http://localhost:8080/api/segment/get/leaf-segment-test来获取分布式递增id;
获取id
监控号段模式:http://localhost:8080/cache
监控页面
数据库表:
数据库

III. 号段模式源码解析

正式进入源码前,再强烈建议阅读官方的两篇博客,对Leaf的号段模式工作模式有个大致的理解。

我们从http://localhost:8080/api/segment/get/leaf-segment-test入口来分析。该请求会交由 com.sankuai.inf.leaf.server.LeafController 处理:

@Autowired
SegmentService segmentService;

/**
 * 号段模式获取id
 * @param key 对应数据库表的biz_tag
 * @return
 */
@RequestMapping(value = "/api/segment/get/{key}")
public String getSegmentID(@PathVariable("key") String key) {
   
    // 核心是segmentService的getId方法
    return get(key, segmentService.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());
}

可以看到主要是调用 SegmentServicegetId(key) 方法。key 参数其实就是路径上对应的 leaf-segment-test,也就是数据库对应的 biz_taggetId(key) 方法返回的是 com.sankuai.inf.leaf.common.Result 对象,封装了 id 和 状态 status

public class Result {
   
    private long id;
    private Status status;

    // getter and setter....
}

public enum  Status {
   
    SUCCESS,
    EXCEPTION
}

创建SegmentService

我们进入 SegmentService 类中,再调用 getId(key) 方法之前,我们先看一下 SegmentService 类的实例化构造函数逻辑。可以看到:

package com.sankuai.inf.leaf.server;

@Service("SegmentService")
public class SegmentService {
   
    private Logger logger = LoggerFactory.getLogger(SegmentService.class);
    IDGen idGen;
    DruidDataSource dataSource;

    /**
     * 构造函数,注入单例SegmentService时,完成以下几件事:
     * 1. 加载leaf.properties配置文件解析配置
     * 2. 创建Druid dataSource
     * 3. 创建IDAllocDao
     * 4. 创建ID生成器实例SegmentIDGenImpl并初始化
     * @throws SQLException
     * @throws InitException
     */
    public SegmentService() throws SQLException, InitException {
   
        // 1. 加载leaf.properties配置文件
        Properties properties = PropertyFactory.getProperties();
        // 是否开启号段模式
        boolean flag = Boolean.parseBoolean(properties.getProperty(Constants.LEAF_SEGMENT_ENABLE, "true"));
        if (flag) {
   
            // 2. 创建Druid dataSource
            dataSource = new DruidDataSource();
            dataSource.setUrl(properties.getProperty(Constants.LEAF_JDBC_URL));
            dataSource.setUsername(properties.getProperty(Constants.LEAF_JDBC_USERNAME));
            dataSource.setPassword(properties.getProperty(Constants.LEAF_JDBC_PASSWORD));
            dataSource.init();

            // 3. 创建Dao
            IDAllocDao dao = new IDAllocDaoImpl(dataSource);

            // 4. 创建ID生成器实例SegmentIDGenImpl
            idGen = new SegmentIDGenImpl();
            ((SegmentIDGenImpl) idGen).setDao(dao);
            // 初始化SegmentIDGenImpl(加载db的tags至内存cache中,并开启定时同步更新任务)
            if (idGen.init()) {
   
                logger.info("Segment Service Init Successfully");
            } else {
   
                throw new InitException("Segment Service Init Fail");
            }
        } else {
   
            // ZeroIDGen一直返回id=0
            idGen = new ZeroIDGen();
            logger.info("Zero ID Gen Service Init Successfully");
        }
    }

    /**
     * 根据key获取id
     * @param key
     * @return
     */
    public Result getId(String key) {
   
        return idGen.get(key);
    }

    /**
     * 获取号段模式id生成器SegmentIDGenImpl
     * @return
     */
    public SegmentIDGenImpl getIdGen() {
   
        if (idGen instanceof SegmentIDGenImpl) {
   
            return (SegmentIDGenImpl) idGen;
        }
        return null;
    }
}

SegmentService 类的构造函数,主要完成以下几件事:

  1. 加载 leaf.properties 配置文件,并解析配置
  2. 创建 Druid 数据源对象 dataSource
  3. 创建 IDAllocDao 接口实例 IDAllocDaoImpl
  4. 创建ID生成器实例 SegmentIDGenImpl 并初始化
① 解析leaf.properties配置文件

通过 PropertyFactory 读取了 leaf.properties 配置文件并进行解析。其中所以的key-value配置信息最终封装为 Properties 中。

/**
 * 加载leaf.properties配置文件中配置信息
 */
public class PropertyFactory {
   
    private static final Logger logger = LoggerFactory.getLogger(PropertyFactory.class);
    private static final Properties prop = new Properties();
    static {
   
        try {
   
            prop.load(PropertyFactory.class.getClassLoade
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值