分佈式系統中,有一些須要全局惟一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 start
spring
修改SnowFlakeWithZK.conf
中RUN_ARGS
參數,新增--zookeeper.enable=false
sql
修改SnowFlakeWithZK.conf
中RUN_ARGS
參數,新增--zookeeper.enable=true --zookeeper.url=[zookeeper-host]:[zookeeper-port]
segmentfault
SnowFlakeWithZK經過Zookeeper管理workId
修改SnowFlakeWithZK.conf
中RUN_ARGS
參數,新增--zookeeper.enable=false --machineId.workId=[You workId]
api
注:集羣中每一個SnowFlake實例的workId須要保證各不相同
--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.enable
爲false
時,經過配置中的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.enable
爲true
時,經過配置中的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