Dubbo学习记录(二) ----dubbo的常见应用

Dubbo的基础与高级应用

  1. 负载均衡、集群容错、服务降级
  2. 本地存根、本地伪装、异步调用

负载均衡

Dubbo一共支持四种策略:

轮询

  • 轮询,按公约后的权重设置轮询比率。
  • 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
  • 如果A, B, C 服务的权重相同, 则会ABCABC的顺序调用下去;
  • 如果A= 1/2, B=1/4, C=1/4服务的权重不相同,则AABCAABC的循序调用下去;

随机

  • 随机,按权重设置随机概率。
  • 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
  • A, B, C的权重相同, 则A,B, C的调用率 平均为1/3,调用顺序是无序的;
  • A= 1/2, B=1/4, C=1/4服务的权重不相同, 则A的调用率为1/2, BC为1/4, 调用顺序时无序的

一致性哈希

  • 一致性 Hash,相同参数的请求总是发到同一提供者。
  • 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。

注意时时相同参数: 使用一致性哈希,调用相同接口时, 传相同参数(获取ID = 1的记录)请求, 则会一致把请求发给同一个服务器;

最少活跃数调用

  • 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。

  • 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。

  • A,B, C服务器同时都在处理1个请求, 那么活跃数都是1, 一段时间后, 只有A的请求处理完, 活跃数-1, 活跃数为0; 那么新的请求发送过来,让A去处理,A的活跃数最小,为0, BC为1;

  • 逻辑

  1. 消费者会缓存所调⽤服务的所有提供者,⽐如记为p1、p2、p3三个服务提供者,每个提供者内都有⼀ 个属性记为active,默认位0
  2. 消费者在调⽤次服务时,如果负载均衡策略是leastactive
  3. 消费者端会判断缓存的所有服务提供者的active,选择最⼩的,如果都相同,则随机
  4. 选出某⼀个服务提供者后,假设位p2,Dubbo就会对p2.active+1
  5. 然后真正发出请求调⽤该服务
  6. 消费端收到响应结果后,对p2.active-1
  7. 这样就完成了对某个服务提供者当前活跃调⽤数进⾏了统计,并且并不影响服务调⽤的性能

使用

@Reference注解的loadbalance属性配置(全是小写), 也可以通过管理台配置;

    @Reference(version = "default", loadbalance = "consistenthash")
    private DemoService demoService;
问题1: 为什么负载统计放在了客户端Consumer,而不是服务提供方Provider?

原因: 无法统计/难以统计;一台机器无法知道另一台机器的活跃数, 可以通过HTTP请求到其他服务器去拿活跃数, 这明显不显示, 如果集群1000台, 那么就需要发送一千次HTTP请求, 耗费时间直接以秒为单位, 不可行;

问题2:为什么负载统计放在了客户端Consumer,而不是注册中心?

原因: 类似问题1, 你每次处理请求,都需要告诉注册中心, 活跃数+1, 处理完请求,活跃数-1, 相当于两次HTTP请求,耗费时间。还有一个原因, 每台机器处理请求都得和注册中心进行交互, 哪怕是集群注册中心, 也扛不住这么大的并发量;

服务超时

在服务提供者和服务消费者上都可以配置服务超时时间,这两者是不一样的。

消费者调用一个服务,分为三步:

  1. 消费者发送请求(网络传输)
  2. 服务端执行服务
  3. 服务端返回响应(网络传输)

如果在服务端和消费端只在其中一方配置了timeout,那么没有歧义,

  • 配置在消费方表示消费端调用服务的超时时间,消费端如果超过时间还没有收到响应结果,则消费端会抛超时异常
  • 配置在服务端,服务端不会抛异常,服务端在执行服务后,会检查执行该服务的时间,如果超过timeout,则会打印一个超时日志。服务会正常的执行完。

如果在服务端和消费端各配了一个timeout,那就比较复杂了,假设

  1. 服务执行为5s
  2. 消费端timeout=3s
  3. 服务端timeout=6s

那么消费端调用服务时,消费端会收到超时异常(因为消费端超时了),服务端一切正常执行完, 打印超时日志(服务端没有超时)。

超时配置

  1. 客户端配置
    @Reference注解的timeout 属性配置, 也可以通过管理台配置;
    @Reference(version = "timeout", timeout = 3000)
    private DemoService demoService;

  1. 管理台设置
    通过@Service注解的timeout属性配置,也可以通过管理台配置;
@Service(version = "timeout", timeout = 4000)
public class TimeoutDemoService implements DemoService {

    @Override
    public String sayHello(String name) {
        System.out.println("执行了timeout服务" + name);

        // 服务执行5秒
        // 服务超时时间为3秒,但是执行了5秒,服务端会把任务执行完的
        // 服务的超时时间,是指如果服务执行时间超过了指定的超时时间则会抛一个warn
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("执行结束" + name);

        URL url = RpcContext.getContext().getUrl();
        return String.format("%s:%s, Hello, %s", url.getProtocol(), url.getPort(), name);  // 正常访问
    }

}

集群容错

集群调用失败时,Dubbo 提供的容错方案;
服务消费者在调用某个服务时,这个服务有多个服务提供者,在经过负载均衡后选出其中一个服务提供者之后进行调用,但调用报错后,Dubbo所采取的后续处理策略。
在这里插入图片描述

failover 重试

默认情况下是failover 重试, 失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries=“2” 来设置重试次数(不含第一次)。 默认是2次;

  1. 服务端设置
@Service(cluster = "failover" , retries = 2)
public class DemoServiceImpl implements DemoInterface {
    @Override
    public User getUser() {
        return new User("zqh");
    }
}
  1. 消费端设置
     @Reference(cluster = "failover" ,retries = 2)
    private DemoInterface demoService;

Failfast Cluster快速失败

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

  1. 消费端设置:
 @Reference(cluster = "failfast")
  1. 服务端设置
 @Service(cluster = "failfast")

Failsafe Cluster失败安全

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

  1. 消费端设置:
 @Reference(cluster = "failsafe ")
  1. 服务端设置
 @Service(cluster = "failsafe ")

Failback Cluster失败自动恢复

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

  1. 消费端设置:
 @Reference(cluster = "failback ")
  1. 服务端设置
 @Service(cluster = "failback ")

Forking Cluster并行调用

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

  1. 消费端设置:
 @Reference(cluster = "forking")
  1. 服务端设置
 @Service(cluster = "forking")

Broadcast Cluster广播逐个调用

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

  • 通过 broadcast.fail.percent 配置节点调用失败的比例,当达到这个比例后,BroadcastClusterInvoker 将不再调用其他节点,直接抛出异常。

  • broadcast.fail.percent 取值在 0~100 范围内。默认情况下当全部调用失败后,才会抛出异常。

  • broadcast.fail.percent 只是控制的当失败后是否继续调用其他节点,并不改变结果(任意一台报错则报错)。

  • broadcast.fail.percent 参数 在 dubbo2.7.10 及以上版本生效。

  • broadcast.fail.percent=20 代表了当 20% 的节点调用失败就抛出异常,不再调用其他节点。

 @Reference(cluster = "broadcast")
  1. 服务端设置
 @Service(cluster = "broadcast")

集群容错成熟度

在这里插入图片描述

服务降级

服务消费者在调用某个服务提供者时,如果该服务提供者报错了,所采取的措施。

集群容错和服务降级的区别在于:

  • 集群容错是整个集群范围内的容错
  • 服务降级是单个服务消费者/ 单个服务提供者的自身容错

使用

  1. 消费端
@Reference(mock = "fail:return+null")
  1. 服务端
@Service(mock = "fail:return+null")

都表示当服务调用失败后, 返回为null;

本地伪装

就是mock, 本质和服务降级一样;
使用场景:一些未开发完成的功能需要mock一些数据, 就是假数据返回;

  1. 服务提供者
@Service(mock = "force:return+null")

表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。

本地存根

远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑;
比如:做 ThreadLocal 缓存,提前验证参数,调用失败后伪造容错数据等等,此时就需要在 API 中带上 Stub,客户端生成 Proxy 实例,会把 Proxy 通过构造函数传给 Stub 1,然后把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy。
在这里插入图片描述

使用

package com.dubbo.consumer;

import com.tuling.DemoService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;

import java.io.IOException;

@EnableAutoConfiguration
public class StubDubboConsumerDemo {


//    @Reference(version = "timeout", timeout = 1000, stub = "com.tuling.DemoServiceStub")
    @Reference(version = "timeout", timeout = 1000, stub = "true")
    private DemoService demoService;

    public static void main(String[] args) throws IOException {
        ConfigurableApplicationContext context = SpringApplication.run(StubDubboConsumerDemo.class);

        DemoService demoService = context.getBean(DemoService.class);

        System.out.println((demoService.sayHello("周瑜")));


    }

}

  1. @Reference(version = “timeout”, timeout = 1000, stub = “true”)
    Dubbo根据包名,类名+Stub方式生成类路径去找类信息, 即com.dubbo.DemoServiceStub, 招不到报错;需要提供一个构造函数,构造参数为接口类类型,用来传入真正的远程代理对象;最终通过这个对象再去调用服务;
  2. @Reference(version = “timeout”, timeout = 1000, stub = “com.tuling.DemoServiceStub”)
package com.dubbo;

public class DemoServiceStub implements DemoService {

    private final DemoService demoService;

    // 构造函数传入真正的远程代理对象
    public DemoServiceStub(DemoService demoService){
        this.demoService = demoService;
    }

    @Override
    public String sayHello(String name) {
        // 此代码在客户端执行, 你可以在客户端做ThreadLocal本地缓存,或预先验证参数是否合法,等等
        try {
            return demoService.sayHello(name); // safe  null
        } catch (Exception e) {
            // 你可以容错,可以做任何AOP拦截事项
            return "容错数据";
        }
    }
}

异步调用

从 2.7.0 开始,Dubbo 的所有异步编程接口开始以 CompletableFuture 为基础

基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小。在这里插入图片描述
接口方法:

public interface AsyncService {
    CompletableFuture<String> sayHello(String name);
}

消费者:

@EnableAutoConfiguration
public class AsyncDubboConsumerDemo {

    @Reference(version = "async")
    private DemoService demoService;

    public static void main(String[] args) throws IOException {
        ConfigurableApplicationContext context = SpringApplication.run(AsyncDubboConsumerDemo.class);

        DemoService demoService = context.getBean(DemoService.class);

        // 调用直接返回CompletableFuture
        CompletableFuture<String> future = demoService.sayHelloAsync("异步调用");  // 5

        future.whenComplete((v, t) -> {
            if (t != null) {
                t.printStackTrace();
            } else {
                System.out.println("Response: " + v);
            }
        });

        System.out.println("结束了");
    }
}

提供者:


@Service(version = "async")
public class AsyncDemoService implements DemoService {

    @Override
    public String sayHello(String name) {
        System.out.println("执行了同步服务" + name);
        URL url = RpcContext.getContext().getUrl();
        return String.format("%s:%s, Hello, %s", url.getProtocol(), url.getPort(), name);  // 正常访问
    }

    @Override
    public CompletableFuture<String> sayHelloAsync(String name) {
        System.out.println("执行了异步服务" + name);

        return CompletableFuture.supplyAsync(() -> {
            return sayHello(name);
        });
    }
}

Java8中新推出的异步编程, 会异步编程, 看这个就很简单。

Dubbo中的REST

官网Rest说明
注意Dubbo的REST也是Dubbo所支持的一种协议。

当我们用Dubbo提供了一个服务后,如果消费者没有使用Dubbo也想调用服务,那么这个时候我们就可以让我们的服务支持REST协议,这样消费者就可以通过REST形式调用我们的服务了。

注意:如果某个服务只有REST协议可用,那么该服务必须用@Path注解定义访问路径

使用

@Service
@Path("demo")
public class RestDemoService implements DemoService {

    @GET
    @Path("say")
    @Produces({ContentType.APPLICATION_JSON_UTF_8, ContentType.TEXT_XML_UTF_8})
    @Override
    public String sayHello(@QueryParam("name") String name) {
        System.out.println("执行了rest服务" + name);

        URL url = RpcContext.getContext().getUrl();
        return String.format("%s: %s, Hello, %s", url.getProtocol(), url.getPort(), name);  // 正常访问
    }

}

类似SpringMVC,学到这的人 不会SpringMVC的(我无语凝咽)

其他

  1. 动态配置
    官网地址:http://dubbo.apache.org/zh/docs/v2.7/user/examples/config-rule/
    通过管理台去修改服务的配置, 进而不用重启服务器;
  2. 服务路由
    官网地址:http://dubbo.apache.org/zh/docs/v2.7/user/examples/routing-rule/
    作用类同网关的作用(一般是架构师整的)
  3. 管理台
    github地址:https://github.com/apache/dubbo-admin

小结

Dubbo应用很多, 官网起码几十种了,学不完的,学下重点的东西。

  • 10
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值