ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Dubbo漫谈之走向集群

2021-12-06 11:37:44  阅读:175  来源: 互联网

标签:Dubbo 调用 url 漫谈 集群 Invoker 路由


服务集群

一个系统从本地调用转成远程调用,原因有很多种。最主要的两个,一个是功能解耦,另外一个就是高可用。为了解决高可用问题,通常再服务部署的时候,都会部署多套,以防止单个服务压力过大或者网络分区等单点故障导致服务不可用,也就是我们常说的集群。 

路由

当服务提供方部署成集群的时候,问题就来了,原来只有一个机器提供服务的时候,调用方只需要知道ip+port都可以把请求发过去了。现在,提供方变成了一个ip+port的列表,每次调用前都需要选择其中一个,显然这不是用户想要关心的。这时候一个新的路由模块会被加到中间,调用方把请求发给路由模块,路由模块决定这个请求应该发给后端的那个提供方。像下图这样,调用方不需要知道具体请求发给谁了。 0 当然,这个路由模块可以是一个单独的Proxy,也可以是一个jar包,集成到Consumer的程序里面。

服务发现

对于路由模块来说,后端的集群配置是不断变化的,比如节点的上下线,ip和端口的变化等。因为服务节点不会知道路由模块的存在,所以双方要有个公共的地方,服务节点变化时更新数据,路由模块通过通知或者轮询拉取变化。这个模块就是注册中心了,常见的作为注册中心的有Eureka、zookeeper等。有了注册中心,路由模块就有了服务发现的能力,上面的图就变成这样: 0

Dubbo集群解决方案

Dubbo集群的特点

跟现在主流的微服务框架的集群概念稍有不同。Dubbo的集群就是指接口提供者的集合,而不是节点或者机器的集合。当然这不妨碍对它的理解: 0 先来回顾下Dubbo Consumer一侧的模块调用链:   0 上图中,Proxy通过url获取到一个远程调用的Invoker,Dubbo要做的,就是Invoker提供路由功能,通过注册中心来获取真实的url列表。而做为一个开放的rpc框架,显然要能够很容易的集成现有的注册中心。 这里Dubbo的逻辑是,如果url是具体服务节点的url,比如dubbo://127.0.0.1:12345,那就正常走建立连接,发起调用。如果url是注册中心url,Dubbo通过替换成集群的Invoker来发起调用,下面来看下集群部分的接口抽象。

Cluster

上面提到需要将Invoker替换成一个支持集群调用的Invoker,这个Invoker就是从Cluster中获取的。
@SPI(FailoverCluster.NAME)
public interface Cluster {
    /**
     * Merge 从directory获取的invoker列表
     */
    @Adaptive
    <T> Invoker<T> join(Directory<T> directory) throws RpcException;
}
Cluster接口通过将从directory获取的Invoker列表合并成一个集群ClusterInvoker。Directory是Dubbo对服务列表提供者的抽象,显然注册中心就是一种Directory的实现,更准确得说是一种Directory的数据来源,注册中心的部分后面讲。

Cluster实现

Dubbo提供了很多Cluster实现,默认的是FailoverCluster:
public class FailoverCluster extends AbstractCluster {
    public final static String NAME = "failover";
    @Override
    public <T> AbstractClusterInvoker<T> doJoin(Directory<T> directory) throws RpcException {
        return new FailoverClusterInvoker<>(directory);
    }
}
可以看到这里的实现就是创建一个具体的ClusterInvoker,将directory传给它。所有的ClusterInvoker实现都继承了AbstractClusterInvoker。

ClusterInvoker实现

AbstractClusterInvoker实现了Invoker接口,所以对Consumer端的Proxy的是透明的。这个类代码比较多,最主要的就是实现接口的invoke() 方法。
public abstract class AbstractClusterInvoker<T> implements Invoker<T> {
        ...
       //从directory获取invoker列表
        List<Invoker<T>> invokers = list(invocation);
       //初始化负载均衡
        LoadBalance loadbalance = initLoadBalance(invokers, invocation);
        //子类实现调用
        return doInvoke(invocation, invokers, loadbalance);
    }
}
这里面除了从directory获取invoker列表外,最主要的就是初始化LoadBalance,然后发起调用。Dubbo中默认的ClusterInvoker实现就是前面FailoverCluster返回的FailoverClusterInvoker,这个实现包含重试逻辑,即调用一个节点失败的情况下会重试其它的。加了ClusterInvoker之后,调用如下: 0 

负载均衡

当ClusterInvoker从Directory处获取到多个后端服务的Invoker后,选择调用哪个Invoker是有负载均衡策略决定的。就是上面提到的LoadBalance接口。 Dubbo实现了4种策略,分别是加权随机、加权轮询、最小活跃调用、一致性Hash,默认采用加权随机。
@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {

    /**
     * select one invoker in list.
     */
    @Adaptive("loadbalance")
    <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
}
接口比较简单,根据要调用参数信息,从invokers中选择一个。这个方法由ClusterInvoker负责调用。 

注册中心

Dubbo为了对接不同的注册中心,抽象出了Registry接口,定义如下:
public interface Registry extends Node, RegistryService {
}
这个接口只是简单组合了Node和RegistryService,组合Node的原因是Dubbo配置中将注册中心也是用url的形式配置的,比如zookeeper://127.0.0.1:2081。主要的接口定义都在RegistryService中:
public interface RegistryService {
    /**
     *  注册
     */
    void register(URL url);
    /**
     * 注销
     */
    void unregister(URL url);
    /**
     * 订阅
     */
    void subscribe(URL url, NotifyListener listener);
    /**
     * 取消订阅
     */
    void unsubscribe(URL url, NotifyListener listener);
    /**
     * 主动查询
     */
    List<URL> lookup(URL url);
}
注册中心包含两类接口,注册和注销主要是给服务提供方使用,订阅和取消订阅主要是给服务消费方使用。Dubbo已经默认实现了主流注册中心的对接,比如zookeeper、eureka等。

RegistryDirectory

上面讲到的Cluster接口,当需要一个ClusterInvoker的时候,需要提供一个Directory参数。RegistryDirectory相当于Directory的注册中心实现,它包含了一个注册中心,当注册中心的数据发生变化后,刷新自身的Invoker缓存。
public class RegistryDirectory<T> extends AbstractDirectory<T> implements NotifyListener {
    ...
    // 初始化时注入
    private Registry registry;
    ...
}

路由策略

服务路由功能中,除了可以通过负载均衡策略来干涉具体调用的服务之外,通常需要一些更加个性化的设置。比如,部分新上线的功能只想让符合一定条件的用户使用,就可以设置根据用户id或者标签来决定请求发送到后端那个提供方。 Dubbo中针对路由策略的接口是Router:
public interface Router extends Comparable<Router> {
    int DEFAULT_PRIORITY = Integer.MAX_VALUE;
    /**
     * Get the router url.
     */
    URL getUrl();
    /**
     * 对于传入的invokers做Rute规则匹配,返回匹配上的invoker列表
     */
    <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
    /**
     * 接收invoker list变化通知
     */
    default <T> void notify(List<Invoker<T>> invokers) {
    }
    /**
     * Router 的优先级
     */
    int getPriority();
    ... 
}
接口中最主要的就是route() 方法,在Cluster从Directory获取到Invoker列表后,首先查询可用的Route规则,并逐个匹配,只有符合条件的invoker才会交给LoadBalance再选择。 Dubbo默认提供两种实现,一种是基于条件表达式的路由规则设置,一种式基于脚本的路由规则设置。 

总结

上面分别从集群支持、负载均衡、注册中心、路由策略等方面分解了Dubbo对集群调用的支持,下面还是通过一张图来看下各个模块的关系。   0 当服务变成一个集群之后,情况复杂了很多,要让用户无感知的调用集群,需要将集群调用做抽象,并对接注册中心。这也是Dubbo在Proxy之后又抽象出Invoker的原因。针对集群调用,内部实现了不同的容错策略,同时围绕Invoker,Dubbo还扩容了其它功能。

标签:Dubbo,调用,url,漫谈,集群,Invoker,路由
来源: https://www.cnblogs.com/johnvwan/p/15648905.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有