1. 程式人生 > >讀書筆記:大型分散式網站架構設計與實踐(1)

讀書筆記:大型分散式網站架構設計與實踐(1)

原計劃花一年半時間看完十二本書,目錄如下:

(1)大型分散式網站架構設計與實踐(陳康賢,電子工業出版社) (2)大型網站系統與Java中介軟體實踐(曾憲傑,電子工業出版社) (3)Java併發程式設計實戰(童雲蘭等譯,機械工業出版社) (4)Java併發程式設計的藝術(方騰飛,機械工業出版社) (5)深入理解Java虛擬機器(機械工業出版社) (6)Effective Java中文版(機械工業出版社) (7)Spring原始碼深度解析(人民郵電出版社) (8)MySQL5.6從零開始學(清華大學出版社) (9)資料庫索引設計與優化(電子工業出版社) (10)Redis設計與實現(黃健巨集,機械工業出版社) (11)分散式服務框架原理與實踐(李林鋒,電子工業出版社) (12)深入分析Java web技術內幕(許令波,電子工業出版社)

到目前為止已經看完了一半,剩下的估計看不完。而看過的書,回憶起來發現也忘記得差不多了。因此溫習一遍並留下筆記,後面也需要經常回過頭來看看,溫故而知新。

1 面向服務的體系架構SOA

第一章節一共分為四小節,分別介紹了基於TCP協議的RPC,基於HTTP協議的RPC,服務的路由和負載均衡,以及HTTP服務閘道器。

1.1 基於TCP的RPC

1.1.1 什麼是RPC

在SOA或者微服務架構中,有一個很基礎的概念就是RPC,全稱是Remote Process Call,即遠端過程呼叫。RPC的實現方式較多,如RMI,WebService等。RPC是相對於本地呼叫來說的,它是指呼叫非本地的伺服器上的方法。比如在微服務架構中,一個微服務系統呼叫另一個微服務系統提供的某個介面,這次呼叫過程就是一次RPC。RPC給系統的處理能力和吞吐量帶來了近似於無限制提升的可能,這是系統發展到一定階段必然性的變革,也是實現分散式計算的基礎。

一次完整的RPC過程描述如下:服務呼叫方(也可叫消費者)傳送RPC請求到服務提供方(生產者),服務提供方根據呼叫方提供的引數執行請求方法,將執行結果返回給呼叫方。

在一次RPC過程中,服務呼叫方發出的請求引數和服務提供方返回的結果需要通過網路進行傳輸,這就涉及到序列化和反序列化操作。而隨著業務的發展,服務呼叫者和服務提供者往往需要進行擴容,以服務叢集的形式存在。這樣,一次呼叫請求分發給哪一臺伺服器進行處理,就涉及到服務路由與負載均衡。

1.1.2 序列化/反序列化

無論是何種型別的資料,最終都要轉換成二進位制流在網路上進行傳輸。資料的傳送方需要將物件轉換成二進位制流,才能在網路上進行傳輸,而資料的接受方則需要把二進位制流再恢復為物件。將物件轉換成二進位制流的過程稱為物件的序列化,將二進位制流恢復為物件的過程稱為物件的反序列化。

序列化/反序列化框架中,常用的有Google的Protocal Buffers、Java本身內建的序列化方式、Hessian以及JSON和XML等。各種框架都有各自的使用場景和優缺點。

1.1.3 基於TCP協議實現RPC

基於Java的Socket API,可以實現一個簡單的RPC呼叫。程式碼示例如下:

public class Provider {
    private static Map<String, Object> services = new HashMap<>();
    static {
        services.put("cn.tonghao.component.book1.firstChapter.ISayHelloService", new SayHelloServiceImpl());
    }

    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(8888);
        while (true) {
            Socket socket = serverSocket.accept(); // 建立socket監聽
            ObjectInputStream input = new ObjectInputStream(socket.getInputStream()); // 獲得socket埠的輸入流
            String interfaceName = input.readUTF(); // 介面名
            String methodName = input.readUTF(); // 方法名
            Class<?>[] parameterTypes = (Class<?>[]) input.readObject(); // 引數型別
            Object[] argus = (Object[]) input.readObject(); // 引數物件

            Class serviceInterfaceClass = Class.forName(interfaceName);
            Object service = services.get(interfaceName); // 取得服務實現的物件
            Method method = serviceInterfaceClass.getMethod(methodName, parameterTypes); // 獲得呼叫方法
            Object result = method.invoke(service, argus);

            ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
            output.writeObject(result);
        }
    }
}

public class Consumer {

    public static void main(String[] args) throws Exception {
        String interfaceName = ISayHelloService.class.getName();
        Method method = ISayHelloService.class.getMethod("sayHello", String.class);
        Object[] argus = {"hello"};
        Socket socket = new Socket("127.0.0.1", 8888);

        ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
        output.writeUTF(interfaceName);
        output.writeUTF(method.getName());
        output.writeObject(method.getParameterTypes());
        output.writeObject(argus);

        ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
        Object result = input.readObject();
        System.out.println(result.toString());
    }
}
public interface ISayHelloService {
    String sayHello(String str);
}

public class SayHelloServiceImpl implements ISayHelloService {
    @Override
    public String sayHello(String str) {
        return str;
    }
}

先執行服務提供者provider,再執行消費者consumer,最終會輸出hello。這個簡單的例子展示了一次RPC呼叫。服務呼叫方將請求引數傳送給服務提供方,服務提供方收到呼叫方的請求引數之後,通過反射機制獲取到要執行的方法,最終得到返回的結果。

1.2 基於HTTP協議的RPC

由於TCP屬於網路傳輸層協議,所以基於TCP協議實現的RPC能夠靈活地對協議欄位進行定製,減低網路開銷,但同時要關注很多底層的複雜細節,實現的代價很高,難以跨平臺呼叫。

而對於基本HTTP協議的實現來說,很多成熟的開源web容器已經幫其處理好了底層細節,開發人員只需要關注業務實現,而不用理會複雜的底層細節。當然,由於是上層協議,封裝了很多底層細節的東西,在同等網路環境下, 使用HTTP協議傳輸相同的內容,效率會比基於TCP協議的資料傳輸要低。

基於HTTP協議的RPC一般使用JSON或XML作為資料傳輸的格式,RPC或Restful風格的URL連結風格,通過http協議進行遠端過程呼叫。可以通過URLConnection或者HttpClient工具來發送http請求。

構建一次簡單的基於HTTP的RPC是比較容易的:搭建一個最基本的springMVC框架,springMVC的controller可以提供restful風格的服務介面,啟動之後該伺服器就是一個服務提供者。然後使用URLConnnection或者HttpClient傳送一個Http請求,呼叫服務提供者並得到返回結果,這樣一次簡單的RPC過程就完成了。

1.3 服務路由和負載均衡

在SOA架構中,一個服務消費者可能要消費多個服務,而不同的服務是由不同的服務提供者提供的,將不同的服務請求分發到給不同的服務提供者,就是所謂的服務路由。為了提高服務能力,一般服務提供者都是以叢集的形式存在的。服務請求被路由給服務提供者(叢集)之後,必須根據均衡演算法和規則,選取其中一臺伺服器進行處理,這個過程就稱為服務的負載均衡。

當服務規模較小時,可以採用硬編碼的方式將服務地址和配置寫在程式碼中,通過編碼方式解決服務路由和均衡問題,也可以通過傳統的硬體負載均衡裝置如F5等,或者採用LVS或Nginx等軟體解決方案。

當服務越來越多,規模越來越大時,硬編碼方式是行不通的,而硬體負載均衡裝置或者LVS/Nginx等軟體方案也會存在單點故障問題,即一旦服務路由或負載均衡伺服器宕機,依賴它的服務均將失效。

此時就需要一個能夠動態註冊和獲取服務資訊的地方,來統一管理服務名稱和其對應的伺服器列表資訊,這就是所謂的服務配置中心。

服務註冊中心應該提供以下能力: 服務提供者在啟動時,將其提供的服務名稱、伺服器地址註冊到服務配置中心

服務消費者通過服務配置中心來獲得需要呼叫的服務的機器列表,通過負載均衡演算法,選取其中一臺伺服器進行呼叫。

當伺服器宕機或者下線時,該伺服器地址能夠動態地從服務配置中心裡面移除,並通知相應的服務消費者(否則消費者會呼叫到已經失效的服務從而導致錯誤)。

服務消費者在第一次呼叫服務時需要查詢服務配置中心,將查詢到的資訊快取到本地。後續呼叫只需要從本地查詢服務地址即可,除非服務的地址列表有變更

服務配置解決了之前負載均衡裝置導致的單點故障問題,並且大大減輕了服務配置中心的壓力。一般來說,常用的服務註冊中心有Zookeeper,Eureka等。

1.3.1 負載均衡演算法與動態配置規則

負載均衡演算法的種類有很多,常見的有輪詢法、隨機法、源地址雜湊法、加權輪詢法、加權隨機法、最小連線法等,應根據具體的使用場景選取對應的演算法。

輪詢和隨機法是比較簡單的負載均衡演算法,用的也比較多。源地址雜湊法能保證相同的客戶端ip地址的請求始終被髮送到同一臺伺服器上,根據此特性可以在服務消費者和服務提供者之間建立有狀態的session會話。最小連線法比較靈活和智慧,根據後端伺服器當前的連線情況,動態地選取其中當前積壓連線數最少的一臺伺服器來處理當前請求,儘可能提供後端伺服器的利用效率。但實現起來較為複雜。 上面說的都是固定的策略,有時候還是無法滿足千變萬化的需求。當固定策略無法滿足使用需求時,可以使用動態配置規則。動態配置規則利用的是Groovy指令碼,由於Groovy指令碼語言能夠直接編譯成Java位元組碼,執行在Java虛擬機器上,且能夠很好地跟Java進行互動,因此利用其動態特性可以實現負載均衡規則的動態配置。

1.4 HTTP服務閘道器

一般服務消費者的請求來自於公共網路,服務提供者位於企業私有網路。相對於企業內部的私有網路而言,公共網路的環境複雜多變,因此必須有一個強大的安全體系,來保障相關資料和介面的安全,而不能說所有來自於公共網路的請求都能呼叫服務提供者。

這個強大的安全體系就是閘道器。Http服務閘道器接收來自外部各種APP的Http請求,完成相應的許可權與安全檢查,當校驗通過後,根據傳入的服務名稱,到服務配置中心找到相應的服務節點名稱,並載入對應服務提供者的地址列表,通過負載均衡演算法,選取伺服器發起遠端呼叫,將客戶端引數傳遞到後端伺服器。服務提供方處理請求並返回響應結果,HTTP 服務閘道器接收到響應後再將響應輸出給客戶端APP。

對於外部來說,所有的請求都依賴閘道器進行服務路由以及請求轉發,gateway是整個網路的核心節點,一旦閘道器失效,所有依賴它的外部APP都將無法使用。而且,gateway把握著外部的請求入口,其流量是整個後端叢集流量之和。因此在設計之初,就需要考慮到系統流量的監控和容量規劃,以及gateway叢集的可擴充套件性,以便在流量達到極限之前,能夠快速方便地進行系統擴容。

總結

對這一章內容的總結如下:

面向服務的體系架構SOA也可以叫分散式應用架構,它是在垂直應用架構的基礎上發展而來的,適應了越來越複雜的業務發展需求。

基於HTTP進行RPC時,服務的選擇和機器的選擇就涉及到了服務路由和負載均衡。負載均衡演算法有隨機、輪詢、地址雜湊、權重隨機、權重輪詢、最小連線法等。當服務規模較小時,可以通過硬編碼或者負載均衡硬體或軟體來實現服務路由和負載均衡,而當服務規模擴大時,就必須引入服務註冊中心。常用的服務註冊中心有zookeeper和eureka,提供了動態的服務註冊和查詢功能。

在成熟的分散式應用架構中,為了安全考慮,還需要一個閘道器,用於過濾非法請求。由於閘道器是所有請求的入口,流量是所有後端的流量之和,在設計之後就需要考慮流量監控和容量規劃。