並發到底帶來了什麼問題?
- 2019 年 10 月 15 日
- 筆記
說在前面
我曾不止一次聽說過這句話:
“十個女人無法在一個月內生出孩子”
我明白這句話的意思,用來形容我們的開發工作需要循序漸進,沒有辦法簡單的增加人員就能加快研發速度。
這句話也經常被用於反駁產品經理或者老闆,試圖讓他們明白我們內心所表達的觀點,老實說我也說過這樣的話,當時還覺得挺有道理,現在想來可能有些一廂情願了。
沒錯,在現實世界中,當然不可能在一個月內生出孩子,但我們畢竟是做產品寫代碼的,而不是真的要去生孩子,所以這種說法未免有點偷換概念。
我並不是較真,如果只是想讓產品經理明白我們所要表達的觀點,我們完全可以用其他的比喻,如實反饋存在的困難與問題即可。
言歸正傳,這句話與本文有什麼關係呢?本文想要就“並發”所帶來的問題進行探討,相信看完後你會對此有一個感覺。
與我之前寫的幾篇文章一樣,並發一詞在本文中所表達的意思是:
“在分佈式環境下,超過一個線程同時對同一個狀態進行訪問和變更所導致的一致性問題和可用性問題”
問題的根源:狀態
我無法給出一個百分比數據用以說明到底有多少後端應用程序在使用數據庫,但我想國內涉及到增刪查改之類的各種“管理系統”應該不在少數。
說到底,增刪改查是落地,而怎麼落地則取決於業務的需要,也就是說,業務規則以及流程表達了我們的邏輯,但終究離不開柴米油鹽(增刪改查)。
那麼什麼是狀態?
它可以是文件,也可以是數據庫,可以是一個變量,也可以是緩存,它代表了計算的結果或者依賴(中間結果),由於它是可變的,並且可以被超過1個以上的程序同時訪問或者修改。
所以由此產生了兩個問題:
- 一致性:確保業務代碼的邏輯符合設計期望。即:我們如何保證在並發的時候確保狀態始終處於我們預期?
- 可用性:確保系統可以滿足伸縮需求,但同時,也必須要滿足一致性。即:在保持一致性的同時,如何提高系統的負載能力?
一致性要求是必須的,無法滿足一致性的情況要麼是業務邏輯本身有問題,要麼就是我們在編碼過程中出現了BUG,而如果是我們的編碼出現問題,很明顯就不符合驗收標準。
可用性要求則取決於運營的實際情況,隨着系統使用規模的上升,我們需要保證系統始終處於用的狀態下,因為業務方不希望服務被中斷或者超時。
我來描述一下完整的邏輯:
- 由於我們需要確保系統的設計與編碼符合業務的流程與規則,所以我們需要保證一致性。
- 由於我們需要為更多的用戶提供服務,所以我們需要提高系統的可用性。
- 由於單台服務器所提供的硬件能力已經達到極限,所以我們不得不使用多台服務器,形成集群同時服務業務請求。
- 由於集群後多個服務器可能會訪問和修改同一個狀態(狀態一致性問題的產生原因),所以我們必須使用協調多個服務修改狀態的機制(鎖、事務,問題的解決方案)。
接下來,我會分別就狀態的一致性和可用性進行討論。
問題:一致性
在上一節中,我們說狀態一致證明我們最終落地的數據是正確的,符合業務邏輯的。不一致是由於我們對業務的理解或者編碼出現了BUG,而BUG是我們必須要解決的。
這裡有兩個層面的問題,我們分別來看一下。
業務層面
如果在業務邏輯的層面上本身就存在悖論,存在漏洞,經驗豐富的開發人員會在進行系統設計或者編碼的時候就能察覺出來,道理很簡單,因為無法被實現,不管怎麼樣都會存在BUG,而這種BUG是我們技術人員無法解決的,我們不能夠去猜業務方到底想要什麼,因為這極有可能不符合他們的期望,最後仍然有可能會導致返工,造成成本的上升。
就像我們拿本文開頭的那個例子去懟產品經理一樣,很多時候是由於我們不善表達,沒有清楚表達出我們的疑惑,從而造成尷尬的場面。
溝通和管理是困難的,眾口難調。如何跟業務人員進行高效的溝通一直以來都是一個難題,但我們必須要清楚一點的是:
“只有我們充分洞悉和理解所要實現的業務領域,才能夠使我們更加輕鬆和增強信心,因為只有這樣,我們才能夠選擇最適合的技術和模型幫助我們靈活的完成任務。”
我的意思並不是讓咱們都成為該領域的專家,因為術業有專攻,分工配合才是重中之重,如果我們沒有跟業務需求方達成緊密的一致,就有可能造成浪費。
有關這一塊的內容,建議大家看一本書,叫做《領域驅動設計》,英文名是《Domain Driven Design》,簡稱DDD。
技術層面
技術層面出現不一致的問題一般有兩種情況:
- 我們沒有理解業務的原始需求。
- 我們完全理解業務的需求,但是由於編碼造成了BUG,出現了意外的不一致。
如果是第一種,這個沒什麼好說的,返回業務層面與業務專家進行真誠層面的溝通,獲取真正的需求。
如果是第二種,那麼我們首先應該找出不一致的原因,分析不一致的原因有助於我們加深理解和避免此類問題。
如果是由於小失誤造成的問題我們直接修復即可,這其實很常見,我們寫的代碼或多或少都要經過測試與修復。
如果是由於並發競爭造成的問題,那我們就需要用到相關的解決方案了,最常見的是使用數據庫事務來保證狀態落地的時候不會產生不一致,因為不一致會導致事務的回滾。
還有就是使用鎖來限制資源的訪問以及修改,這些都是很常見的技術,鑒於本文的重點是想說明產生這些問題的原因,所以不會這些解決方案進行詳細的講述,有興趣的朋友可以翻看一下我之前的文章或者查閱相關資料文檔。
問題:可用性
可用性往往決定了系統架構的實現方式,可用性導致了我們最終將不得不使用分佈式集群來應對大規模的訪問需求。
可以說,產生並發問題的直接原因就是可用性,因為它讓我們對狀態的管理變得十分複雜。
如果沒有可用性要求,最簡單的我們甚至可能都不需要數據庫,但現實中,對於一款成功的產品,我們不可能告訴我們的老闆咱們無法實現對不對?
“量變導致質變,當可用性要求越來越高,系統規模越來越大,即便是再簡單的增刪改查都將不會再簡單”
如何在提升可用性的同時還能保證狀態的一致性?這真的不是技術能夠解決的。
先冷靜一下,我的意思是,由於網絡分區的存在,狀態的強一致會導致可用性降低,而可用性的提高又會造成分區狀態的不一致,從而降低了一致性。
這就是著名CAP定理[1],我們要麼取CP,降低系統的可用性,要麼取AP,降低狀態的一致性。
那麼我們有沒有什麼辦法來達到一個比較好的平衡呢?
答案是當然可以,但是,就如我一開始所說的,這並不是技術一個人就能夠解決的問題。
事實已經很明顯了,我們不可能取CP來降低系統的可用性,那樣就沒得玩了,所以我們只能夠選擇AP。
“在業務可以允許範圍內,設計一種最終一致的中間流程步驟,來提高系統的可用性,同時,又得以讓業務可以正常不受影響,處於預期的運轉。”
因此基於BASE理論[2][3]的最終一致更貼近於現實與業務,CAP定理只是證明和告訴我們,哪些事情行不通,但是BASE理論告訴我們,上有政策下有對策,使用柔性事務,反脆弱的系統才能讓我們更加的靈活。
所以我們要怎麼做呢?我們要告訴產品經理,系統的可用性瓶頸必須要更改業務的流程才能得以實現。
我們要學會表達,告訴產品經理,這不是能不能做的問題,或者我能不能行的問題。
而是計算機科學目前的發展水平就是如此,也會存在極限,我們必須要學會相互適應,才能保證健康的發展。
那麼事務怎麼辦?我該使用分佈式事務嗎?
盡量避免使用分佈式事務,這是由衷的建議,事實證明分佈式事務不僅不會提高性能,反而會拖垮高可用場景的系統。
如果你遇到了這個問題,那麼說明狀態分區隔離以及事務場景有可能存在不合理的地方。
如果你確實有這種需求,那麼盡量避免使用分佈式事務,或者將分佈式改為本地事務,也就是說不要將它們放到不同的地方進行事務處理。
如果這樣也不行的話,那就必須讓事務這個概念被明確包含進業務範圍,作為一個獨立的實體存在,不要讓它隱藏在技術細節中。
這樣的話,我們就能夠對這個定義明確的事務“句柄”或者“鉤子”進行補償或者回滾等最終一致冪等操作。
這也是我一直強調錶達的,不要僅僅從技術層面來看待分佈式事務,它可能是一個潛在的業務需求,存在生命周期的潛在概念。
就像我們對接支付寶一樣,根據訂單號來確保冪等,返回明確的信息給支付寶表明處理成功或失敗,實現最終一致的交易處理。
並行就一定快於串行?
這個問題要從兩個方面來看,如果並行運行的程序沒有相互依賴,沒有狀態、資源競爭,那麼水平拓展是非常容易的。
比如MapReduce,將大規模的數據分發並行處理,最後並歸計算結果,速度肯定快於串行執行。
相反,如果狀態特徵在進行並歸的時候,前後依賴就產生了耦合,並行處理導致的切換加載開銷就變得沒有意義。
什麼意思呢?
比如將一個個事件進行存儲,因為狀態本質上來講就是就是一系列事件施加計算後的快照而已(詳見https://www.cnblogs.com/xingxueliao/p/11561263.html#event_and_state)
所以如果我們想要計算某一個時間點上的快照,就需要從頭將事件播放到指定的位置上,事件對狀態的影響是嚴格按照先後順序來的。
因為事件導致的狀態結果是前後依賴的關係,因此並行運算並不會得到什麼幫助,反而會因為切換導致無必要的狀態加載以及卸載開銷。
這種情況下,串行處理是唯一的方式,我們應該在這個上下文中,對輸入的事件流進行持久批量的計算,而且不用考慮並發所帶來的一致性問題。
因此,盲目的使用多線程或者多實例集群不僅不會讓可用性得以提升,反倒因為競爭的關係導致可用性被降低。
對數據特徵進行分析,如何隔離狀態才是我們需要關注的重點,因為只有將沒有依賴關係的狀態隔離後我們才有可能提高整個系統的可用性。
最後
這就完了?好像你沒有提到任何關於如何解決這些問題“具體方案”。
沒有具體的方案,因為這超出了本文的範疇,如果你想知道BASE理論或者最終一致需要用到哪些技術,可以參閱一下下面引用連接。
本文只是簡單的講述問題產生的原因,試圖以比較清晰的一個視角來發現和看待問題是為什麼存在的。
如有疑問可以評論,我有時間將會回復。
[1]:CAP定理維基百科
[2]:BASE和最終一致
[3]:Abandoning ACID in Favor of BASE in Database Engineering
其他有用的資料:
書籍《微服務設計》,強烈推薦。
網站:https://microservices.io/patterns/cn/index.html,一個有關微服務模式匯總網站,每一個模式都能解決特定的問題,可以收藏當做手冊,雖然是微服務,但它終究是有關分佈式以及部署相關的知識,這對於我們的學習也很有必要。