Motan是新浪於2016年開源的一個RPC框架,類似的已有的RPC框架有像阿里開源的Dubbo,還有後來在此基礎上做二次開源的當當網的dubbox。
Motan分爲服務提供方和服務調用方,服務提供方發佈服務,服務調用方調用服務。
看這樣一個簡單的例子:
1、創建一個接口,並且簡單實現:
public interface FooService {
public String hello(String name);
}
public class FooServiceImpl implements FooService {
public String hello(String name) {
System.out.println("Invoke RPC service method, "+name);
return "Hello, "+name;
}
}
2、配置xm
<?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">
<bean id="serviceImpl" class="quickstart.FooServiceImpl" />
<motan:registry address="127.0.0.1:2181" regProtocol="zookeeper" name="myzk"></motan:registry>
<motan:service interface="quickstart.FooService" ref="serviceImpl" registry="myzk" group="myMotanService1" export="8080"/>
</beans>
xml的配置也很簡單易懂:
先定義一個id爲serviceImpl的類,也就是我們的服務具體實現,然後將服務聲明爲zookeeper協議,並且註冊到本地2181端口上,最後將服務實現發佈成服務,暴露的端口爲8080.
3、啓動本地zk,端口默認爲2181.運行服務器程序:
public class ServerZK {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:motanServer.xml");
MotanSwitcherUtil.setSwitcherValue(MotanConstants.REGISTRY_HEARTBEAT_SWITCHER,true);
System.out.println("Server start...");
}
}
運行輸出信息:Server start…
服務發佈成功了嗎?可以使用zkClient查看目錄:
/motan/myMotanService1/quickstart.FooService/server
發現有可用服務:
[192.168.0.6:8080]
運行main方法之後,這發生了什麼?
觀察我們的xml配置,有兩點值得說明:
1、該xml使用了名爲【motan】的Namespace
2、bean和registry的配置都被使用在了service配置中
對於第一點,由於是和Spring結合使用,所以motan命名空間肯定是會有相關聯的NamespaceHandler進行解析的,也一定可以在motan的jar包中找到對應的xsd約束(在motan-core.jar包中就可以找到)。
根據一個不成文的約定(很多命名空間都是這樣的,像Spring自帶的aop命名空間,解析的Handler就是AOPNamespaceHandler),查找MotanNamespaceHandler類。
這個類負責來解析xml到Motan所需要使用到的類,具體過程很繁瑣而無味。
service被解析成了ServiceConfigBean這個類,查看這個類,發現它的方法簽名:
public class ServiceConfigBean<T> extends ServiceConfig<T>
implements
BeanPostProcessor,
BeanFactoryAware,
InitializingBean,
DisposableBean,
ApplicationListener<ContextRefreshedEvent>
實現了Spring暴露的擴展接口。
最後一個是ApplicationListener,這個類使得我們的ServiceConfigBean類監聽了Spring的ContextRefreshEvent,在ApplicationContext加載時(此處是ClasspathXmlApplicationContext)的finishRefresh方法觸發監聽器,調用ServiceConfigBean的onApplicationEvent方法。
可以看到,這個方法調用了export方法,也就是發佈服務的方法。
========分割線==========
實現InitializingBean的類,需要實現方法:
public void afterPropertiesSet() throws Exception {
// 注意:basicConfig需要首先配置,因爲其他可能會依賴於basicConfig的配置
checkAndConfigBasicConfig();
checkAndConfigExport();
checkAndConfigRegistry();
// 等spring初始化完畢後,再export服務
// export();
}
三個方法調用,分別加載了basicService;
checkAndConfigExport檢查是否有export配置(如果沒有,則用basicService中配置的export配置);
checkAndConfigRegistry保證發佈的服務能有合適的registry,如果service配置了就使用該配置,如果沒有,則取basicService中配置的,如果basicService中沒有配置則從MotanNamespaceHandler.registryDefineNames中去加載(以防沒有成功生成對象),如果以上的操作都沒有獲取到,則使用默認的local配置。
講了這麼一堆,現在纔剛剛開始進入本節的主題,終於要開始發佈服務了。
public void onApplicationEvent(ContextRefreshedEvent event) {
if (!getExported().get()) {
export();
}
}
每個ServiceConfigBean都有一個exported布爾值,表示該Service是不是已經被髮布過。如果發佈過,就不再發布。
接下來進入發佈的方法,export():
public synchronized void export() {
//在同步塊中再次檢查是不是已經被髮布過了
//此處的exported變量是J.C.U包中的原子變量,可以保證在多個線程之間及時可見
if (exported.get()) {
LoggerUtil.warn(String.format("%s has already been expoted, so ignore the export request!", interfaceClass.getName()));
return;
}
//① 檢查 方法是不是在接口中存在
checkInterfaceAndMethods(interfaceClass, methods);
//② 加載註冊地址列表
List<URL> registryUrls = loadRegistryUrls();
if (registryUrls == null || registryUrls.size() == 0) {
throw new IllegalStateException("Should set registry config for service:" + interfaceClass.getName());
}
//③ 生成協議和端口的鍵值對
Map<String, Integer> protocolPorts = getProtocolAndPort();
for (ProtocolConfig protocolConfig : protocols) {
Integer port = protocolPorts.get(protocolConfig.getId());
if (port == null) {
throw new MotanServiceException(String.format("Unknow port in service:%s, protocol:%s", interfaceClass.getName(),
protocolConfig.getId()));
}
// ④ 在註冊的地址上,按協議和端口 進行服務發佈
doExport(protocolConfig, port, registryUrls);
}
//⑤ 發佈之後的一些操作
afterExport();
}
幾個主要的步驟都被抽成方法,嗯,所以這個方法看上去還是挺簡潔易懂的。
1、檢查方法是不是在接口中存在
由於moton發佈service的時候可以指定的方法,所以需要在export之前,確認指定的接口是不是存在該方法。
2、生成註冊地址列表
protected List<URL> loadRegistryUrls() {
List<URL> registryList = new ArrayList<URL>();
if (registries != null && !registries.isEmpty()) {
//遍歷registry配置信息
for (RegistryConfig config : registries) {
String address = config.getAddress();
if (StringUtils.isBlank(address)) {
address = NetUtils.LOCALHOST + ":" + MotanConstants.DEFAULT_INT_VALUE;
}
Map<String, String> map = new HashMap<String, String>();
//將registry的各個屬性,以鍵值對的形式存儲在map中
config.appendConfigParams(map);
map.put(URLParamType.application.getName(), getApplication());
map.put(URLParamType.path.getName(), RegistryService.class.getName());
map.put(URLParamType.refreshTimestamp.getName(), String.valueOf(System.currentTimeMillis()));
// 設置默認的registry protocol,parse完protocol後,需要去掉該參數
if (!map.containsKey(URLParamType.protocol.getName())) {
if (address.contains("://")) {
map.put(URLParamType.protocol.getName(), address.substring(0, address.indexOf("://")));
}
map.put(URLParamType.protocol.getName(), MotanConstants.REGISTRY_PROTOCOL_LOCAL);
}
// address內部可能包含多個註冊中心地址
//將地址和參數構造成一個motan服務地址,類似於下面的格式:
// zookeeper://127.0.0.1:2181/com.weibo.api.motan.registry.RegistryService?group=default_rpc
List<URL> urls = UrlUtils.parseURLs(address, map);
if (urls != null && !urls.isEmpty()) {
for (URL url : urls) {
url.removeParameter(URLParamType.protocol.getName());
registryList.add(url);
}
}
}
}
return registryList;
}
3、生成協議和端口的鍵值對
一個service可以按多個protocol提供服務,不同protocol使用不同port 利用export來設置protocol和port,格式如下:
protocol1:port1,protocol2:port2
4、發佈服務
Motan使用Netty做爲底層的服務發佈框架。
對於發佈服務來說,doExport方法重點在下面的兩句:
ConfigHandler configHandler = ExtensionLoader.getExtensionLoader(ConfigHandler.class).getExtension(MotanConstants.DEFAULT_VALUE);
exporters.add(configHandler.export(interfaceClass, ref, urls));
ConfigHandler是一個很重要的對象,連接了配置和協議。
實現爲SimpleConfigHandler,它主要提供如下幾個方面的功能:
1、提供服務集羣模塊,可以增加過濾器
2、將service關聯到具體的Protocol對象,後者是提供Netty服務的對象
3、提供註冊和取消註冊的功能
4、提供服務的註銷功能
這個具體以後還會再提到。
configHandler的export方法將返回Export對象,該例子中使用的的”DefaultRpcExporter”實現;
Exporter是Motan中一個很重要的對象,它通過SPI配置創建EndpointFactory對象,並通過createServer方法暴露服務。
這部分的內容,之後還會詳細看。
-EOF-