新浪于今年开源了其内部的轻量级RPC框架Motan,支持千亿级调用。Motan偏重于简洁实用的服务治理功能和优秀的RPC协议扩展能力,既可以提供高效的RPC远程调用,又能提供服务发现、服务高可用(High Available)、负载均衡、服务监控、管理等服务治理功能。
开源 https://github.com/weibocom/motan ,源码及相应的介绍文档都能在git上找到,有兴趣的可以下下来看看。
本篇主要分析下其在服务治理方面的实现。
而对于rpc中 常用 的网络通信,通信协议,序列化方式等不做详细的介绍。重点在于其提供服务治理 ,能够自动完成服务的发现与注册,且提供 HA(fastfail),提供多种LoadBalance策略配置。
这些都可以通过配置文件指定,通过自定义的 6个 sping标签(registry、protocol、service、baseService、referer、baseReferer), 注册中心(zookeeper,cousul) registry 实现服务自动化的核心, 协议配置管理(如HA策略,balance,filter等) protocol。还有服务端的service,baseService;客户端的配置 referer,baseRefer。
接下来 从 motan两个最为核心的操作 入手, 对应服务治理中的体现。 服务端的 服务导出 与 客户端服务的获取referer。在分析过程中会贴出部分核心源码。
Server export service
两种方式配置服务导出
标签配置
<!-- 业务具体实现类 -->
<bean id="motanDemoServiceImpl" class="com.weibo.motan.demo.server.MotanDemoServiceImpl"/>
<!-- 注册中心配置 使用不同注册中心需要依赖对应的jar包。如果不使用注册中心,可以把check属性改为false,忽略注册失败。-->
<!--<motan:registry regProtocol="local" name="registry" />-->
<!--<motan:registry regProtocol="consul" name="registry" address="127.0.0.1:8500"/>-->
<motan:registry regProtocol="zookeeper" name="registry" address="127.0.0.1:2181" connectTimeout="2000"/>
<!-- 协议配置。为防止多个业务配置冲突,推荐使用id表示具体协议。-->
<motan:protocol id="demoMotan" default="true" name="motan"
maxServerConnection="80000" maxContentLength="1048576"
maxWorkerThread="800" minWorkerThread="20"/>
<!-- 通用配置,多个rpc服务使用相同的基础配置. group和module定义具体的服务池。export格式为“protocol id:提供服务的端口”-->
<motan:basicService export="demoMotan:8002"
group="motan-demo-rpc" accessLog="false" shareChannel="true" module="motan-demo-rpc"
application="myMotanDemo" registry="registry" id="serviceBasicConfig"/>
<!-- 具体rpc服务配置,声明实现的接口类。-->
<motan:service interface="com.weibo.motan.demo.service.MotanDemoService"
ref="motanDemoServiceImpl" export="demoMotan:8001" basicService="serviceBasicConfig">
</motan:service>
<motan:service interface="com.weibo.motan.demo.service.MotanDemoService"
ref="motanDemoServiceImpl" export="demoMotan:8002" basicService="serviceBasicConfig">
</motan:service>
ServiceConfig 代码设置
ServiceConfig<MotanDemoService> motanDemoService = new ServiceConfig<MotanDemoService>();
//设置接口及实现类
motanDemoService.setInterface(MotanDemoService.class);
motanDemoService.setRef(new MotanDemoServiceImpl());
// 配置服务的group以及版本号
motanDemoService.setGroup("motan-demo-rpc");
motanDemoService.setVersion("1.0");
// 配置注册中心
RegistryConfig registry = new RegistryConfig();
registry.setRegProtocol("local");
registry.setCheck("false"); //不检查是否注册成功
motanDemoService.setRegistry(registry);
// 配置RPC协议
ProtocolConfig protocol = new ProtocolConfig();
protocol.setId("motan");
protocol.setName("motan");
motanDemoService.setProtocol(protocol);
motanDemoService.setExport("motan:8002");
motanDemoService.export();//服务导出
MotanSwitcherUtil.setSwitcherValue(MotanConstants.REGISTRY_HEARTBEAT_SWITCHER, true);
两种方式背后执行的逻辑是一致的,配置完成后,需要将服务导出。
1)先检测配置,而后从配置从得到 registry 的urls,如zookeeper可以有多个注册中心。
2) 根据协议端口,导出服务 。一个服务可以设置通过不同协议设置不同端口提供service。
3) 执行某一协议的导出。组装参数到map,而后封装到URL 为serviceURl
ConfigHandler configHandler = ExtensionLoader.getExtensionLoader(ConfigHandler.class).getExtension(MotanConstants.DEFAULT_VALUE);
//ref为 接口interfaceClass的实现,
exporters.add(configHandler.export(interfaceClass, ref, urls));//usrls为注册中心的多个url,封装了serviceURL的pram
4) 导出服务
ExtensionLoader 根据接口名,找到配置文件,加载对应接口的实现类,而后根据 spiMeta的value找到所配置的实例,如protocol 配置的motan,则对应的protocol实例为 DefaultRpcProtocol。而configHandler为 SimpleConfigHandler。
// SimpleConfigHandler
@Override
public <T> Exporter<T> export(Class<T> interfaceClass, T ref, List<URL> registryUrls) {
String serviceStr = StringTools.urlDecode(registryUrls.get(0).getParameter(URLParamType.embed.getName()));
URL serviceUrl = URL.valueOf(serviceStr);
// export service
// 利用protocol decorator来增加filter特性
String protocolName = serviceUrl.getParameter(URLParamType.protocol.getName(), URLParamType.protocol.getValue());
Protocol protocol = new ProtocolFilterDecorator(ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(protocolName));
Provider<T> provider = new DefaultProvider<T>(ref, serviceUrl, interfaceClass);//服务的实际提供者,构造方法会初始化interfaceClass中的Method
Exporter<T> exporter = protocol.export(provider, serviceUrl);
// register service
register(registryUrls, serviceUrl);
return exporter;
}
执行protocol的 export , 从代码中看到,对DefaultRpcProtocol进行了封装,ProtocolFilterDecorator,在执行其封装的protocol前,对默认的provicder进行装饰,默认的provider 实现中,封装了接口的实现 类,因此其核心完成了该服务的实现,通过request 找到Method ,与服务实例ref,最终通过反射执行rpc调用。
@Override
public <T> Exporter<T> export(Provider<T> provider, URL url) {
return protocol.export(decorateWithFilter(provider, url), url);
}
decorateWithFilter就是根据url 装饰provider,得到一个provider的链, 其最为核心的就是 对call方法进行了处理,执行了fiter,而在filter中调用下一个provider的call方法,这样一来在真正业务的call方法执行前要执行设定的filter,而filter 可以做一些日志,接口并发处理数限制等逻辑。(装饰模式)且通过匿名内部类生成装饰类 装饰所扩展的功能就是Filter 链实现 的功能。
for (Filter filter : filters) {
final Filter f = filter;
final Provider<T> lp = lastProvider; //第一个lastProvider就为DefalutRPCProvider
lastProvider = new Provider<T>() {
@Override
public Response call(Request request) {
return f.filter(lp, request);
}
@Override
public String desc() {
return lp.desc();
}
.......
}
得到了一个装饰后 Provider处理链之后 ,导出该Provider。 执行真正protocol的导出,首先生成Exporter,
DefaultRpcProtocol
@Override
protected <T> Exporter<T> createExporter(Provider<T> provider, URL url) {
return new DefaultRpcExporter<T>(provider, url);
}
构造初始化Exporter
public DefaultRpcExporter(Provider<T> provider, URL url) {
super(provider, url);
ProviderMessageRouter requestRouter = initRequestRouter(url);
endpointFactory =
ExtensionLoader.getExtensionLoader(EndpointFactory.class).getExtension(
url.getParameter(URLParamType.endpointFactory.getName(), URLParamType.endpointFactory.getValue()));
server = endpointFactory.createServer(url, requestRouter);
}
第一步 初始化Router,用于找 provider(service),以IPPort为key,从ipPort2RequestRouter(表示一个端口提供路由服务列表)找到此前该端口是否提供了其它的service,如果没有,则new一个Router,有的话,则将provider加入到 一个服务的map中,key为标识该服务的servicekey
group/interface/version 用其表示 。找服务实现同样通过 该key找到provider执行 call方法。
synchronized (ipPort2RequestRouter) {
requestRouter = ipPort2RequestRouter.get(ipPort);
if (requestRouter == null) {
requestRouter = new ProviderProtectedMessageRouter(provider);
ipPort2RequestRouter.put(ipPort, requestRouter);
} else {
requestRouter.addProvider(provider);
}
}
现在我们得到了一个提供多个servcice的requestRouter 。
第二步 createServrer,也就是netty的Server 端。处理 服务与server channel之间的关系。两个map
ipPort2ServerShareChannel IPPort 对应 server
server2UrlsShareChannel Server与 提供服务的set集合
根据参数 决定 是否共享端口,如果该端口之前打开,则会抛出异常。否则生成 Netty4Server(url, messageHandler); 这里的messageHandler就是上面的requestRouter, 封装到server中的 ChannelHandler中。处理请求。 但是如果之前 ipPort 有Channel,也就是之前已有service打开了channel,则只需要在server对应的value set中 增加 本服务的servicekey group/interface/version。
最后一步 初始化 如果之前端口没有打开,初始化 serverBootstrap,绑定端口,启动服务线程。然后以ServiceKey与该exporter加入到exporterMap 。
服务导出后,到 Registry进行服务注册。根据配置的注册中心urls,
private void register(List<URL> registryUrls, URL serviceUrl) {
for (URL url : registryUrls) {
// 根据check参数的设置,register失败可能会抛异常,上层应该知晓
RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getExtension(url.getProtocol());
if (registryFactory == null) {
throw new MotanFrameworkException(new MotanErrorMsg(500, MotanErrorMsgConstant.FRAMEWORK_REGISTER_ERROR_CODE,
"register error! Could not find extension for registry protocol:" + url.getProtocol()
+ ", make sure registry module for " + url.getProtocol() + " is in classpath!"));
}
Registry registry = registryFactory.getRegistry(url);
registry.register(serviceUrl);
}
}
通过每个url 找到registry注册中心,如果没有,则生成并加入缓存。而后注册 serviceURL. 完成service的 发布。client就可以通过 注册中心订阅本服务。
简单要介绍下zookeeper做为配置中心时的逻辑。判断服务的根路径是否存在,不存在则创建一个持久的node,而后在该node下建立一个本服务器所提供的该服务key为ip-port,value为url的值 。这样的一来,就不会影响到其它节点所提供的相同服务。服务的root Node为
toGroupPath(url) + MotanConstants.PATH_SEPARATOR + url.getPath() + ZkNodeType,而不同的server提供的服务为子node,在rootPath的基本上增加 对应的server的IP-Port。这样一来,客户端只需订阅rootPath。
private void createNode(URL url, ZkNodeType nodeType) {
String nodeTypePath = toNodeTypePath(url, nodeType);
if (!zkClient.exists(nodeTypePath)) {
zkClient.createPersistent(nodeTypePath, true);
}
zkClient.createEphemeral(toNodePath(url, nodeType), url.toFullStr());
}
Client ref
客户端 要实现rpc的调用,首先在客户端要生成 服务器端的代理,一般实现是 通过 动态代理 该接口,而后在invoke 方法里 根据 所调方法,参数 等序列化后 发送到server端 去请求。
motan将这这一过程服务化,只需在注册中心订阅服务 ,服务发布后会通知客户端。 用户 调用时 根据设定的策略找到某一个server的配置信息,发送请求,执行将result 返回。
对于motan client,两种方式配置c,
通过spring配置文件,根据定义的sping标签配置referer,
<motan:registry regProtocol="zookeeper" name="registry" address="127.0.0.1:2181" connectTimeout="2000"/>
<!-- motan协议配置 -->
<motan:protocol default="true" name="motan" haStrategy="failover"
loadbalance="roundrobin" maxClientConnection="10" minClientConnection="2"/>
<!-- 通用referer基础配置 -->
<motan:basicReferer requestTimeout="200" accessLog="false"
retries="2" group="motan-demo-rpc" module="motan-demo-rpc"
application="myMotanDemo" protocol="motan" registry="registry"
id="motantestClientBasicConfig" throwException="false" check="true"/>
<!-- 具体referer配置。使用方通过beanid使用服务接口类 -->
<motan:referer id="motanDemoReferer"
interface="com.weibo.motan.demo.service.MotanDemoService"
connectTimeout="300" requestTimeout="300" basicReferer="motantestClientBasicConfig"/>
RefererConfig, 根据方法设置。官网上的示例如下:
RefererConfig<MotanDemoService> motanDemoServiceReferer = new RefererConfig<MotanDemoService>();
// 设置接口及实现类
motanDemoServiceReferer.setInterface(MotanDemoService.class);
// 配置服务的group以及版本号
motanDemoServiceReferer.setGroup("motan-demo-rpc");
motanDemoServiceReferer.setVersion("1.0");
motanDemoServiceReferer.setRequestTimeout(300);
// 配置注册中心
RegistryConfig registry = new RegistryConfig();
registry.setRegProtocol("local");
motanDemoServiceReferer.setRegistry(registry);
// 配置RPC协议
ProtocolConfig protocol = new ProtocolConfig();
protocol.setId("motan");
protocol.setName("motan");
motanDemoServiceReferer.setProtocol(protocol);
motanDemoServiceReferer.setDirectUrl("localhost:8002");
// 使用服务
MotanDemoService service = motanDemoServiceReferer.getRef();
背后是通过ClusterSupport 中ClusterSPI 提供服务,其内部是一组可用的Server在逻辑上的封装,包含若干可以提供RPC服务的Server代理,实际请求时会根据不同的高可用与负载均衡策略选择一个可用的Server发起远程调用。
过程大致如下:
1)new URL,根据配置,定义一个refUrl,根据 ip,port,protocol,path等。
URL refUrl = new URL(protocol.getName(), localIp, MotanConstants.DEFAULT_INT_VALUE, interfaceClass.getName(), params);
ClusterSupport<T> clusterSupport = createClusterSupport(refUrl, configHandler, registryUrls);
2)生成ClusterSpi(设置 HA,LoadBalance)。
private void prepareCluster() {
String clusterName = url.getParameter(URLParamType.cluster.getName(), URLParamType.cluster.getValue());
String loadbalanceName = url.getParameter(URLParamType.loadbalance.getName(), URLParamType.loadbalance.getValue());
String haStrategyName = url.getParameter(URLParamType.haStrategy.getName(), URLParamType.haStrategy.getValue());
cluster = ExtensionLoader.getExtensionLoader(Cluster.class).getExtension(clusterName);
LoadBalance<T> loadBalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(loadbalanceName);
HaStrategy<T> ha = ExtensionLoader.getExtensionLoader(HaStrategy.class).getExtension(haStrategyName);
cluster.setLoadBalance(loadBalance);
cluster.setHaStrategy(ha);
cluster.setUrl(url);
}
3)subscribe url,注册中心 如zookeeper中订阅service。
// client 注册自己,同时订阅service列表
Registry registry = getRegistry(ru);
registry.subscribe(subUrl, this); //this 为clusterSupport
String serverTypePath = toNodeTypePath(url, ZkNodeType.AVAILABLE_SERVER);
List<String> currentChilds = zkClient.subscribeChildChanges(serverTypePath, zkChildListener);//订阅,zhChildListener 定义的节点事件监听者
LoggerUtil.info(String.format("[ZookeeperRegistry] subscribe: path=%s, info=%s", toNodePath(url, ZkNodeType.AVAILABLE_SERVER), url.toFullStr()));
notify(url, notifyListener, nodeChildsToUrls(serverTypePath, currentChilds));
zkChildListener 里的notify方法 里执行的也是 上面的notify方法,通知 ClusterSupport。提供服务的urls
4)更新referer(封装了提供服务的netty Client,通过client发送rpc请求)
referer = protocol.refer(interfaceClass, refererURL, u);
@Override
protected <T> Referer<T> createReferer(Class<T> clz, URL url, URL serviceUrl) {
return new DefaultRpcReferer<T>(clz, url, serviceUrl);
}
生成rpcRefer,其内部封装了通过 endpointFactory 生成 netty client,
public DefaultRpcReferer(Class<T> clz, URL url, URL serviceUrl) {
super(clz, url, serviceUrl);
endpointFactory =ExtensionLoader.getExtensionLoader(EndpointFactory.class).getExtension(
url.getParameter(URLParamType.endpointFactory.getName(), URLParamType.endpointFactory.getValue()));
client = endpointFactory.createClient(url);
}
Referer中 根据url生成netty的客户端。如此refere中就有了netty client。
· 5)更新ClusterSPI中的referes,
public synchronized void onRefresh(List<Referer<T>> referers) {
if (CollectionUtil.isEmpty(referers)) {
return;
}
loadBalance.onRefresh(referers);
List<Referer<T>> oldReferers = this.referers;
this.referers = referers;
haStrategy.setUrl(getUrl());
if (oldReferers == null || oldReferers.isEmpty()) {
return;
}
List<Referer<T>> delayDestroyReferers = new ArrayList<Referer<T>>();
for (Referer<T> referer : oldReferers) {
if (referers.contains(referer)) {
continue;
}
delayDestroyReferers.add(referer);
}
if (!delayDestroyReferers.isEmpty()) {
RefererSupports.delayDestroy(delayDestroyReferers);
}
}
6)在根据protocols 生成ClusterSupport后,代理封装了ClusterSpi
ref = configHandler.refer(interfaceClass, clusters, proxy);
用户得到的service代理,
@Override
public <T> T refer(Class<T> interfaceClass, List<Cluster<T>> clusters, String proxyType) {
ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension(proxyType);
return proxyFactory.getProxy(interfaceClass, new RefererInvocationHandler<T>(interfaceClass, clusters));
}
其中的RefererInvocatioHandler中的 invoke就是核心,代理了service的接口,我们看到 其 底层是通过 clusters 中的referers 来实现 真正的rpc。而 从clusters中 选择一个合适的referers 就是 服务治理,也就是HA,loadBalance 中生效的过程。 而clusters中的referers 是动态更新的,根据 在注册中心注册的服务,通知client。最终达到服务治理。
http://weibo.com/p/1001643875439147097368
http://tech.sina.com.cn/i/2016-05-10/doc-ifxryhhh1869879.shtml
https://github.com/weibocom/motan/blob/master/docs/wiki/zh_configuration.md spring标签配置
https://www.kancloud.cn/kancloud/sina-boot-camp/64006