Sdcb Chats 技術(shù)博客:數(shù)據(jù)庫 ID 選型的曲折之路 - 從 Guid 到自增 ID,再到 Guid
當(dāng)前位置:點晴教程→知識管理交流
→『 技術(shù)文檔交流 』
在軟件開發(fā)中,數(shù)據(jù)庫主鍵的選擇,Guid 還是自增整數(shù) ID,一直是一個備受開發(fā)者關(guān)注和討論的經(jīng)典話題。作為開源 ChatGPT 前端項目 Sdcb Chats 的開發(fā)者,我們在這個問題上也經(jīng)歷了一系列探索和演進,頗具代表性。Sdcb Chats 項目致力于打造一個強大、易用、可高度定制的 ChatGPT 及大語言模型前端,幫助用戶輕松連接、管理和使用各種主流的大語言模型。 總的來說,Sdcb Chats 的 ID 策略經(jīng)歷了從最初使用 Guid,到遷移至自增 ID,再到界面顯示加密 ID,最終又回歸到界面顯示 Guid 的過程,其中蘊含著許多有趣的思考和實踐經(jīng)驗,也反映了我們在項目迭代過程中對性能、安全和用戶體驗的不斷權(quán)衡與優(yōu)化。 第一階段 - 擁抱 Guid:“一步到位”的方案項目初期,我的好友 G 負(fù)責(zé)總體系統(tǒng)設(shè)計,包括前端和數(shù)據(jù)庫。他果斷選擇了 Guid 作為主鍵方案。這在當(dāng)時是很自然的選擇,因為在普遍的技術(shù)認(rèn)知中,自增 ID 在分布式系統(tǒng)中似乎存在諸多不便,而 Guid(全局唯一標(biāo)識符)則被視為一種更現(xiàn)代、更通用的解決方案。Guid 的核心優(yōu)勢在于其全局唯一性,能夠在不同的數(shù)據(jù)庫和服務(wù)器之間獨立生成,無需擔(dān)心 ID 沖突問題。 以下是項目初期基于 PostgreSQL 設(shè)計的數(shù)據(jù)庫創(chuàng)建腳本鏈接(如果您感興趣可以查看): 例如,這是
然而,隨著項目的深入發(fā)展,Guid 方案的一些局限性逐漸顯現(xiàn)。首先,Guid 較長的長度和復(fù)雜的結(jié)構(gòu)在某些場景下給數(shù)據(jù)庫性能帶來了一定負(fù)擔(dān)。尤其是在數(shù)據(jù)量快速增長的情況下,索引體積增大,查詢速度變慢等問題開始凸顯。作為負(fù)責(zé)維護公司內(nèi)部 Chats 數(shù)據(jù)庫服務(wù)器的人,我注意到核心表 第二階段 - 性能至上:遷移至自增 IDChats 項目的重構(gòu)是一項系統(tǒng)性工程,數(shù)據(jù)庫的大規(guī)模重構(gòu)只是其中關(guān)鍵環(huán)節(jié)之一。實際上,軟件重構(gòu)是一個持續(xù)迭代的過程,貫穿于項目的整個生命周期。 在我看來,項目重構(gòu)如同軟件的自我革新,需要開發(fā)者具備“刀刃向內(nèi)”的勇氣和決心。當(dāng)我們審視代碼,發(fā)現(xiàn)不足之處,就如同在前進的道路上遇到了障礙。我們當(dāng)然可以選擇繞行,暫時規(guī)避問題,但這些技術(shù)債務(wù)會像隱患一樣潛伏下來,并在未來某個時刻影響系統(tǒng)的穩(wěn)定性和可維護性。特別是對于數(shù)據(jù)庫這種核心模塊,開發(fā)者往往出于謹(jǐn)慎,傾向于避免改動既有結(jié)構(gòu)和數(shù)據(jù)。但長此以往,問題會逐漸累積,最終侵蝕系統(tǒng)的健康。因此,正視并解決這些問題才是負(fù)責(zé)任的做法。 當(dāng)然,在 Chats 項目的重構(gòu)中,我也有著得天獨厚的優(yōu)勢。作為后端設(shè)計的主導(dǎo)者和核心開發(fā)者,我對系統(tǒng)的每一個細(xì)節(jié)都了如指掌。正所謂“船小好調(diào)頭”,即使是數(shù)據(jù)庫大規(guī)模遷移這樣的“大手術(shù)”,我也能快速決策、高效執(zhí)行。事實上,Chats 數(shù)據(jù)庫已經(jīng)經(jīng)歷過多次重要的數(shù)據(jù)遷移,各位可以通過項目倉庫中的數(shù)據(jù)庫遷移腳本了解詳情:https://github.com/sdcb/chats/tree/ebefd93cb187961f8c69dcf04163433ce753a5f3/src/scripts/db-migration。 將主鍵從 Guid 切換為自增 int ID,最直接的好處就是性能的提升,具體體現(xiàn)在以下幾個方面:
當(dāng)然,從 Guid 遷移到自增 ID 并非一帆風(fēng)順,最大的挑戰(zhàn)在于數(shù)據(jù)遷移的復(fù)雜性。我們需要編寫嚴(yán)謹(jǐn)?shù)臄?shù)據(jù)遷移腳本,確保數(shù)據(jù)遷移過程中數(shù)據(jù)的一致性和完整性不受破壞。同時,還需要仔細(xì)評估遷移可能帶來的業(yè)務(wù)影響,例如外鍵關(guān)聯(lián)的更新,以及應(yīng)用程序代碼的調(diào)整。幸運的是,正如前面提到的,Chats 項目已經(jīng)積累了多次數(shù)據(jù)庫遷移的經(jīng)驗,這為我們這次從 Guid 到自增 ID 的遷移奠定了堅實的基礎(chǔ)。 例如,這段 339 行的 C# 數(shù)據(jù)庫遷移腳本(在 LINQPad 中編寫): 在遷移腳本中,我們使用了類似
通過這樣的映射,我們可以在數(shù)據(jù)遷移過程中,將舊的 Guid 主鍵平滑地轉(zhuǎn)換為新的自增 ID,并確保數(shù)據(jù)關(guān)聯(lián)關(guān)系的正確性。 第三階段 - 安全升級:界面 ID 加密完成數(shù)據(jù)庫主鍵從 Guid 到自增 ID 的遷移后,我們又面臨了新的問題:如何在用戶界面上安全地展示 ID。最初,我們直接沿用了數(shù)據(jù)庫的自增 ID,將其暴露在前端界面和 API 接口中。然而,這種做法很快引發(fā)了一些安全性和用戶體驗方面的問題。 例如,當(dāng)創(chuàng)建一個新的聊天會話時,界面 URL 可能會顯示為 此外,從用戶體驗的角度來看,連續(xù)的數(shù)字 ID 也顯得不夠?qū)I(yè)和優(yōu)雅。用戶可能會覺得這些 ID 過于簡單和隨意,與他們對現(xiàn)代聊天應(yīng)用的期望不符。 為了解決這些問題,我們決定對界面上顯示的 ID 進行加密處理。 最初,我使用了這段 C# 代碼來實現(xiàn)整數(shù) ID 的加密:
在這個實現(xiàn)中,整數(shù) ID 首先被轉(zhuǎn)換為小端序的字節(jié)數(shù)組,然后使用 AES 算法的 CBC 模式進行加密。加密后的數(shù)據(jù)被編碼為 Base64 URL 格式,以便在 URL 中安全傳輸。最終,URL 可能呈現(xiàn)為如下形式: 通過這種加密方式,我們不僅提升了系統(tǒng)的安全性,也改善了用戶體驗。加密后的 ID 看起來更加復(fù)雜和專業(yè),有效避免了簡單數(shù)字序列帶來的潛在問題。 其中,初始化向量 IV 的生成方式如下。我定義了一個
每個枚舉值代表一種加密目的。代碼會根據(jù)不同的目的生成不同的 IV。這樣做的目的是確保即使同一個整數(shù) ID 在不同的上下文(例如 ChatId 和 FileId)中被加密,也會產(chǎn)生不同的加密結(jié)果。這種做法提升了安全性和靈活性,我們可以在不同的場景下復(fù)用相同的加密機制,而無需擔(dān)心 ID 重復(fù)或沖突。 可能有朋友會問,為什么不使用隨機 IV,并將 IV 添加到加密后的 ID 中,這樣安全性不是更高嗎?這主要是基于以下兩點考慮: 首先,前端的某些計算邏輯依賴于穩(wěn)定的 ID。在我們的前端代碼中,特別是在聊天會話管理和消息渲染方面,我們大量使用了基于 ID 的緩存和狀態(tài)管理機制。例如,當(dāng)用戶在一個聊天窗口中滾動瀏覽消息時,前端會根據(jù)消息的 ID 來渲染消息之間的父子關(guān)系。如果使用隨機 IV,即使是同一個聊天會話,在不同的時間或不同的上下文中被加密,生成的 ID 都會不同。這會導(dǎo)致前端緩存失效,狀態(tài)管理混亂,最終引發(fā)難以追蹤的 bug。想象一下,用戶明明還在同一個聊天中,但由于 ID 變化,前端卻認(rèn)為這是一個新的聊天,之前的消息緩存全部失效,這無疑會造成糟糕的用戶體驗。為了保證前端邏輯的穩(wěn)定性和可預(yù)測性,我們需要確保在同一上下文中,同一個整數(shù) ID 加密后的結(jié)果始終一致。 其次,不固定的 IV 會顯著增加 ID 的長度。如果將隨機生成的 IV 也附加到加密后的 ID 中,最終的 ID 長度會大大增加。AES 算法的 IV 通常為 16 字節(jié),轉(zhuǎn)換為 Base64 URL 編碼后,會增加約 21 個字符的長度(16 * 4 / 3 ≈ 21.3)。原本加密后的 ID 已經(jīng)比純數(shù)字 ID 長了不少,如果再加上 20 多個字符的 IV,整個 ID 會顯得非常臃腫,尤其是在 URL 中展示時,既不美觀,也增加了 URL 的長度負(fù)擔(dān)。我們希望在保證安全性的前提下,盡可能保持 ID 的簡潔易用。 因此,綜合考慮前端的穩(wěn)定性和 ID 長度,我們最終選擇了使用基于 第四階段 - 兼顧用戶感知:界面顯示為 Guid(當(dāng)前方案)經(jīng)過一段時間的實際運行,我們意識到,雖然加密 ID 解決了安全性問題,但在某些場景下,用戶仍然希望看到一種更具辨識度的 ID 格式。因此,我們最終決定在界面上將 ID 顯示為 Guid 格式。 這種做法的優(yōu)點在于,Guid 格式的 ID 看起來更加隨機和復(fù)雜,更符合用戶對現(xiàn)代應(yīng)用的普遍認(rèn)知,同時也有效避免了直接暴露自增 ID 的問題。在具體實現(xiàn)上,我們將加密后的 ID 轉(zhuǎn)換為 Guid 格式進行展示。這樣一來,用戶在界面上看到的 ID 既安全又專業(yè)。 細(xì)心的朋友可能已經(jīng)注意到,由于我的輸入長度為 4 字節(jié)或 8 字節(jié)(分別對應(yīng) int32 和 int64 類型的 ID),AES CBC 加密后的輸出長度固定為 16 字節(jié)(但前端代碼額外增加了一個字節(jié)作為版本號前綴,固定為 0)。而一個 Guid 的長度恰好也是 16 字節(jié)。因此,只需將 Base64Url 序列化方式替換為 Guid 序列化,即可輕松將加密 ID 轉(zhuǎn)換為 Guid 形式: https://github.com/sdcb/chats/blob/r-407/src/BE/Services/UrlEncryption/Utils.cs#L50-L60
可能有朋友會進一步追問,為什么堅持使用 AES CBC 算法,而不是現(xiàn)在更流行的 AES GCM 算法呢?這又可以展開一篇長文討論。簡單來說: 首先,AES GCM 的隨機性高度依賴于 nonce 的唯一性。Nonce(Number used once)是一個一次性使用的隨機數(shù),在 AES GCM 中扮演著至關(guān)重要的角色,類似于 AES CBC 中的 IV(Initialization Vector,初始化向量)。如果 nonce 在多次加密中重復(fù)使用,尤其是在加密序列化的、遞增的 ID 時,AES GCM 的安全性會大打折扣,甚至可能暴露出加密模式的規(guī)律性。 在我們的場景中,雖然我們?yōu)槊糠N 為了更直觀地說明問題,請看以下 C# 代碼示例:
輸出結(jié)果如下:
請注意觀察 相比之下,AES CBC 雖然也依賴 IV,但即使 IV 固定,只要密鑰安全,其加密結(jié)果的隨機性依然能得到較好的保證。尤其是在我們使用了填充模式(Padding)的情況下,即使輸入數(shù)據(jù)存在一定的規(guī)律性,也能有效地隱藏這種規(guī)律。 其次,AES GCM 的輸出長度會顯著增加,難以適配 Guid 格式。AES GCM 在提供加密功能的同時,還提供了數(shù)據(jù)完整性校驗功能,這是通過附加一個認(rèn)證標(biāo)簽(Authentication Tag,簡稱 Tag)來實現(xiàn)的。這個 Tag 通常是 12 到 16 字節(jié),用于驗證數(shù)據(jù)的完整性和真實性,防止數(shù)據(jù)被篡改。除了 Tag 之外,AES GCM 還需要一個顯式的 nonce 作為輸入。對于我們來說,nonce 至少需要 12 字節(jié)才能保證足夠的安全性。 這意味著,如果我們使用 AES GCM 加密一個 4 字節(jié)的 int32 ID,最終的輸出長度將至少是:4 字節(jié)(密文) + 12 字節(jié)(nonce) + 12 字節(jié)(最小 Tag 大?。?= 28 字節(jié)。即使我們加密一個 8 字節(jié)的 int64 ID,輸出長度也會超過 32 字節(jié)。這樣的長度,無論如何都無法直接塞到一個 16 字節(jié)的 Guid 中。而且,為了將 nonce 和 tag 都塞進去,我們勢必需要設(shè)計更復(fù)雜的序列化方案,這會增加前端和后端的處理復(fù)雜度,也可能導(dǎo)致 ID 格式的不統(tǒng)一,例如一部分 ID 是 Guid,一部分是更長的 Base64 編碼字符串,這會給前端開發(fā)帶來額外的困擾。 我們之所以最終選擇將加密后的 ID 展示為 Guid 格式,一個重要的考量就是希望保持 ID 的統(tǒng)一性和簡潔性。Guid 作為一個 16 字節(jié)的固定長度標(biāo)識符,在很多場景下都非常方便使用和處理。如果我們?yōu)榱俗非?AES GCM 的“更高安全性”而犧牲了 ID 的簡潔性和統(tǒng)一性,反而可能會得不償失。 最后,AES GCM 的額外安全優(yōu)勢在我們的應(yīng)用場景下并非不可或缺。AES GCM 最主要的優(yōu)勢在于它提供的認(rèn)證加密(Authenticated Encryption)功能,即在加密的同時,也保證了數(shù)據(jù)的完整性和真實性。這意味著,如果數(shù)據(jù)在傳輸過程中被篡改,解密時會立即發(fā)現(xiàn)并報錯。這種認(rèn)證功能對于一些對數(shù)據(jù)完整性要求極高的場景非常重要,例如金融交易、電子簽名等。 然而,在我們的 Chats 應(yīng)用中,我們對 ID 的安全性需求主要集中在防止惡意猜測和未經(jīng)授權(quán)的訪問,而不是防止數(shù)據(jù)篡改。即使加密后的 ID 在傳輸過程中被篡改,最終解密出來的 ID 也大概率無法在數(shù)據(jù)庫中找到對應(yīng)的記錄,或者即使找到了,后續(xù)的業(yè)務(wù)邏輯也會進行權(quán)限驗證,確保用戶只能訪問自己擁有的聊天或消息。 更重要的是,即使我們使用 AES CBC,也并非完全沒有數(shù)據(jù)完整性驗證機制。首先,AES CBC 配合填充模式(例如 PKCS7 Padding)本身就提供了一定程度的完整性校驗。對于 int32 類型的 ID,AES CBC 加密后會生成 16 字節(jié)的密文,其中有 12 字節(jié)實際上是填充數(shù)據(jù)。如果密文被篡改,解密時填充校驗會失敗,從而可以檢測到數(shù)據(jù)損壞。雖然這種校驗強度不如 AES GCM 的 Tag 那么高,但也足以應(yīng)對一般的篡改嘗試。 其次,在我們的系統(tǒng)中,解密后的 ID 最終會用于數(shù)據(jù)庫查詢。即使攻擊者能夠繞過 AES CBC 的填充校驗,篡改了加密后的 ID,解密出來的錯誤 ID 在數(shù)據(jù)庫中大概率也找不到對應(yīng)的記錄。即使碰巧找到了記錄,我們也會在數(shù)據(jù)庫層面和業(yè)務(wù)邏輯層面進行多重權(quán)限驗證,確保數(shù)據(jù)的安全性。 因此,綜合考慮以上三點,我們最終權(quán)衡之后,仍然選擇了 AES CBC 算法。它在保證足夠安全性的前提下,能夠生成 16 字節(jié)的密文,完美適配 Guid 格式,并且實現(xiàn)相對簡單,性能也更優(yōu)。當(dāng)然,技術(shù)選型永遠(yuǎn)是一個不斷演進的過程,未來如果我們的安全需求發(fā)生變化,或者 AES GCM 在性能和易用性方面有了新的提升,我們也不排除會重新評估并切換到 AES GCM 的可能性。 總結(jié)與展望回顧 Sdcb Chats 項目 ID 演進的四個階段,從最初擁抱 Guid 的“一步到位”,到為了性能考量轉(zhuǎn)向自增 ID,再到為了安全和體驗在界面上加密 ID,最終又回歸到使用 Guid 形式展示,這的確是一段曲折而又充滿思考的旅程。 在這個過程中,我們不斷地在性能、安全性、用戶體驗和開發(fā)效率之間權(quán)衡取舍。沒有一勞永逸的完美方案,只有在持續(xù)迭代和演進中,才能找到最適合當(dāng)前階段的最佳實踐。每一次看似“倒退”的改動,實際上都基于更深入的理解和更全面的考量。例如,從 Guid 到自增 ID 的轉(zhuǎn)變,是為了解決實際存在的數(shù)據(jù)庫性能瓶頸;而界面上從加密 ID 到 Guid 的回歸,則是在安全性得到保障的前提下,更好地滿足用戶對“現(xiàn)代感”和“專業(yè)性”的用戶感知。 這段經(jīng)歷也印證了軟件開發(fā)中一個重要的理念:沒有銀彈。技術(shù)選型需要結(jié)合具體的應(yīng)用場景和需求,持續(xù)地監(jiān)控和評估,并根據(jù)實際情況靈活調(diào)整。我們不能因為“大家都說 Guid 好”就盲目跟風(fēng),也不能因為“性能至上”就忽略安全性和用戶體驗。只有深入理解各種方案的優(yōu)缺點,才能做出最明智的選擇。 而 Sdcb Chats 項目的 ID 演進之路,也正是開源項目不斷迭代、持續(xù)進化的一個縮影。我們始終秉持著開放、務(wù)實的態(tài)度,積極擁抱變化,勇于嘗試新的技術(shù)方案,并不斷地從實踐中總結(jié)經(jīng)驗教訓(xùn)。 如果您對我們這曲折的 ID 選型故事,以及 Sdcb Chats 項目本身感興趣,歡迎繼續(xù)了解! Sdcb Chats:一個強大的開源 ChatGPT 前端 我是開源項目 Sdcb Chats 的作者。Sdcb Chats 定位為一個強大且易于部署的 ChatGPT 前端,旨在幫助用戶輕松接入和管理各種主流的大語言模型。 Sdcb Chats 的主要特性包括:
無論您是個人開發(fā)者、技術(shù)愛好者,還是企業(yè)用戶,Sdcb Chats 都能為您提供一個強大、靈活、易用的 ChatGPT 前端解決方案。 如果您覺得 Sdcb Chats 對您有所幫助,或者您認(rèn)同我們的技術(shù)理念和開源精神,請在 GitHub 上給我們一個 Star ?。您的支持是我們持續(xù)前進的最大動力! GitHub 倉庫地址: https://github.com/sdcb/chats 希望這篇博客和項目介紹能幫助您對 Sdcb Chats 項目有更深入的了解。期待您的關(guān)注和參與,讓我們一起打造更優(yōu)秀的開源項目! 轉(zhuǎn)自https://www.cnblogs.com/sdcb/p/18691585/sdcb-chats-id-in-url 該文章在 2025/2/5 9:49:38 編輯過 |
關(guān)鍵字查詢
相關(guān)文章
正在查詢... |