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-