身份證上的 ID 能保證唯一性麼?

以下文章來源於真沒什麼邏輯 ,作者Draveness

為什麼這麼設計(Why's THE Design)是一系列關於計算機領域中程序設計決策的文章,我們在這個系列的每一篇文章中都會提出一個具體的問題並從不同的角度討論這種設計的優缺點、對具體實現造成的影響。如果你有想要了解的問題,可以在文章下面留言。

標識符 ID 是我們作為軟件工程師再熟悉不過的字段和概念了,我們經常打交道的 MySQL 就經常使用 ID 作為主鍵,ID 在軟件工程甚至生活中都是一個非常常見的概念,我們為什麼總是需要在業務或者系統中引入『無意義』的 ID 呢,在這裡先來看兩個有意義的 ID:

  • 日常生活中使用的 18 位身份證號;
  • 支付票據上面的 20191002XXXXXXX;

上述的兩個 ID 是否會有重複的可能?這對於今天想要分析和討論的事情密切相關,在這篇文章中作者就會分析『為什麼總是需要無意義的 ID』。

概述

我們首先需要解決的問題是 ID 到底是什麼,ID 一般會被認為是 identifier 的縮寫,在 Wikipedia 上我們能夠找到這樣的定義:

An identifier is a name that identifies (that is, labels the identity of) either a unique object or a unique class of objects, where the "object" or class may be an idea, physical [countable] object (or class thereof), or physical [noncountable] substance (or class thereof). The abbreviation ID often refers to identity, identification (the process of identifying), or an identifier (that is, an instance of identification). An identifier may be a word, number, letter, symbol, or any combination of those.

標識符(identifier)就是一個可以唯一識別一個對象或者物體的名稱,被識別的對象可能是一些想法、物理上可數的對象或者物理上的不可數物質。它的前綴 ID 經常被用來表示身份、鑑定過程或者標識符,其中的標識符可能是一個單詞、數字、字母、符號或者上述元素的任意組合。

在標識符的定義中我們需要特別注意的是『唯一』這個詞,這個詞是其定義中最關鍵的信息,標識符一定能夠幫助我們識別唯一一個的對象或者物體,如果它不能實現這個作用,就不是標識符。唯一這個詞幫助我們確定了標識符的特性,也為我們後面的分析過程鋪平了道路。

設計

在這一節中我們將開始分析為什麼很多業務或者場景中都需要一個唯一 ID,例如:消息隊列、TCP 通信等場景,我們可以將這一問題歸結到兩個原因上:

  • 需要通過唯一的標識符對數據或者事件進行識別或者去重;
  • 只有無意義的標識符才會絕對唯一的,任何攜帶其他信息的標識都可能重複;

唯一性

消息隊列往往需要對外保證服務質量,可能需要提供包括最多一次、最少一次和正好一次在內的服務質量,由於網絡可能存在超時等不確定性,當我們想要實現正好一次時,就一定需要一種機制能夠在接收方識別發送方發出的重複消息,在這時就需要使用唯一的標識符來解決這個問題:

身份證上的 ID 能保證唯一性麼?

我們在之前的系列文章 為什麼 TCP 建立連接需要三次握手 提到的 TCP 連接中的序列號也是一個唯一的標識符,它能夠幫助我們判斷對數據進行去重,保證應用層的協議不會收到異常的數據包,這些場景都需要用到標識符的唯一性,唯一性為我們帶來的就是精確識別對象的能力。

在與網絡相關的場景下,使用唯一 ID 的例子非常普遍,假如我們想通過支付寶或者微信的 API 向其他人發起一筆轉賬,如果這次請求發生了超時,那麼我的這筆轉賬請求到底有沒有被處理呢?

身份證上的 ID 能保證唯一性麼?

當前的節點對於這筆轉賬請求的結果是不知道的,如果這時重新請求可能會發生二次轉賬這類嚴重的問題,但是如果不重新請求,轉賬可能沒有生效,這時如果我們引入一個無意義的 ID 來幫助接收方識別請求的唯一性就能很好地解決這個問題:

  1. 如果接收方已經成功處理 ID 對應的請求,那麼就直接返回;
  2. 如果接收方沒有處理 ID 對應的請求,就正常進行處理;

為了保證請求的唯一性,根據業務對於唯一性要求的強弱,我們需要在接收方對 ID 進行存儲,可以在內存中,也可以在數據庫中,最重要的是唯一的 ID 為接收方提供了判斷重複的重要依據。

除了在不穩定的網絡中,數據庫也包含 ID 標識符這一概念,我們在數據庫中往往叫做主鍵,它在一般情況下都是一個遞增的唯一整數,絕大多數的表都會使用 ID 作為表的主鍵來保證數據的唯一性,當我們想要對數據進行增刪改查等操作時,使用主鍵 ID 查詢數據也是性能最優並且最不容易出現問題的做法。

無意義

無意義的意思其實就是 ID 中不應該包含任何與具體場景或者業務相關的內容,包含這些內容並不是不可以,只是一旦出現這些內容,要麼 ID 重複的可能性會增加,這很可能對我們的業務邏輯造成比較嚴重的影響,以我們的身份證號為例,它的 18 位數字(或符號)大多都是有意義的。

身份證上的 ID 能保證唯一性麼?

這 18 位數字中的前 6 位表示的是地區,也就是省份、城市和區縣,隨後的 8 位表示的是出生年月日,接下來的 3 位才同時表示 ID 和性別,最後 1 位用於做校驗碼防止出現身份證號輸錯的情況。用上述圖中的黃色部分中有一半的數字是用來表示出生的男性,另一半表示出生的女性,所以如果同一個地區的同一天,同時出生了 501 位男性或者女性就會導致潛在的重複問題。

上面談到的問題其實也是我們在各種業務場景中經常能夠遇到的問題,18 位的數字中真正用於表示序列的 ID 其實只有 1000 的一半,如果 18 位數都是無意義的,那它們可以表示 10 億億個人,但是一旦在 ID 中引入了業務上的具體信息,就增加了衝突的可能性。

業務記錄上主鍵的長度往往都是固定的,大多數業務的主鍵都會使用整數,它的上限一般就是 2^64,如果這些位數都用來表示記錄的 ID,那麼在有生之年基本上是不可能被使用完的,但是一旦我們將業務信息加入 ID,就會讓原本無意義的 ID 變得有意義從而影響它的唯一性。

另一個比較類似的例子其實是分佈式的 ID 生成器,Snowflake 算法會為 64 個比特的整數賦予不同的信息:

範圍長度作用0-01不使用1-4141毫秒級時間戳42-465數據中心標識符47-515機器標識符52-6312序列號

從這個設計來看,我們的假設其實是一臺機器上一毫秒最多隻能生成 4096 個 ID,一旦超過了這個這個數量就有可能導致 ID 衝突或者亂序,從而失去其唯一性;這個算法中涉及的時間戳、數據中心標識符、機器標識符都沒有辦法解決唯一性的問題,哪怕這三者完全相等,最終還是有衝突的可能,我們仍然需要使用無其他意義的序列號來保證 ID 的唯一。

總結

其實不難看出,使用無意義 ID 的主要目的就是利用它的唯一性保證對象的標識符不會發生衝突,無意義 ID 的唯一作用就是保證唯一性,這能幫助我們避免業務字段可能存在潛在衝突的可能,這也提示我們想要使用聯合字段構成主鍵時一定要深思熟慮。

如果我們想要在具有唯一性的標識符中加入業務信息,一定要注意這可能會減少用於保證唯一性的『空間』,當然對於一個足夠大的空間來說,這其實並沒有什麼問題;但是類型為 int64 的 ID 中加入業務數據還是需要仔細思考可擴展性以及預留的信息是否足夠業務的發展。

到最後,我們還是來看一些比較開放的相關問題,有興趣的讀者可以仔細思考一下下面的問題:

  • 軟件工程還有哪些場景利用了 ID 的唯一性?
  • 在日常生活中除了身份證號之外,還有哪些 ID 也有比較高的衝突可能性?

如果對文章中的內容有疑問或者想要了解更多軟件工程上一些設計決策背後的原因,可以在博客下面留言,作者會及時回覆本文相關的疑問並選擇其中合適的主題作為後續的內容。


分享到:


相關文章: