首先我们要清楚为什么要服务治理,服务治理是什么
服务治理演进过程
还有限流、链路分析等
如果说dubbo只完成远程调用的话,还算不上是一个合格的SOA服务架构,它之所以那么碉堡,是因为它还提供了服务治理的功能,我们来研究一下关于服务治理,dubbo都做了什么
听起来服务治理挺高大上的,了解了dubbo的做法后,你会发现一切并没有想的那么复杂。远程调用要解决的最本质问题是通信,通信就好像人和人之间的互动,有效的沟通建立在双方彼此了解的基础上,这基础就是dubbo的URL。
前几篇中大量提到dubbo分层之间依靠什么纽带工作的:Invoker,而客户端和服务端之间的纽带就是URL
依靠URL,dubbo不仅打通了通信两端,而且还依靠URL完成了服务治理的任务,我们先看一些以下内容:
其实dubbo的集群、目录和路由是在服务引用(客户端)中透明完成的,dubbo官方提供了一张清晰的关于dubbo服务治理的图:
这张图是站在服务消费方的视角来看的(dubbo的服务治理都是针对服务消费方),当业务逻辑中需要调用一个服务时,你真正调用的是dubbo创建的一个proxy,该proxy会把调用转化成指定的Invoker(Cluster封装过的),而在这一系列的委托调用的过程里就完成了服务治理的逻辑。
当相同服务由多个提供方同时提供时,消费方就需要有个选择的步骤,就好比你去电商平台买本书,你会先选择去哪个电商平台。同样,消费方根据需要会选择到底使用哪个提供方的服务,而Cluser的主要作用就是从容错的维度来帮我们选择合适的服务提供方。
在服务引用这篇文章中有这样一个调用过程RegistryProtocol.refer()->doRefer(),其中有行代码:
cluster.join(directory);
---------------------------------------------------
Cluster接口如下:
@SPI(FailfastCluster.NAME)
public interface Cluster {
/**
* Merge the directory invokers to a virtual invoker.
*
* @param
* @param directory
* @return cluster invoker
* @throws RpcException
*/
@Adaptive
Invoker join(Directory directory) throws RpcException;
}
/**
* 快速失败,只发起一次调用,失败立即报错,通常用于非幂等性的写操作。
*
* Fail-fast
*
* @author william.liangf
*/
public class FailfastCluster implements Cluster {
public final static String NAME = "failfast";
public Invoker join(Directory directory) throws RpcException {
return new FailfastClusterInvoker(directory);
}
}
可以看到Cluster封装了存有多个Invoker的Directory对象,默认仅仅是创建了一个FailfastCluster,具体的调用在AbstractClusterInvoker.invoke(Invocation)方法中处理,各种集群容错算法就交给大家自己阅读源码来消化了
如果说Cluster帮我们以容错的维度来完成选择,那么路由和配置是在更细颗粒度的层面做的选择,如下多图:
总之很细吧,这么多配置参数最终交给谁来管理呢?
我们需要从Directory接口出发,你应该想到了该接口的一个实现类:
没错,就是这个RegistryDirectory,它在服务引用时被创建,用于充当url与多个Invoker的代理(或者叫目录类),从源码可以看出,当服务引用时,对应该服务的目录类实例会负责向注册中心(zookeeper)订阅该服务,第一次订阅会拿到所有服务提供方的信息(注册中心会调用NotifyListener的notify函数返回服务列表),包括:地址、配置、路由等,然后该目录实例会根据这些信息来为后续的服务调用提供支撑。
根据以上描述,我们看RegistryDirectory.notify():
......
// configurators 更新缓存的服务提供方动态配置规则
if (configuratorUrls != null && configuratorUrls.size() >0 ){
this.configurators = toConfigurators(configuratorUrls);
}
// routers 更新缓存的路由配置规则
if (routerUrls != null && routerUrls.size() >0 ){
List<Router> routers = toRouters(routerUrls);
if(routers != null){ // null - do nothing
setRouters(routers);
}
}
......
这些配置在什么时候发挥作用呢?我们来看FailoverClusterInvoker.invoke():
public Result invoke(final Invocation invocation) throws RpcException {
checkWheatherDestoried();
LoadBalance loadbalance;
//这里就是路由,配置等发挥作用地方,返回所有合法的invoker供集群做下一步的筛选
List> invokers = list(invocation);
if (invokers != null && invokers.size() > 0) {
loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
.getMethodParameter(invocation.getMethodName(),Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
} else {
loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE);
}
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
return doInvoke(invocation, invokers, loadbalance);
}
这段代码中,还有一个很重要的概念:负载均衡Loadbalance
到了负载均衡坏境,维度就成了性能,接口如下:
@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {
/**
* select one invoker in list.
*
* @param invokers invokers.
* @param url refer url
* @param invocation invocation.
* @return selected invoker.
*/
@Adaptive("loadbalance")
Invoker select(List> invokers, URL url, Invocation invocation) throws RpcException;
}
具体细节感兴趣的读者可以去研究一下