SnowFlake分佈式自增ID--基於Zookeeper的集羣實現

分佈式系統中,有一些須要全局惟一ID的場景,爲了防止ID衝突,通常都會使用發號器
最簡單的方式即是採用UUID,但UUID無序。
分佈式id生成算法的有不少種,Twitter的SnowFlake就是其中經典的一種,而且生成的ID在總體上有序。

如,mysql集羣能夠經過自增步長解決集羣中生成ID的惟一性及有序性,可是集羣中增刪mysql節點時,須要對全部mysql節點的自增步長進行調整。java

對於SnowFlake的原理介紹,能夠參考文章 理解分佈式id生成算法SnowFlakemysql

依然國際慣例,先上代碼 SnowFlakeWithZKgit

SnowFlakeWithZK能夠輕鬆搭建發號器集羣,並經過Zookeeper管理workId,免去頻繁修改集羣節點配置的麻煩github

使用

安裝

下載並解壓 SnowFlakeWithZK-1.0.1.zip算法

進入解壓目錄並執行 ./SnowFlakeWithZK.jar startspring

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=falsesql

集羣使用

使用zookeeper

修改SnowFlakeWithZK.confRUN_ARGS參數,新增--zookeeper.enable=true --zookeeper.url=[zookeeper-host]:[zookeeper-port]segmentfault

SnowFlakeWithZK經過Zookeeper管理workId

不使用zookeeper

修改SnowFlakeWithZK.confRUN_ARGS參數,新增--zookeeper.enable=false --machineId.workId=[You workId]api

注:集羣中每一個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

源碼解析

項目採用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

相關文章
相關標籤/搜索