我曾不止一次據說過這句話:html
「十個女人沒法在一個月內生出孩子」數據庫
我明白這句話的意思,用來形容咱們的開發工做須要按部就班,沒有辦法簡單的增長人員就能加快研發速度。後端
這句話也常常被用於反駁產品經理或者老闆,試圖讓他們明白咱們心裏所表達的觀點,老實說我也說過這樣的話,當時還以爲挺有道理,如今想來可能有些一廂情願了。緩存
沒錯,在現實世界中,固然不可能在一個月內生出孩子,但咱們畢竟是作產品寫代碼的,而不是真的要去生孩子,因此這種說法未免有點偷換概念。服務器
我並非較真,若是隻是想讓產品經理明白咱們所要表達的觀點,咱們徹底能夠用其餘的比喻,如實反饋存在的困難與問題便可。網絡
言歸正傳,這句話與本文有什麼關係呢?本文想要就「併發」所帶來的問題進行探討,相信看完後你會對此有一個感受。多線程
與我以前寫的幾篇文章同樣,併發一詞在本文中所表達的意思是:架構
「在分佈式環境下,超過一個線程同時對同一個狀態進行訪問和變動所致使的一致性問題和可用性問題」併發
我沒法給出一個百分比數據用以說明到底有多少後端應用程序在使用數據庫,但我想國內涉及到增刪查改之類的各類「管理系統」應該不在少數。分佈式
說到底,增刪改查是落地,而怎麼落地則取決於業務的須要,也就是說,業務規則以及流程表達了咱們的邏輯,但終究離不開柴米油鹽(增刪改查)。
那麼什麼是狀態?
它能夠是文件,也能夠是數據庫,能夠是一個變量,也能夠是緩存,它表明了計算的結果或者依賴(中間結果),因爲它是可變的,而且能夠被超過1個以上的程序同時訪問或者修改。
因此由此產生了兩個問題:
一致性要求是必須的,沒法知足一致性的狀況要麼是業務邏輯自己有問題,要麼就是咱們在編碼過程當中出現了BUG,而若是是咱們的編碼出現問題,很明顯就不符合驗收標準。
可用性要求則取決於運營的實際狀況,隨着系統使用規模的上升,咱們須要保證系統始終處於用的狀態下,由於業務方不但願服務被中斷或者超時。
我來描述一下完整的邏輯:
接下來,我會分別就狀態的一致性和可用性進行討論。
在上一節中,咱們說狀態一致證實咱們最終落地的數據是正確的,符合業務邏輯的。不一致是因爲咱們對業務的理解或者編碼出現了BUG,而BUG是咱們必需要解決的。
這裏有兩個層面的問題,咱們分別來看一下。
若是在業務邏輯的層面上自己就存在悖論,存在漏洞,經驗豐富的開發人員會在進行系統設計或者編碼的時候就能察覺出來,道理很簡單,由於沒法被實現,無論怎麼樣都會存在BUG,而這種BUG是咱們技術人員沒法解決的,咱們不可以去猜業務方到底想要什麼,由於這極有可能不符合他們的指望,最後仍然有可能會致使返工,形成成本的上升。
就像咱們拿本文開頭的那個例子去懟產品經理同樣,不少時候是因爲咱們不善表達,沒有清楚表達出咱們的疑惑,從而形成尷尬的場面。
溝通和管理是困難的,衆口難調。如何跟業務人員進行高效的溝通一直以來都是一個難題,但咱們必需要清楚一點的是:
「只有咱們充分洞悉和理解所要實現的業務領域,纔可以使咱們更加輕鬆和加強信心,由於只有這樣,咱們纔可以選擇最適合的技術和模型幫助咱們靈活的完成任務。」
個人意思並非讓我們都成爲該領域的專家,由於術業有專攻,分工配合纔是重中之重,若是咱們沒有跟業務需求方達成緊密的一致,就有可能形成浪費。
有關這一塊的內容,建議你們看一本書,叫作《領域驅動設計》,英文名是《Domain Driven Design》,簡稱DDD。
技術層面出現不一致的問題通常有兩種狀況:
若是是第一種,這個沒什麼好說的,返回業務層面與業務專家進行真誠層面的溝通,獲取真正的需求。
若是是第二種,那麼咱們首先應該找出不一致的緣由,分析不一致的緣由有助於咱們加深理解和避免此類問題。
若是是因爲小失誤形成的問題咱們直接修復便可,這其實很常見,咱們寫的代碼或多或少都要通過測試與修復。
若是是因爲併發競爭形成的問題,那咱們就須要用到相關的解決方案了,最多見的是使用數據庫事務來保證狀態落地的時候不會產生不一致,由於不一致會致使事務的回滾。
還有就是使用鎖來限制資源的訪問以及修改,這些都是很常見的技術,鑑於本文的重點是想說明產生這些問題的緣由,因此不會對這些解決方案進行詳細的講述,有興趣的朋友能夠翻看一下我以前的文章或者查閱相關資料文檔。
可用性每每決定了系統架構的實現方式,可用性致使了咱們最終將不得不使用分佈式集羣來應對大規模的訪問需求。
能夠說,產生併發問題的直接緣由就是可用性,由於它讓咱們對狀態的管理變得十分複雜。
若是沒有可用性要求,最簡單的咱們甚至可能都不須要數據庫,但現實中,對於一款成功的產品,咱們不可能告訴咱們的老闆我們沒法實現對不對?
「量變致使質變,當可用性要求愈來愈高,系統規模愈來愈大,即使是再簡單的增刪改查都將不會再簡單」
如何在提高可用性的同時還能保證狀態的一致性?這真的不是技術可以解決的。
先冷靜一下,個人意思是,因爲網絡分區的存在,狀態的強一致會致使可用性下降,而可用性的提升又會形成分區狀態的不一致,從而下降了一致性。
這就是著名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,一個有關微服務模式彙總網站,每個模式都能解決特定的問題,能夠收藏當作手冊,雖然是微服務,但它終究是有關分佈式以及部署相關的知識,這對於咱們的學習也頗有必要。