移植美团 leaf 从 java 到 golang

853 阅读5分钟

移植美团 leaf 从 java 到 golang


引言

关于生成分布式 ID 服务的方案有很多,基本上都是基于 twitter 的 snowflake 来实现,而美团的leaf则把snowflake和号段模式给集成到一起。问题是美团开源的是java实现的,团队则使用 golang 的多,在网上搜索了一波相关 golang 版本到 leaf 服务,没找到相关仓库,于是把 java 的版本移植到了 go 版本。


目录

  1. 准备
  2. 移植中遇到的问题
  3. 新增etcd支持
  4. 总结
  5. 了解更多

1、准备

美团分享的技术文章里针对两种方案的设计和优化进行阐述。技术要求点是db模式的双 buffer 以及动态调整号段的 step,号段模式下对 db 的高可用保障要求高。 snowflake 模式下,美团实现了通过 zookeeper 维护 workerID 来保障高可用性,snowflake 模式下服务器时间回调可能出现重复问题。

美团开源的 java 代码里分两块服务,leaf-core 和 leaf-server,leaf-core 则是通用服务的封装,实现了号段模式和 snowflake 的 id 生成。leaf-server 则是提供对外API的接口和服务自身的监控。代码聚焦于 SegmentIDGenImplSnowflakeZookeeperHolder 以及 SnowflakeIDGenImpl。其中代码里用到了抽象工厂模式来实现统一接口封装。 需要关注的是 java leaf 项目用到的几个库 spring boot mybatis curator,需要用 go 相关的库替代。

2、移植中遇到的问题

代码移植就是挨个把 java 的方法实现用 go 再实现一遍,其中工厂模式的设计也是用 go 的接口来封装,java 里的 class,在 go 里则用 struct。 java 里的原子数操作方法封装好,go 里面操作不是很友好,最后用 uber 的 atomic 库,实现和 java 的差不多。其中刚开始 go 版本的 db 模式的序号加1操作有问题,导致号段最后会跳号,查了 java getAndIncrement() 的方法 api,确定是 go 里面直接先加1返回,而 java 则是先返回再加1。

移植 zookeeper 的代码时,在java 里是用了 CuratorFramework客户端框架,于是搜了一下官方的 zookeeper 社区是否有官方实现的 go 版本客户端框架,最后搜一个能用的 go 版本封装的库,最大问题是注释少,只能看代码实现。

初期使用没问题,但是测试发现 zookeeper 的节点名字有问题,会自动编号,对于 zookeeper 用的少,了解不多,又是补了一波 zookeeper 的相关内容,发现节点创建模式,分别是


Enum Constant and Description

CONTAINER                        The znode will be a container node.
EPHEMERAL                        The znode will be deleted upon the client's disconnect.
EPHEMERAL_SEQUENTIAL             The znode will be deleted upon the client's disconnect, and its name will be appended with a monotonically increasing number.
PERSISTENT                       The znode will not be automatically deleted upon client's disconnect.
PERSISTENT_SEQUENTIAL            The znode will not be automatically deleted upon client's disconnect, and its name will be appended with a monotonically increasing number.
PERSISTENT_SEQUENTIAL_WITH_TTL   The znode will not be automatically deleted upon client's disconnect, and its name will be appended with a monotonically increasing number.
PERSISTENT_WITH_TTL              The znode will not be automatically deleted upon client's disconnect.

go 的 zookeeper 库里面实现了3种模式,分别是


const (
	FlagEphemeral = 1
	FlagSequence  = 2
	FlagTTL       = 4
) 

变量定义没有注释,导致查了半天几个使用的区别。最后才确定正确的使用方式。

移植中,测试发现美团的号段模式在一种情况下会浪费号段情况,就是在发生宕机或者服务重启的情况下,号段会从下个号段开始加载,这样之前号段没用使用到的就会被浪费掉。这个问题官方分享的技术文章里也提到了 号段模式:低位趋势增长,较少的ID号段浪费,能够容忍MySQL的短时间不可用。

java 版本里则只提供了 zk 模式维护 workerID 的方式,对于小规模部署来说,上 zk 可能太重了,所以在移植过程中提供了配置选项,可通过参数开启和关闭 zk 模式,这样小规模部署只需配置好 workerID 就可以了。

3、新增 etcd 支持

java 版本的 leaf 只实现了 zookeeper 维护 workerID,基于团队的技术栈是 go,所以有了对了 etcd 的支持。本地开发搭建 etcd 集群环境,通过 docker-composer 来启动3个节点,

docker-composer.yml 配置如下[点击打开]

version: '3'
networks:
  cluster_net:
    driver: ${NETWORKS_DRIVER}
    ipam:
      driver: default
      config:
      -
        subnet: 10.0.75.1/24
volumes:
  etcd1:
    driver: ${VOLUMES_DRIVER}
  etcd2:
    driver: ${VOLUMES_DRIVER}
  etcd3:
    driver: ${VOLUMES_DRIVER}

services:
    etcd1:
      image: quay.io/coreos/etcd:v3.5.0-alpha.0
      volumes:
        - "${DATA_PATH_HOST}/etcd/node1:/etcd-data"      
      expose:
        - 2379
        - 2380
      ports:
        - "${ETCD1_API_PORT}:2379"
        - "${ETCD1_ADMIN_PORT}:2380"
      networks:
        cluster_net:
          ipv4_address: 10.0.75.100
      environment:
        - ETCDCTL_API=3
      command:
        - /usr/local/bin/etcd
        - --data-dir=/etcd-data
        - --name
        - node1
        - --initial-advertise-peer-urls
        - http://10.0.75.100:2380
        - --listen-peer-urls
        - http://0.0.0.0:2380
        - --advertise-client-urls
        - http://0.0.0.0:2379
        - --listen-client-urls
        - http://10.0.75.100:2379
        - --initial-cluster
        - node1=http://10.0.75.100:2380,node2=http://10.0.75.101:2380,node3=http://10.0.75.102:2380
        - --initial-cluster-state
        - new
        - --initial-cluster-token
        - docker-etcd

    etcd2:
      image: quay.io/coreos/etcd:v3.5.0-alpha.0
      volumes:
        - "${DATA_PATH_HOST}/etcd/node2:/etcd-data"      
      expose:
        - 2379
        - 2380
      ports:
        - "${ETCD2_API_PORT}:2379"
        - "${ETCD2_ADMIN_PORT}:2380"
      networks:
        cluster_net:
          ipv4_address: 10.0.75.101
      environment:
        - ETCDCTL_API=3
      command:
        - /usr/local/bin/etcd
        - --data-dir=/etcd-data
        - --name
        - node2
        - --initial-advertise-peer-urls
        - http://10.0.75.101:2380
        - --listen-peer-urls
        - http://0.0.0.0:2380
        - --advertise-client-urls
        - http://10.0.75.101:2379
        - --listen-client-urls
        - http://0.0.0.0:2379
        - --initial-cluster
        - node1=http://10.0.75.100:2380,node2=http://10.0.75.101:2380,node3=http://10.0.75.102:2380
        - --initial-cluster-state
        - new
        - --initial-cluster-token
        - docker-etcd

    etcd3:
      image: quay.io/coreos/etcd:v3.5.0-alpha.0
      volumes:
        - "${DATA_PATH_HOST}/etcd/node3:/etcd-data"      
      expose:
        - 2379
        - 2380
      ports:
        - "${ETCD3_API_PORT}:2379"
        - "${ETCD3_ADMIN_PORT}:2380"
      networks:
        cluster_net:
          ipv4_address: 10.0.75.102
      environment:
        - ETCDCTL_API=3
      command:
        - /usr/local/bin/etcd
        - --data-dir=/etcd-data
        - --name
        - node3
        - --initial-advertise-peer-urls
        - http://10.0.75.102:2380
        - --listen-peer-urls
        - http://0.0.0.0:2380
        - --advertise-client-urls
        - http://10.0.75.102:2379
        - --listen-client-urls
        - http://0.0.0.0:2379
        - --initial-cluster
        - node1=http://10.0.75.100:2380,node2=http://10.0.75.101:2380,node3=http://10.0.75.102:2380
        - --initial-cluster-state
        - new
        - --initial-cluster-token
        - docker-etcd

代码逻辑基本上和 zookeeper 实现差不多,就是客户端连接不一样。

4、总结

移植准备工作不充分,对使用到的库不熟悉,后面应对 java 的常用组件要加强熟悉,对 go 相关库的源码要看一遍,能够快速确定相关方法及定义的大概功能。

下面是运行的录频,可以看一下效果。

启动运行录频[点击打开]

asciicast

asciicast


项目里相关链接

5、了解更多

原文链接:移植美团 leaf 从 java 到 golang

微信搜索公众号:DigitMagic魔数实验室