微博開源框架Motan初體驗


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

什么是Motan

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

微博的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日志信息。

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

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

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

簡單調用示例

參照github中wiki,可以快速的跑一跑motan,提前感受一下,由於Motan剛開源,很多東西還不完整,我個人在這中間也遇到很多坑,后面一一介紹。我按照wiki介紹,先創建maven項目motandemo,

添加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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM