按照做者分的章節名稱來區分重點,進行總結和提煉。java
一、提出重編程能力仍是重架構的問題程序員
問:作一個高質量的軟件,應該把精力集中在提高其中每個人員、過程、產出物的能力和質量上,仍是該把更多精力放在總體流程和架構上?算法
答:這二者都重要。前者重術,後者重道;前者更多與編碼能力相關,後者更多與軟件架構相關;前者主要由開發者個體水平決定,後者主要由技術決策者水平決定。數據庫
二、提出構建一個大規模但依然可靠的軟件系統是不是可行的編程
經過馮諾依曼研發自複製自動機的例子舉例咱們一直是在用不可靠部件構造可靠的系統。好比咱們開發的每一個環節都是有可能出錯的,但最終設計出的軟件必然是不可靠的,但事實並不是如此。用馮諾依曼的自動機這個例子來說就是說這些零部件可能會出錯,某個具體的零部件可能會崩潰消亡,但在存續生命的微生態系統中必定會有其後代的出現,從新代替該零部件的做用,以維持系統的總體穩定。在這個微生態裏,每個部件均可以看做一隻不死鳥,它會老去而後又能涅槃重生。後端
三、強調架構演變最終都是爲了使咱們的服務更好地死去和重生設計模式
軟件架構風格演變順序:大型機 -> 原始分佈式 -> 大型單體 -> 面向服務 -> 微服務 -> 服務網格 -> 無服務瀏覽器
技術架構上呈現出從大到小的發展趨勢。做者提出了:相比於易於伸縮拓展應對更高的性能等新架構的優勢,架構演變最重要的驅動力始終都是爲了方便某個服務可以順利地「死去」與「重生」而設計的,個體服務的生死更迭,是關係到整個系統可否可靠續存的關鍵因素。緩存
UNIX 的設計原則提出了:保持接口與實現的簡單性,比系統的任何其餘屬性,包括準確性、一致性和完整性,都來得更加劇要。安全
負責制定 UNIX 系統技術標準的開放軟件基金會(也叫OSF) 邀請了各大計算機廠商一塊兒參與共同制訂了名爲「分佈式運算環境」(也叫DCE)的分佈式技術體系。DCE 包含一套相對完整的分佈式服務組件規範與參考實現。
OSF 嚴格遵循 UNIX 設計風格,有一個預設的重要原則是使分佈式環境中的服務調用、資源訪問、數據存儲等操做盡量透明化、簡單化,使開發人員沒必要過於關注他們訪問的方法或其餘資源是位於本地仍是遠程。這樣的主旨很是符合一向的UNIX 設計哲學。可是實現的目標背後包含着當時根本不可能完美解決的技術困難。 由於DCE一旦要考慮性能上的差別就不太行了。爲了讓程序在運行效率上可被用戶接受,開發者只能在方法自己運行時間很長,能夠相對忽略遠程調用成本時的狀況下才能考慮分佈式,若是方法自己運行時間不夠長,就要人爲用各類方式刻意地構造出這樣的場景,譬如將幾個本來毫無關係的方法打包到一個方法體內,一塊進行遠程調用。這種構造長耗時方法自己就與指望用分佈式來突破硬件算力限制、提高性能的初衷相互矛盾。而且此時的開發人員實際上仍然必須每時每刻都意識到本身是在編寫分佈式程序,不可輕易踏過本地與遠程的界限。這和簡單透明的原則相違背。
經過這個原始分佈式開發得出了一個教訓:某個功能可以進行分佈式,並不意味着它就應該進行分佈式,強行追求透明的分佈式操做,只會自尋苦果。
基於當時的狀況擺在計算機科學麪前有兩條通往更大規模軟件系統的道路,一條是儘快提高單機的處理能力,以免分佈式帶來的種種問題;另外一條路是找到更完美的解決如何構築分佈式系統的解決方案
單體架構中「單體」只是代表系統中主要的過程調用都是進程內調用,不會發生進程間通訊。
單體架構的系統又叫巨石系統。單體架構自己具備簡單的特性,簡單到在至關長的時間內,你們都已經習慣了軟件架構就應該是單體這種樣子,因此並無多少人將「單體」視做一種架構來看待。
和不少書中的內容不一樣的是,單體其實並非一個「反派角色」,單體並無你們口中的那麼不堪。實際上,它時運行效率最高的一種架構風格。基於軟件的性能需求超過了單機,軟件開發人員規模擴大這樣的狀況,才體現了單體系統的不足之處。
單體架構因爲全部代碼都運行在同一個進程空間以內,全部模塊、方法的調用都無須考慮網絡分區、對象複製這些麻煩的事和性能損失。一方面得到了進程內調用的簡單、高效等好處的同時,另外一方面也意味着若是任何一部分代碼出現了缺陷,過分消耗了進程空間內的資源,所形成的影響也是全局性的、難以隔離的。好比內存泄漏、線程爆炸、阻塞、死循環等問題,都會影響整個程序,而不只僅是影響某一個功能、模塊自己的正常運做。
一樣的,因爲全部代碼都共享着同一個進程空間,不能隔離,也就沒法作到單獨中止、更新、升級某一部分代碼,因此從可維護性來講,單體系統也是不佔優點的。程序升級、修改缺陷每每須要制定專門的停機更新計劃,作灰度發佈、A/B 測試也相對更復雜。
除了以上問題是單體架構的缺陷外,做者提出,最重要的仍是:單體系統很難兼容「Phoenix」的特性
單體架構這種風格潛在的觀念是但願系統的每個部件,每一處代碼都儘可能可靠,靠不出或少出缺陷來構建可靠系統。可是單體靠高質量來保證高可靠性的思路,在小規模軟件上還能運做良好,但系統規模越大,交付一個可靠的單體系統就變得愈來愈具備挑戰性。
爲了容許程序出錯,爲了得到隔離、自治的能力,爲了能夠技術異構等目標,是繼爲了性能與算力以後,讓程序再次選擇分佈式的理由。在單體架構後,有一段時間是在嘗試將一個大的單體系統拆分爲若干個更小的、不運行在同一個進程的獨立服務,這些服務拆分方法後來致使了面向服務架構(Service-Oriented Architecture)的一段興盛期,這就是SOA 時代。
SOA是一次具體地、系統性地成功解決分佈式服務主要問題的架構模式。
三種有表明性的SOA
信息煙囪又叫信息孤島。使用這種架構的系統也被稱爲孤島式信息系統或者煙囪式信息系統。它指的是一種徹底不與其餘相關信息系統進行互操做或者協調工做的設計模式。
這樣的系統其實並無什麼「架構設計」可言。這樣徹底不進行交互的模式不符合真實業務狀況。
微內核架構也被稱爲插件式架構。微內核將主數據,連同其餘可能被各子系統使用到的公共服務、數據、資源集中到一塊,成爲一個被全部業務系統共同依賴的核心(Kernel,也稱爲 Core System),具體的業務系統以插件模塊(Plug-in Modules)的形式存在,這樣也可提供可擴展的、靈活的、自然隔離的功能特性。
這種模式適合桌面應用程序和Web 應用程序。對於平臺型應用來講,常常會加入新的功能,就很像時不時加一個新的插件模塊進來因此微內核架構比較適合。微內核架構也能夠嵌入到其餘的架構模式之中,經過插件的方式來提供新功能的定製開發能。
微內核架構也有它的侷限和使用前提,架構中這些插件能夠訪問內核中一些公共的資源,但不會直接交互。可是不管是企業信息系統仍是互聯網應用必須既能拆分出獨立的系統,也能讓拆分後的子系統之間順暢地互相調用通訊。
爲了能讓子系統互相通訊,事件驅動架構的方案是在子系統之間創建一套事件隊列管道,來自系統外部的消息將以事件的形式發送至管道中,各個子系統從管道里獲取可以處理的事件消息,能夠本身發佈一些新的事件到管道隊列中去,如此,每個消息的處理者都是獨立的,高度解耦的,但又能與其餘處理者經過事件管道進行互動。
演化至事件驅動架構時遠程服務調用迎來了 SOAP 協議的誕生
微服務是一種經過多個小型服務組合來構建單個應用的架構風格,這些服務圍繞業務能力而非特定的技術標準來構建。各個服務能夠採用不一樣的編程語言,不一樣的數據存儲技術,運行在不一樣的進程之中。服務採起輕量級的通訊機制和自動化的部署機制實現通訊與運維。
「微服務」這個技術名詞是由 Peter Rodgers 博士在 2005 年度的雲計算博覽會提出的。「Micro-Web-Service」,指的是一種專一於單一職責的、語言無關的、細粒度 Web 服務。最初的微服務能夠說是 SOA 發展時催生的產物。隨着時間的推動,技術的發展,微服務已經再也不是維基百科定義的那樣,「僅僅只是一種 SOA 的變種形式」了。
微服務真正的崛起是在 2014 年,Martin Fowler 與 James Lewis 合寫的文章《Microservices: A Definition of This New Architectural Term》中 給出了現代微服務的概念: 「微服務是一種經過多個小型服務組合來構建單個應用的架構風格,這些服務圍繞業務能力而非特定的技術標準來構建。各個服務能夠採用不一樣的編程語言,不一樣的數據存儲技術,運行在不一樣的進程之中。服務採起輕量級的通訊機制和自動化的部署機制實現通訊與運維。」
文中列舉了微服務的九個核心的業務與技術特徵:
《Microservices》文中除了定義微服務是什麼,還專門申明瞭微服務不是什麼——微服務不是 SOA 的變體或衍生品,應該明確地與 SOA 劃清了界線,再也不貼上任何 SOA 的標籤。
微服務追求的是更加自由的架構風格,摒棄了幾乎全部 SOA 裏能夠拋棄的約束和規定,提倡以「實踐標準」代替「規範標準」。沒有了統一的規範和約束,服務的註冊發現、跟蹤治理、負載均衡、故障隔離、認證受權、伸縮擴展、傳輸通訊、事務處理,等等這些問題,在微服務中再也不會有統一的解決方案,即便只討論 Java 範圍內會使用到的微服務,光一個服務間遠程調用問題,能夠列入解決方案的候選清單的就有:RMI(Sun/Oracle)、Thrift(Facebook)、Dubbo(阿里巴巴)、gRPC(Google)、Motan2(新浪)、Finagle(Twitter)、brpc(百度)、Arvo(Hadoop)、JSON-RPC、REST,等等;光一個服務發現問題,能夠選擇的就有:Eureka(Netflix)、Consul(HashiCorp)、Nacos(阿里巴巴)、ZooKeeper(Apache)、Etcd(CoreOS)、CoreDNS(CNCF),等等。其餘領域的狀況也是與此相似,總之,徹底是八仙過海,各顯神通的局面。
做爲一個普通的服務開發者,「螺絲釘」式的程序員,微服務架構是友善的。但是,微服務對架構者是滿滿的惡意,由於對架構能力要求已提高到前所未有的程度
定義:從軟件層面獨力應對微服務架構問題,發展到軟、硬一體,協力應對架構問題的時代,此即爲「後微服務時代」。
當虛擬化的基礎設施從單個服務的容器擴展至由多個容器構成的服務集羣、通訊網絡和存儲設施時,軟件與硬件的界限便已經模糊。一旦虛擬化的硬件可以跟上軟件的靈活性,那些與業務無關的技術性問題便有可能從軟件層面剝離,悄無聲息地解決於硬件基礎設施以內,讓軟件得以只專一業務,真正「圍繞業務能力構建」團隊與產品。
Kubernetes 成爲容器戰爭勝利者標誌着後微服務時代的開端,但 Kubernetes 仍然沒有可以完美解決所有的分佈式問題——「不完美」的意思是,僅從功能上看,單純的 Kubernetes 反而不如以前的 Spring Cloud 方案。這是由於有一些問題處於應用系統與基礎設施的邊緣,使得徹底在基礎設施層面中確實很難精細化地處理。
舉個例子,微服務 A 調用了微服務 B 的兩個服務,稱爲 B1和 B2,假設 B1表現正常但 B2出現了持續的 500 錯,那在達到必定閾值以後就應該對 B2進行熔斷,以免產生雪崩效應。若是僅在基礎設施層面來處理,這會遇到一個兩難問題,切斷 A 到 B 的網絡通路則會影響到 B1的正常調用,不切斷的話則持續受 B2的錯誤影響。
以上問題在經過 Spring Cloud 這類應用代碼實現的微服務是能夠處理和解決的,只受限於開發人員的想象力與技術能力,但基礎設施是針對整個容器來管理的,粒度相對粗曠,只能到容器層面,對單個遠程服務就很難有效管控。相似的狀況不只僅在斷路器上出現,服務的監控、認證、受權、安全、負載均衡等都有可能面臨細化管理的需求,譬如服務調用時的負載均衡,每每須要根據流量特徵,調整負載均衡的層次、算法,等等,而 DNS 儘管能實現必定程度的負載均衡,但一般並不能知足這些額外的需求。
爲了解決這一類問題,虛擬化的基礎設施很快完成了第二次進化,引入了「服務網格」(Service Mesh)的「邊車代理模式」(Sidecar Proxy),所謂的「邊車」是由系統自動在服務容器(一般是指 Kubernetes 的 Pod)中注入一個通訊代理服務器,以相似網絡安全裏中間人攻擊的方式進行流量劫持,在應用毫無感知的狀況下,悄然接管應用全部對外通訊。這個代理除了實現正常的服務間通訊外(稱爲數據平面通訊),還接收來自控制器的指令(稱爲控制平面通訊),根據控制平面中的配置,對數據平面通訊的內容進行分析處理,以實現熔斷、認證、度量、監控、負載均衡等各類附加功能。這樣便實現了既不須要在應用層面加入額外的處理代碼,也提供了幾乎不亞於程序代碼的精細管理能力。
很難從概念上斷定清楚一個與應用系統運行於同一資源容器以內的代理服務到底應該算軟件仍是算基礎設施,但它對應用是透明的,不須要改動任何軟件代碼就能夠實現服務治理,這便足夠了。服務網格在 2018 年才火起來,今天它仍然是個新潮的概念,仍然未徹底成熟,甚至連 Kubernetes 也還算是個新生事物。但做者提出,將來 Kubernetes 將會成爲服務器端標準的運行環境,如同如今 Linux 系統;服務網格將會成爲微服務之間通訊交互的主流模式,把「選擇什麼通訊協議」、「怎樣調度流量」、「如何認證受權」之類的技術問題隔離於程序代碼以外,取代今天 Spring Cloud 全家桶中大部分組件的功能,微服務只須要考慮業務自己的邏輯,這纔是最理想的解決方案。
若是說微服務架構是分佈式系統這條路的極致,那無服務架構,也許就是「不分佈式」的雲端系統這條路的起點。
雖然發展到了微服務架構解決了單臺機器的性能沒法知足系統的運行須要的問題,可是得到更好性能的需求在架構設計中依然佔很大的比重。對軟件研發而言,不去作分佈式無疑纔是最簡單的,若是單臺服務器的性能能夠是無限的,那架構演進必定不是像今天這個樣子。
絕對意義上的無限性能必然是不存在的,但在雲計算落地已有十年時間的今日,相對意義的無限性能已經成爲了現實。2012 年,Iron.io 公司率先提出了「無服務」的概念,2014 年開始,亞馬遜發佈了名爲 Lambda 的商業化無服務應用,並在後續的幾年裏逐步獲得開發者承認,發展成目前世界上最大的無服務的運行平臺;到了 2018 年,中國的阿里雲、騰訊雲等廠商也開始跟進,發佈了旗下的無服務的產品,「無服務」已成了近期技術圈裏的「新網紅」之一。
無服務如今尚未一個特別權威的「官方」定義,但它的概念並無前面各類架構那麼複雜,原本無服務也是以「簡單」爲主要賣點的,它只涉及兩塊內容:後端設施和函數。
無服務的願景是讓開發者只須要純粹地關注業務,不須要考慮技術組件,後端的技術組件是現成的,能夠直接取用,沒有采購、版權和選型的煩惱;不須要考慮如何部署,部署過程徹底是託管到雲端的,工做由雲端自動完成;不須要考慮算力,有整個數據中心支撐,算力能夠認爲是無限的;也不須要操心運維,維護系統持續平穩運行是雲計算服務商的責任而再也不是開發者的責任。
做者認爲無服務很難成爲一種普適性的架構模式,由於無服務不適配於全部的應用。對於那些信息管理系統、網絡遊戲等應用,全部具備業務邏輯複雜,依賴服務端狀態,響應速度要求較高,須要長連接等這些特徵的應用,至少目前是相對並不適合的。由於無服務天生「無限算力」的假設決定了它必需要按使用量計費以控制消耗算力的規模,因此函數不會一直以活動狀態常駐服務器,請求到了纔會開始運行,這致使了函數不便依賴服務端狀態,也致使了函數會有冷啓動時間,響應的性能不可能太好
做者認爲軟件開發的將來不會只存在某一種「最早進的」架構風格,多種具針對性的架構風格同時並存,是軟件產業更有生命力的形態。筆者一樣相信軟件開發的將來,多種架構風格將會融合互補,「分佈式」與「不分佈式」的邊界將逐漸模糊,兩條路線在雲端的數據中心中交匯。
RPC 出現的最初目的,就是爲了讓計算機可以跟調用本地方法同樣去調用遠程方法。
進程間通訊的方式有
管道相似於兩個進程間的橋樑,可經過管道在進程間傳遞少許的字符流或字節流。普通管道只用於有親緣關係進程(由一個進程啓動的另一個進程)間的通訊,具名管道擺脫了普通管道沒有名字的限制,除具備管道全部的功能外,它還容許無親緣關係進程間的通訊。管道典型的應用就是命令行中的|操做符,好比:
ps -ef | grep java
複製代碼
ps與grep都有獨立的進程,以上命令就經過管道操做符|將ps命令的標準輸出鏈接到grep命令的標準輸入上。
信號用於通知目標進程有某種事件發生,除了用於進程間通訊外,進程還能夠發送信號給進程自身。信號的典型應用是kill命令,好比:
kill -9 pid
複製代碼
以上就是由 Shell 進程向指定 PID 的進程發送 SIGKILL 信號。
信號量用於兩個進程之間同步協做手段,它至關於操做系統提供的一個特殊變量,程序能夠在上面進行wait()和notify()操做。
以上三種方式只適合傳遞傳遞少許信息,消息隊列用於進程間數據量較多的通訊。進程能夠向隊列添加消息,被賦予讀權限的進程則能夠從隊列消費消息。消息隊列克服了信號承載信息量少,管道只能用於無格式字節流以及緩衝區大小受限等缺點,但實時性相對受限。
容許多個進程訪問同一塊公共的內存空間,這是效率最高的進程間通訊形式。本來每一個進程的內存地址空間都是相互隔離的,但操做系統提供了讓進程主動建立、映射、分離、控制某一塊內存的程序接口。當一塊內存被多進程共享時,各個進程每每會與其它通訊機制,譬如信號量結合使用,來達到進程間同步及互斥的協調操做。
以上兩種方式只適合單機多進程間的通訊,套接字接口是更爲普適的進程間通訊機制,可用於不一樣機器之間的進程通訊。套接字(Socket)起初是由 UNIX 系統的 BSD 分支開發出來的,如今已經移植到全部主流的操做系統上。出於效率考慮,當僅限於本機進程間通訊時,套接字接口是被優化過的,不會通過網絡協議棧,不須要打包拆包、計算校驗和、維護序號和應答等操做,只是簡單地將應用層數據從一個進程拷貝到另外一個進程,這種進程間通訊方式有個專名的名稱:UNIX Domain Socket,又叫作 IPC Socket
由於Socket是網絡棧的統一接口,因此基於套接字接口的通訊方式不只適用於本地相同機器的不一樣進程間通訊,也能支持基於網絡的跨機器的進程間通訊。好比 Linux 系統的圖形化界面中,X Window 服務器和 GUI 程序之間的交互就是由這套機制來實現。因爲 Socket 是各個操做系統都有提供的標準接口,因此能夠把遠程方法調用的通訊細節隱藏在操做系統底層,從應用層面上看來能夠作到遠程調用與本地的進程間通訊在編碼上徹底一致。這種透明的調用形式卻形成了程序員誤覺得通訊是無成本的假象,於是被濫用以至於顯著下降了分佈式系統的性能。1987 年,在「透明的 RPC 調用」一度成爲主流範式的時候,Andrew Tanenbaum 教授曾發表了論文對這種透明的 RPC 範式提出了一系列質問,論文的中心觀點是,本地調用與遠程調用當作同樣處理,這是犯了方向性的錯誤,把系統間的調用作成透明,反而會增長程序員工做的複雜度。此後幾年,關於 RPC 應該如何發展、如何實現的論文層出不窮。最終,到 1994 年至 1997 年間,一衆大佬們共同總結了經過網絡進行分佈式運算的八宗罪
以上這八條反話被認爲是程序員在網絡編程中常常被忽略的八大問題,潛臺詞就是若是遠程服務調用要弄透明化的話,就必須爲這些罪過埋單,這算是給 RPC 是否能等同於 IPC 來實現暫時定下了一個具備公信力的結論。至此,RPC 應該是一種高層次的或者說語言層次的特徵,而不是像 IPC 那樣,是低層次的或者說系統層次的特徵成爲工業界、學術界的主流觀點。
遠程服務調用的定義:遠程服務調用是指位於互不重合的內存地址空間中的兩個程序,在語言層面上,以同步的方式使用帶寬有限的信道來傳輸程序控制信息。
從20 世紀 80 年代中後期開始直至接下來幾十年來全部流行過的 RPC 協議,都不外乎變着花樣使用各類手段來解決如下三個基本問題:
這裏數據包括了傳遞給方法的參數,以及方法執行後的返回值。
如何經過網絡,在兩個服務的 Endpoint 之間相互操做、交換數據。
「如何表示同一個方法」,「如何找到對應的方法」仍是得弄個跨語言的統一的標準才行。
以上 RPC 中的三個基本問題,所有均可以在本地方法調用過程當中找到相對應的操做。RPC 的想法始於本地方法調用,儘管早已再也不追求實現成與本地方法調用徹底一致,但其設計思路仍然帶有本地方法調用的深入烙印。
一、面向透明的、簡單的 RPC 協議,如 DCE/RPC、DCOM、Java RMI,要麼依賴於操做系統,要麼依賴於特定語言,總有一些先天約束;
二、面向通用的、普適的 RPC 協議;如 CORBA,就沒法逃過使用複雜性的困擾,CORBA 煩瑣的 OMG IDL、ORB 都是很好的佐證;
三、經過技術手段來屏蔽複雜性的 RPC 協議,如 Web Service,又難免受到性能問題的束縛。
對於RPC協議,簡單、普適、高性能這三點,彷佛真的難以同時知足。
因爲一直沒有一個同時知足以上三點的完美 RPC 協議出現,因此RPC這個領域裏逐漸進入了百家爭鳴並一直延續至今。如今,任何一款具備生命力的 RPC 框架,都再也不去追求大而全的完美,而是有本身的針對性特色做爲主要的發展方向,舉例分析以下。
不知足於 RPC 將面向過程的編碼方式帶到分佈式,但願在分佈式系統中也可以進行跨進程的面向對象編程,表明爲 RMI、.NET Remoting,這個分支也有個別名叫作分佈式對象。
表明爲 gRPC 和 Thrift。決定 RPC 性能的主要就兩個因素:序列化效率和信息密度。序列化輸出結果的容量越小,速度越快,效率天然越高;信息密度則取決於協議中有效荷載所佔總傳輸數據的比例大小,使用傳輸協議的層次越高,信息密度就越低。gRPC 和 Thrift 都有本身優秀的專有序列化器,而傳輸協議方面,gRPC 是基於 HTTP/2 的,支持多路複用和 Header 壓縮,Thrift 則直接基於傳輸層的 TCP 協議來實現,省去了額外應用層協議的開銷。
表明爲 JSON-RPC,說要選功能最強、速度最快的 RPC 可能會頗有爭議,但選功能弱的、速度慢的,JSON-RPC 確定會候選人中之一。犧牲了功能和效率,換來的是協議的簡單輕便,接口與格式都更爲通用,尤爲適合用於 Web 瀏覽器這類通常不會有額外協議支持、額外客戶端支持的應用場合。
經歷了 多種RPC 框架百家爭鳴,你們都認識到了不一樣的 RPC 框架所提供的特性或多或少是有矛盾的,很難有某一種框架能十全十美。由於必須有取捨,因此致使不斷有新的 RPC 輪子出現,決定了選擇框架時在得到一些利益的同時,要付出另一些代價。
到了最近幾年,RPC 框架不只僅負責調用遠程服務,還管理遠程服務,再也不追求獨立地解決 RPC 的所有三個問題(表示數據、傳遞數據、表示方法),而是將一部分功能設計成擴展點,讓用戶本身去選擇。框架聚焦於提供核心的、更高層次的能力,好比提供負載均衡、服務註冊、可觀察性等方面的支持。這一類框架的表明有 Facebook 的 Thrift 與阿里的 Dubbo。
不少人會拿 REST 與 RPC 互相比較,可是REST 和RPC本質上並非同一類型的東西,不管是在思想上、概念上,仍是使用範圍上,與 RPC 都只能算有一些類似。REST 只能說是風格而不是規範、協議,REST 與 RPC 做爲主流的兩種遠程調用方式,在使用上是確有重合的。
一套理想的、徹底知足 REST 風格的系統應該知足如下六大原則。
無狀態是 REST 的一條核心原則。REST 但願服務器不要去負責維護狀態,每一次從客戶端發送的請求中,應包括全部的必要的上下文信息,會話信息也由客戶端負責保存維護,服務端依據客戶端傳遞的狀態來執行業務處理邏輯,驅動整個應用的狀態變遷。
這裏所指的並非表示層、服務層、持久層這種意義上的分層。而是指客戶端通常不須要知道是否直接鏈接到了最終的服務器,抑或鏈接到路徑上的中間服務器。中間服務器能夠經過負載均衡和共享緩存的機制提升系統的可擴展性,這樣也便於緩存、伸縮和安全策略的部署。
這是 REST 的另外一條核心原則,REST 但願開發者面向資源編程,但願軟件系統設計的重點放在抽象系統該有哪些資源上,而不是抽象系統該有哪些行爲(服務)上。
這是一條可選原則。它是指任何按照客戶端的請求,將可執行的軟件程序從服務器發送到客戶端的技術,按需代碼賦予了客戶端無需事先知道全部來自服務端的信息應該如何處理、如何運行的寬容度。
《RESTful Web APIs》和《RESTful Web Services》的做者 Leonard Richardson 提出過一個衡量「服務有多麼 REST」的 Richardson 成熟度模型。Richardson 將服務接口「REST 的程度」從低到高,分爲 1 至 4 級:
事務處理存在的意義是爲了保證系統中全部的數據都是符合指望的,且相互關聯的數據之間不會產生矛盾,即數據狀態的一致性(Consistency)。
事務的三個重點方面:
原子性(Atomic)
在同一項業務處理過程當中,事務保證了對多個數據的修改,要麼同時成功,要麼同時被撤銷。
隔離性(Isolation)
在不一樣的業務處理過程當中,事務保證了各自業務正在讀、寫的數據互相獨立,不會彼此影響。
持久性(Durability)
事務應當保證全部成功被提交的數據修改都可以正確地被持久化,不丟失數據。
四種屬性即事務的ACID特性
事務的概念最初起源於數據庫系統但已經有所延伸,而再也不侷限於數據庫自己了。全部須要保證數據一致性的應用場景,都有可能會用到事務。
外部一致性問題一般很難再使用 A、I、D 來解決,由於這樣須要付出很大乃至不切實際的代價;可是外部一致性又是分佈式系統中必然會遇到且必需要解決的問題,爲此將一致性從「是或否」的二元屬性轉變爲能夠按不一樣強度分開討論的多元屬性,在確保代價可承受的前提下得到強度儘量高的一致性保障,也正因如此,事務處理才從一個具體操做上的「編程問題」上升成一個須要全局權衡的「架構問題」。
本地事務是最基礎的一種事務解決方案,只適用於單個服務使用單個數據源的場景。從應用角度看,它是直接依賴於數據源自己提供的事務能力來工做的,在程序代碼層面,最多隻能對事務接口作一層標準化的包裝(如 JDBC 接口),並不能深刻參與到事務的運做過程中,事務的開啓、終止、提交、回滾、嵌套、設置隔離級別,乃至與應用代碼貼近的事務傳播方式,所有都要依賴底層數據源的支持才能工做。
舉個例子,假設你的代碼調用了 JDBC 中的Transaction::rollback()
方法,方法的成功執行也並不必定表明事務就已經被成功回滾,若是數據表採用的引擎是MyISAM,那rollback()
方法即是一項沒有意義的空操做。所以,咱們要想深刻地討論本地事務,便不得不越過應用代碼的層次,去了解一些數據庫自己的事務實現原理,弄明白傳統數據庫管理系統是如何經過 ACID 來實現事務的。
原子性和持久性在事務裏是密切相關的兩個屬性,原子性保證了事務的多個操做要麼都生效要麼都不生效,不會存在中間狀態;持久性保證了一旦事務生效,就不會再由於任何緣由而致使其修改的內容被撤銷或丟失。實現原子性和持久性的最大困難是「寫入磁盤」這個操做並非原子的,不只有「寫入」與「未寫入」狀態,還客觀地存在着「正在寫」的中間狀態。正由於寫入中間狀態與崩潰都不可能消除,因此若是不作額外保障措施的話,將內存中的數據寫入磁盤,並不能保證原子性與持久性。
好比購買圖書的場景,在用戶帳戶中減去貨款、在商家帳戶中增長貨款、在商品倉庫中標記一本書爲配送狀態。因爲寫入存在中間狀態,因此可能發生如下情形。
因爲寫入中間狀態與崩潰都是沒法避免的,爲了保證原子性和持久性,就只能在崩潰後採起恢復的補救措施,這種數據恢復操做被稱爲「崩潰恢復」。爲了可以順利地完成崩潰恢復,在磁盤中寫入數據就不能像程序修改內存中變量值那樣,直接改變某表某行某列的某個值,而是必須將修改數據這個操做所需的所有信息,包括修改什麼數據、數據物理上位於哪一個內存頁和磁盤塊中、從什麼值改爲什麼值,等等,以日誌的形式——即僅進行順序追加的文件寫入的形式先記錄到磁盤中。只有在日誌記錄所有都安全落盤,數據庫在日誌中看到表明事務成功提交的「提交記錄」後,纔會根據日誌上的信息對真正的數據進行修改,修改完成後,再在日誌中加入一條「結束記錄」表示事務已完成持久化,這種事務實現方法被稱爲「Commit Logging」(提交日誌)。
Commit Logging 保障數據持久性,日誌一旦成功寫入 Commit Record,那整個事務就是成功的,即便真正修改數據時崩潰了,重啓後根據已經寫入磁盤的日誌信息恢復現場、繼續修改數據便可,這保證了持久性;其次,若是日誌沒有成功寫入 Commit Record 就發生崩潰,那整個事務就是失敗的,系統重啓後會看到一部分沒有 Commit Record 的日誌,那將這部分日誌標記爲回滾狀態便可,整個事務就像徹底沒好有發生過同樣,這保證了原子性。
Commit Logging 存在一個大缺點,就是全部對數據的真實修改都必須發生在事務提交之後,不管有何種理由,都不容許在事務提交以前就修改磁盤上的數據,對提高數據庫的性能十分不利。
爲了解決這個問題,ARIES 提出了「Write-Ahead Logging」的日誌改進方案,所謂「提早寫入」(Write-Ahead),就是容許在事務提交以前,提早寫入變更數據的意思。
Write-Ahead Logging 先將什麼時候寫入變更數據,按照事務提交時點爲界,劃分爲 FORCE 和 STEAL 兩類狀況。
Commit Logging 容許 NO-FORCE,但不容許 STEAL。由於假如事務提交前就有部分變更數據寫入磁盤,那一旦事務要回滾,或者發生了崩潰,這些提早寫入的變更數據就都成了錯誤。
Write-Ahead Logging 容許 NO-FORCE,也容許 STEAL,它給出的解決辦法是增長了另外一種被稱爲 Undo Log 的日誌類型,當變更數據寫入磁盤前,必須先記錄 Undo Log,註明修改了哪一個位置的數據、從什麼值改爲什麼值,等等。以便在事務回滾或者崩潰恢復時根據 Undo Log 對提早寫入的數據變更進行擦除。Undo Log 如今通常被翻譯爲「回滾日誌」,此前記錄的用於崩潰恢復時重演數據變更的日誌就相應被命名爲 Redo Log,通常翻譯爲「重作日誌」。因爲 Undo Log 的加入,Write-Ahead Logging 在崩潰恢復時會執行如下三個階段的操做。
分析階段
該階段從最後一次檢查點開始掃描日誌,找出全部沒有 End Record 的事務,組成待恢復的事務集合,這個集合至少會包括 Transaction Table 和 Dirty Page Table 兩個組成部分。
重作階段
該階段依據分析階段中產生的待恢復的事務集合來重演歷史,具體操做爲:找出全部包含 Commit Record 的日誌,將這些日誌修改的數據寫入磁盤,寫入完成後在日誌中增長一條 End Record,而後移除出待恢復事務集合。
回滾階段
該階段處理通過分析、重作階段後剩餘的恢復事務集合,此時剩下的都是須要回滾的事務,它們被稱爲 Loser,根據 Undo Log 中的信息,將已經提早寫入磁盤的信息從新改寫回去,以達到回滾這些 Loser 事務的目的。
重作階段和回滾階段的操做都應該設計爲冪等的。
隔離性保證了每一個事務各自讀、寫的數據互相獨立,不會彼此影響。若是沒有併發,全部事務全都是串行的,那就不須要任何隔離,若是有併發,就須要加鎖同步。
數據庫均提供瞭如下三種鎖
寫鎖
也叫做排他鎖,若是數據有加寫鎖,就只有持有寫鎖的事務才能對數據進行寫入操做,數據加持着寫鎖時,其餘事務不能寫入數據,也不能施加讀鎖。
讀鎖
也叫做共享鎖,多個事務能夠對同一個數據添加多個讀鎖,數據被加上讀鎖後就不能再被加上寫鎖,因此其餘事務不能對該數據進行寫入,但仍然能夠讀取。對於持有讀鎖的事務,若是該數據只有它本身一個事務加了讀鎖,容許直接將其升級爲寫鎖,而後寫入數據。
範圍鎖
對於某個範圍直接加排他鎖,在這個範圍內的數據不能被寫入。
事務的隔離級別
串行化訪問提供了強度最高的隔離性。不考慮性能優化的話,對事務全部讀、寫的數據全都加上讀鎖、寫鎖和範圍鎖便可作到可串行化
可重複讀
對事務所涉及的數據加讀鎖和寫鎖,且一直持有至事務結束,但再也不加範圍鎖。相比於可串行化可能出現幻讀問題(指在事務執行過程當中,兩個徹底相同的範圍查詢獲得了不一樣的結果集)
讀已提交
對事務涉及的數據加的寫鎖會一直持續到事務結束,但加的讀鎖在查詢操做完成後就立刻會釋放。相比於可重複度多了不可重複讀的問題(在事務執行過程當中,對同一行數據的兩次查詢獲得了不一樣的結果)
讀未提交就是「徹底不隔離」,讀、寫鎖都不加。讀未提交
會有髒讀問題,但不會有髒寫問題
幻讀、不可重複讀、髒讀等問題都是因爲一個事務在讀數據過程當中,受另一個寫數據的事務影響而破壞了隔離性。針對這種「一個事務讀+另外一個事務寫」的隔離問題,有一種叫作多版本併發控制」(Multi-Version Concurrency Control,MVCC)的無鎖優化方案被主流的數據庫普遍採用。
MVCC 是一種讀取優化策略,它的「無鎖」是特指讀取時不須要加鎖。MVCC 的基本思路是對數據庫的任何修改都不會直接覆蓋以前的數據,而是產生一個新版副本與老版本共存,以此達到讀取時能夠徹底不加鎖的目的。 「版本」是個關鍵詞,能夠理解爲數據庫中每一行記錄都存在兩個看不見的字段:CREATE_VERSION 和 DELETE_VERSION,這兩個字段記錄的值都是事務 ID,事務 ID 是一個全局嚴格遞增的數值,而後根據如下規則寫入數據。
此時,若有另一個事務要讀取這些發生了變化的數據,將根據隔離級別來決定到底應該讀取哪一個版本的數據。
可重複讀
:老是讀取 CREATE_VERSION 小於或等於當前事務 ID 的記錄,在這個前提下,若是數據仍有多個版本,則取最新(事務 ID 最大)的。讀已提交
:老是取最新的版本便可,即最近被 Commit 的那個版本的數據記錄。另外兩個隔離級別都沒有必要用到 MVCC,由於讀未提交
直接修改原始數據便可,其餘事務查看數據的時候馬上能夠看到,根本無須版本字段。可串行化
原本的語義就是要阻塞其餘事務的讀取操做,而 MVCC 是作讀取時無鎖優化的。
MVCC 是隻針對「讀+寫」場景的優化,若是是兩個事務同時修改數據,即「寫+寫」的狀況,那就沒有多少優化的空間了,此時加鎖幾乎是惟一可行的解決方案,惟一須要討論的就是加鎖的策略採起樂觀鎖仍是悲觀鎖。相對地,樂觀鎖策略的思路被稱爲樂觀併發控制,沒有必要迷信什麼樂觀鎖要比悲觀鎖更快的說法,這純粹看競爭的劇烈程度,若是競爭劇烈的話,樂觀鎖反而更慢。
爲了解決分佈式事務的一致性問題,X/Open組織在1991年提出了一套叫XA的( eXtended Architecture 的縮寫)處理事務架構,其核心內容是定義了全局的事務管理器和局部的資源管理器之間的通訊接口。
XA 接口是雙向的,能在一個事務管理器和多個資源管理器之間造成通訊橋樑,經過協調多個數據源的一致動做,實現全局事務的統一提交或者統一回滾。基於 XA 模式在 Java 語言中的實現了全局事務處理的標準,這也就是咱們如今所熟知的 JTA。
JTA 最主要的兩個接口是:
javax.transaction.TransactionManager
。這套接口是給 Java EE 服務器提供容器事務(由容器自動負責事務管理)使用的,還提供了另一套javax.transaction.UserTransaction
接口,用於經過程序代碼手動開啓、提交和回滾事務。javax.transaction.xa.XAResource
,任何資源(JDBC、JMS 等等)若是想要支持 JTA,只要實現 XAResource 接口中的方法便可。XA 將事務提交拆分紅爲兩階段過程:
準備階段
又叫做投票階段,在這個階段,協調者詢問事務的全部參與者是否準備好提交,參與者若是已經準備好提交則回覆 Prepared,不然回覆 Non-Prepared。準備操做是在重作日誌中記錄所有事務提交操做所要作的內容,它與本地事務中真正提交的區別只是暫不寫入最後一條 Commit Record 而已,這意味着在作完數據持久化後仍繼續持有鎖,維持數據對其餘非事務內觀察者的隔離狀態。
提交階段
又叫做執行階段,協調者若是在上一階段收到全部事務參與者回覆的 Prepared 消息,則先本身在本地持久化事務狀態爲 Commit,在此操做完成後向全部參與者發送 Commit 指令,全部參與者當即執行提交操做;不然,任意一個參與者回覆了 Non-Prepared 消息,或任意一個參與者超時未回覆,協調者將將本身的事務狀態持久化爲 Abort 以後,向全部參與者發送 Abort 指令,參與者當即執行回滾操做。對於數據庫來講,這個階段的提交操做應是很輕量的,僅僅是持久化一條 Commit Record 而已,一般可以快速完成,只有收到 Abort 指令時,才須要根據回滾日誌清理已提交的數據,這多是相對重負載操做。
以上這兩個過程被稱爲「兩段式提交」(2 Phase Commit,2PC)協議,它可以成功保證一致性還須要一些其餘前提條件:
協調者、參與者都是能夠由數據庫本身來扮演的,不須要應用程序介入。協調者通常是在參與者之間選舉產生的,而應用程序相對於數據庫來講只扮演客戶端的角色。
兩段式提交原理簡單,但有幾個很是顯著的缺點:
單點問題
協調者等待參與者回覆時能夠有超時機制,容許參與者宕機,但參與者等待協調者指令時沒法作超時處理。一旦協調者宕機全部參與者都會受到影響。 若是協調者一直沒有恢復,沒有正常發送 Commit 或者 Rollback 的指令,那全部參與者都必須一直等待。
性能問題
兩段提交過程當中,全部參與者至關於被綁定成爲一個統一調度的總體,期間要通過兩次遠程服務調用,三次數據持久化,整個過程將持續到參與者集羣中最慢的那一個處理操做結束爲止,這決定了兩段式提交的性能一般都較差。
一致性風險
當網絡不穩定或宕機沒法恢復可能出現一致性問題。儘管提交階段時間很短,但仍存在風險。若是協調者在發出準備指令後,根據收到各個參與者發回的信息肯定事務狀態是能夠提交的,協調者會先持久化事務狀態,並提交本身的事務,若是這時候網絡突然被斷開,沒法再經過網絡向全部參與者發出 Commit 指令的話,就會致使部分數據(協調者的)已提交,但部分數據(參與者的)既未提交,也沒有辦法回滾,產生了數據不一致的問題。
爲了緩解兩段式提交的單點問題和準備階段的性能問題,後續發展出了三段式提交(3 Phase Commit,3PC)協議。
三段式提交把本來的兩段式提交的準備階段再細分爲兩個階段,分別稱爲 CanCommit、PreCommit,把提交階段改稱爲 DoCommit 階段。其中,新增的 CanCommit 是一個詢問階段,協調者讓每一個參與的數據庫根據自身狀態,評估該事務是否有可能順利完成。將準備階段一分爲二的理由是這個階段是重負載的操做,一旦協調者發出開始準備的消息,每一個參與者都將立刻開始寫重作日誌,它們所涉及的數據資源即被鎖住,若是此時某一個參與者宣告沒法完成提交,至關於你們都白作了一輪無用功。因此,增長一輪詢問階段,若是都獲得了正面的響應,那事務可以成功提交的把握就比較大了,這也意味着因某個參與者提交時發生崩潰而致使你們所有回滾的風險相對變小。所以,在事務須要回滾的場景中,三段式的性能一般是要比兩段式好不少的,但在事務可以正常提交的場景中,二者的性能都依然不好,甚至三段式由於多了一次詢問,還要稍微更差一些。
三段式提交對單點問題和回滾時的性能問題有所改善,可是它對一致性風險問題並未有任何改進,在這方面它面臨的風險甚至反而是略有增長了的。好比,進入 PreCommit 階段以後,協調者發出的指令不是 Ack 而是 Abort,而此時因網絡問題,有部分參與者直至超時都未能收到協調者的 Abort 指令的話,這些參與者將會錯誤地提交事務,這就產生了不一樣參與者之間數據不一致的問題。
共享事務是指多個服務共用同一個數據源。
數據源和數據庫的區別:數據源是指提供數據的邏輯設備,沒必要與物理設備一一對應。
分佈式事務指多個服務同時訪問多個數據源的事務處理機制
CAP 定理(Consistency、Availability、Partition Tolerance Theorem),也稱爲 Brewer 定理,爲分佈式計算領域所公認的著名定理。這個定理裏描述了一個分佈式的系統中,涉及共享數據問題時,如下三個特性最多隻能同時知足其中兩個:
表明數據在任什麼時候刻、任何分佈式節點中所看到的都是符合預期的。
表明系統不間斷地提供服務的能力,理解可用性要先理解與其密切相關兩個指標:可靠性(Reliability)和可維護性(Serviceability)。可靠性使用平均無端障時間(Mean Time Between Failure,MTBF)來度量;可維護性使用平都可修復時間(Mean Time To Repair,MTTR)來度量。可用性衡量系統能夠正常使用的時間與總時間之比,其表徵爲:A=MTBF/(MTBF+MTTR),便可用性是由可靠性和可維護性計算得出的比例值,譬如 99.9999%可用,即表明平均年故障修復時間爲 32 秒。
表明分佈式環境中部分節點因網絡緣由而彼此失聯後,即與其餘節點造成「網絡分區」時,系統仍能正確地提供服務的能力。
eBay 的系統架構師提出了一種獨立於 ACID 得到的強一致性以外的、使用 BASE 來達成一致性目的的途徑。BASE 分別是基本可用性(Basically Available)、柔性事務(Soft State)和最終一致性(Eventually Consistent)的縮寫。
TCC 是另外一種常見的分佈式事務機制,它是「Try-Confirm-Cancel」三個單詞的縮寫
可靠消息隊列雖能保證最終的結果是相對可靠的,過程也足夠簡單但整個過程徹底沒有任何隔離性可言,有一些業務中隔離性是可有可無的,但有一些業務中缺少隔離性就會帶來許多麻煩。
TCC實現上較爲煩瑣,是一種業務侵入式較強的事務方案,要求業務處理過程必須拆分爲「預留業務資源」和「確認/釋放消費資源」兩個子過程。它分爲如下三個階段。
Try
嘗試執行階段,完成全部業務可執行性的檢查,而且預留好所有需用到的業務資源。
Confirm
確認執行階段,不進行任何業務檢查,直接使用 Try 階段準備的資源來完成業務處理。Confirm 階段可能會重複執行,所以本階段所執行的操做須要具有冪等性。
Cancel
取消執行階段,釋放 Try 階段預留的業務資源。Cancel 階段可能會重複執行,也須要知足冪等性。
TCC 相似 2PC 的準備階段和提交階段,但 TCC 是位於用戶代碼層面,而不是在基礎設施層面。
TCC因爲它的業務侵入性很強因此不能知足全部的場景,咱們在有的時候能夠考慮採用SAGA事務,SAGA 在英文中是「長篇故事、長篇記敘、一長串事件」的意思。
現代的企業級或互聯網系統,「分流」是必需要考慮的設計
HTTP 協議的無狀態性決定了它必須依靠客戶端緩存來解決網絡傳輸效率上的缺陷。
一、強制緩存
HTTP 的強制緩存對一致性處理的策略是很直接的,強制緩存在瀏覽器的地址輸入、頁面連接跳轉、新開窗口、前進和後退中都可生效,但在用戶主動刷新頁面時應當自動失效。HTTP 協議中設有Expires、Cache-Control兩類 Header 實現強制緩存。
Expires 是 HTTP 協議最第一版本中提供的緩存機制,設計很是直觀易懂,但缺點有受限於客戶端的本地時間、沒法處理涉及到用戶身份的私有資源、沒法描述「不緩存」的語義。
Cache-Control 是HTTP/1.1 協議中定義的強制緩存 Header,相比於Expires語義更加豐富。
二、協商緩存
強制緩存是基於時效性的,協商緩存是基於變化檢測的緩存機制基於變化檢測的緩存機制,在一致性上會有比強制緩存更好的表現,但須要一次變化檢測的交互開銷,性能上就會略差一些。
DNS 也許是全世界最大、使用最頻繁的信息查詢系統,若是沒有適當的分流機制,DNS 將會成爲整個網絡的瓶頸。DNS 的做用是將便於人類理解的域名地址轉換爲便於計算機處理的 IP 地址。
最近幾年出現了另外一種新的 DNS 工做模式:HTTPDNS(也稱爲 DNS over HTTPS,DoH)。它將本來的 DNS 解析服務開放爲一個基於 HTTPS 協議的查詢服務,替代基於 UDP 傳輸協議的 DNS 域名解析,經過程序代替操做系統直接從權威 DNS 或者可靠的 Local DNS 獲取解析數據,從而繞過傳統 Local DNS。好處是徹底免去了「中間商賺差價」的環節,再也不害怕底層的域名劫持,可以有效避免 Local DNS 不可靠致使的域名生效緩慢、來源 IP 不許確、產生的智能線路切換錯誤等問題。
傳輸鏈路涉及到鏈接數優化、傳輸壓縮、快速UDP網絡鏈接。
內容分發網絡,英文名稱Content Distribution Network,簡稱CDN,現在CDN的應用有
調度後方的多臺機器,以統一的接口對外提供服務,承擔此職責的技術組件被稱爲「負載均衡」。四層負載均衡的優點是性能高,七層負載均衡的優點是功能強。作多級混合負載均衡,一般應是低層的負載均衡在前,高層的負載均衡在後。「四層」、「七層」,指的是OSI 七層模型中第四層傳輸層和第七層應用層
\