Dubbo的多协议支持及负载、容错、分组聚合(二)

 

多协议支持 

  这篇内容是接着上篇文章内容来的,前面讲过dubbo多注册中心的支持,这篇就讲下多协议支持 除了Dubbo服务暴露协议Dubbo协议外,Dubbo框架还支持另外8种服务暴露协议:RMI协议、Hessian协议、HTTP协议、WebService协议、Thrift协议、Memcached协议、Redis协议、Rest协议。但在实际生产中,使用最多的就是Dubbo服务暴露协议

 多协议支持网址:

(1) dubbo协议

小数据大并发使用Dubbo,大数据小并发使用其它

  • Dubbo默认传输协议
  • 连接个数:单连接
  • 连接方式:长连接
  • 传输协议:TCP
  • 传输方式:NIO 异步传输
  •  适用范围:传入传出参数数据包较小(建议小于100K),消费者比提供者个数多,单一消费者无法压满提供者,尽量不要用 dubbo 协议传输大文件或超大字符串

(2) rmi协议

采用 JDK 标准的 java.rmi.* 实现

  • 连接个数:多连接
  • 连接方式:短连接
  • 传输协议:TCP
  • 传输方式:BIO同步传输
  • 适用范围:传入传出参数数据包大小混合,消费者与提供者个数差不多,可传文件

(3) hession协议

  • 连接个数:多连接
  • 连接方式:短连接
  • 传输协议:HTTP
  • 传输方式:BIO同步传输
  • 适用范围:传入传出参数数据包较大,提供者比消费者个数多,提供者抗压能力较大,可传文件

(4) http协议

  • 连接个数:多连接
  • 连接方式:短连接
  • 传输协议:HTTP
  • 传输方式:BIO同步传输
  • 适用范围:传入传出参数数据包大小混合,提供者比消费者个数多,可用浏览器查看,可用表单或URL传入参数,暂不支持传文件

(5) webService协议

  • 连接个数:多连接
  • 连接方式:短连接
  • 传输协议:HTTP
  • 传输方式:BIO同步传输
  • 适用场景:系统集成,跨语言调用

(6) thrift协议

Thrift 是 Facebook 捐给 Apache 的一个 RPC 框架,其消息传递采用的协议即为thrift协议。当前 dubbo 支持的 thrift 协议是对 thrift 原生协议的扩展。Thrift协议不支持null值的传递

(7) memcached协议与redis协议

它们都是高效的KV缓存服务器。它们会对传输的数据使用相应的技术进行缓存

(8) rest协议

若需要开发具有RESTful风格的服务,则需要使用该协议

 

rest协议

现在开发选择rest风格的越来越多,下面我们就关于rest协议进行讲解,

添加REST支持 

添加jar包依赖

<!--resteasy-->
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-jaxrs</artifactId>
            <version>3.13.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-client</artifactId>
            <version>3.13.0.Final</version>
        </dependency>
        <!--jetty-->
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-server</artifactId>
            <version>9.4.19.v20190610</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-servlet</artifactId>
            <version>9.4.19.v20190610</version>
        </dependency>

 修改配置信息

# Netty ->
dubbo.protocols.dubbo.name=dubbo
dubbo.protocols.dubbo.port=-1

# jetty (配置了rest协议)
dubbo.protocols.rest.name=rest
dubbo.protocols.rest.port=-1
dubbo.protocols.rest.server=jetty

  

修改api的接口定义(针对rest风格修改)

@Path("/")
public interface GhyServer {
    @GET
    @Path("/say")
    String ghyServer(String str);
}

 

在spring-boot-server的GhyServiceImpl1中添加protocol表示支持多协议,下面配置后就代表可以支持dobbo和rest协议来访问接口了

 

上面步骤做完后我们就可以发布我们的项目可以发现我们可以通过两个协议访问

 

 

dubbo的负载均衡

dubbo负载均衡是天然集成的,是不用配置的,我们只用启动两个spring-boot-server实例,然后通过客户端进行访问就可以发现两个服务端都能收到请求,针对如何选择哪种负载均衡,我们只用在客户端注解上配置一个参数

 

 

 

 

 

 

 

Dubbo的算法有:

Random(默认)

它的算法思想很简单。假设我们有一组服务器 servers = [A, B, C],他们对应的权重为 weights = [5, 3, 2],权重总和为10。现在把这些权重值平铺在一维坐标值上,[0, 5) 区间属于服务器 A,[5, 8) 区间属于 服务器 B,[8, 10) 区间属于服务器 C。接下来通过随机数生成器生成一个范围在 [0, 10) 之间的随机数, 然后计算这个随机数会落到哪个区间上。比如数字3会落到服务器 A 对应的区间上,此时返回服务器 A 即可。权重越大的机器,在坐标轴上对应的区间范围就越大,因此随机数生成器生成的数字就会有更大 的概率落到此区间内。只要随机数生成器产生的随机数分布性很好,在经过多次选择后,每个服务器被 选中的次数比例接近其权重比例

 随机负载均衡源码

有权重:在权重和的范围内生成一个随机数,遍历invoker,用权重和循环减去invoker的权重,结果小于0时的invoker被选中

 

下面是随机负载均衡的源码,为了方便阅读和理解,我把无关部分都去掉了。

public class RandomLoadBalance extends AbstractLoadBalance {

    private final Random random = new Random();

    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        int length = invokers.size();      // Invoker 总数
        int totalWeight = 0;               // 所有 Invoker 的权重的和

        // 判断是不是所有的 Invoker 的权重都是一样的
        // 如果权重都一样,就简单了。直接用Random生成索引就可以了。
        boolean sameWeight = true;
        for (int i = 0; i < length; i++) {
            int weight = getWeight(invokers.get(i), invocation);
            totalWeight += weight; // Sum
            if (sameWeight && i > 0 && weight != getWeight(invokers.get(i - 1), invocation)) {
                sameWeight = false;
            }
        }

        if (totalWeight > 0 && !sameWeight) {
            // 如果不是所有的 Invoker 权重都相同,那么基于权重来随机选择。权重越大的,被选中的概率越大
            int offset = random.nextInt(totalWeight);
            for (int i = 0; i < length; i++) {
                offset -= getWeight(invokers.get(i), invocation);
                if (offset < 0) {
                    return invokers.get(i);
                }
            }
        }
        // 如果所有 Invoker 权重相同
        return invokers.get(random.nextInt(length));
    }
}

  

roundrobin (轮询)

所谓轮询是指将请求轮流分配给每台服务器。举个例子,我们有三台服务器 A、B、C。我们将第一个请 求分配给服务器 A,第二个请求分配给服务器 B,第三个请求分配给服务器 C,第四个请求再次分配给 服务器 A。这个过程就叫做轮询。轮询是一种无状态负载均衡算法,实现简单,适用于每台服务器性能 相近的场景下。但现实情况下,我们并不能保证每台服务器性能均相近。如果我们将等量的请求分配给 性能较差的服务器,这显然是不合理的。因此,这个时候我们需要对轮询过程进行加权,以调控每台服 务器的负载。经过加权后,每台服务器能够得到的请求数比例,接近或等于他们的权重比。比如服务器 A、B、C 权重比为 5:2:1。那么在8次请求中,服务器 A 将收到其中的5次请求,服务器 B 会收到其中的 2次请求,服务器 C 则收到其中的1次请求

 

一致性hash 负载

概述:

如果采用常用的hash(object)%N算法,那么在有机器添加或者删除后,映射关系就变了,很多原有的缓存就无法找到了

一致性hash:添加删除机器前后映射关系一致,当然,不是严格一致。实现的关键是环形Hash空间。将数据和机器都hash到环上,数据映射到顺时针离自己最近的机器中。

 一致性hash单调性体现在: 
无论是新增主机还是删除主机,被影响的都是离那台主机最近的那些节点,其他节点映射关系没有影响

 

使用一致性 Hash 算法,让相同参数的请求总是发到同一 Provider。 当某一台 Provider 崩溃时,原本发往该 Provider 的请求,基于虚拟节点,平摊到其它 Provider,不会引起剧烈变动。 

缺省只对第一个参数Hash,如果要修改,请配置:

<dubbo:parameter key="hash.arguments" value="0,1" />

缺省用160份虚拟节点,如果要修改,请配置:

<dubbo:parameter key="hash.nodes" value="320" />

优点:一致性Hash算法可以和缓存机制配合起来使用。比如有一个服务getUserInfo(String userId)。设置了Hash算法后,相同的userId的调用,都会发送到同一个 Provider。这个 Provider 上可以把用户数据在内存中进行缓存,减少访问数据库或分布式缓存的次数。如果业务上允许这部分数据有一段时间的不一致,可以考虑这种做法。减少对数据库,缓存等中间件的依赖和访问次数,同时减少了网络IO操作,提高系统性能。

 

最小活跃度

官方解释:最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差,使慢的机器收到更少。

这个解释好像说的不是太明白。目的是让更慢的机器收到更少的请求,但具体怎么实现的还是不太清楚。举个例子:每个服务维护一个活跃数计数器。当A机器开始处理请求,该计数器加1,此时A还未处理完成。若处理完毕则计数器减1。而B机器接受到请求后很快处理完毕。那么A,B的活跃数分别是1,0。当又产生了一个新的请求,则选择B机器去执行(B活跃数最小),这样使慢的机器A收到少的请求。

处理一个新的请求时,Consumer 会检查所有 Provider 的活跃数,如果具有最小活跃数的 Invoker 只有一个,直接返回该 Invoker:

if (leastCount == 1) {
    // 如果只有一个最小则直接返回
    return invokers.get(leastIndexs[0]);
}

如果最小活跃数的 Invoker 有多个,且权重不相等同时总权重大于0,这时随机生成一个权重,范围在 (0,totalWeight) 间内。最后根据随机生成的权重,来选择 Invoker。

if (! sameWeight && totalWeight > 0) {
    // 如果权重不相同且权重大于0则按总权重数随机
    int offsetWeight = random.nextInt(totalWeight);
    // 并确定随机值落在哪个片断上
    for (int i = 0; i < leastCount; i++) {
        int leastIndex = leastIndexs[i];
        offsetWeight -= getWeight(invokers.get(leastIndex), invocation);
        if (offsetWeight <= 0)
            return invokers.get(leastIndex);
    }
}

负载均衡配置   

如果不指定负载均衡,默认使用随机负载均衡。我们也可以根据自己的需要,显式指定一个负载均衡。 可以在多个地方类来配置负载均衡,比如 Provider 端,Consumer端,服务级别,方法级别等。

服务端服务级别

<dubbo:service interface="..." loadbalance="roundrobin" />

该服务的所有方法都使用roundrobin负载均衡。

客户端服务级别

<dubbo:reference interface="..." loadbalance="roundrobin" />

该服务的所有方法都使用roundrobin负载均衡。

服务端方法级别

<dubbo:service interface="...">
    <dubbo:method name="hello" loadbalance="roundrobin"/>
</dubbo:service>

只有该服务的hello方法使用roundrobin负载均衡。

客户端方法级别

<dubbo:reference interface="...">
    <dubbo:method name="hello" loadbalance="roundrobin"/>
</dubbo:reference>

只有该服务的hello方法使用roundrobin负载均衡。

和Dubbo其他的配置类似,多个配置是有覆盖关系的:

  1. 方法级优先,接口级次之,全局配置再次之。
  2. 如果级别一样,则消费方优先,提供方次之。

所以,上面4种配置的优先级是:

  1. 客户端方法级别配置。
  2. 客户端接口级别配置。
  3. 服务端方法级别配置。
  4. 服务端接口级别配置。

扩展负载均衡

Dubbo的4种负载均衡的实现,大多数情况下能满足要求。有时候,因为业务的需要,我们可能需要实现自己的负载均衡策略。

  1. 实现LoadBalance接口
 

集群容错

   

   Failover cluster  (默认)    

  • 失败自动切换,当出现失败,重试其它负载服务器。(缺省)
  • 通常用于读操作,但重试会带来更长延迟。
  • 可通过retries="2"来设置重试次数(不含第一次)。
  • @DubboService(cluster="failover",retires=2)(在服务端配置)

   failfast cluster      

  • 快速失败,只发起一次调用,失败立即报错。
  • 通常用于非幂等性的写操作,比如新增记录。

    failsafe cluster        

  • 失败安全,出现异常时,直接忽略。
  • 通常用于写入审计日志等操作。

    failback cluster

  • 失败自动恢复,后台记录失败请求,定时重发。
  • 通常用于消息通知操作。

    forking cluster   

  • 并行调用多个服务器,只要一个成功即返回。
  • 通常用于实时性要求较高的读操作,但需要浪费更多服务资源。
  • 可通过forks="2"来设置最大并行数。

   broadcast cluster

  • 广播调用所有提供者,逐个调用,任意一台报错则报错。(2.1.0开始支持)
  • 通常用于通知所有提供者更新缓存或日志等本地资源信息。

 

  Dubbo泛化

  @DubboService(cluster="failover",retires=2)
   在前面的演示案例中,我们每次去发布一个服务,必然会先定义一个接口,并且把这个接口放在一个 spring-boot-api的jar包中,给到服务调用方来使用。本质上,对于开发者来说仍然是面向接口编程,而且对于使用 者来说,可以不需要关心甚至不需 要知道这个接口到底是怎么触发调用的。而泛化调用就是说服务消费者和服务提供者之间并没有这样的公共服务接口
    下面我们来讲解下不依靠Sping-boot-api进行服务的发布与发现,我们在服务端定义一个接口

 

 

 

 

 

 然后在客户端spring-boot-user写一个调用类

 

 

 启动服务进行调用我们会发现直接调通了

   服务降级

  dubbo的降级也挺简单的,我们在spring-boot-user中建一个FallController类和FallService类

 

 

 

 

然后在调用端的控制层加几个配置

 

 

 

 

 

然后启动服务访问就可以验证

 

 

服务分组

使用服务分组区分服务接口的不同实现当一个接口有多种实现时,可以用 group 区分。
服务端配置:

 

 

 

 

 

 

 

消费者配置:

分组聚合

通过分组对结果进行聚合并返回聚合后的结果,通过分组对结果进行聚合并返回聚合后的结果,比如菜单服务,用group区分同一接口的多种实现,现在消费方需从每种group中调用一次并返回结果,对结果进行合并之后返回,这样就可以实现聚合菜单项。生产者还是和上面一样,主要是消费者有变化

SPI文件配置
在resources下创建META-INF文件夹并在其下面创建dubbo文件夹,然后在dubbo文件夹下面创建org.apache.dubbo.rpc.cluster.Merger文件,在该文件下写好Merger的实现类,如: 
string=com.ghy.spring.dubbo.springbootuser.StringMerger
public class StringMerger implements Merger<String> {
    @Override
    public String merge(String... items) {
        if(items.length == 0) {
            return null;
        }
        StringBuilder builder = new StringBuilder();
        for (String item : items) {
            if(item != null) {
                builder.append(item).append("-");
            }
        }
        return builder.toString();
    }
}

 

 Dubbo常见的配置

    1 . 启动时检查 

## 服务启动的时候,如果注册中心有问题,那么服务就启动失败
dubbo.registries.provider1.check=true

  

    2. Dubbo使用之主机绑定

       在发布一个Dubbo服务的时候,会生成一个dubbo://ip:port的协议地址:

     

2018-11-19 17:51:11,737 INFO [com.alibaba.dubbo.config.AbstractConfig] -  [DUBBO] Export dubbo service dongguabai.dubbo.version.ICall to url dubbo://172.30.57.63:20880/dongguabai.dubbo.version.ICall?anyhost=true&application=dubbo-server&dubbo=2.5.3&interface=dongguabai.dubbo.version.ICall&methods=call&owner=dongguabai&pid=13916&revision=1.0.0&side=provider&timestamp=1542621071730&version=1.0.0, dubbo version: 2.5.3, current host: 127.0.0.1

  

那么这个IP是根据什么生成的呢?可以看看com.alibaba.dubbo.config.ServiceConfig#doExportUrlsFor1Protocol,在生成绑定的主机的时候,会通过一层一层的判断,直到获取到合法的IP地址。:

首先会从配置文件中获取:

 

 

    如果是非法的,会从本地网卡中获取:

 

 

 如果还是非法的,会获取注册中心的地址(可以配置多个注册中心)。通过Socket去连接注册中心

 

 如果最后还拿不到,会调用这个方法:

 

 

 

git源码:https://github.com/ljx958720/spring-boot-dubbo.git

 

posted @ 2021-02-08 17:59  童话述说我的结局  阅读(262)  评论(0编辑  收藏  举报