分佈式系統技術概要前端
如今互聯網應用,尤爲是大型互聯網公司的應用已經發展爲大規模或超大規模的分佈式的,集羣化的應用。而中小規模的分佈式應用也已普遍出如今各個領域。將來,隨着雲計算向社會生活的方方面面去***,分佈式應用將更加地普及。因此,任何一個要從事服務器端應用開發的人員,都有具有對分佈式應用的基本認識。java
本文將簡要介紹分佈式應用的各基本領域的相關技術。這些技術在一個分佈式應用中都會有或多或少的設計,即使暫時沒有涉及到,設計人員也要有所考慮,保證系統有進一步發展的空間。算法
1. 集羣管理數據庫
關鍵字:Apache Zookeeper、Paxos 算法、Etcd、Raft、Apache Curator編程
在一個分佈式系統中,存在着一些和系統運行,以及重要業務緊密相關的數據,如節點相關的數據、應用服務和數據服務相關的數據等,這些數據對集羣的正常運行相當重要。緩存
服務器節點相關數據:服務器的地址、狀態安全
服務相關數據:服務的IP、端口、版本、協議、狀態、主備節點信息服務器
數據庫相關數據:路由規則、分庫分表規則網絡
這些重要的數據在分佈式系統中存在着多份拷貝,以保證高可用性。但這產生了另一個問題,就是如何保證這些數據的一致性。由於這些數據是如此重要,不一致的數據會產生嚴重甚至致命的錯誤。在一個小規模的分佈式系統中,由於能夠用一兩臺服務器去作集羣管理,因此數據的一致性容易實現。可是對於一個大規模的分佈式系統,一兩臺集羣配置管理服務器沒法支撐整個集羣所帶來的大量併發讀寫操做,因此要使用幾臺、十幾臺,甚至更多的服務器去支撐這些請求。此時,就須要一個保持這些服務器中集羣配置數據的一致性的方案了。數據結構
這衆多方案中,Paxos 算法算是最佳方案之一。關於 Paxos 算法的內容,不在這裏詳述了。簡單描述就是集羣中各節點相互以提議的方式通訊(對一項數據的修改),提議中帶有不斷增長的 ID 號,節點永遠贊成當前 ID 號最大的提議,並拒絕其它提議。當有半數以上節點贊成一項提議以後,這個提議便被整個節點所接受並採納。
1.1. Apache Zookeeper
Paxos 算法的語言表述看上去不難,可是其中的技術難點並很多。好在如今已經有了不少的解決方案,其中最爲著名的即是 Apache Zookeeper。Zookeeper 不只能夠用來存儲配置數據,還能夠用來實現集羣 Master 選舉、分佈式鎖等場景。Apache Curator 是 Zookeeper 的客戶端,能夠簡化對 Zookeeper 的使用,實現各式的場景。
Zookeeper 是一個分佈式的服務管理框架。Zookeeper 的典型的應用場景包括配置文件的管理、集羣管理、分佈式鎖、Leader 選舉、隊列管理等。Zookeeper 可工做在集羣模式下,zoo.cfg 中記錄着集羣中全部 Zookeeper 服務器的地址,每一個服務器有本身惟一的 ID。同時,每一個服務器在本身的 dataDir 目錄下還要有一個 myid 文件,以標示本身的 ID。在 Zookeeper 中,數據以樹狀的結構存儲,相似於 LDAP 數據庫。
如今相似 Zookeeper 的項目還有使用 go 語言實現的 Etcd。
1.2. 參考:
Paxos算法
zookeeper節點數與watch的性能測試
etcd:從應用場景到實現原理的全方位解讀
etcd v2.1 benchmarks
分佈式配置服務etcd VS 分佈式協調服務zookeeper
2. 遠程調用
關鍵字: NIO、Netty、epoll、Thrift、Protobuf
分佈式系統中,模塊間的調用一般須要用遠程調用來實現。並且隨着微服務架構模式的流行,使用遠程調用的比例會愈來愈高。其實遠程調用這種方式很早之前就出現了,早年的技術有諸如 COBRA、EJB、SOAP 等,但這些技術存在着用法複雜、性能差等缺點。這些缺點限制着遠程調用的普及。這些年,隨着異步 IO 技術、序列化技術的發展進步,以及像 Zookeeper 這樣的集羣管理服務的出現普及,妨礙遠程調用普及的技術障礙逐漸被打破。
使用 HTTP + JSON 的方式一樣能夠實現模塊之間的遠程調用,但這種方式一般用來實現 Public API。在系統內部,遠程調用要求更快的速度,更小的延遲,還有還有異步調用的需求,因此 HTTP + JSON 一般沒法知足這樣的要求。遠程調用有兩個重要的技術點,一個是 IO 技術、一個是序列化技術。另外,遠程調用還引出來另兩個問題:1. 服務註冊、發現、路由的問題。這個問題的須要結合例如 Zookeeper 服務去解決;2. 如何簡化遠程調用的使用,使其如同本地調用同樣簡單。這個問題須要結合 AOP 之類的技術。這兩個問題的具體解決不在本節討論範圍以內。
2.1. IO
(這裏只說 Socket IO)常見的 IO 模型有阻塞 IO、非阻塞 IO 和異步 IO。阻塞 IO 指的是若是一個線程要在 Socket 鏈接上進行某種 IO 操做時(讀或寫數據),當沒有操做不可執行時(沒有數據可讀或沒法寫數據),執行操做的線程便會被掛起,操做便會被阻塞,直到操做能夠執行。這種方式的好處是業務代碼編寫起來很簡單,缺點是資源利用率不高。由於一個鏈接必須有一個線程去處理。當有大量鏈接時,便會消耗大量的線程。這個缺點放在服務器端開發領域就顯得很是嚴重了。
非阻塞 IO 實現了線程的多路複用,一個線程被用來能夠處理多個鏈接;異步 IO 則是由操做系統來實現 IO 的讀寫操做。在數據 ready 以後,通知業務線程處理。
上面只是對阻塞 IO 和非阻塞 IO 的一個籠統的介紹。從具體的技術來看,Linux 經過 epoll 技術提供了對非阻塞 IO 的支持。epoll 是 Linux 內核的一個系統調用,最先在 2.5.44 版中被加入。epoll 的意思是 event poll。簡單來講就是當有一個 IO 事件發生時,Linux 內核便會通知用戶。使用方式是在建立 epoll 句柄以後,用戶在其上不斷地循環以獲取新的事件(當有事件發生時)。這些事件是來自多個鏈接的,從而實現了線程的多路複用。
在 Java 1.4 中,也引入了 NIO 的支持 (java.nio.*)。在 Java NIO API 中,用戶的程序能夠將一個鏈接 (SelectableChannel.register(Selector sel, int ops)) 註冊到一個 Selector 上(一個 Selector 能夠有多個鏈接註冊)。註冊以後,用戶的程序即可以經過不斷地循環調用 Selector.selectedKeys() 方法得到這個鏈接上的事件並進行處理(一般會使用另外的線程去處理事件,即 Reactor 模型)
雖然 Java 爲 NIO 開發提供了良好的 API 支持(從 1.7 開始還支持了 AIO),可是 IO 開發依舊有很高的複雜性,且 Java NIO 類庫的是 JDK 中 bug 較多的部分。故不推薦普通開發者直接基於 JDK 開發網絡 IO 功能,而是建議使用 Netty 進行開發。關於 Netty 這裏就不作介紹了。
2.2. 序列化技術
序列化技術是遠程調用的通訊協議中的重要一部分,它定義了編程語言中的數據結構和數據傳輸協議中的數據結構之間如何相互轉化。序列化技術的性能的好壞會影響到對遠程調用性能的好壞在序列化方面。序列化技術性能的好壞主要包含兩方面的含義:一個是序列化時佔用的資源(CPU、內存、所需時間);另外一個是序列化以後數據的大小。SOAP WebService 和 REST WebService 一般會把數據序列化成 XML 格式或者 JSON 格式。這兩種格式由於都是文本格式,因此有着良好的可讀性,可是對於須要頻繁使用的遠程調用來講,它們的體積偏大。因此邊有了性能更好的序列化解決方案,被你們所熟知的有 Protocol Buffers 和 Apache Arvo。此外,Apache Thrift 的序列化的性能也很好,可是 Thrift 沒法被當作一個單獨的序列化技術被使用,而是一個完整的遠程調用解決方案。其序列化部分不太容易被剝離出來,沒有完整的 API 被開放使用。這裏列出了常見的序列化技術的性能比較
2.3. Apache Thrift
Thrift 由 Facebook 貢獻,它是一個高性能、跨語言的 RPC 服務框架,適合用來實現內部服務的 RPC 調用。Thrift 採用經過 IDL 接口描述語言定義並生成服務接口,再結合其提供的服務端和客戶端調用棧實現 RPC 功能。
service Calculator extends shared.SharedService { /** * A method definition looks like C code. It has a return type, arguments, * and optionally a list of exceptions that it may throw. Note that argument * lists and exception lists are specified using the exact same syntax as * field lists in struct or exception definitions. */ void ping(), i32 add(1:i32 num1, 2:i32 num2), i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch), /** * This method has a oneway modifier. That means the client only makes * a request and does not listen for any response at all. Oneway methods * must be void. */ oneway void zip() }
Thrift 提供了工具,根據 IDL 文件,爲各類編程語言(C++, Java, Python, PHP, Ruby 等)生成相應的接口和數據結構。Thrift 不只提供了傳統的 Request/Response 方式的接口調用,也有單向的調用方式(用關鍵字 oneway 修飾)。
Thrift 的序列化部分和整個框架結合緊密,並無直接提供序列化和反序列化的接口,因此不容易和其它傳輸協議配合使用。
示例與解釋
這裏提供了一個 Thrift 的簡單使用的示例。其中除了 ThriftClient、ThriftServer 和 CalculatorHandler 三個類,剩下的類都是從 *.thrift 文件,即 Thrift 的 IDL 文件生成的。Thrift 的 IDL 支持 namespace(即包空間)、繼承等語法。
以 Java 爲例,Thrift IDL 中的 service 將生成接口、服務器端棧和客戶端棧,這三部分又都有同步和異步兩種類型。即一個 IDL 文件將生成6個內部類。客戶端經過這個調用棧,在配置了傳輸協議、地址信息和序列化協議以後,就能夠調用服務器端了。
服務器端的實現也不復雜。固然開發人員須要實現相應的業務類,這個業務類要實現至少一種由 IDL 生成的接口,同步接口或異步接口,也可二者都實現。
基於 IDL 進行開發是使用 Thrift 這樣 RPC 框架的一種方式。這種方式對於新開發的、須要被遠程訪問的服務、而且有多重語言的客戶端的場景來講是很合適的。可是對於已有的業務方法,若是要讓其能夠被遠程訪問的話,那這種方式就顯得不方便了。因此 Facebook 有提供了另外一個項目 —— Swift(不是蘋果的 Swift)。這個項目能夠經過在 Java 代碼上添加 Annotation,使得普通的 Java 方法調用轉變成 Thrift 的遠程調用。這種方式相似於 JAX-RS 或其它許多 REST 框架所提供的功能。這種方式對主要使用 Java 或其它一些 JVM 語言,如 Scala 和 Groovy 開發的項目來講是很合適的。使用了 Thrift 的遠程調用的同時,還下降了引入 IDL 所致使的複雜度的提升和可讀性的降低。Thrift Swift 示例
2.4. 其它技術介紹
Protocol Buffers
Protobuf 是一個高性能序列化解決方案,有完善的文檔,能夠和例如 HTTP 這樣的協議搭配使用。
Apache Avro
Apache Avro 是 Apache Hadoop 的一個子項目。它提供了兩種序列化格式:JSON和二進制格式。JSON格式有良好的可讀性,而二進制格式在性能上和 Protobuf 不相上下。
2.5. 參考
序列化和反序列化
Netty系列之Netty高性能之道
Netty系列之Netty線程模型
Netty系列之Netty併發編程分析
消息中間件
關鍵字:Kafka、RabbitMQ 在分佈式系統中,消息中間件的重要性愈來愈明顯。消息中間件能夠解耦模塊、提供異步調用功能、消息持久化、消息削峯。已有的如 Apache ActiveMQ 沒法知足新的須要,因而出現瞭如 RabbitMQ、Apache Kafka 等新型的消息中間件產品。
Apache Kafka
Apache Kafka 充分利用了機械磁盤順序讀寫速度快的特色,在接受消息以後同步地寫入到磁盤中,保證數據可靠性的同時,也保證了很是快的速度。每一個 Kafka 集羣上都有多個 Topic,Topic 至關於一個 category,消費者能夠訂閱一個或多個 Topic。每一個 Topic 由多個 Partition 組成。消息被順序的添加到 Partition 中,每條消息有一個惟一的、有序的 ID,這個 ID 被稱爲 Offset。Consumer 須要維護本身消費到的消息的位置 (Offset)。
Apache Kafka 不一樣於傳統的消息中間件,它採用「拉」消息模式,而不是傳統的「推」消息模式。即客戶端須要主動從消息中間件獲取消息,好處是客戶端能夠更好地控制請求量。
Queue 模式和 Topic 模式
傳統消息隊列服務中有隊列模式和發佈訂閱模式兩種模式,前者一條消息只會被一個消費者消費;後者一條消息會發布給全部的訂閱這個 Topic 的消費者。在 Kafka 中,這兩種模式是使用一種方式 —— 消費者組來實現的。在同一個消費者組中的不一樣消費者不會受到相同的消息。若是想實現發佈訂閱模式,消費者必須處於不一樣的消費者組中。
Kafka 集羣
RabbitMQ
RabbitMQ 是一個使用 Erlang 開發的 AMQP (Advanced Message Queue Protocol) 實現。如今 RabbitMQ 是由 VMware 旗下的 SpringSource 負責開發。AMQP 是一個語言無關的消息隊列協議。在 RabbitMQ 中,有三個概念:Exchange、Queue 和 Route key。Exchange 用來標示生產者,Queue 用來標示消費者,而 Route key 用來關聯這二者。RabbitMQ 中這種方式提供了更靈活的應用模式。
分佈式文件系統
塊存儲與對象存儲
塊存儲是將一塊裸盤提供給客戶使用,可是這塊裸盤多是來自一塊物理硬盤,也有多是多塊,或是來自不一樣服務器上的硬盤。對象存儲提供了更高級的接口,經過這些接口能夠讀寫文件以及相關的元數據。其中的元數據包含了文件每個塊的存儲信息。經過文件元數據,文件能夠被並行地操做。
分佈式文件系統的高可用
爲了保證數據的安全,分佈式文件系統一般會將文件複製爲三份。這三份數據會位於不一樣的服務器上,對應要求更高的系統,好比公有云存儲。其中的一份數據會放置在另外一個機房中,以保證即使整個機房出現故障,整個文件系統還是可用的。
Ceph
Ceph 目前是 OpenStack 的一個組件,提供了塊存儲和對象存儲的功能。
GridFS
GridFS 是 MongoDB 的一部分。用來存儲超過 BSON 大小限制(16MB)的文件。GridFS 將文件分紅一個個部分,分開存儲。
FastDFS
FastDFS 是一個輕量的分佈式文件系統,適合存儲中小文件(對象存儲)。FastDFS 的跟蹤服務器不負責記錄文件的元信息。文件的具體存儲位置等信息包含在返回給用戶的 File ID 中。
分佈式內存
內存是新的硬盤,硬盤是新的磁帶 -- Jim Gray
Hazelcast
Hazelcast 是一個面向 Java 的分佈式內存解決方案,提供了豐富的功能特性。實現了諸如分佈式 Java 集合類、分佈式鎖、分佈式 ExecutorService 實現等等。但現實每每是殘酷的,Hazelcast 在實際應用中存在大量的缺陷。詳見 「hazelcast的坑爹事」
Memcached
Memcached 是老牌的「分佈式」緩存解決方案。分佈式之因此加引號,是由於 Memcached 服務器端自己並不支持分佈式,服務器端每一個節點之間並不會相互通訊。分佈式的支持須要客戶端來實現。早期的內存分佈式是經過節點之間複製來實現的,但這種方式卻限制了可伸縮性。這也是由於諸如 Terrecotta 這樣的內存分佈式解決方案沒有成爲主流的緣由。
Redis
Gemfire
分佈式數據庫
關係型數據庫
在大規模的分佈式應用中,單庫或者簡單的讀寫分離已經沒法知足要求,所以必須對數據庫進行水平和垂直的劃分和分庫分表。在對數據庫進行分庫分表以後,應用對數據庫的訪問便再也不是一件簡單的事情了。應用在進行一次數據庫操做時,其所對應的數據庫的地址和表名必須經過某種邏輯運算才能獲得。例如,ID 從1到1,000,000的User數據是數據庫1的User_1表中,ID從1,000,001到2,000,000的User數據在數據庫1的 User_2表中,而其它的User數據又會在不一樣的數據庫的不一樣的表中。同時,還要考慮主從數據庫,讀寫分離的問題。這樣的數據庫使用方式會使數據操做變得極爲複雜,也會增長數據遷移,增容擴容時的難度。
對於這樣複雜的問題,靠應用本身解決顯然是不合適的。因此各家分佈式應用的使用大戶——互聯網廠商,都本身實現了相應的解決方案。這些解決方案可分爲中間間方式和框架方式,前者做爲數據庫訪問的代理,使得分佈式的數據庫對應用是透明的。後者做爲一個框架嵌入到應用中,也能起到相似的做用。這兩種方式各有優劣,分別適合不一樣的場合。
搜狗 Compass,阿里 TDDL、Cobar
NoSQL
大部分 NoSQL 雖然對分佈式的支持是友好的,但這並不意味着使用這些 NoSQL 數據庫就能夠輕輕鬆鬆地實現一個集羣。例如著名的 Key/Value 數據庫 Redis。它 3.0 以前一直沒有官方的集羣方案,因此各個大規模使用 Redis 都須要本身實現分佈式方案,例如 Twitter 的 Twemproxy、豌豆莢的 Codis 等等。
在實現數據的分佈式解決方案的時候,有一個算法是最常被使用的 —— 一致性哈希算法,這裏只是簡單提一下,不作進一步介紹。
虛擬化技術
關鍵字:OpenStack、Docker、容器技術虛擬化技術是提升硬件利用率的重要手段。虛擬化技術是實現雲計算的重要技術。虛擬化技術的最底層是各類硬件的虛擬化,如 CPU 虛擬化、內存虛擬化、存儲虛擬化、網絡虛擬化等等。而後再基於這些技術,構建出各類虛擬機技術。而後各個廠商又基於虛擬機技術和其它虛擬化技術構建出 IaaS、PaaS 和 SaaS 等平臺和軟件產品。
OpenStack
OpenStack 這個開源項目包含了一系列用於 IaaS 平臺搭建的組件的合稱。這些組件包含用於網絡虛擬化的 Neutron、提供存儲虛擬化的 Ceph 和 Swift、以及提供例如鏡像管理、控制面板等功能的諸多組件。OpenStack 自己並不提供虛擬化技術,而是經過支持諸多現有的虛擬化技術,例如 KVM,並在此之上提供一系列構建 IaaS 解決方案的技術。OpenStack 中的組件能夠靈活搭配使用,而且由於開源的緣由,使用者能夠對其進行自定義或二次開發。可是也是由於這個緣由,任何廠商想要成功使用 OpenStack 必須有一個強大的技術團隊作後盾。這也是目前 OpenStack 技術發展遇到的最大困難。
Docker
嚴格說來 Docker 並非一個虛擬化技術,可是由於 Docker 可以提供給使用者一種輕量化的虛擬機的使用體驗,因此也將 Docker 列在這裏。Docker 是一個容器技術,它經過 Linux 內核的支持,使不一樣的進程能夠相互隔離並作到資源的限制,從而實現了部分的虛擬機資源隔離的須要。Docker 相比較虛擬機的優點在於輕量和系統資源使用效率接近於實體機。由於如今隨着需求的發展和技術的進步,服務器端應用向着一種輕量化和愈來愈分佈式的方向發展。虛擬機這樣重量級的技術對於小而多的應用來講便再也不合適,這也是 Docker 這樣的容器技術近些年迅速發展並呈現火熱狀態的重要緣由。
Docker 和前面提到的 OpenStack 是兩個不一樣層面的技術,二者並不衝突。如今 OpenStack 和 Docker 社區正在緊密合做(容器不會取代OpenStack,但兩者如何深度整合?)。
分佈式系統之負載均衡
HAProxy
HAProxy 是一個高性能的 TCP 和 HTTP 反向代理代理和負載均衡服務器。可用反向代理和負載均衡還有 Nginx。Niginx 更偏向於 HTTP 協議。另外 Varnish 和 Squid 能夠做爲前端的代理,可是它們更偏重緩存功能
更上一層
服務編排:註冊、發現和路由
結合技術:配置管理、遠程調用等
有些相似早年的 JNDI。即一個應用去遠程訪問另一個應用時,只需知道它所要訪問的應用的名稱、版本等信息,便可調用成功。不須要考慮它所要調用的應用的具體地址。
雲操做系統
結合技術:虛擬機、容器技術、網絡虛擬化、配置管理、消息隊列
Apache Mesos、Google Berg、騰訊 Gaia、百度 Matrix
總結
就像上面所提到的,上面的這些技術之間都是你中有我,我中有你的關係,或者有着相相似的設計思想。掌握它們,基本不去使用,也會對你設計開發能力的提升大有裨益。