SnowFlake分布式自增ID--基于Zookeeper的集群实现

分布式系统中,有一些需要全局唯一ID的场景,为了防止ID冲突,一般都会使用发号器
最简单的方式便是采用UUID,但UUID无序。
分布式id生成算法的有很多种,Twitter的SnowFlake就是其中经典的一种,并且生成的ID在整体上有序。

如,mysql集群可以通过自增步长解决集群中生成ID的唯一性及有序性,但是集群中增删mysql节点时,需要对所有mysql节点的自增步长进行调整。

对于SnowFlake的原理介绍,可以参考文章 理解分布式id生成算法SnowFlake

依然国际惯例,先上代码 SnowFlakeWithZK

SnowFlakeWithZK可以轻松搭建发号器集群,并通过Zookeeper管理workId,免去频繁修改集群节点配置的麻烦

使用

安装

下载并解压 SnowFlakeWithZK-1.0.1.zip

进入解压目录并执行 ./SnowFlakeWithZK.jar start

API

  • GET http(s)://[host]:[port]/api/next/long 以长整型返回
  • GET http(s)://[host]:[port]/api/next/hex 以十六进制返回
  • GET http(s)://[host]:[port]/api/next/bin 以二进制返回
  • GET http(s)://[host]:[port]/api/parse/long/{id} 解析长整型id
  • GET http(s)://[host]:[port]/api/parse/hex/{id} 解析十六进制id

单机使用

修改SnowFlakeWithZK.confRUN_ARGS参数,新增--zookeeper.enable=false

集群使用

使用zookeeper

修改SnowFlakeWithZK.confRUN_ARGS参数,新增--zookeeper.enable=true --zookeeper.url=[zookeeper-host]:[zookeeper-port]

SnowFlakeWithZK通过Zookeeper管理workId

不使用zookeeper

修改SnowFlakeWithZK.confRUN_ARGS参数,新增--zookeeper.enable=false --machineId.workId=[You workId]

注:集群中每个SnowFlake实例的workId需要保证各不相同

RUN_ARGS参数

--server.port 服务端口
--machineId.dataCenterId 数据中心id,0~31,默认16
--machineId.workerId 实例id,0~31,默认5,--zookeeper.enable=false时生效,同一数据中心的不同实例,需要保证各不相同
--zookeeper.enable 是否使用zookeeper管理workerId,默认true
--zookeeper.url zookeeper 连接地址,默认localhost:2181,--zookeeper.enable=true时生效

源码解析

项目采用springboot框架,通过@ConditionalOnProperty注解控制是否使用zookeeper

当配置zookeeper.enablefalse时,通过配置中的machineId.workId来启动worker

/**
 * 单机配置SnowFlake的Machine ID
 *
 * 设置 zookeeper.enable = false
 */
@ConditionalOnProperty("zookeeper.enable", matchIfMissing = true, havingValue = "false")
@Configuration
class SingletonConfiguration {
    private val logger = LoggerFactory.getLogger(SingletonConfiguration::class.java)

    @Value("\${machineId.dataCenterId:16}")
    private var dataCenterId: Long = 16

    @Value("\${machineId.workerId:0}")
    private var workerId: Long = 0

    @Bean
    fun idWorker(): IdWorker {
        logger.info("Singleton Detected! Create IdWorker using SingletonConfiguration!")
        return IdWorker(workerId, dataCenterId)
    }
}

当配置zookeeper.enabletrue时,通过配置中的zookeeper.url连接zk,并在zk中创建临时有序节点,通过节点的序号控制workId

/**
 * 使用zookeeper配置SnowFlake集群的Machine ID
 *
 * 设置 zookeeper.enable = true
 */
@ConditionalOnProperty("zookeeper.enable")
@Configuration
class ZKConfiguration {
    private val logger = LoggerFactory.getLogger(ZKConfiguration::class.java)

    @Value("\${zookeeper.url}")
    private lateinit var url: String

    @Value("\${machineId.datacenterId:16}")
    private var dataCenterId: Long = 16

    @Bean
    @Primary
    fun idWorker(): IdWorker {
        logger.info("Zookeeper Detected! Create IdWorker using ZKConfiguration!")
        val client = CuratorFrameworkFactory.builder()
                .connectString(url)
                .sessionTimeoutMs(5000)
                .connectionTimeoutMs(5000)
                .retryPolicy(ExponentialBackoffRetry(1000, 3))
                .build()

        client.start()

        val parent = "/snowflake/$dataCenterId"
        val worker = "$parent/worker"
        client.checkExists().forPath("/snowflake/$dataCenterId")
                ?: client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(parent)

        // 利用临时节点序列设置workerId
        val name = client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(worker)
        val workerId = name.substring(worker.length).toLong()
        var idWorker = IdWorker(workerId, dataCenterId)

        // 重连监听
        client.connectionStateListenable.addListener(ConnectionStateListener { _client: CuratorFramework, state: ConnectionState ->
            when (state) {
                ConnectionState.RECONNECTED -> {
                    val name = _client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(worker)
                    val workerId = name.substring(worker.length).toLong()
                    idWorker.workerId = workerId
                    logger.info("ZK ReConnected. workerId changed: $workerId")
                }
                ConnectionState.LOST, ConnectionState.SUSPENDED -> {
                    logger.warn("ZK is Abnormal. State is $state")
                }
                else -> {
                    logger.info("ZK State Changed: $state")
                }
            }
        })

        return idWorker
    }
}

您可以fork该项目,轻松的接入Spring Cloud、Dubbo等微服务框架中
如果本工具有帮助到您,欢迎star


订阅号

阅读 4.9k

推荐阅读
林中小舍
用户专栏

工作中的坑点及经验

51 人关注
41 篇文章
专栏主页