一、單據(jù)號(hào)是指什么

  我們作為一個(gè)軟件系統(tǒng),肯定到處充滿著各種單據(jù),也必然需要有各種單據(jù)號(hào)與之對(duì)應(yīng)。比如:電商行業(yè)的訂單號(hào)、支付流水號(hào)、退款單號(hào)等等。SCM的采購單號(hào)、進(jìn)貨單號(hào)、出貨單號(hào)、盤點(diǎn)單號(hào)等。在一個(gè)企業(yè)內(nèi)部或者一個(gè)2C的平臺(tái),無法避免的需要通過某個(gè)單據(jù)號(hào)來進(jìn)行溝通。所以一個(gè)好的單據(jù)號(hào)必然是便于溝通的,簡(jiǎn)單來說優(yōu)先級(jí)就是 好記 > 好輸入 > 好看,當(dāng)然也是越短越好。

 

 

二、和唯一ID的不同是什么

  有的人可能會(huì)問,好像聽的最多的就是唯一ID,包括大量的文章都是講分布式唯一ID的生成的,好像和單據(jù)號(hào)相關(guān)的很少。但是其實(shí)我覺得這2者并沒有沖突,只是重要性和針對(duì)場(chǎng)景不同。下面從不同的角度來分析一下:  

1)唯一性:唯一是ID其實(shí)更多的是為了保證這個(gè)ID在整個(gè)系統(tǒng)中都是唯一的,它對(duì)唯一的定義范圍更加廣。而對(duì)單據(jù)號(hào)來說,它只要保證在所屬的單據(jù)類型下唯一即可,比如訂單號(hào):00001和物流號(hào):00001其實(shí)并不相互影響。

  

2)可讀性:如果僅僅作為唯一ID來用,其實(shí)最簡(jiǎn)單粗暴的方式就是使用UUID,因?yàn)樗鼉H僅給程序使用,人并不需要理解這個(gè)ID的意義。但是單據(jù)號(hào)則不同,上面也提到了,它需要有一定的可讀性,便于人與人之間的溝通。想象一下你和其它人電話溝通時(shí)報(bào)一串UUID是什么體驗(yàn)。

  

3)業(yè)務(wù)性:?jiǎn)螕?jù)號(hào)大部分情況下還需要承擔(dān)一定的業(yè)務(wù)含義的體現(xiàn),比如訂單號(hào)T00001中的T = Trade、支付號(hào)P00001中的P = Pay等。甚至還有可能需要多筆單據(jù)號(hào)之間有一定的關(guān)聯(lián),比如一個(gè)訂單號(hào)T00001下相關(guān)的支付號(hào)都必須是P00001-1,P00001-2這個(gè)樣子。再甚至有些場(chǎng)景需要包含一些日期信息在其中。

 

 

三、為什么需要全局唯一單據(jù)號(hào)生成程序

   和唯一ID一樣,單據(jù)號(hào)的生成本身也是一個(gè)相對(duì)穩(wěn)定并且通用的規(guī)則,所以把它提煉成一個(gè)單獨(dú)的程序可以提供更好的復(fù)用性,避免了各自項(xiàng)目維護(hù)單據(jù)號(hào)所花費(fèi)的重復(fù)勞動(dòng)。特別在互聯(lián)網(wǎng)行業(yè)中的大流量企業(yè),還需要考慮性能和高可用問題。所以真的要把生成單據(jù)號(hào)這個(gè)“小功能”做好,還是需要一定的投入的。那么把它作為一個(gè)單獨(dú)的程序能夠把投入所產(chǎn)生的收益,也就是所謂的“ROI”放大,何樂而不為?

 

 

四、實(shí)現(xiàn)的方式有哪些

  下面羅列一下常用的實(shí)現(xiàn)方式和各自的優(yōu)缺點(diǎn):

  1)前綴列+全局自增列:

    這個(gè)和唯一ID的方案類似,利用自增列的數(shù)字來做。且最簡(jiǎn)單的方式就是依賴數(shù)據(jù)庫的自增列來做。

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

      實(shí)現(xiàn)簡(jiǎn)單,不斷的++
      能夠保證全局的唯一性
      能夠保證遞增
      可讀性尚可

    缺點(diǎn):
      需要依賴一個(gè)持久化的地方存儲(chǔ)當(dāng)前已經(jīng)生成的“游標(biāo)”位置,所以性能有上限,基本就是單應(yīng)用的TPS上限或者所依賴DB的TPS上限
      在一些對(duì)外的單據(jù)號(hào)上容易泄露一些商業(yè)信息。比如競(jìng)爭(zhēng)對(duì)手可以通過單號(hào)猜出你每天的訂單量甚至每個(gè)小時(shí)、每分鐘的訂單量。

    破除單點(diǎn)的改進(jìn)方案:

      ①水平拆分進(jìn)行多寫+同步長(例:機(jī)器1的自增數(shù)為1,4,7,...;機(jī)器2的自增數(shù)為2,5,8,...;機(jī)器3的自增數(shù)為3,6,9,...):

        新的缺點(diǎn):由于是多寫,所以需要依賴于負(fù)載均衡策略和網(wǎng)絡(luò)通訊的延時(shí)問題,無法保證生成的序號(hào)是100%遞增的。(例:哪怕是round robin策略先請(qǐng)求1再請(qǐng)求2,但是還是有可能2先返回響應(yīng)。)

     ?、诖怪辈鸱侄鄬?自增列(機(jī)器1專門用于生成訂單號(hào)、機(jī)器2專門用于生成支付單號(hào)):

        新的缺點(diǎn):

          a.由于根據(jù)業(yè)務(wù)來分,所以流量不均導(dǎo)致某些大請(qǐng)求量的單據(jù)還是存在著單點(diǎn)瓶頸問題。

          b.擴(kuò)展性較差。每增加一個(gè)業(yè)務(wù)單據(jù)就需要增加一個(gè)程序

     ?、鬯讲鸱?增加機(jī)器碼位(給每臺(tái)生成單據(jù)號(hào)的程序編個(gè)號(hào):1,2,3插入到自增列的前面):

        新的缺點(diǎn):

          a.這個(gè)編碼要么硬配置到配置文件中,或者依賴與某個(gè)分配編號(hào)的獨(dú)立程序。并且號(hào)碼長度變長了。

          b.無法保證遞增。

    提高性能的改進(jìn)方案:

      ①預(yù)生成到緩存,減少對(duì)DB的依賴

        新的缺點(diǎn):

          a.如果需要徹底減少對(duì)DB的依賴,那么每次單據(jù)號(hào)被消耗是不應(yīng)該回寫DB的,也導(dǎo)致了一旦程序重啟會(huì)存在比較大的序號(hào)空洞。

          b.緩存的大小與DB獲取下一段緩存數(shù)據(jù)的頻率負(fù)相關(guān)的,當(dāng)頻率比較高的時(shí)候,需要做雙緩存來預(yù)加載下一段緩存數(shù)據(jù),避免緩存消耗完之后從DB拉取最新數(shù)據(jù)產(chǎn)生的阻塞。

 

  2)前綴列+日期+自增列:

    我想這個(gè)方案應(yīng)該是大部分系統(tǒng)會(huì)采用的方案。這個(gè)日期的精度和自增數(shù)的數(shù)據(jù)長度是有關(guān)聯(lián)的。日期精度越高,對(duì)于自增數(shù)的數(shù)據(jù)長度需求就越短,反之則越長。

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

      實(shí)現(xiàn)比較容易
      能夠保證唯一性
      能夠保證遞增
      包含日期能體現(xiàn)更多的業(yè)務(wù)信息

    缺點(diǎn):
      方案1的缺點(diǎn)都有

      針對(duì)日期讓自增列進(jìn)行重置需要做一定的邏輯判斷,復(fù)雜度提高(在多線程下有線程安全問題),性能降低。

    破除單點(diǎn)的改進(jìn)方案:

      ① 1)中的改進(jìn)方案。

    提高性能的改進(jìn)方案

      ① 1)中的改進(jìn)方案。

     ?、?nbsp;對(duì)自增列的重置可以忽略日期變動(dòng)(也就是哪怕到了下一個(gè)時(shí)間段,自增數(shù)也不重置,繼續(xù)使用),而直接對(duì)整數(shù)進(jìn)行++,直到自動(dòng)進(jìn)入下一循環(huán)。在C#中,你可以這樣寫:

       var uint32 = (long)UInt32.MaxValue;
            Interlocked.Add(ref uint32, 1);
            Console.WriteLine((UInt32)uint32);

       但是這里需要注意的是,這個(gè)自增列的數(shù)字上限必須能保證在日期的最小精度范圍內(nèi)不會(huì)產(chǎn)生重復(fù)。

        新的缺點(diǎn):

          a.哪怕請(qǐng)求量不大,也會(huì)產(chǎn)生過長的單據(jù)號(hào),因?yàn)樽栽鰯?shù)不會(huì)主動(dòng)重置。

 

五、筆者推薦的方式

   筆者個(gè)人覺得綜合來看,

增加機(jī)器碼位(給每臺(tái)生成單據(jù)號(hào)的程序編個(gè)號(hào):1,2,3插入到自增列的前面)

  這個(gè)方案是相對(duì)最一勞永逸的。但是需要在數(shù)據(jù)長度和可讀性上需要做出一定的權(quán)衡。首先為了保證遞增,那么我們必然需要增加時(shí)間到整個(gè)單據(jù)號(hào)的前面。時(shí)間可以使用常規(guī)的日期格式也可以使用時(shí)間戳,當(dāng)然相同精度來說,肯定是時(shí)間戳更短??紤]到實(shí)際的大部分場(chǎng)景中,單據(jù)號(hào)只要能夠識(shí)別到是哪一種類型的單據(jù),剩下的一般來說本身就是需要去對(duì)應(yīng)的單據(jù)列表中找到該筆單據(jù)的詳細(xì)信息查看。所以其實(shí)對(duì)日期的可讀性并不是那么高。(舉個(gè)例子:客戶報(bào)出一個(gè)訂單號(hào)出來給我們的客服人員,其實(shí)客服人員必然是需要去查看這筆訂單的詳細(xì)信息的。)

  OK,那它的長度我們可以如此來設(shè)計(jì):

  電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

 

  其中時(shí)間戳、自增數(shù)是全局共用的,所以對(duì)于單獨(dú)某一類型的單據(jù)號(hào)并不是連續(xù)的,但是是趨勢(shì)遞增的,這解決了根據(jù)訂單號(hào)猜到訂單量之類的問題。

  那么在這樣的設(shè)計(jì)下可以支撐單據(jù)號(hào)不重復(fù)的上限是多少呢?其實(shí)就是單點(diǎn)在1秒內(nèi)的最大量100000000 /1000 = 100000/ms,1毫秒10W個(gè),以snowflake的生成速度4000/ms來算(網(wǎng)絡(luò)來源,未經(jīng)實(shí)際驗(yàn)證),再根據(jù)摩爾定律考慮CPU升級(jí)的影響,大約需要50年后才有可能產(chǎn)生重復(fù)。并且理論最大值是100臺(tái)程序負(fù)載均衡,1000W/ms,估計(jì)這輩子不用考慮重復(fù)問題了。

  有的人可能會(huì)問,為什么不直接時(shí)間戳取到毫秒位,會(huì)增加3位長度,后面自增數(shù)就可以短一點(diǎn)。首先按照比snowflake算法多冗余一個(gè)位數(shù)來看,哪怕取到時(shí)間戳到毫秒,后面還是需要5位(snowflake是4位:4000/ms),所以這個(gè)并沒有什么區(qū)別。那么精度取到秒的好處是什么?我認(rèn)為有2點(diǎn):

1)解決了預(yù)加載問題,由于精度到秒,所以哪怕程序重啟了,我的自增數(shù)從0開始累加也不會(huì)產(chǎn)生重復(fù)。

  

2)如果精度是毫秒,那么相當(dāng)于不管我的每秒并發(fā)量是多少,哪怕1秒就1個(gè)請(qǐng)求進(jìn)來,也固定占用3位長度。但是如果是秒,那么就省去了這3位,我想除了像阿里騰訊這種體量的公司,實(shí)際的環(huán)境中毫秒并發(fā)達(dá)到1W已經(jīng)不得了了。

  其中還有一些細(xì)節(jié)是:

    1.機(jī)器碼如果是個(gè)位數(shù),那么前面加0填充,以免與后面的自增列結(jié)合后產(chǎn)生重復(fù)(例:機(jī)器1,序號(hào)11。和機(jī)器11,序號(hào)1會(huì)重復(fù))。

    2.每個(gè)程序所在服務(wù)器上的時(shí)鐘同步需要做好,因?yàn)槲覀円蕾囉诖吮WC遞增問題。

  最終,理論上實(shí)際生產(chǎn)環(huán)境生成的號(hào)碼長度在15~19之間。

 

六、結(jié)語

  一個(gè)設(shè)計(jì)良好的單據(jù)號(hào),不但可以用于主鍵,也可以用于做分庫分表,比如我們把用戶ID按照某個(gè)規(guī)則得出的幾位數(shù)字拼到單據(jù)號(hào)的最后,那么直接用這個(gè)號(hào)來定位數(shù)據(jù)庫,可以確保一個(gè)用戶的訂單全部落在一個(gè)同一個(gè)數(shù)據(jù)庫里。

  但是值得提醒的是,我們不能過于盲目的追求一步到位,需要結(jié)合自身的實(shí)際情況來選擇合適的方式就好。前面列出的一些常見的方案在系統(tǒng)初期也是能很好的工作的。

 

 

 

 

作者:Zachary_Fan
出處:http://www.cnblogs.com/Zachary-Fan/p/Global_Unique_No.html

 

 

如果你想及時(shí)得到個(gè)人自寫文章的消息推送,歡迎掃描下面的二維碼~。

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

http://www.cnblogs.com/Zachary-Fan/p/Global_Unique_No.html