一、背景

          分布式系統(tǒng)中我們會(huì)對(duì)一些數(shù)據(jù)量大的業(yè)務(wù)進(jìn)行分拆,如:用戶表,訂單表。因?yàn)閿?shù)據(jù)量巨大一張表無(wú)法承接,就會(huì)對(duì)其進(jìn)行分庫(kù)分表。
          但一旦涉及到分庫(kù)分表,就會(huì)引申出分布式系統(tǒng)中唯一主鍵ID的生成問題。

          1.1 唯一ID的特性

          * 整個(gè)系統(tǒng)ID唯一;
          * ID是數(shù)字類型,而且是趨勢(shì)遞增;
          * ID簡(jiǎn)短,查詢效率快。
          1.2 遞增與趨勢(shì)遞增

          遞增 趨勢(shì)遞增
          第一次生成的ID為12,下一次生成的ID是13,再下一次生成的ID是14。
          什么是?如:在一段時(shí)間內(nèi),生成的ID是遞增的趨勢(shì)。如:再一段時(shí)間內(nèi)生成的ID在【0,1000】之間,過段時(shí)間生成的ID在【1000,2000】之間。但在【0-1000】區(qū)間內(nèi)的時(shí)候,ID生成有可能第一次是12,第二次是10,第三次是14。
          二、方案

          2.1 UUID

          UUID全稱:Universally Unique Identifier。標(biāo)準(zhǔn)型式包含32個(gè)16進(jìn)制數(shù)字,以連字號(hào)分為五段,形式為8-4-4-4-12
          的36個(gè)字符,示例:9628f6e9-70ca-45aa-9f7c-77afe0d26e05。

          * 優(yōu)點(diǎn):
          * 代碼實(shí)現(xiàn)簡(jiǎn)單;
          * 本機(jī)生成,沒有性能問題;
          * 因?yàn)槭侨蛭ㄒ坏腎D,所以遷移數(shù)據(jù)容易。
          * 缺點(diǎn):
          * 每次生成的ID是無(wú)序的,無(wú)法保證趨勢(shì)遞增;
          * UUID的字符串存儲(chǔ),查詢效率慢;
          * 存儲(chǔ)空間大;
          * ID本身無(wú)業(yè)務(wù)含義,不可讀。
          * 應(yīng)用場(chǎng)景:
          * 類似生成token令牌的場(chǎng)景;
          * 不適用一些要求有趨勢(shì)遞增的ID場(chǎng)景,不適合作為高性能需求的場(chǎng)景下的數(shù)據(jù)庫(kù)主鍵。
          也有在線生成UUID的網(wǎng)站,如果你的項(xiàng)目上用到了UUID,可以用來生成臨時(shí)的測(cè)試數(shù)據(jù)。https://www.uuidgenerator.net/
          <https://www.uuidgenerator.net/>

          2.2 MySQL主鍵自增

          利用了MySQL的主鍵自增auto_increment,默認(rèn)每次ID加1。

          優(yōu)點(diǎn):

          * 數(shù)字化,ID遞增;
          * 查詢效率高;
          * 具有一定的業(yè)務(wù)可讀。
          * 缺點(diǎn):
          * 存在單點(diǎn)問題,如果MySQL掛了,就沒法生成ID了;
          * 數(shù)據(jù)庫(kù)壓力大,高并發(fā)抗不住。
          2.3 MySQL多實(shí)例主鍵自增

          這個(gè)方案就是解決MySQL的單點(diǎn)問題,在auto_increment基本上面,設(shè)置step步長(zhǎng)


          如上,每臺(tái)的初始值分別為1,2,3...N,步長(zhǎng)為N(這個(gè)案例步長(zhǎng)為4)

          * 優(yōu)點(diǎn):解決了單點(diǎn)問題;
          * 缺點(diǎn):一旦把步長(zhǎng)定好后,就無(wú)法擴(kuò)容;而且單個(gè)數(shù)據(jù)庫(kù)的壓力大,數(shù)據(jù)庫(kù)自身性能無(wú)法滿足高并發(fā)。
          * 應(yīng)用場(chǎng)景:數(shù)據(jù)不需要擴(kuò)容的場(chǎng)景。
          2.4 基于Redis實(shí)現(xiàn)

          *
          單機(jī):Redis的incr函數(shù)在單機(jī)上是原子操作,可以保證唯一且遞增。

          *
          集群:?jiǎn)螜C(jī)Redis可能無(wú)法支撐高并發(fā)。集群情況下,可以使用步長(zhǎng)的方式。比如有5個(gè)Redis節(jié)點(diǎn)組成的集群,它們生成的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
          * 優(yōu)點(diǎn):有序遞增,可讀性強(qiáng)。
          * 缺點(diǎn):占用帶寬,每次要向Redis進(jìn)行請(qǐng)求。
          三、優(yōu)化方案

          3.1、改造數(shù)據(jù)庫(kù)主鍵自增

          數(shù)據(jù)庫(kù)的自增主鍵的特性,可以實(shí)現(xiàn)分布式ID,適合做userId,正好符合如何永不遷移數(shù)據(jù)和避免熱點(diǎn)? 但這個(gè)方案有嚴(yán)重的問題:

          * 一旦步長(zhǎng)定下來,不容易擴(kuò)容;
          * 數(shù)據(jù)庫(kù)壓力山大。
          * 為什么壓力大?
          因?yàn)槲覀兠看潍@取ID的時(shí)候,都要去數(shù)據(jù)庫(kù)請(qǐng)求一次。那我們可以不可以不要每次去???

          可以請(qǐng)求數(shù)據(jù)庫(kù)得到ID的時(shí)候,可設(shè)計(jì)成獲得的ID是一個(gè)ID區(qū)間段。


          * 上圖ID規(guī)則表含義:
          * id表示為主鍵,無(wú)業(yè)務(wù)含義;
          * biz_tag為了表示業(yè)務(wù),因?yàn)檎w系統(tǒng)中會(huì)有很多業(yè)務(wù)需要生成ID,這樣可以共用一張表維護(hù);
          * max_id表示現(xiàn)在整體系統(tǒng)中已經(jīng)分配的最大ID;
          * desc描述;
          * update_time表示每次取的ID時(shí)間;
          * 整體流程:
          * 【用戶服務(wù)】在注冊(cè)一個(gè)用戶時(shí),需要一個(gè)用戶ID;會(huì)請(qǐng)求【生成ID服務(wù)(是獨(dú)立的應(yīng)用)】的接口;
          * 【生成ID服務(wù)】會(huì)去查詢數(shù)據(jù)庫(kù),找到user_tag的id,現(xiàn)在的max_id為0,step=1000;
          * 【生成ID服務(wù)】把max_id和step返回給【用戶服務(wù)】;并且把max_id更新為max_id = max_id + step,即更新為1000;
          * 【用戶服務(wù)】獲得max_id=0,step=1000;
          * 這個(gè)用戶服務(wù)可以用ID=【max_id + 1,max_id+step】區(qū)間的ID,即為【1,1000】;
          * 【用戶服務(wù)】會(huì)把這個(gè)區(qū)間保存到j(luò)vm中;
          * 【用戶服務(wù)】需要用到ID的時(shí)候,在區(qū)間【1,1000】中依次獲取ID,可采用AtomicLong中的getAndIncrement方法;
          *
          如果把區(qū)間的值用完了,再去請(qǐng)求【生產(chǎn)ID服務(wù)】接口,獲取到max_id為1000,即可以用【max_id + 1,max_id+step】區(qū)間的ID,即為
          【1001,2000】。

          * 該方案就非常完美的解決了數(shù)據(jù)庫(kù)自增的問題,而且可以自行定義max_id的起點(diǎn),和step步長(zhǎng),非常方便擴(kuò)容;
          *
          也解決了數(shù)據(jù)庫(kù)壓力的問題,因?yàn)樵谝欢螀^(qū)間內(nèi),是在jvm內(nèi)存中獲取的,而不需要每次請(qǐng)求數(shù)據(jù)庫(kù)。即使數(shù)據(jù)庫(kù)宕機(jī)了,系統(tǒng)也不受影響,ID還能維持一段時(shí)間。

          3.2 競(jìng)爭(zhēng)問題

          以上方案中,如果是多個(gè)用戶服務(wù),同時(shí)獲取ID,同時(shí)去請(qǐng)求【ID服務(wù)】,在獲取max_id的時(shí)候會(huì)存在并發(fā)問題。如:

          用戶服務(wù)A,取到的max_id=1000 ;用戶服務(wù)B取到的也是max_id=1000,那就出現(xiàn)了問題,ID重復(fù)了。

          解決方案是:加分布式鎖,保證同一時(shí)刻只有一個(gè)用戶服務(wù)獲取max_id。

          3.3 突發(fā)阻塞問題



          因?yàn)楦?jìng)爭(zhēng)問題,所有只有一個(gè)用戶服務(wù)去操作數(shù)據(jù)庫(kù),其他二個(gè)會(huì)被阻塞。出現(xiàn)的現(xiàn)象就是一會(huì)兒突然系統(tǒng)耗時(shí)變長(zhǎng),怎么去解決?

          * 雙buffer方案


          流程如下:

          * 當(dāng)前獲取ID在buffer1中,每次獲取ID在buffer1中獲取;
          * 當(dāng)buffer1中的ID已經(jīng)使用到了100,也就是達(dá)到區(qū)間的10%;
          * 達(dá)到了10%,先判斷buffer2中有沒有去獲取過,如果沒有就立即發(fā)起請(qǐng)求獲取ID線程,此線程把獲取到的ID,設(shè)置到buffer2中;
          * 如果buffer1用完了,會(huì)自動(dòng)切換到buffer2;
          * buffer2用到10%了,也會(huì)啟動(dòng)線程再次獲取,設(shè)置到buffer1中;
          * 依次往返。
          3.4 總結(jié)

          * 雙buffer的方案就達(dá)到了業(yè)務(wù)場(chǎng)景用的ID,都是在jvm內(nèi)存中獲得的,從此不需要到數(shù)據(jù)庫(kù)中獲取了,數(shù)據(jù)庫(kù)宕機(jī)時(shí)長(zhǎng)長(zhǎng)點(diǎn)兒也沒太大影響了。
          * 因?yàn)闀?huì)有一個(gè)線程,會(huì)觀察什么時(shí)候去自動(dòng)獲取。兩個(gè)buffer之間自行切換使用,就解決了突發(fā)阻塞的問題。
          四、其他方式

          還有一些其他的ID生成方案,比如:

          * 滴滴:時(shí)間+起點(diǎn)編號(hào)+車牌號(hào);
          * 淘寶訂單:時(shí)間戳+用戶ID
          * 其他電商:時(shí)間戳+下單渠道+用戶ID,有的會(huì)加上訂單第一個(gè)商品的ID;
          * MongoDB 的ID:通過時(shí)間+機(jī)器碼+pid+inc共12個(gè)字節(jié),4+3+2+3的方式最終標(biāo)識(shí)成一個(gè)24長(zhǎng)度的十六進(jìn)制字符。

          友情鏈接
          ioDraw流程圖
          API參考文檔
          OK工具箱
          云服務(wù)器優(yōu)惠
          阿里云優(yōu)惠券
          騰訊云優(yōu)惠券
          京東云優(yōu)惠券
          站點(diǎn)信息
          問題反饋
          郵箱:[email protected]
          QQ群:637538335
          關(guān)注微信

                双腿打开h调教穿环 | 操比在线观看 | 日本五十路视频 | 麻豆视频免费入口 | 日韩A片在线观看 |