微博開源框架Motan初體驗

前兩天,我在開源中國的微信公衆號看到新浪微博的輕量Rpc框架——Motan開源了。上網查了下,才得知這個Motan來頭不小,支撐着新浪微博的千億調用,曾經在2014年的春晚中有着千億次的調用,對抗了春晚的最高峯值。java

什麼是Motan

2013 年微博 RPC 框架 Motan 在前輩大師們(福林、fishermen、小麥、王喆等)的精心設計和辛勤工做中誕生,向各位大師們致敬,也獲得了微博各個技術團隊的鼎力支持及不斷完善,現在 Motan 在微博平臺中已經普遍應用,天天爲數百個服務完成近千億次的調用。」 —— 張雷git

微博的Motan RPC服務,底層通信引擎採用了Netty網絡框架,序列化協議支持Hessian和Java序列化,通信協議支持Motan、http、tcp、mc等,Motan框架在內部大量使用,在系統的健壯性和服務治理方面,有較爲成熟的技術解決方案,健壯性上,基於Config配置管理服務實現了High Availability與Load Balance策略(支持靈活的FailOver和FailFast HA策略,以及Round Robin、LRU、Consistent Hash等Load Balance策略),服務治理方面,生成完整的服務調用鏈數據,服務請求性能數據,響應時間(Response Time)、QPS以及標準化Error、Exception日誌信息。github

Motan 屬於服務治理類型,是一個基於 Java 開發的高性能的輕量級 RPC 框架,Motan 提供了實用的服務治理功能和優秀的 RPC 協議擴展能力。
Motan 提供的主要功能包括:
服務發現 :服務發佈、訂閱、通知
高可用策略 :失敗重試(Failover)、快速失敗(Failfast)、異常隔離(Server 連續失敗超過指定次數置爲不可用,而後按期進行心跳探測)
負載均衡 :支持低併發優先、一致性 Hash、隨機請求、輪詢等
擴展性 :支持 SPI 擴展(service provider interface)
其餘 :調用統計、訪問日誌等spring

Motan 能夠支持不一樣的 RPC 協議、傳輸協議。Motan 可以無縫支持 Spring 配置方式使用 RPC 服務,經過簡單、靈活的配置就能夠提供或使用 RPC 服務。經過使用 Motan 框架,能夠十分方便的進行服務拆分、分佈式服務部署。shell

關於Motan的更多內容可參考:http://h2ex.com/820
以及Motan的源碼:https://github.com/weibocom/motanapache

簡單調用示例

參照github中wiki,能夠快速的跑一跑motan,提早感覺一下,因爲Motan剛開源,不少東西還不完整,我我的在這中間也遇到不少坑,後面一一介紹。我按照wiki介紹,先建立maven項目motandemo,api

添加pom依賴

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.hjc.demo</groupId>
    <artifactId>motandemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>motandemo</name>
    <repositories>
        <repository>
            <id>spy</id>
            <name>36</name>
            <layout>default</layout>
            <url>http://repo1.maven.org/maven2</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.weibo</groupId>
            <artifactId>motan-core</artifactId>
            <version>0.0.1</version>
        </dependency>
        <dependency>
            <groupId>com.weibo</groupId>
            <artifactId>motan-transport-netty</artifactId>
            <version>0.0.1</version>
        </dependency>
        <!-- 集羣相關 -->
        <dependency>
            <groupId>com.weibo</groupId>
            <artifactId>motan-registry-consul</artifactId>
            <version>0.0.1</version>
        </dependency>
        <dependency>
            <groupId>com.weibo</groupId>
            <artifactId>motan-registry-zookeeper</artifactId>
            <version>0.0.1</version>
        </dependency>
        <!-- dependencies blow were only needed for spring-based features -->
        <dependency>
            <groupId>com.weibo</groupId>
            <artifactId>motan-springsupport</artifactId>
            <version>0.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.0.5.RELEASE</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>motan</finalName>
    </build>
</project>

注意若是maven下載不下來能夠去倉庫直接搜,而後下載jar包,我在搭建的過程就遇到了jar包下載不下來的狀況,多是網絡緣由吧。
倉庫地址:http://mvnrepository.com/
下載下來後使用maven進行本地安裝,安裝命令以下:微信

mvn install:install-file -Dfile=<path-to-file> -DgroupId=<group-id> -DartifactId=<artifact-id> -Dversion=<version> -Dpackaging=<packaging>

肯定pom.xml不報錯以後再進行下面的步驟網絡

爲服務方和調用方建立接口

建立接口,服務方和調用方都使用這個接口FooService
FooService:併發

package com.hjc.motan.server;

import java.util.List;
import java.util.Map;

import com.hjc.motan.DemoBean;

public interface FooService {
    public String hello(String name);

    public int helloInt(int number1);

    public double helloDouble(double number2);

    public List<String> helloList(List<String> list);

    public Map<String, List<String>> helloMap(Map<String, List<String>> map);

    public DemoBean helloJavabean(DemoBean bean);
}

服務方來實現這個接口的邏輯
FooServiceImpl:

package com.hjc.motan.server;

import java.util.List;
import java.util.Map;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.hjc.motan.DemoBean;

public class FooServiceImpl implements FooService {

    public static void main(String[] args) throws InterruptedException {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                "classpath:motan_server.xml");
        System.out.println("server start...");
    }

    public String hello(String name) {
        System.out.println("invoked rpc service " + name);
        return "hello " + name;
    }

    public int helloInt(int number1) {
        System.out.println("invoked rpc service " + number1);
        return number1;
    }

    public double helloDouble(double number2) {
        System.out.println("invoked rpc service " + number2);
        return number2;
    }

    public List<String> helloList(List<String> list) {
        System.out.print("invoked rpc service ");
        for (String string : list) {
            System.out.print(string + ",");
        }
        System.out.println();
        return list;
    }

    public Map<String, List<String>> helloMap(Map<String, List<String>> map) {
        System.out.print("invoked rpc service ");
        for (String key : map.keySet()) {
            System.out.print(key + ":[");
            for (String list : map.get(key)) {
                System.out.print(list + ",");
            }
            System.out.print("],");
        }
        System.out.println();
        return map;
    }

    public DemoBean helloJavabean(DemoBean bean) {
        System.out.print("invoked rpc service " + bean);
        System.out.print("," + bean.getId());
        System.out.print("," + bean.getName());
        System.out.print("," + bean.getScore());
        System.out.println();
        return bean;
    }

}

配置服務方暴露接口

在項目根目錄(src/main/datasource)建立motan_server.xml,以下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:motan="http://api.weibo.com/schema/motan"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
   http://api.weibo.com/schema/motan http://api.weibo.com/schema/motan.xsd">

    <!-- service implemention bean -->
    <bean id="serviceImpl" class="com.hjc.motan.server.FooServiceImpl" />
    <!-- exporting service by motan -->
    <motan:service interface="com.hjc.motan.server.FooService" ref="serviceImpl" export="8002" />
</beans>

在這個過程當中,我發現個人eclipse不能自動下載motan.xsd,這時候我只能手動配置,從motan-core的jar包中,找到這個schema文件,複製到任意位置,而後eclipse中,選擇Window->Preferences->XML->XML Catalog->User Specified Entries,點擊Add,輸入Location和Key,按照以下圖所示:

手動添加schema

以上步驟完成以後,就能夠啓動Motan Rpc的服務方了,在FooServiceImpl中已經寫好了main方法,右鍵run便可

配置調用方調用接口

在項目根目錄(src/main/datasource)建立motan_server.xml,以下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:motan="http://api.weibo.com/schema/motan"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
   http://api.weibo.com/schema/motan http://api.weibo.com/schema/motan.xsd">

    <!-- reference to the remote service -->
    <motan:referer id="remoteService" interface="com.hjc.motan.server.FooService" directUrl="localhost:8002"/>
</beans>

調用方調用

建立Client類調用服務方的接口並輸出
Client:

package com.hjc.motan.client;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.hjc.motan.DemoBean;
import com.hjc.motan.server.FooService;

public class Client {

    public static void main(String[] args) throws InterruptedException {
        ApplicationContext ctx = new ClassPathXmlApplicationContext(
                "classpath:motan_client.xml");
        // 獲取到service
        FooService service = (FooService) ctx.getBean("remoteService");
        // rpc調用
        /** String **/
        String ret1 = service.hello("motan");
        System.out.println(ret1);
        /** int **/
        int ret2 = service.helloInt(110);
        System.out.println(ret2);
        /** double **/
        double ret3 = service.helloDouble(11.2);
        System.out.println(ret3);
        /** list **/
        List<String> list = new ArrayList<String>();
        list.add("hello");
        list.add("motan");
        List<String> ret4 = service.helloList(list);
        for (String string : ret4) {
            System.out.print(string + ",");
        }
        System.out.println();
        /** map **/
        Map<String, List<String>> map = new HashMap<String, List<String>>();
        map.put("key1", Arrays.asList(new String[] { "val1","val2" }));
        map.put("key2", Arrays.asList(new String[] { "val1","val2","val3" }));
        map.put("key3", Arrays.asList(new String[] { "val1","val2","val3","val4" }));
        Map<String, List<String>> ret5 = service.helloMap(map);
        for (String key : ret5.keySet()) {
            System.out.print(key + ":[");
            for (String tmp : map.get(key)) {
                System.out.print(tmp + ",");
            }
            System.out.print("],");
        }
        System.out.println();
        /** javabean **/
        DemoBean bean = new DemoBean();
        bean.setId(1001l);
        bean.setName("motan bean");
        bean.setScore(99.998);
        DemoBean ret6 = service.helloJavabean(bean);
        System.out.print(ret6.getId());
        System.out.print("," + ret6.getName());
        System.out.print("," + ret6.getScore());
        System.out.println();
    }

}

啓動Client的main方法開始調用

輸出結果

經過以上demo建立了server端和client端,分別啓動服務方和調用方以後,查看控制檯輸出以下:
服務方:

server start...
invoked rpc service motan
invoked rpc service 110
invoked rpc service 11.2
invoked rpc service hello,motan,
invoked rpc service key3:[val1,val2,val3,val4,],key2:[val1,val2,val3,],key1:[val1,val2,],
invoked rpc service com.hjc.motan.DemoBean@2cf3e1fd,1001,motan bean,99.998

調用方:

hello motan
110
11.2
hello,motan,
key3:[val1,val2,val3,val4,],key2:[val1,val2,val3,],key1:[val1,val2,],
1001,motan bean,99.998

集羣調用示例

實現集羣使用只須要在上面的基礎作一點稍稍的改變就能夠,motan的集羣與阿里的dubbo的原理相似,經過註冊方、服務方、調用方三方來實現,三者關係圖以下:
集羣關係圖

  1. Server 向 Registry 註冊服務,並向註冊中心發送心跳彙報狀態。
  2. Client 須要向註冊中心訂閱 RPC 服務,Client 根據 Registry 返回的服務列表,對具體的 Sever 進行 RPC 調用。
  3. 當 Server 發生變動時,Registry 會同步變動,Client 感知後會對本地的服務列表做相應調整。

目前按照wiki說明,motan支持Consul和Zookeeper兩種外部服務發現組件
如下咱們再上面實現的基礎之上進行更改(兩種組件分別有介紹)

添加pom依賴

在最上面,其實已經寫出來了

<!-- consul -->
<dependency>
    <groupId>com.weibo</groupId>
    <artifactId>motan-registry-consul</artifactId>
    <version>0.0.1</version>
</dependency>
<!-- zookeeper -->
<dependency>
    <groupId>com.weibo</groupId>
    <artifactId>motan-registry-zookeeper</artifactId>
    <version>0.0.1</version>
</dependency>

在server和client的配置文件中分別增長registry定義

consul

<motan:registry regProtocol="consul" name="my_consul" address="127.0.0.1:8500"/>

zookeeper單節點

<motan:registry regProtocol="zookeeper" name="my_zookeeper" address="127.0.0.1:2181"/>

zookeeper多節點集羣

<motan:registry regProtocol="zookeeper" name="my_zookeeper" address="127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183"/>

在motan client及server配置改成經過registry服務發現

consul client

<motan:referer id="remoteService" interface="quickstart.FooService" registry="my_consul"/>

consul server

<motan:service interface="quickstart.FooService" ref="serviceImpl" registry="my_consul" export="8002" />

zookeeper client

<motan:referer id="remoteService" interface="quickstart.FooService" registry="my_zookeeper"/>

zookeeper server

<motan:service interface="quickstart.FooService" ref="serviceImpl" registry="my_zookeeper" export="8002" />

調用方調用服務

consul須要顯示調用心跳開關注冊到consul(zookeeper不須要)

MotanSwitcherUtil.setSwitcher(ConsulConstants.NAMING_PROCESS_HEARTBEAT_SWITCHER, true)

總結

我也是正好最近項目空閒,恰好又看到這麼一則新聞,因而就動手瞭解了下,固然,我所作的只是淺層次的使用,至於更深層次的內容(如,跟dubbo等其餘rpc框架的對比,集羣上下線對zookeeper的影響等)還沒來得及去研究,不過既然它曾經支撐過千億的調用,那必定是通過實際運營檢驗的,做爲搞技術的,也應該多去了解了解開源的東西,這裏我想說一句,開源真好!
另外,以上demo代碼我也傳到了個人github上,歡迎交流學習:
https://github.com/hjcenry/motan-demo


個人我的博客開通啦,每一篇文章都在簡書跟我的博客同步,地址是:http://hjcenry.github.io

相關文章
相關標籤/搜索