應用服務框架的演變歷史

在較長的一段時間裏,LAMP框架是Web網站的常見方式:Linux+ Apache+ PHP+ MySQL,或者另外一套MVC規範,java裏比較常見的選擇通常是Spring +Struts +MyBatis +Tomcat,有時候也會選擇重量級的EJB來實現,儘管在技術細節上的處理各不相同,但是都有一個共性:垂直應用架構。
垂直應用架構
垂直應用架構

一、傳統的垂直應用架構

1.MVC架構
以經典的MVC垂直應用架構爲栗子,通常分爲三層:

  • View展示層,是用戶看到並與之交互的界面。
  • Control層,用於前端Web請求的分發,調度後臺的業務處理。
  • Model模型層,包含業務數據和執行邏輯。

標準的MVC模式並不包括數據訪問層,所以通常還需要專門的ORM框架,可以屏蔽對底層數據庫連接池和數據源的實現,提供對上層JDBC的訪問,提升開發效率,常見的一般都是Hibernate和Mybatis。通常基於MVC框架的應用都會打成一個war包,部署在Tomcat等Web容器中。

業務組網也不復雜,通常做好雙熱機即可,可通過watchDog來檢測應用,判斷應用進程是否異常,如果一個出現問題可以立即啓動到備機,如果考慮到更復雜的併發場景,可在後端做集羣部署,還有前端F5等負載均衡處理。
雙機邏輯組網圖

2.垂直應用架構的缺陷

1.難以應付複雜的業務場景,且開發和維護的成本會增高。
2.團隊協作效率差,公共功能重複開發,重複率高。
3.系統的可靠性變差,某個節點的故障會導致整個系統的“雪崩效應”。
4.維護和定製困難,複雜應用的業務拆分困難,代碼修改牽一髮而動全身。

當垂直應用越來越多,應用之間的交互不可避免,將核心業務抽取出來,作爲獨立的服務,逐漸形成穩定的服務中心,使得前端能夠更快的相應市場需求,同時將公共的API抽取出來,可以作爲獨立的公共服務給其他調用者消費,實現服務的共享和重用,於是有了RPC框架的需求。

二、RPC框架

RPC的全稱爲(Remote Procedure Call),遠程過程調用,是一種進程間的通信方式,在2006年後的移動互聯網時代開始興起,出現了各種各樣的開源RPC框架。
RRC框架原理

RPC的框架屏蔽了底層的傳輸方式(TCP/UDP),序列化方式(XML / JASON / ProtoBuf)和通信細節,使用者只需要知道who(誰)在where(哪裏)提供了what(什麼)服務即可。

一個最簡單的RPC框架只需要考慮如下三個部分的實現:

  • 服務提供者,運行在服務端,負責提供服務接口定義和實現。
  • 服務發佈者,運行在RPC服務端,負責將本地服務發佈成遠程服務,供其他消費者調用;
  • 本地服務代理,運行在RPC客戶端,通過代理調用遠程服務提供者,然後將結果進行封裝返回給本地消費者;

在這裏根據思路來簡單提供一段代碼實現,首先是服務的接口定義和實現:

/**
 * HelloService 服務接口
 */
public interface HelloService {

    String hello(String name);

}

/**
 * HelloServiceImpl 服務接口的實現
 */
public class HelloServiceImpl implements HelloService {
    public String hello(String name) {
        return "Hello " + name;
    }
}

服務的發佈:

/**
 * RpcFramework
 **/
public class RpcFramework {

    /**
     * 暴露服務
     *
     * @param service 服務實現
     * @param port 服務端口
     */
    public static void export(final Object service, int port) throws Exception {
        if (service == null) {
            throw new IllegalArgumentException("service instance == null");
        }
        if (port <= 0 || port > 65535) {
            throw new IllegalArgumentException("Invalid port " + port);
        }
        System.out.println("Export service " + service.getClass().getName() + " on port " + port);
        ServerSocket server = new ServerSocket(port);

        for(;;) {
            try {
                final Socket socket = server.accept();
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            try {
                                ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
                                try {
                                    String methodName = input.readUTF();
                                    Class<?>[] parameterTypes = (Class<?>[])input.readObject();
                                    Object[] arguments = (Object[])input.readObject();
                                    ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
                                    try {
                                        Method method = service.getClass().getMethod(methodName, parameterTypes);
                                        Object result = method.invoke(service, arguments);
                                        output.writeObject(result);
                                    } catch (Throwable t) {
                                        output.writeObject(t);
                                    } finally {
                                        output.close();
                                    }
                                } finally {
                                    input.close();
                                }
                            } finally {
                                socket.close();
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

服務的引用

   /**
    * 引用服務
    * @param interfaceClass 接口類型
    * @param host 服務器主機名
    * @param port 服務器端口
    * @return 遠程服務
    */
   @SuppressWarnings("unchecked")
   public static <T> T refer(final Class<T> interfaceClass, final String host, final int port) throws Exception {
       if (interfaceClass == null)
           throw new IllegalArgumentException("Interface class == null");
       if (! interfaceClass.isInterface())
           throw new IllegalArgumentException("The " + interfaceClass.getName() + " must be interface class!");
       if (host == null || host.length() == 0)
           throw new IllegalArgumentException("Host == null!");
       if (port <= 0 || port > 65535)
           throw new IllegalArgumentException("Invalid port " + port);
       System.out.println("Get remote service " + interfaceClass.getName() + " from server " + host + ":" + port);
       return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<?>[] {interfaceClass}, new InvocationHandler() {
           public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable {
               Socket socket = new Socket(host, port);
               try {
                   ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
                   try {
                       output.writeUTF(method.getName());
                       output.writeObject(method.getParameterTypes());
                       output.writeObject(arguments);
                       ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
                       try {
                           Object result = input.readObject();
                           if (result instanceof Throwable) {
                               throw (Throwable) result;
                           }
                           return result;
                       } finally {
                           input.close();
                       }
                   } finally {
                       output.close();
                   }
               } finally {
                   socket.close();
               }
           }
       });
   }
  }

  public class RpcProvider {
        public static void main(String[] args) throws Exception {
            HelloService service = new HelloServiceImpl();
            RpcFramework.export(service, 1234);
        }
    }

服務的調用:

public class RpcConsumer {
    public static void main(String[] args) throws Exception {
        HelloService service = RpcFramework.refer(HelloService.class, "127.0.0.1", 1234);
        for (int i = 0; i < Integer.MAX_VALUE; i ++) {
            String hello = service.hello("World" + i);
            System.out.println(hello);
            Thread.sleep(1000);
        }
    }
}

2.RPC的不足
在大規模服務化以前,應用以前只能通過暴露接口和應用遠程服務的方式去調用,服務越來越多的時候會有以下情況:

  • 服務URL的配置管理變的困難
  • 服務間的依賴關係變成錯綜複雜,難以分清哪個應用要在哪個應用前啓動
  • 服務的調用量越來越大,服務的容量問題出現問題,某個服務需要多少機器、什麼時候該加機器
  • 缺乏一個服務註冊中心,動態的註冊和發現服務。

服務化之後,隨之而來的就是服務治理問題,現在的RPC框架在這方面都有所欠缺,要解決這些問題必須通過服務框架+服務治理來完成,單憑RPC框架無法解決服務治理的問題。

三、SOA服務化架構

SOA,Service-Oriented Architecture,面向服務的架構(SOA)是一個組件模型,是一種粗粒度、鬆耦合的以服務爲中心的架構,接口之間通過定義明確的協議和接口進行通信。
面向服務的核心是對傳統的垂直架構進行改造,其中的核心技術就是分佈式服務框架,應用也從集中式走向了分佈式,大規模系統的架構設計原則就是儘可能的拆分,以達到更好的獨立擴展與伸縮,更靈活的部署、更好的隔離和容錯,更高的開發效率,具體的拆分策略是:橫向拆分縱向拆分

業務縱向拆分:
根據業務的特性把應用拆開,不同的業務模塊獨立部署,將複雜的業務線拆分成相對獨立的、靈活的具體能力域,由大到小分而治之。

縱向拆分示意圖

業務橫向拆分:
將核心的、公共的業務拆分出來,通過分佈式服務框架對業務進行服務化,消費者通過標準的契約來消費這些服務,服務提供者獨立打包、部署,與消費者解耦。
縱向拆分示意圖

服務治理
拆分了之後,隨着服務數的增多,亟需一個服務治理框架,有效管理服務,提升服務的運行質量,服務治理需要滿足:服務生命週期管理,服務容量規劃,運行期治理和服務安全等。目前較爲成熟的商用服務框架有Spring cloud,阿里巴巴提供的開源的Dubbo框架,非開源的HSF框架,

至於Dubbo和HSF這兩者的差別,抄一段來展示:阿里巴巴第一代RPC框架Dubbo是國內第一款成熟的商用級RPC框架,已於2011年正式對外開源,目前已發展成爲國內開源價值最高、用戶使用規模最大的開源軟件之一。2016年度中國開源軟件Top10。最新一代RPC框架HSF,全稱High Speed Framework,也叫"好舒服","很舒服"框架,是阿里內部對這一款高性能服務框架的暱稱,是一款面向企業級互聯網架構量身定製的分佈式服務框架。HSF以高性能網絡通信框架爲基礎,提供了諸如服務發佈與註冊,服務調用,服務路由,服務鑑權,服務限流,服務降級和服務調用鏈路跟蹤等一系列久經考驗的功能特性。

架構原理
分佈式服務的架構可以抽象爲三層:

1、RPC層:底層通信框架(例如NIO框架的封裝),序列化和反序列化框架等。
2、FilterChain層:服務調用職責鏈,例如負載均衡,服務調用性能統計,服務調用完成通知,失敗重發等等。
3、Service層:java動態代理,將服務提供者的接口封裝成遠程服務調用;java反射,服務提供者使用,根據消費者請求消息中的接口名、方法名、參數列表反射調用服務提供者的接口本地實現類。

分佈式服務框架的兩個核心功能:服務治理和服務註冊中心,服務中心中dubbo默認使用的是ZooKeeper,HSF默認使用的爲ConfigServer。

四、微服務

SOA解決了應用服務化的問題,隨着服務化實踐的深入,服務的規模也越來越大,服務治理的問題也越來越多,這時候出現了微服務的思想。微服務架構由多個微小服務構成,每個服務就是一個獨立的可部署單元或組件,它們是分佈式的,相互解耦的,通過輕量級遠程通信協議(比如REST)來交互,每個服務可以使用不同的數據庫,而且是語言無關性的。它的特徵是彼此獨立、微小、輕量、鬆耦合,又能方便的組合和重構,猶如《超能陸戰隊》中的微型機器人,個體簡單,但組合起來威力強大。

微服務之所以這麼火,另一個原因是因爲 Docker 的出現,它讓微服務有一個非常完美的運行環境,Docker 的獨立性和細粒度非常匹配微服務的理念,Docker的優秀性能和豐富的管理工具,讓大家對微服務有了一定的信息,概括來說 Docker 有如下四點適合微服務:

  • 獨立性:一個容器就是一個完整的執行環境,不依賴外部任何的東西。
  • 細粒度:一臺物理機器可以同時運行成百上千個容器。其計算粒度足夠的小。
  • 快速創建和銷燬:容器可以在秒級進行創建和銷燬,非常適合服務的快速構建和重組。
  • 完善的管理工具:數量衆多的容器編排管理工具,能夠快速的實現服務的組合和調度。

作者:YitaiCloud
鏈接:https://www.jianshu.com/p/62398fa9bc64
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯繫作者獲得授權並註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章