java高并發(fā) 1、高并發(fā)情況下,生成分布式全局id策略 2、利用全球唯一UUID生成訂單號優(yōu)缺點 3、基于數(shù)據(jù)庫自增或者序列生成訂單號 4、數(shù)據(jù)庫集群如何考慮數(shù)據(jù)庫自增唯一性 5、基于Redis生成生成全局id策略 6、Twitter的Snowflake算法生成全局id 7、基于Zookeeper生成全局id 高并發(fā)情況下,生成分布式全局id策略 1、注意冪等性且全局唯一性 2、注意安全性,不能被猜疑 3、趨勢遞增性 訂單號命名規(guī)則:比如“業(yè)務(wù)編碼 + 時間戳 + 機(jī)器編號[前4位] + 隨機(jī)4位數(shù) + 毫秒數(shù)”。 利用全球唯一UUID生成訂單號 UUID基本概念: UUID是指在一臺機(jī)器上生成的數(shù)字,它保證對在同一時空中的所有機(jī)器都是唯一的。 UUID組成部分:當(dāng)前日期和時間+時鐘序列+隨機(jī)數(shù)+全局唯一的IEEE機(jī)器識別號 全局唯一的IEEE機(jī)器識別號:如果有網(wǎng)卡,從網(wǎng)卡MAC地址獲得,沒有網(wǎng)卡以其他方式獲得。 UUID優(yōu)缺點: 優(yōu)點: 簡單,代碼方便 生成ID性能非常好,基本不會有性能問題 全球唯一,在遇見數(shù)據(jù)遷移,系統(tǒng)數(shù)據(jù)合并,或者數(shù)據(jù)庫變更等情況下,可以從容應(yīng)對 缺點: 沒有排序,無法保證趨勢遞增 UUID往往是使用字符串存儲,查詢的效率比較低 存儲空間比較大,如果是海量數(shù)據(jù)庫,就需要考慮存儲量的問題。 傳輸數(shù)據(jù)量大 UUID不需要聯(lián)網(wǎng)生成,redis需要。 基于數(shù)據(jù)庫自增方式 實現(xiàn)思路:利用數(shù)據(jù)庫自增或者序列號方式實現(xiàn)訂單號 注意:在數(shù)據(jù)庫集群環(huán)境下,默認(rèn)自增方式存在問題,因為都是從1開始自增,可能會存在重復(fù),應(yīng)該設(shè)置每臺不同數(shù)據(jù)庫自增的間隔方式不同。 優(yōu)點: 簡單,代碼方便,性能可以接受。 數(shù)字ID天然排序,對分頁或者需要排序的結(jié)果很有幫助。 缺點: 不同數(shù)據(jù)庫語法和實現(xiàn)不同,數(shù)據(jù)庫遷移的時候或多數(shù)據(jù)庫版本支持的時候需要處理。 在性能達(dá)不到要求的情況下,比較難于擴(kuò)展。 在單個數(shù)據(jù)庫或讀寫分離或一主多從的情況下,只有一個主庫可以生成。有單點故障的風(fēng)險。 分表分庫的時候會有麻煩。 數(shù)據(jù)庫集群如何考慮數(shù)據(jù)庫自增唯一性 在數(shù)據(jù)庫集群環(huán)境下,默認(rèn)自增方式存在問題,因為都是從1開始自增,可能會存在重復(fù),應(yīng)該設(shè)置每臺節(jié)點自增步長不同。 查詢自增的步長 SHOW VARIABLES LIKE 'auto_inc%' 修改自增的步長 SET @@auto_increment_increment=10; 修改起始值 SET @@auto_increment_offset=5; 假設(shè)有兩臺mysql數(shù)據(jù)庫服務(wù)器 節(jié)點①自增 1 3 5 7 9 11 …. 節(jié)點②自增 2 4 6 8 10 12 …. 注意:在最開始設(shè)置好了每臺節(jié)點自增方式步長后,確定好了mysql集群數(shù)量后,無法擴(kuò)展新的mysql,不然生成步長的規(guī)則可能會發(fā)生變化。 MySQL1 1 2 3 MySQL2 1 2 3 方法1 讀寫分離 方法2 設(shè)置自增步長 需要提前設(shè)置好步長 否則如果新增一臺MySQL就麻煩了 如果想提高擴(kuò)展性 采用UUID方式作為主鍵 基于Redis生成生成全局id策略 因為Redis是單線的,天生保證原子性,可以使用Redis的原子操作 INCR和INCRBY來實現(xiàn) 優(yōu)點: 不依賴于數(shù)據(jù)庫,靈活方便,且性能優(yōu)于數(shù)據(jù)庫。 數(shù)字ID天然排序,對分頁或者需要排序的結(jié)果很有幫助。 缺點: 如果系統(tǒng)中沒有Redis,還需要引入新的組件,增加系統(tǒng)復(fù)雜度。 需要編碼和配置的工作量比較大。 注意:在Redis集群情況下,同樣和Redis一樣需要設(shè)置不同的增長步長,同時key一定要設(shè)置有效期 可以使用Redis集群來獲取更高的吞吐量。假如一個集群中有5臺Redis。可以初始化每臺Redis的值分別是1,2,3,4,5,然后步長都是5。各個Redis生成的ID為: A:1,6,11,16,21 B:2,7,12,17,22 C:3,8,13,18,23 D:4,9,14,19,24 E:5,10,15,20,25 比較適合使用Redis來生成每天從0開始的流水號。比如訂單號=日期+當(dāng)日自增長號??梢悦刻煸赗edis中生成一個Key,使用INCR進(jìn)行累加。 如果生成的訂單號超過自增增長的話,可以采用前綴+自增+并且設(shè)置有效期 當(dāng)前日期-5位自增 統(tǒng)一時間 最多生成10w-1個不重復(fù)的 假設(shè)雙十一 每秒99w訂單 package com.toov5.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.support.atomic.RedisAtomicLong;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class OrderController { @Autowired private RedisTemplate redisTemplate; @RequestMapping("/order") public Long order(String key) { RedisAtomicLong redisAtomicLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory() ); long andIncrement = redisAtomicLong.getAndIncrement(); return andIncrement; }} 補(bǔ)零: import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.support.atomic.RedisAtomicLong;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class OrderController { @Autowired private RedisTemplate redisTemplate; @RequestMapping("/order") public String order(String key) { RedisAtomicLong redisAtomicLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory() ); long increment = redisAtomicLong.getAndIncrement(); String id = String.format("%1$05d", increment); //5位數(shù) return id; }} 加上前綴: import java.text.SimpleDateFormat;import java.util.Date;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.support.atomic.RedisAtomicLong;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class OrderController { @Autowired private RedisTemplate redisTemplate; @RequestMapping("/order") public String order(String key) { RedisAtomicLong redisAtomicLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory() ); long increment = redisAtomicLong.getAndIncrement(); String id =prefix()+"-"+String.format("%1$05d", increment); //5位數(shù) return id; } public static String prefix() { String temp_str = ""; Date dt = new Date(); // 最后的aa表示“上午”或“下午” HH表示24小時制 如果換成hh表示12小時制 SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); temp_str = sdf.format(dt); return temp_str; }} Redis如果做集群,會產(chǎn)生重復(fù)的問題! @RequestMapping("/order1") public String order1(String key) { RedisAtomicLong redisAtomicLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory()); // // 起始值 // redisAtomicLong.set(10); // 設(shè)置步長加10!!!! redisAtomicLong.addAndGet(9); return redisAtomicLong.incrementAndGet() + ""; } redis 的key的失效時間問題! 24h 第二天時間變了 不會重復(fù)了哦 Twitter的snowflake(雪花)算法 (跟UUID一樣不用聯(lián)網(wǎng)) snowflake是Twitter開源的分布式ID生成算法,結(jié)果是一個long型的ID。其核心思想是: 高位隨機(jī)+毫秒數(shù)+機(jī)器碼(數(shù)據(jù)中心+機(jī)器id)+10位的流水號碼 Github地址: https://github.com/twitter-archive/snowflakeSnowflake 原理: snowflake生產(chǎn)的ID是一個18位的long型數(shù)字,二進(jìn)制結(jié)構(gòu)表示如下(每部分用-分開): 0 - 00000000 00000000 00000000 00000000 00000000 0 - 00000 - 00000 - 00000000 0000 第一位未使用,接下來的41位為毫秒級時間(41位的長度可以使用69年,從1970-01-01 08:00:00),然后是5位datacenterId(最大支持2^5=32個,二進(jìn)制表示從00000-11111,也即是十進(jìn)制0-31),和5位workerId(最大支持2^5=32個,原理同datacenterId),所以datacenterId*workerId最多支持部署1024個節(jié)點,最后12位是毫秒內(nèi)的計數(shù)(12位的計數(shù)順序號支持每個節(jié)點每毫秒產(chǎn)生2^12=4096個ID序號).所有位數(shù)加起來共64位,恰好是一個Long型(轉(zhuǎn)換為字符串長度為18).單臺機(jī)器實例,通過時間戳保證前41位是唯一的,分布式系統(tǒng)多臺機(jī)器實例下,通過對每個機(jī)器實例分配不同的datacenterId和workerId避免中間的10位碰撞。最后12位每毫秒從0遞增生產(chǎn)ID,再提一次:每毫秒最多生成4096個ID,每秒可達(dá)4096000個。 |
|