分散式系統中的必備良藥 —— 全局唯一單據號生成

来源:http://www.cnblogs.com/Zachary-Fan/archive/2017/07/04/Global_Unique_No.html
-Advertisement-
Play Games

閱讀目錄 單據號是指什麼 和唯一ID的不同是什麼 為什麼需要全局唯一單據號生成程式 實現的方式有哪些 筆者推薦的方式 結語 一、單據號是指什麼 我們作為一個軟體系統,肯定到處充滿著各種單據,也必然需要有各種單據號與之對應。比如:電商行業的訂單號、支付流水號、退款單號等等。SCM的採購單號、進貨單號、 ...


閱讀目錄

 

 

一、單據號是指什麼

  我們作為一個軟體系統,肯定到處充滿著各種單據,也必然需要有各種單據號與之對應。比如:電商行業的訂單號、支付流水號、退款單號等等。SCM的採購單號、進貨單號、出貨單號、盤點單號等。在一個企業內部或者一個2C的平臺,無法避免的需要通過某個單據號來進行溝通。所以一個好的單據號必然是便於溝通的,簡單來說優先順序就是 好記 > 好輸入 > 好看,當然也是越短越好。

 

 

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

  有的人可能會問,好像聽的最多的就是唯一ID,包括大量的文章都是講分散式唯一ID的生成的,好像和單據號相關的很少。但是其實我覺得這2者並沒有衝突,只是重要性和針對場景不同。下麵從不同的角度來分析一下:  

1)唯一性:唯一是ID其實更多的是為了保證這個ID在整個系統中都是唯一的,它對唯一的定義範圍更加廣。而對單據號來說,它只要保證在所屬的單據類型下唯一即可,比如訂單號:00001和物流號:00001其實並不相互影響。

  

2)可讀性:如果僅僅作為唯一ID來用,其實最簡單粗暴的方式就是使用UUID,因為它僅僅給程式使用,人並不需要理解這個ID的意義。但是單據號則不同,上面也提到了,它需要有一定的可讀性,便於人與人之間的溝通。想象一下你和其它人電話溝通時報一串UUID是什麼體驗。

  

3)業務性:單據號大部分情況下還需要承擔一定的業務含義的體現,比如訂單號T00001中的T = Trade、支付號P00001中的P = Pay等。甚至還有可能需要多筆單據號之間有一定的關聯,比如一個訂單號T00001下相關的支付號都必須是P00001-1,P00001-2這個樣子。再甚至有些場景需要包含一些日期信息在其中。

 

 

三、為什麼需要全局唯一單據號生成程式

   和唯一ID一樣,單據號的生成本身也是一個相對穩定並且通用的規則,所以把它提煉成一個單獨的程式可以提供更好的復用性,避免了各自項目維護單據號所花費的重覆勞動。特別在互聯網行業中的大流量企業,還需要考慮性能和高可用問題。所以真的要把生成單據號這個“小功能”做好,還是需要一定的投入的。那麼把它作為一個單獨的程式能夠把投入所產生的收益,也就是所謂的“ROI”放大,何樂而不為?

 

 

四、實現的方式有哪些

  下麵羅列一下常用的實現方式和各自的優缺點:

  1)首碼列+全局自增列:

    這個和唯一ID的方案類似,利用自增列的數字來做。且最簡單的方式就是依賴資料庫的自增列來做。

    優點:

      實現簡單,不斷的++
      能夠保證全局的唯一性
      能夠保證遞增
      可讀性尚可

    缺點:
      需要依賴一個持久化的地方存儲當前已經生成的“游標”位置,所以性能有上限,基本就是單應用的TPS上限或者所依賴DB的TPS上限
      在一些對外的單據號上容易泄露一些商業信息。比如競爭對手可以通過單號猜出你每天的訂單量甚至每個小時、每分鐘的訂單量。

    破除單點的改進方案:

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

        新的缺點:由於是多寫,所以需要依賴於負載均衡策略和網路通訊的延時問題,無法保證生成的序號是100%遞增的。(例:哪怕是round robin策略先請求1再請求2,但是還是有可能2先返迴響應。)

      ②垂直拆分多寫+自增列(機器1專門用於生成訂單號、機器2專門用於生成支付單號):

        新的缺點:

          a.由於根據業務來分,所以流量不均導致某些大請求量的單據還是存在著單點瓶頸問題。

          b.擴展性較差。每增加一個業務單據就需要增加一個程式

      ③水平拆分+增加機器碼位(給每台生成單據號的程式編個號:1,2,3插入到自增列的前面):

        新的缺點:

          a.這個編碼要麼硬配置到配置文件中,或者依賴與某個分配編號的獨立程式。並且號碼長度變長了。

          b.無法保證遞增。

    提高性能的改進方案:

      ①預生成到緩存,減少對DB的依賴

        新的缺點:

          a.如果需要徹底減少對DB的依賴,那麼每次單據號被消耗是不應該回寫DB的,也導致了一旦程式重啟會存在比較大的序號空洞。

          b.緩存的大小與DB獲取下一段緩存數據的頻率負相關的,當頻率比較高的時候,需要做雙緩存來預載入下一段緩存數據,避免緩存消耗完之後從DB拉取最新數據產生的阻塞。

 

  2)首碼列+日期+自增列:

    我想這個方案應該是大部分系統會採用的方案。這個日期的精度和自增數的數據長度是有關聯的。日期精度越高,對於自增數的數據長度需求就越短,反之則越長。

    優點:

      實現比較容易
      能夠保證唯一性
      能夠保證遞增
      包含日期能體現更多的業務信息

    缺點:
      方案1的缺點都有

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

    破除單點的改進方案:

      ① 1)中的改進方案。

    提高性能的改進方案

      ① 1)中的改進方案。

      ② 對自增列的重置可以忽略日期變動(也就是哪怕到了下一個時間段,自增數也不重置,繼續使用),而直接對整數進行++,直到自動進入下一迴圈。在C#中,你可以這樣寫:

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

       但是這裡需要註意的是,這個自增列的數字上限必須能保證在日期的最小精度範圍內不會產生重覆。

        新的缺點:

          a.哪怕請求量不大,也會產生過長的單據號,因為自增數不會主動重置。

 

五、筆者推薦的方式

   筆者個人覺得綜合來看,

增加機器碼位(給每台生成單據號的程式編個號:1,2,3插入到自增列的前面)

  這個方案是相對最一勞永逸的。但是需要在數據長度和可讀性上需要做出一定的權衡。首先為了保證遞增,那麼我們必然需要增加時間到整個單據號的前面。時間可以使用常規的日期格式也可以使用時間戳,當然相同精度來說,肯定是時間戳更短。考慮到實際的大部分場景中,單據號只要能夠識別到是哪一種類型的單據,剩下的一般來說本身就是需要去對應的單據列表中找到該筆單據的詳細信息查看。所以其實對日期的可讀性並不是那麼高。(舉個例子:客戶報出一個訂單號出來給我們的客服人員,其實客服人員必然是需要去查看這筆訂單的詳細信息的。)

  OK,那它的長度我們可以如此來設計:

  

 

  其中時間戳、自增數是全局共用的,所以對於單獨某一類型的單據號並不是連續的,但是是趨勢遞增的,這解決了根據訂單號猜到訂單量之類的問題。

  那麼在這樣的設計下可以支撐單據號不重覆的上限是多少呢?其實就是單點在1秒內的最大量100000000 /1000 = 100000/ms,1毫秒10W個,以snowflake的生成速度4000/ms來算(網路來源,未經實際驗證),再根據摩爾定律考慮CPU升級的影響,大約需要50年後才有可能產生重覆。並且理論最大值是100台程式負載均衡,1000W/ms,估計這輩子不用考慮重覆問題了。

  有的人可能會問,為什麼不直接時間戳取到毫秒位,會增加3位長度,後面自增數就可以短一點。首先按照比snowflake演算法多冗餘一個位數來看,哪怕取到時間戳到毫秒,後面還是需要5位(snowflake是4位:4000/ms),所以這個並沒有什麼區別。那麼精度取到秒的好處是什麼?我認為有2點:

1)解決了預載入問題,由於精度到秒,所以哪怕程式重啟了,我的自增數從0開始累加也不會產生重覆。

  

2)如果精度是毫秒,那麼相當於不管我的每秒併發量是多少,哪怕1秒就1個請求進來,也固定占用3位長度。但是如果是秒,那麼就省去了這3位,我想除了像阿裡騰訊這種體量的公司,實際的環境中毫秒併發達到1W已經不得了了。

  其中還有一些細節是:

    1.機器碼如果是個位數,那麼前面加0填充,以免與後面的自增列結合後產生重覆(例:機器1,序號11。和機器11,序號1會重覆)。

    2.每個程式所在伺服器上的時鐘同步需要做好,因為我們依賴於此保證遞增問題。

  最終,理論上實際生產環境生成的號碼長度在15~19之間。

 

六、結語

  一個設計良好的單據號,不但可以用於主鍵,也可以用於做分庫分表,比如我們把用戶ID按照某個規則得出的幾位數字拼到單據號的最後,那麼直接用這個號來定位資料庫,可以確保一個用戶的訂單全部落在一個同一個資料庫里。

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

 

 

 

 

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

 

 

如果你想及時得到個人自寫文章的消息推送,歡迎掃描下麵的二維碼~。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 通過添加powershell插件後,使用它強大的windows系統命令,就把發佈好的程式包推送到具體的應用伺服器了。 系統管理-插件管理-powershell 把它安裝,重啟jenkins,然後修改你之前的job,把powershell的推送文件腳本加上 添加一個構建類型,在msbuild下麵添加 ...
  • Jenkins是一個持續集成的環境,它是java開發的,大叔認為它的工作流程是 從源代碼拉一個項目下來到它本地(可以配置定時機制) 恢復相關程式包nuget 編譯程式 發佈程式 現在說一下在配置jenkins里要註意的幾個地方: jenkins的構建工作目錄和job目錄說明 構建目錄:C:\Prog ...
  • 本章內容還在整理上傳中,你可以等全部更新完畢後再查閱也可以先預覽已上傳的內容。。。。。。 7. 應用層的命令模式 在上個章節里我們設計並編碼了領域對象Permission,但是目前Permission並沒有任何行為上的設計。這是因為我們不建議“憑空去製造行為”,而是在領域對象第一個版本的代碼實現之後 ...
  • "DDD理論學習系列——案例及目錄" 1. 引言 A domain event is a full fledged part of the domain model, a representation of something that happened in the domain. Ignore ...
  • 設計模式分類 設計模式是一套被反覆使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。主流的設計模式有23種,總體來說可以分為三大類 創建型模式(5) 工廠方法模式(Factory Pattern) 一種場景是希望工廠與產品的 ...
  • 通過使用ps加上msbuild可以方便的編譯你的.net應用程式,並且可以把它發佈到你的磁碟上,部署非常方例! 我們在c盤添加一個hello網站,解決方案名是hello.sln,它的網站是hello.csproj,現在使用這個腳本來生成這個網站和發佈這個網站! 最後生成的網站結果為 這個功能對於自動 ...
  • 靜態頁面:純html頁面 動態頁面:內容存在資料庫中,根據要求顯示,url中以? &顯示不同的參數 偽靜態:僅僅是對靜態頁面的重寫,不能讓動態頁面靜態化。搜索引擎不會認為偽靜態就是HTML文檔。其次,偽靜態可取,但應把重心放在去除冗餘參數、規範URL、儘可能的避免 重覆頁上。 舉例說明: 這是一個動 ...
  • 對於應用程式而言,日誌是非常重要的功能,通過日誌,我們可以跟蹤應用程式的數據狀態,記錄Crash的日誌可以幫助我們分析應用程式崩潰的原因,我們甚至可以通過日誌來進行性能的監控。總之,日誌的好處很多,特別是對Release之後的線上版本進行異常的跟蹤。 日誌存儲的分類 在平常開發時,我們通常喜歡在De ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...