Dubbo框架原理剖析
单一应用架构
当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。
垂直应用架构
当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。
分布式服务架构
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。
流动计算架构
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。
简单的说,dubbo就是个服务框架,如果没有分布式的需求,其实是不需要用的,只有在分布式的时候,才有dubbo这样的分布式服务框架的需求,并且本质上还是服务调用,远程服务调用的分布式框架。
远程通讯: 提供对多种基于长连接的NIO框架抽象封装,包括多种线程模型,序列化,以及“请求-响应”模式的信息交换方式。
RPC的亮点在于将远程调用的细节隐藏起来,使得调用远程服务就像调用本地服务一样简单,而实现的方式就是代理
集群容错:提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。
自动发现:基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。
节点 | 角色说明 |
---|---|
Provider |
暴露服务的服务提供方 |
Consumer |
调用远程服务的服务消费方 |
Registry |
服务注册与发现的注册中心 |
Monitor |
统计服务的调用次数和调用时间的监控中心 |
Container |
服务运行容器 |
dubbo-provider.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="demo-provider" />
<dubbo:registry address="zookeeper://127.0.0.1:2181" register="false"/>
<dubbo:protocol name="dubbo" port="20880" />
<bean id="demoService" class="cn.demo.dubbo.demo.provider.DemoServiceImpl" />
<dubbo:service interface="cn.demo.dubbo.demo.api.DemoService" ref="demoService" />
beans>
dubbo-consumer.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="demo-consumer" />
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:reference id="demoService" interface="cn.demo.dubbo.demo.api.DemoService" check="false"/>
beans>
spring.handlers文件中
http://code.alibabatech.com/schema/dubbo=
com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
DefaultBeanDefinitionDocumentReader
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}else {
delegate.parseCustomElement(ele);
}
}
}
}else {
delegate.parseCustomElement(root);
}
}
DubboNamespaceHandler
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}
public void init() {
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
}
}
ServiceBean继承了ServiceConfig,在ServiceConfig方法里有段代码, 找到所有的注册中心,再遍历所有的协议注册
private void doExportUrls() {
List<URL> registryURLs = loadRegistries(true);
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&check=false&dubbo=2.0.1&pid=10772®istry=zookeeper×tamp=1546226393659
registry://127.0.0.1:6379/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&check=false&dubbo=2.0.1&pid=10772®istry=redis×tamp=1546226408125
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
1、所有的服务都封装成Invoker模型,在服务提供端,以及服务引用端均会出现 Invoker。Dubbo 官方文档中对 Invoker 进行了说明,这里引用一下。
Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
public interface Node {
URL getUrl();
boolean isAvailable();
void destroy();
}
public interface Invoker<T> extends Node {
Class<T> getInterface();
/**
* invoke.
* @param invocation
* @return result
* @throws RpcException
*/
Result invoke(Invocation invocation) throws RpcException;
}
2、RegistryProtocol暴露服务,开启Netty链接,保持对20881端口的通信,
同时向zk上注册临时节点,所谓的服务注册,本质上是将服务配置数据写入到 Zookeeper 的某个路径的节点下。
节点名:
/dubbo/cn.demo.dubbo.demo.api.DemoService/providers/dubbo://192.168.2.150:20881/cn.demo.dubbo.demo.api.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.1&generic=false&interface=cn.demo.dubbo.demo.api.DemoService&methods=sayHello,login&pid=10128&revision=0.0.1-SNAPSHOT&side=provider×tamp=1546236231866
服务引用是服务的消费方向注册中心订阅服务提供方提供的服务地址后向服务提供方引用服务的过程。
<dubbo:reference id="demoService" interface="cn.demo.dubbo.demo.api.DemoService" check="false"/>
spring在容器启动的时候会解析自定义的schema元素
竟然实现了FactoryBean接口,
为什么要实现FactoryBean,因为我们是要引用远程的服务,而并不是要ReferenceBean本身这个对象,get()方法里面肯定封装了返回接口的实际代理
ReferenceConfig.java
private T createProxy(Map<String, String> map) {
....
// create service proxy
return (T) proxyFactory.getProxy(invoker);
}
JavassistProxyFactory.java javassist生成代理的工厂类
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
// 生成 Proxy 子类(Proxy 是抽象类)。并调用 Proxy 子类的 newInstance 方法创建 Proxy 实例
return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
InvokerInvocationHandler.java 拦截方法
public class InvokerInvocationHandler implements InvocationHandler {
private final Invoker<?> invoker;
public InvokerInvocationHandler(Invoker<?> handler) {
this.invoker = handler;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
if (method.getDeclaringClass() == Object.class) {
return method.invoke(invoker, args);
}
.....
return invoker.invoke(new RpcInvocation(method, args)).recreate();
}
}
最终调用DubboInvoker.java中doInvoke方法与提供方通信并等待返回执行结果
@Override
protected Result doInvoke(final Invocation invocation) throws Throwable {
RpcInvocation inv = (RpcInvocation) invocation;
final String methodName = RpcUtils.getMethodName(invocation);
inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());
inv.setAttachment(Constants.VERSION_KEY, version);
ExchangeClient currentClient;
if (clients.length == 1) {
currentClient = clients[0];
} else {
currentClient = clients[index.getAndIncrement() % clients.length];
}
try {
boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
if (isOneway) {
boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
currentClient.send(inv, isSent);
RpcContext.getContext().setFuture(null);
return new RpcResult();
} else if (isAsync) {
ResponseFuture future = currentClient.request(inv, timeout);
RpcContext.getContext().setFuture(new FutureAdapter<Object>(future));
return new RpcResult();
} else {
RpcContext.getContext().setFuture(null);
return (Result) currentClient.request(inv, timeout).get();
}
} catch (TimeoutException e) {
throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
} catch (RemotingException e) {
throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
注册中心承担着服务的注册与发现,服务的提供者将服务发布到注册中心,服务的使用者到注册中引用服务。
配置样例如下
<dubbo:registry address="zookeeper://10.20.153.10:2181" register="true" />
<dubbo:registry address="multicast://224.5.6.7:1234" />
<dubbo:registry address="redis://10.20.153.10:6379" />
简单介绍一下zk注册中心
Zookeeper 是 Apacahe Hadoop 的子项目,是一个树型的目录服务,支持变更推送,适合作为 Dubbo 服务的注册中心,工业强度较高,可用于生产环境,并推荐使用 。
流程说明:
/dubbo/com.foo.BarService/providers
目录下写入自己的 URL 地址/dubbo/com.foo.BarService/providers
目录下的提供者 URL 地址。并向 /dubbo/com.foo.BarService/consumers
目录下写入自己的 URL 地址/dubbo/com.foo.BarService
目录下的所有提供者和消费者 URL 地址。支持以下功能:
时,记录失败注册和订阅请求,后台定时重试
设置 zookeeper 登录信息
设置 zookeeper 的根节点,不设置将使用无根树*
号通配符
,可订阅服务的所有分组和所有版本的提供者作为一个分布式的框架,同一个服务肯定存在于多个提供方,当一个服务调用失败之后,就涉及到容错
在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。
将Directory中的多个Invoker伪装成一个Invoker, 对上层透明,包含集群的容错机制
Cluster 接口定义
public interface Cluster {
<T> Invoker<T> join(Directory<T> directory) throws RpcException;
}
Failover Cluster
失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries="2"
来设置重试次数(不含第一次)。
重试次数配置如下:
<dubbo:reference retries="2" />
AvailableCluster
获取可用的调用。遍历所有Invokers判断Invoker.isAvalible,只要一个有为true直接调用返回,不管成不成功
Failfast Cluster
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
Failsafe Cluster
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
Failback Cluster
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
Forking Cluster
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2"
来设置最大并行数。
Broadcast Cluster
广播调用所有提供者,逐个调用,任意一台报错则报错 。通常用于通知所有提供者更新缓存或日志等本地资源信息。
负载均衡负责从多个 Invokers中选出具体的一个Invoker用于本次调用,调用过程中包含了负载均衡的算法,调用失败后需要重新选择
LoadBalance接口定义
public interface LoadBalance {
<T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
}
在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random
随机调用。
Random LoadBalance
RoundRobin LoadBalance
LeastActive LoadBalance
ConsistentHash LoadBalance
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.admin.root.password=123456
dubbo.admin.guest.password=123456
这里需要指定注册中心地址, 因为对于服务的治理从获取相关信息这里需要指定注册中心地址, 因为对于服务的治理从获取相关信息如提供者、消费路由信息权重等
管理控制台演示地址 http://192.168.2.150:8090/dubbo-admin/
1、提供方和调用方接口依赖严重,大型工程很难协同
2、需要解决分布式事务
服务接口尽可能大粒度,每个服务方法应代表一个功能,而不是某功能的一个步骤,否则将面临分布式事务问题,Dubbo 暂未提供分布式事务支持。
3、支持的功能多,但也有很多鸡肋,比如支持的协议有:dubbo,rmi,http,webservice,redis,memcache,rest
4、太专注于服务治理,如果我们需要使用配置中心、分布式跟踪这些内容都需要自己去集成,这样无形中增加了使用 Dubbo 的难度,也减少了选择。
5、只支持JAVA
6、、、大家一起想想
1、背景 http://dubbo.apache.org/zh-cn/index.html
2、社区活跃度
3、功能支持
4、学习成本,吃透难度
5、springcloud来搭建微服务体系对研发成员的要求更高
6、、、、