前言,本文包括,rpc解释与为什么使用rpc、rpc性能对比、Motan依赖问题、Motan源码梳理、Motan功能、特点、使用。
主要中心:为什么使用Motan?
官方解释:RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络IO从其他节点程序上请求服务,而不需要了解底层网络技术的协议。RPC协议通过传输协议,如TCP或UDP,为通信程序之间携带信息数据。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
我眼中的RPC:本地接口使用代理通过网络IO在远程节点进行加强的调用过程。
2、并发下的平均响应。Motan>Dubbo
3、请求最长响应时间。Motan>Dubbo
总结:Thrift、Grpc虽然支持JavaServer且性能较优,但实现语言不是Java语言,这为故障排查梳理带来了难度,且对应的序列化方式中都需要按照其自己的代码结构定义传输的数据结构,这种方式虽然带来了跨语言优势但是降低了易用性。
而Java语言实现的Motan、Dubbo框架。从图中可以发现,单从远程调用这一个方面讲,Motan略优于Dubbo.
1)Motan依赖m:otan 注册中心 consul时依赖motan-registry-consul-->
com.weibo
motan-core
1.1.6
com.weibo
motan-registry-zookeeper
1.1.6
com.weibo
motan-springsupport
1.1.6
com.weibo
motan-transport-netty4
1.1.6
Vertx3.5.4 依赖的Netty版本为4.1.19.Final
Motan1.1.6中 motan-transport-Netty4模块依赖的版本为4.1.16.Final
motan-transport-Netty模块依赖的版本为3.2.5.Final
motan-transport-Netty与motan-transport-Netty4两个模块同时依赖会产生异常。motan-transport-Netty4是升级版,故依赖此模块,与Vertx3.4.5以上的版本使用时execlusion掉netty-all包。例:
com.weibo
motan-transport-netty4
1.1.6
io.netty
netty-all
2)、调用图如下
ServiceConfig的doExport方法将我们的服务参数,如分组、协议、序列化方式、注册中心等等进行组装成为将要在注册中心存储的URL。若设置了多个注册中心则都会在此时与上面的URL进行处理拼接。在过程中拿到SimpleConfigHandler前在ExtensionLoader处可以进行SPI扩展。SimpleConfigHandler的export方法具体进行在注册中心中注册。注册到注册中心的url如下:
motan2://10.0.4.248:8002/com.cloudwise.service.TestService?maxContentLength=1048576&module=usergroup&nodeType=service&version=1.0&accessLog=false&codec=motan2&protocol=motan2&application=dodp&shareChannel=true&refreshTimestamp=1565619237617&id=protocolConfig2&export=protocolConfig2:8002&group=usergroup-mac&
注意:Protocol、ConfigHandler、RegistryFactory都是ExtensionLoader的getExtension()得到,故若想自定义扩展协议、注册中心、url配置处理应从此处着手。而ProtocolFilterDecorator则装饰了Protocol,留下了可以自定义配置Filter的扩展点。
Provider:具体实现的执行细节。存在Method+Params反射拿到结果再放入response中这样的逻辑。
Exportor:Protocol的export方法返回,构造方法中通过EndpointFactory的createServer方法开启NettyServer。发现Yar协议则为Netty4HttpServer,可知Yar协议本质上还是Netty实现的Http协议。
上图中的transport部分则是私有协议内部的协议编解码和序列化编解码。
RefererConfig的initRef()方法将我们的服务参数,如分组、协议、序列化方式、注册中心等等进行组装成为要访问的url,SimpleConfigHandler的buildClusterSupport获得ClusterSupport,用来注册自己(Client)及根据url从注册中心订阅。
SimpleConfigHandler的refer方法获得ProxyFactory.
默认为CommonProxyFactory其中实例化RefererCommonHandler
此类为远程执行的具体逻辑,其中存在refer时多协议的开关降级,具体的call方法的具体执行由ClusterSpi中的HaStrategy的call方法执行,HaStrategy为容错策略目前由快速失败和失效切换两个实现,
HaStrategy的构造参数需要loadBalance即指定的负载策略,根据策略选择出当前服务的refer。HaStrategy的call方法的具体执行又由不同协议(Protocol)的doCall方法执行如Motan2协议的refer实现为MotanV2Protocol,以此类为例,doCall最终由NettyClient向NettyServer发起了调用。
4)可以多种协议同时发布。既同一个接口多种发布方式。
2、服务治理
1)负载均衡
目前支持的负载策略有:
ActiveWeight(缺省):低并发度优先: referer 的某时刻的 call 数越小优先级越高。
Random:随机选择。
RoundRobin:轮询选择
ConfigurableWeight:权重可配置的负载策略。
Consistent:一致性 Hash,相同参数的请求总是发到同一提供者
LocalFirst:本地优先+ ActiveWeight。
2)容错策略
Failover :失效切换。失效自动切换,重试其他服务器。
Failfast :快速失败。一次调用,失效立即报错。
Motan支持Zookeeper、Consul等注册中心,做到服务注册、服务订阅等。由于Client在调用时是拿到注册中心的对应服务提供节点的IP等信息,再按照设置的负载均衡算法来选择一个节点进行调用。从这个角度实现了服务自动发现。
3、简单易用
1)代码层次简单
Motan
dubbo、
2、Api易用。
这里主要指集成Spring后的Motan注解开发。简单配置即可使用。
1)配置类
Slf4j
@Configuration
public class MotanConfig {
@Bean(name="protocolConfig")
public ProtocolConfigBean protocolConfig1() {
ProtocolConfigBean config = new ProtocolConfigBean();
config.setDefault(true);
config.setName("motan");
config.setMaxContentLength(1048576);
return config;
}
@Bean(name = "protocolConfig2")
public ProtocolConfigBean protocolConfig2() {
ProtocolConfigBean config = new ProtocolConfigBean();
//config.setId("demoMotan2");
config.setName("motan2");
config.setMaxContentLength(1048576);
return config;
}
@Bean(name="registryConfig")
public RegistryConfigBean registryConfig() {
RegistryConfigBean config = new RegistryConfigBean();
config.setRegProtocol("zookeeper");
config.setAddress("127.0.0.1:2181");
//config.setRegProtocol("local");
return config;
}
@Bean
public BasicServiceConfigBean baseServiceConfig() {
BasicServiceConfigBean config = new BasicServiceConfigBean();
config.setExport("protocolConfig2:10086");
config.setGroup("usergroup-mac");
config.setAccessLog(false);
config.setShareChannel(true);
config.setModule("usergroup");
config.setApplication("dodp");
config.setRegistry("registryConfig");
return config;
}
2)注解开发
server:
@MotanService(export = "protocolConfig2:10086")
public class ServerServiceImpl implements ServerService {
client:
@Component
public class ReferServerService {
@MotanReferer
ServerService service;
Client还要有注册中心、协议等配置。因与server相似,不再展示
1)Server
public class MotanApiExportDemo {
public static void main(String[] args) {
// 配置注册中心直连调用
RegistryConfig registry = new RegistryConfig();
//use local registry
//registry.setRegProtocol("local");
// use ZooKeeper registry
registry.setRegProtocol("zookeeper");
registry.setAddress("127.0.0.1:2181");
// 配置RPC协议
ProtocolConfig protocol = new ProtocolConfig();
protocol.setId("restful");
protocol.setName("restful");
protocol.setEndpointFactory("netty");
//protocol.setSerialization("protobuf");
BasicServiceInterfaceConfig basic = new BasicServiceInterfaceConfig();
basic.setVersion("1.0");
basic.setGroup("usergroup-mac");
basic.setApplication("dodp");
basic.setModule("usergroup");
ServiceConfig motanDemoService = new ServiceConfig();
motanDemoService.setBasicService(basic);
motanDemoService.setRegistry(registry);
motanDemoService.setProtocol(protocol);
// 设置接口及实现类
motanDemoService.setInterface(ServerService.class);
motanDemoService.setRef(new ServerServiceImpl());
// 配置服务的group以及版本号
//motanDemoService.setGroup("motan2-demo-rpc");
//motanDemoService.setVersion("1.0");
// registry.setCheck("false"); //是否检查是否注册成功
//motanDemoService.setRegistry(registry);
motanDemoService.setExport("motan2:8002");
// 服务的发布及注册的核心 这里是重点 接下来,深入分析
motanDemoService.export();
MotanSwitcherUtil.setSwitcherValue(MotanConstants.REGISTRY_HEARTBEAT_SWITCHER, true);
System.out.println("server start...");
}
2)Client
public class MotanApiClient {
public static void main(String[] args) {
RefererConfig motanDemoServiceReferer = new RefererConfig();
// 设置接口
motanDemoServiceReferer.setInterface(ServerService.class);
// 配置服务的group以及版本号
motanDemoServiceReferer.setGroup("usergroup-mac");
motanDemoServiceReferer.setVersion("1.0");
motanDemoServiceReferer.setRequestTimeout(1000);
// 配置注册中心直连调用
RegistryConfig registry = new RegistryConfig();
//use direct registry
registry.setRegProtocol("zookeeper");
registry.setAddress("127.0.0.1:2181");
// use ZooKeeper registry
//registry.setRegProtocol("zookeeper");
//registry.setAddress("127.0.0.1:2181");
motanDemoServiceReferer.setRegistry(registry);
// 配置RPC协议
ProtocolConfig protocol = new ProtocolConfig();
protocol.setId("motan2");
protocol.setName("motan2");
// protocol.setSerialization("protobuf");
motanDemoServiceReferer.setProtocol(protocol);
// motanDemoServiceReferer.setDirectUrl("localhost:8002"); // 注册中心直连调用需添加此配置
// 使用服务
ServerService service = motanDemoServiceReferer.getRef();
RequestHead head = new RequestHead("groupName", "version", "serviceName"
, "transferFile", "requestTime");
Map body = new HashMap() {{
put("attr", new HashMap() {{
put("ll", 11);
}});
}};
File file = new File("id", "filePath", "isCompress", "isScrete",
"isConsume", "attrpath");
Message mess = new Message(head, body, new ArrayList() {{
this.add(new UserModel(1, "back"));
this.add(new UserModel(2, "test"));
}}, file);
String s = JSONObject.toJSONString(mess);
System.out.println(s);
RespMessage resp = service.testMessageTrans(mess);
System.out.println(JSONObject.toJSONString(resp));
System.exit(0);
}
最后,对于不使用Spring的Motan JavaAPI的注解简化思路。
1)实现简单IOC用于启动初始化Motan配置信息,如注册中心、协议等。
2)注解扫描,完成service 的export前的设置,公共设置从上面的简单ioc中拿,特色的就要通过注解的值来拿。
3)referer和service思路一样,但考虑到依赖注入的问题,比如使用了Gui ce来做依赖注入,则bean的实例化不能反射拿,应该从Guice中获取,然后设置,但这也意味着被依赖的类要为单例。
进行初步封装后,使用展示:
1、配置类。
package com.cloudwise.config;
import com.cloudwise.annotation.Bean;
import com.cloudwise.annotation.Config;
import com.cloudwise.constant.MotanConstant;
import com.weibo.api.motan.config.ProtocolConfig;
import com.weibo.api.motan.config.RegistryConfig;
import com.weibo.api.motan.config.springsupport.BasicServiceConfigBean;
/**
* @program RpcTest
* @description: motan server config
* @author: back
* @create: 2019/08/05 11:42
*/
@Config
public class MotanConfig {
@Bean("protocolConfig")
public ProtocolConfig protocolConfig1() {
ProtocolConfig config = new ProtocolConfig();
config.setDefault(true);
config.setName("motan");
config.setMaxContentLength(1048576);
return config;
}
@Bean("protocolConfig2")
public ProtocolConfig protocolConfig2() {
ProtocolConfig config = new ProtocolConfig();
config.setId("protocolConfig2");
config.setName("motan2");
config.setMaxContentLength(1048576);
return config;
}
@Bean(MotanConstant.REGISTRY_NAME)
public RegistryConfig registryConfig() {
RegistryConfig config = new RegistryConfig();
config.setRegProtocol("zookeeper");
config.setAddress("127.0.0.1:2181");
//config.setRegProtocol("local");
return config;
}
@Bean(MotanConstant.BASIC_SERVICE_NAME)
public BasicServiceConfigBean baseServiceConfig() {
BasicServiceConfigBean config = new BasicServiceConfigBean();
config.setGroup(MotanConstant.CLIENT_GROUP_NAME);
config.setAccessLog(false);
config.setShareChannel(true);
config.setVersion("1.0");
config.setModule("usergroup");
config.setApplication("dodp");
config.setRegistry(MotanConstant.REGISTRY_NAME);
return config;
}
}
2、server
@MotanService(interfaceClass = TestService.class,protocols="protocolConfig2",export="protocolConfig2:8002")
public class TestServiceImpl implements TestService{
server start:
public class Latch {
public static void main(String[] args) {
MotanServiceAutoConfig.start("com.cloudwise.service");
MotanSwitcherUtil.setSwitcherValue(MotanConstants.REGISTRY_HEARTBEAT_SWITCHER
,true);
log.info("Motan Server start !");
3、Client
@Slf4j
@Singleton
@Service("myServiceImpl")
public class MyServiceImpl implements MyService{
@MotanRefer
TestService testService;
Client start:
@Slf4j
public class ClientLatch {
public static void main(String[] args) {
Injector injector = Guice.createInjector(new MotanModule());
MotanReferAutoConfig.start("com.cloudwise.service",injector);
MyService instance = injector.getInstance(MyServiceImpl.class);
instance.print();
}