銀行業中的彈性系統
本文要點
彈性是容忍失敗,而不是消除它。
你不能把所有的時間都花在避免失敗上。如果你這樣做了,你將構建出一個脆弱到無可救藥的系統。如果你真希望建立一個彈性系統,就必須構建一個能夠吸收衝擊並繼續或恢復的系統。
在“斯塔林銀行(Starling Bank)”,在現有銀行公共服務大量中斷的背景下,我們在一年的時間裡從零開始建立了一家銀行;我們知道,我們需要可靠的彈性方法。我們需要來自混沌工程的保障,以及使最好的創業公司得以茁壯成長的對簡單性和嚴格優先順序的執著追求。
以下是我們的彈性方法背後的原則和信念。
順便說一下,我們的方法有一些特性,我在這裡不做介紹,但它們仍然是必不可少的:
- 混沌工程——因為彈性必須測試;
- 監控——因為彈性需要可支援性;
- 事件響應——因為彈性適用於系統和執行它的組織。
相反,我們關注的是從基礎設施往上的彈性架構。
彈性架構
為客戶提供彈性服務意味著,要確保在故障發生時,受錯誤影響的系統部分與系統整體相比很小。
有兩種方法可以確保這一點。冗餘 是指確保系統作為一個整體擴充套件到故障範圍之外。不管有多少受損,我們都有其他備用的。隔離 是指確保故障侷限在一個很小的範圍內,而不能擴充套件到我們系統的邊界。
無論你如何設計系統,都必須對這兩個問題有很好的答案。有鑑於此,你必須針對你能想象到的每一個錯誤在腦海中測試你的設計。
請考慮以下方面的失敗,希望對你有所幫助:
- 基礎設施 層故障(如網路故障)以及應用程式 層的故障(如未捕獲的異常或終止);
- 我們構建的軟體的內部 故障(由我們即Bug導致)以及外部 故障(由其他人如非法訊息導致)
僅僅假定通過測試可以消除內部故障是不夠的。保護自己不受外界干擾和保護自己不受自己干擾一樣重要。
設計的簡單性是影響失敗歸因難易程度的最重要因素,所以總是選擇滿足你需求的最簡單設計。
冗餘
基礎設施冗餘在雲中很容易實現。基礎架構即程式碼使我們在建立多個時可以和建立一個一樣容易。
雲提供商,如AWS和GCP,提供了IaaS原語,如負載均衡器 和縮放組 ,並在文件中清楚記錄了它們的使用模式。像Kubernetes這樣的執行環境提供了部署 和複製控制器 等資源,其中,複製控制器持續保證服務的多個副本正在執行並可用。無伺服器技術將在不進行任何配置的情況下為你擴充套件服務。
但也有一些限制。
狀態是老敵人了。當例項假設本地狀態準確時,擴充套件服務的難度就大許多。要麼通過一個高可用的資料儲存和外部共享狀態,要麼引入叢集協調機制來保證每個例項的本地狀況與對等例項的一致,總之,將伺服器轉化為分散式快取。
我的建議是,至少對於創業公司來說,在第一天要保持非常簡單。資料庫用於共享狀態。用它共享所有狀態,直到達到其極限。本地記憶體僅用於瞬態狀態。這為你提供了一個簡潔的“不可變的”計算層和一個具有明確操作需求的資料庫。你知道,你需要在某個時候改進這個基礎設施,同步修改架構並監控指標。到那一天,你就從簡單的擴充套件和冗餘受益了。
無伺服器環境通常提供一個無狀態函式服務,並針對狀態提供單獨的資料庫服務,這實際上是預設提供了簡單巧妙的分離。
共享資源限制可能是另外一個問題。當後端資料儲存僅支援接受3個連線時,將服務擴充套件到10個例項是沒有意義的。事實上,當服務公開其他元件以載入它們無法支援的元件時,擴充套件服務可能是危險的。增加系統某一部分的效能會降低系統的整體效能。實際上,如果沒有高強度的測試,你就無法瞭解所有這些限制。
隔離
在雲基礎設施層,AWS在每個區域內提供單獨的可用區,在基礎設施元件之間提供強隔離。負載均衡器和伸縮組可以跨越多個可用區。
在AWS中ofollow,noindex" target="_blank">已建立的構建模式 可以確保你做好基礎工作。其他雲也有類似的功能。
在應用程式層,微服務 、自包含系統 、甚至傳統的SOA都是可以在服務之間引入強隔離帶的設計方法,允許這些元件獨立失敗,而不影響整體的服務能力。
如果服務的單個例項崩潰,則服務不會中斷。如果服務的每個例項都崩潰,客戶體驗可能會在某種程度上受到影響,但其他服務必須繼續執行。
使用這種模式,系統分割得越細,單個服務故障影響的半徑就越小。但還是那句話,魔鬼是在細節當中。服務之間可以通過許多微妙的方式相互依賴,這些方式跨越服務邊界,使得錯誤會在系統中擴散。當服務密切相關時,通常可以簡單地將它們視為單個服務,並將它們構建為單個服務。
有些方式可能不太明顯,比如隱藏協議 ;服務例項可能通過負責Master選舉的叢集協議進行連線,或者通過針對每個事務(如果你參與了這類事務)進行網路投票的兩階段提交協議。工程師們常常會完全脫離這些網路互動,但它們用錯誤可以傳播的方式將服務連線在一起。
就像在生活中一樣,同居 是一個不可掉以輕心的決定。如果你在同一個例項上執行多個服務,無論是經過深思熟慮的靜態分組,還是使用像Kubernetes這樣的排程器,這些服務的隔離程度都要比在單獨的機器上低。它們都受許多相同故障條件的影響——尤其是硬體故障。
如果服務X、Y和Z的例項總是在同一臺主機上一起執行,那麼如果X可以任意執行並“毒害”主機,那麼不管有多少個例項,X的鄰居都是Y和Z。把它們分到一組,就侵蝕了服務X、Y、Z之間的隔離——即使你的技術應該保證這種“中毒”情況不會發生。
排程程式可以通過避免在任何地方部署相同的服務集來緩解這一問題。因此,如果一個有Bug的服務毒害了它的主機,你不會看到任何其他服務的完全失敗,而是在更廣泛的服務範圍內產生較為分散的區域性影響,而這可能根本不會影響客戶體驗。
在我們推出的架構中,斯塔林銀行選擇了一個簡單的單服務EC2例項模型。這是一種權衡,特別注重簡單性而不是經濟性,但也提供了非常強的隔離。失去一個例項是很難察覺的。即使我們部署了一個流氓服務,對它的主機做了一些有害的事情,它也不容易影響到其他服務。
在應用層程式碼中傳播錯誤的方法也很多。
也許最有害的因素是時間本身 。
在任何事務型系統中,時間都是最有價值的資源。即使你沒有在處理器上進行主動排程,你也可能在完成工作的時間內持有重要的資源:連線、鎖、檔案、套接字。如果你磨磨蹭蹭,就會讓別人捱餓。如果另一個執行緒無法繼續,它可能不會釋放它所持有的資源。它可能迫使其他服務進入等待狀態,在整個系統中傳播不良影響。
雖然死鎖經常被描述為併發程式設計中的邪惡反派,但在現實中,由於事務執行緩慢而導致的資源匱乏可能是一個更常見而又同樣嚴重的問題。
這一點太容易忽視了。工程師的第一直覺是安全而正確的:獲取鎖,進入一個事務,也許簡單考慮了下超時設定然後就跳過了它——(工程師討厭任何看起來隨意的東西,對於如何處理超時,會有尷尬的選擇)……日常開發中的這些簡單誘惑能給生產環境造成嚴重傷害。
不能允許等待和延遲在服務之間傳播。但是,除非你對這種不當行為有一些全域性的預防措施,否則不難想象,你的系統容易受到攻擊。你有固定大小的資料庫連線池嗎?或者執行緒池?你確定在進行REST呼叫時從不持有資料庫連線嗎?如果這樣做了,當網路通訊降級時,是否會耗盡連線池?其他還有什麼會耗費時間?那些你幾乎看不到的東西呢,比如日誌呼叫?或者,你可能有一些需要反覆收集的指標需要資料庫訪問,但由於池中的連線被用光了,所以資料庫訪問開始失敗,導致大量的垃圾被寫到日誌中?然後,你也許會突破日誌架構的限制,從而失去對正在發生的一切的可見性。構建一個噩夢般的場景是一件很有趣的事情,而這個場景是以一點點的拖延開始的。
預防措施有很多:斷路器、“隔板(bulkheads)”、重試設定、最後傳播期限……還有一些庫可以幫助你在軟體(Netflix/Hystrix" rel="nofollow,noindex" target="_blank">hystrix 、resilience4j )中實現這些模式,也有一些服務網格或中介軟體(istio 、conduit 、linkerd )在底層提供了其中部分模式。但如果不投入時間和精力,它們都不會做正確的事情。和往常一樣,任何程式碼的效能和故障模式都將反饋給工程師。
同步呼叫借用了別人的時間。而且,作為一個同步API,按照合理的設計原則,你通常不知道這段時間對呼叫者有多麼寶貴,也不知道它們多麼容易受你的拖延或失敗影響。你把他們扣為了人質。因此,我們更喜歡在服務之間使用非同步API,並且只在絕對必要或者是非常簡單的只讀呼叫中使用同步API。
前端可以進行“樂觀”的UI更新,而不是等待確認,並在離線時顯示過時的資訊。在大多數情況下,根據過時的資訊進行決策需要付出一定的代價,但這種代價不一定很高。Pat Helland曾經寫道:計算由記憶、猜測和道歉組成 。當系統表示偏離現實時總是有代價的,當現實在你背後發生變化時,它總會在某個時候偏離現實。在分散式系統中沒有真正的“現在”。在決定需要完美的事務語義或同步響應之前,評估下“錯誤”的代價。
對於銀行系統來說,容忍失敗意味著容忍失敗而不丟失資訊。因此,雖然實現非同步系統不一定需要佇列,但你可能希望系統作為一個整體表現出一些類似佇列的屬性——至少一次 或最多一次 交付,即使存在錯誤。通過這種方式,你可以確保某些服務始終擁有資料或命令的所有權。
你可以通過在服務中同時使用冪等性和追蹤處理來達到這兩個目的(如斯塔林的“DITTO”架構 ——有很多自治服務不斷嘗試做互相冪等的事情),或使用佇列保證至少一次,使用冪等載荷保證至多一次(如Nubank的Kafka基礎設施 )、或設法把它們都委託給“企業服務匯流排”(如許多傳統銀行的架構)。
使用這些技術,完全有可能在非同步互動服務的基礎上建立響應性應用程式。
總結
編寫彈性系統比以往任何時候都容易。在前12個月的運營中,斯塔林銀行經歷了許多區域性故障和系統部分退化——從來沒有出現過整個系統不可用。這主要是因為我們在每個層面上都強調冗餘和隔離。
我們設計了一個能夠容忍服務中斷的架構,並特別重視這樣一種觀點,即慢服務對終端使用者的威脅可能比死服務更大。
如果一開始我們對於上面列出的各項——混沌工程、監控和事件響應——沒有可靠的方法,那也沒有關係。但這得再一天介紹了。
關於作者
Greg Hawkins是技術、金融科技、雲端計算和DevOps等領域的一名獨立顧問。他在2016年至2018年期間擔任斯塔林銀行的首席技術官,這是英國一家僅限移動端的挑戰者銀行。在此期間,這家金融科技初創企業獲得了銀行牌照,兩大移動平臺上的下載量都突破10萬次。現在,他仍然是斯塔林銀行的高階顧問。斯塔林從零開始構建了全棧銀行系統,成為英國第一個完全部署在雲上、普通公眾可以使用的經常賬戶,並滿足了適用於零售銀行的所有監管、安全和可用性預期。
檢視英文原文:Resilient Systems in Banking