05.Dubbo高级特性(三)

124 阅读19分钟

Dubbo高级特性(三)

1.异步调用

介绍

Dubbo异步调用分为Provider端异步调用和Consumer端异步调用。Provider端异步执行将阻塞的业务从Dubbo内部线程池切换到业务自定义线程,避免Dubbo线程池的过度占用,有助于避免不同服务间的互相影响。异步执行无异于节省资源或提升RPC响应性能

注意

Provider端异步执行和Consumer端异步调用是相互独立的,你可以任意正交组合两端配置

使用场景

  • 对于Provider端来说,如果接口比较耗时,避免dubbo线程被阻塞,可以使用异步将线程切换到业务线程
  • 对于Consumer端来说,调用Dubbo接口没有严格时序上的关系、不是原子操作、不影响逻辑情况下可以使用异步调用

provider异步

实现方式
  • 使用CompletableFuture实现
  • 使用AsyncContext实现异步
使用CompletableFuture实现
接口定义
public interface AsyncService {
    /**
     * 同步调用方法
     */
    String invoke(String param);
    /**
     * 异步调用方法
     */
    CompletableFuture<String> asyncInvoke(String param);
}
接口实现
@DubboService
public class AsyncServiceImpl implements AsyncService {

    @Override
    public String invoke(String param) {
        try {
            long time = ThreadLocalRandom.current().nextLong(1000);
            Thread.sleep(time);
            StringBuilder s = new StringBuilder();
            s.append("AsyncService invoke param:").append(param).append(",sleep:").append(time);
            return s.toString();
        }
        catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return null;
    }

    @Override
    public CompletableFuture<String> asyncInvoke(String param) {
        // 建议为supplyAsync提供自定义线程池
        return CompletableFuture.supplyAsync(() -> {
            try {
                // Do something
                long time = ThreadLocalRandom.current().nextLong(1000);
                Thread.sleep(time);
                StringBuilder s = new StringBuilder();
                s.append("AsyncService asyncInvoke param:").append(param).append(",sleep:").append(time);
                return s.toString();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            return null;
        });
    }
}
使用AsyncContext实现异步
介绍

Dubbo提供了一个类似Servlet3.0的异步接口AsyncContext,在没有CompletableFuture签名接口的情况下,也可以实现Provider端的异步执行

接口定义
public interface AsyncService {
    String sayHello(String name);
}
接口实现
public class AsyncServiceImpl implements AsyncService {
    public String sayHello(String name) {
        final AsyncContext asyncContext = RpcContext.startAsync();
        new Thread(() -> {
            // 如果要使用上下文,则必须要放在第一句执行
            asyncContext.signalContextSwitch();
            try {
                // 注意不要休眠的太长,消费者那边没配置超时的话,默认超时时间可能会导致报错
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 写回响应
            asyncContext.write("Hello " + name + ", response from provider.");
        }).start();
        return null;
    }
}
consumer异步
@DubboReference
private AsyncService asyncService;

@Override
public void run(String... args) throws Exception {
    //调用异步接口
    CompletableFuture<String> future1 = asyncService.asyncInvoke("async call request1");
    future1.whenComplete((v, t) -> {
        if (t != null) {
            t.printStackTrace();
        } else {
            System.out.println("AsyncTask Response-1: " + v);
        }
    });
    //两次调用并非顺序返回
    CompletableFuture<String> future2 = asyncService.asyncInvoke("async call request2");
    future2.whenComplete((v, t) -> {
        if (t != null) {
            t.printStackTrace();
        } else {
            System.out.println("AsyncTask Response-2: " + v);
        }
    });
    //consumer异步调用
    CompletableFuture<String> future3 =  CompletableFuture.supplyAsync(() -> {
        return asyncService.invoke("invoke call request3");
    });
    future3.whenComplete((v, t) -> {
        if (t != null) {
            t.printStackTrace();
        } else {
            System.out.println("AsyncTask Response-3: " + v);
        }
    });

    System.out.println("AsyncTask Executed before response return.");
}

2.版本与分组

介绍

Dubbo服务中,接口并不能唯一确定一个服务,只有接口+分组+版本号才能唯一确定一个服务

使用场景

  • 当同一个接口针对不同的业务场景、不同的使用需求或者不同的功能模块等场景,可使用服务分组来区分不同的实现方式。同时,这些不同实现所提供的服务是可并存的,也支持互相调用
  • 当接口实现需要升级又要保留原有实现的情况下,即出现不兼容升级时,我们可以使用不同版本号进行区分

使用

定义接口
public interface DevelopService {
    String invoke(String param);
}
接口1实现
@DubboService(group = "group1",version = "1.0")
public class DevelopProviderServiceV1 implements DevelopService{
    @Override
    public String invoke(String param) {
        StringBuilder s = new StringBuilder();
        s.append("ServiceV1 param:").append(param);
        return s.toString();
    }
}
接口2实现
@DubboService(group = "group2",version = "2.0")
public class DevelopProviderServiceV2 implements DevelopService{
    @Override
    public String invoke(String param) {
        StringBuilder s = new StringBuilder();
        s.append("ServiceV2 param:").append(param);
        return s.toString();
    }
}
观察注册中心

客户端调用
@DubboReference(group = "group1",version = "1.0")
private DevelopService developService;

@DubboReference(group = "group2",version = "2.0")
private DevelopService developServiceV2;

@Override
public void run(String... args) throws Exception {
    //调用DevelopService的group1分组实现
    System.out.println("Dubbo Remote Return ======> " + developService.invoke("1"));
    //调用DevelopService的另一个实现
    System.out.println("Dubbo Remote Return ======> " + developServiceV2.invoke("2"));
}

3.上下文参数传递

介绍

在Dubbo3中,RpcContext被拆分为四大模块(ServerContext、ClientAttachment、ServerAttachment 和 ServiceContext)

它们分别承担了不同的职责:

  • ServiceContext:在Dubbo内部使用,用于传递调用链路上的参数信息,如invoker对象等
  • ClientAttachment:在Client端使用,往ClientAttachment中写入的参数将被传递到Server端
  • ServerAttachment:在Server端使用,从ServerAttachment中读取的参数是从Client中传递过来的
  • ServerContext:在Client端和Server端使用,用于从Server端回传Client端使用,Server端写入到ServerContext的参数在调用结束后可以在Client端的ServerContext获取到

使用场景

  • Dubbo系统间调用时,想传递一些通用参数,可通过Dubbo提供的扩展如Filter等实现统一的参数传递
  • Dubbo系统间调用时,想传递接口定义之外的参数,可在调用接口前使用setAttachment传递参数

使用方式

注意
  1. setAttachment设置的KV对,在完成下面一次远程调用会被清空,即多次远程调用要多次设置
  2. path,group,version,dubbo,token,timeout。几个key是保留字段,请使用其它值
接口定义
public interface ContextService {
    String invoke(String param);
}
实现类
@DubboService
public class ContextServiceImpl implements ContextService{
    @Override
    public String invoke(String param) {
        // ServerAttachment接收客户端传递过来的参数
        Map<String, Object> serverAttachments = RpcContext.getServerAttachment().getObjectAttachments();
        System.out.println("ContextService serverAttachments:" + JSON.toJSONString(serverAttachments));
        //往客户端传递参数
        RpcContext.getServerContext().setAttachment("serverKey","serverValue");
        StringBuilder s = new StringBuilder();
        s.append("ContextService param:").append(param);
        return s.toString();
    }
}
接口调用
    //往服务端传递参数
    RpcContext.getClientAttachment().setAttachment("clientKey1","clientValue1");
    String res = contextService.invoke("context1");
    //接收传递回来参数
    Map<String, Object> clientAttachment = RpcContext.getServerContext().getObjectAttachments();
    System.out.println("ContextTask clientAttachment:" + JSON.toJSONString(clientAttachment));
    System.out.println("ContextService Return : " + res);

历史遗留问题

在之前的版本中,你可能会见到这样的使用方式:

@Activate(group = {CommonConstants.CONSUMER})
public class DubboConsumerFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        
        RpcContext.getContext().setAttachment("demo","demo02");


        return invoker.invoke(invocation);
    }
}

但是在新版本中我们的建议是在Filter里面的尽可能不要操作RpcContext,上面的使用方式会导致不生效。原因在于新版本中,我们在ConsumerContextFilter类中做了ClientAttachment ->Invocation属性的复制,该类是Dubbo内置Filter类,而内置Filter类先于用户定义Filter类执行,所以在自定义Filter类中这样使用不会生效。 可以直接使用这种方式进行传递:

@Activate(group = {CommonConstants.CONSUMER})
public class DubboConsumerFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        
        invocation.setAttachment("demo","demo02");

        return invoker.invoke(invocation);
    }
}

4.泛化调用

介绍

泛化调用(客户端泛化调用)是指在调用方没有服务方提供的 API(SDK)的情况下,对服务方进行调用,并且可以正常拿到调用结果

适用场景

  • 测试平台:如果要搭建一个可以测试RPC调用的平台,用户输入分组名、接口、方法名等信息,就可以测试对应的RPC服务。那么由于同样的原因(即会导致每有一个新的服务发布,就需要修改网关的代码以及重新部署),所以平台本身不应该依赖于服务提供方的接口API。所以需要泛化调用的支持
  • 网关服务:如果要搭建一个网关服务,那么服务网关要作为所有RPC服务的调用端。但是网关本身不应该依赖于服务提供方的接口API(这样会导致每有一个新的服务发布,就需要修改网关的代码以及重新部署),所以需要泛化调用的支持

使用

接口定义
public interface DevelopService {
    String invoke(String param);
}
接口实现1
@DubboService(group = "group1",version = "1.0")
public class DevelopProviderServiceV1 implements DevelopService{
    @Override
    public String invoke(String param) {
        StringBuilder s = new StringBuilder();
        s.append("ServiceV1 param:").append(param);
        return s.toString();
    }
}
客户端调用
@Component
public class GenericTask implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        GenericService genericService = buildGenericService("org.apache.dubbo.samples.develop.DevelopService","group1","1.0");
        //传入需要调用的方法,参数类型列表,参数列表
        Object result = genericService.$invoke("invoke", new String[]{"java.lang.String"}, new Object[]{"g1"});
        System.out.println("GenericTask Response: " + JSON.toJSONString(result));
    }

    private GenericService buildGenericService(String interfaceClass, String group, String version) {
        ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
        reference.setInterface(interfaceClass);
        reference.setVersion(version);
        //开启泛化调用
        reference.setGeneric("true");
        reference.setTimeout(30000);
        reference.setGroup(group);
        ReferenceCache cache = SimpleReferenceCache.getCache();
        try {
            return cache.get(reference);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }
}

5.动态调整服务超时时间

介绍

Dubbo提供动态调整服务超时时间的能力,在无需重启应用的情况下调整服务的超时时间,这对于临时解决一些服务上下游依赖不稳定而导致的调用失败问题非常有效

场景

商城项目通过org.apache.dubbo.samples.UserService提供用户信息管理服务,访问http://localhost:8080/打开商城并输入任意账号密码,点击Login即可以正常登录到系统

有些场景下,User服务的运行速度会变慢,比如存储用户数据的数据库负载过高导致查询变慢,这时就会出现UserService访问超时的情况,导致登录失败

为了解决突发的登录超时问题,我们只需要适当增加UserService服务调用的等待时间即可

使用

原理

side: provider配置会将规则发送到服务提供方实例,所有UserService服务实例会基于新的timeout值进行重新发布,并通过注册中心通知给所有消费方

6.服务重试

介绍

在服务初次调用失败后,通过重试能有效的提升总体调用成功率。但也要注意重试可能带来的响应时间增长,系统负载升高等,另外,重试一般适用于只读服务,或者具有幂等性保证的写服务

使用

7.访问日志

介绍

访问日志可以很好的记录某台机器在某段时间内处理的所有服务请求信息,包括请求接收时间、远端IP、请求参数、响应结果等,运行态动态的开启访问日志对于排查问题非常有帮助

使用

效果

再次访问登录页面,登录到User应用的任意一台机器,可以看到如下格式的访问日志

[2022-12-30 12:36:31.15900] -> [2022-12-30 12:36:31.16000] 192.168.0.103:60943 -> 192.168.0.103:20884 - org.apache.dubbo.samples.UserService login(java.lang.String,java.lang.String) ["test",""], dubbo version: 3.2.0-beta.4-SNAPSHOT, current host: 192.168.0.103
[2022-12-30 12:36:33.95900] -> [2022-12-30 12:36:33.95900] 192.168.0.103:60943 -> 192.168.0.103:20884 - org.apache.dubbo.samples.UserService getInfo(java.lang.String) ["test"], dubbo version: 3.2.0-beta.4-SNAPSHOT, current host: 192.168.0.103
[2022-12-30 12:36:31.93500] -> [2022-12-30 12:36:34.93600] 192.168.0.103:60943 -> 192.168.0.103:20884 - org.apache.dubbo.samples.UserService getInfo(java.lang.String) ["test"], dubbo version: 3.2.0-beta.4-SNAPSHOT, current host: 192.168.0.103

accesslog取值

  • true或default:访问日志将随业务logger一同输出,此时可以在应用内提前配置dubbo.accesslog appender调整日志的输出位置和格式
  • 具体的文件路径如/home/admin/demo/dubbo-access.log,这样访问日志将打印到指定的文件

单独开启一台机器访问日志

在Admin界面,还可以单独指定开启某一台机器的访问日志,以方便精准排查问题,对应的后台规则如下:

configVersion: v3.0
enabled: true
configs:
  - match
     address:
       oneof:
        - wildcard: "{ip}:*"
    side: provider
    parameters:
      accesslog: true

其中,{ip} 替换为具体的机器地址即可

8.端口协议复用

介绍

通过对protocol进行配置,dubbo3可以支持端口的协议复用。比如使用Triple协议启动端口复用后,可以在相同的端口上为服务增加Dubbo协议支持,以及Qos协议支持。这些协议的识别都是由一个统一的端口复用服务器进行处理的,可以用于服务的协议迁移,并且可以节约端口以及相关的资源,减少运维的复杂性

使用场景

  • 最常用的是用于服务发现。这允许应用程序通过网络发现服务,然后使用同一端口与它们通信,有助于降低网络通信的复杂性,并使其更易于管理
  • 可以用于负载平衡。这允许应用程序在多个远程服务或服务集群之间平衡负载,有助于提高服务的可扩展性、可靠性和可用性
  • 可以用于服务监控。这允许应用程序监视远程服务的运行状况,并在服务出现故障或变得不可用时发出警报,有助于确保服务的可用性并减少停机时间

配置方式

ext-protocol参数支持配置多个不同的协议,协议之间通过",“进行分隔

dubbo:
  application:
    name: dubbo-springboot-demo-provider
  protocol:
    name: tri
    port: -1
    ext-protocol: dubbo,

9.启动时检查

介绍

Dubbo缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止Spring初始化完成,以便上线时,能及早发现问题,默认为true

可以通过设置为false关闭检查,比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动

使用场景

  • 单向依赖:有依赖关系(建议默认设置)和无依赖关系(可以设置 check=false)
  • 相互依赖:即循环依赖,(不建议设置 check=false)
  • 延迟加载处理

注意

check只用来启动时检查,运行时没有相应的依赖仍然会报错

使用

关闭某个服务的启动时检查
@DubboReference(group = "group2",version = "1.0",check = false)
private UserService userService;
关闭所有服务的启动时检查
dubbo:
  consumer:
    check: false
关闭注册中心启动时检查
dubbo:
  application:
    name: dubbo-consumer
  registry:
    address: nacos://localhost:8848
    check: false
其他
dubbo:
  application:
    name: dubbo-consumer
  # 配置中心启动检查
  config-center:
    check: false
  # 元数据中心启动检查
  metadata-report:
    check: false
通过-D参数指定
java -Ddubbo.reference.com.foo.BarService.check=false
java -Ddubbo.consumer.check=false 
java -Ddubbo.registry.check=false

10.参数校验

介绍

参数验证功能是基于JSR303实现的,用户只需标识JSR303标准的验证annotation,并通过声明filter来实现验证

依赖

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>1.0.0.GA</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>4.2.0.Final</version>
</dependency>

使用场景

服务端在向外提供接口服务时,解决各种接口参数校验问题

使用

接口定义
public interface ValidationService {
    // 验证参数不为空,实体类里面也可以添加很多验证注解
    void save(@NotNull ValidationParameter parameter); 
    // 直接对基本类型参数验证
    void delete(@Min(1) int id); 
}
客户端验证参数
@DubboReference(group = "group2",version = "1.0",validation = "true")
private UserService userService;
服务端验证参数
@DubboService(group = "group1",version = "1.0",validation = "true")
验证异常信息
public class ValidationConsumer {   
    public static void main(String[] args) throws Exception {
        String config = ValidationConsumer.class.getPackage().getName().replace('.', '/') + "/validation-consumer.xml";
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(config);
        context.start();
        ValidationService validationService = (ValidationService)context.getBean("validationService");
        // Error
        try {
            parameter = new ValidationParameter();
            validationService.save(parameter);
            System.out.println("Validation ERROR");
        } catch (RpcException e) { // 抛出的是RpcException
            ConstraintViolationException ve = (ConstraintViolationException) e.getCause(); // 里面嵌了一个ConstraintViolationException
            Set<ConstraintViolation<?>> violations = ve.getConstraintViolations(); // 可以拿到一个验证错误详细信息的集合
            System.out.println(violations);
        }
    } 
}

版本问题

  • Dubbo默认支持hibernate-validator版本<=6.x,若使用 hibernate-validator7.x版本,请将validation参数声明为jvalidationNew
  • 如果需要启动客户端验证,并且使用jdk17,则需添加jvm启动参数--add-opens java.base/java.lang=ALL-UNNAMED做兼容处理

11.集群容错

介绍

在集群调用失败时,Dubbo提供了多种容错方案,缺省为failover重试

使用场景

多个服务器部署同一集群中,运行同一应用程序,如果一台服务器出现故障,其他服务器将接管负载,确保应用程序对用户仍然可用

策略总览

  • Failover Cluster:默认配置,失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过retries="2"来设置重试次数(不含第一次)
  • Failfast Cluster:快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录
  • Failsafe Cluster:失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作
  • Failback Cluster:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作
  • Forking Cluster:并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过forks="2"来设置最大并行数
  • Broadcast Cluster:广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息
  • Available Cluster:调用目前可用的实例(只调用一个),如果当前没有可用的实例,则抛出异常。通常用于不需要负载均衡的场景
  • Mergeable Cluster:将集群中的调用结果聚合起来返回结果,通常和group一起配合使用。通过分组对结果进行聚合并返回聚合后的结果,比如菜单服务,用group区分同一接口的多种实现,现在消费方需从每种group中调用一次并返回结果,对结果进行合并之后返回,这样就可以实现聚合菜单项
  • ZoneAware Cluster:多注册中心订阅的场景,注册中心集群间的负载均衡

使用方式

// retries:重试次数
// cluster:重试策略
@DubboReference(group = "group2",retries = 2,cluster = ClusterRules.FAIL_BACK)
private UserService userService;
@DubboService(group = "group1", version = "1.0", retries = 2)

12.服务降级

介绍

推荐使用相关限流降级组件(如 Sentinel)以达到最佳体验

服务降级是指服务在非正常情况下进行降级应急处理

适用场景

  • 某服务或接口负荷超出最大承载能力范围,需要进行降级应急处理,避免系统崩溃
  • 调用的某非关键服务或接口暂时不可用时,返回模拟数据或空,业务还能继续可用
  • 降级非核心业务的服务或接口,腾出系统资源,尽量保证核心业务的正常运行
  • 某上游基础服务超时或不可用时,执行能快速响应的降级预案,避免服务整体雪崩

使用方式

方式1
@DubboReference(group = "group2",mock = "true")
private UserService userService;

这种方式需要在相同包下有类名+Mock后缀的实现类

方式2
@DubboReference(group = "group2",mock = "com.xxx.service.DemoServiceMock")
private UserService userService;

这种方式指定Mock类的全路径

方式3
mock="[fail|force]return|throw xxx"
  • fail或force关键字可选,表示调用失败或不调用强制执行mock方法,如果不指定关键字默认为fail
  • return 表示指定返回结果,throw 表示抛出指定异常
  • xxx根据接口的返回类型解析,可以指定返回值或抛出自定义的异常

下面是xml的配置示例,注解类似

<dubbo:reference id="demoService" interface="com.xxx.service.DemoService" mock="return" />
<dubbo:reference id="demoService" interface="com.xxx.service.DemoService" mock="return null" />
<dubbo:reference id="demoService" interface="com.xxx.service.DemoService" mock="fail:return aaa" />
<dubbo:reference id="demoService" interface="com.xxx.service.DemoService" mock="force:return true" />
<dubbo:reference id="demoService" interface="com.xxx.service.DemoService" mock="fail:throw" />
<dubbo:reference id="demoService" interface="com.xxx.service.DemoService" mock="force:throw java.lang.NullPointException" />

配置文件

dubbo:
  application:
    name: dubbo-consumer
  registry:
    address: nacos://localhost:8848
  provider:
    host: 192.168.1.17
    protocol: dubbo
  consumer:
    # 当服务执行失败时调用的模拟类的名称
    mock: 

配合dubbo-admin使用

  • 应用消费端引入dubbo-mock-admin依赖
  • 应用消费端启动时设置JVM参数,-Denable.dubbo.admin.mock=true
  • 启动dubbo-admin,在服务Mock->规则配置菜单下设置Mock规则
  • 以服务方法的维度设置规则,设置返回模拟数据,动态启用/禁用规则

13.直连提供者

介绍

在开发及测试环境下,经常需要绕过注册中心,只测试指定服务提供者,这时候可能需要点对点直连,点对点直连方式,将以服务接口为单位,忽略注册中心的提供者列表

使用

@DubboReference(url = "dubbo://192.168.1.17:20880/cn.sgy.study.dubboapi.service.UserService")

14.多协议支持

介绍

Dubbo允许配置多协议,在不同服务上支持不同协议或者同一服务上同时支持多种协议

使用场景

与不同系统的兼容:如果你正在与一个支持特定协议的系统集成,你可以使用Dubbo对该协议的支持来方便通信。例如,如果您正在与使用RMI的遗留系统进行集成,则可以使用 Dubbo的RMI协议支持与该系统进行通信。使用多种协议可以提供灵活性,并允许您为您的特定用例选择最佳选项

好处

  • 改进的性能:不同的协议可能具有不同的性能特征,这取决于传输的数据量和网络条件等因素。通过使用多种协议,可以根据您的性能要求选择最适合给定情况的协议
  • 安全性:一些协议可能提供比其他协议更好的安全特性。HTTPS协议通过加密传输的数据来提供安全通信,这对于保护敏感数据非常有用
  • 易用性:某些协议在某些情况下可能更易于使用。如果正在构建Web应用程序并希望与远程服务集成,使用HTTP协议可能比使用需要更复杂设置的协议更方便

使用

服务提供者相关配置
# 可以同时定义多个
dubbo:
  protocols:
    protocol1:
      id: rest
      name: rest
      prot: 20881
      host: 0.0.0.0
    protocol2:
      id: dubbo1
      name: dubbo
      prot: 20881
      host: 0.0.0.0
    protocol3:
      id: dubbo2
      name: dubbo
      prot: 20882
      host: 0.0.0.0
服务提供者实现类指定协议类型
@DubboService(version = "async",protocol = "protocol1,protocol2")
public class AsyncSiteService implements SiteService {
    @Override
    public String siteName(String name) {
        return "async:"+name;
    }
}

15.使用Rest访问Dubbo服务

介绍

服务提供方使用rest协议的方式暴漏接口,既然是rest那么是支持PostMan或者浏览器这类工具去调用的

使用

服务提供者配置
# 可以同时定义多个
dubbo:
  protocols:
    protocol1:
      name: rest
      prot: 20881
服务提供者代码
@DubboService(version = "rest",protocol = "protocol1")
@Path("site")
public class RestSiteService implements SiteService {
    @Override
    @GET
    @Path("name")
    @Produces({ContentType.APPLICATION_JSON_UTF_8})
    public String siteName(@QueryParam("name")String name){
        return "rest:"+name;
    }
}
使用PostMan访问

省略...

16.服务超时

介绍

服务提供者和服务消费者都可以配置服务超时时间,默认1秒

分类

  • 服务提供者配置效果:执行该服务的超时时间。如果超时,则会打印超时日志(warn),但服务会正常执行完
  • 服务消费者配置效果:从发起服务调用到收到服务响应的整个过程的时间。如果超时,则进行重试,重试失败抛异常
  • 注意:如果消费方和提供方只有一方配置了,相当于两方都配置了

配置

分别通过@DubboService和@DubboReference注解中的timeout属性配置即可

17.负载均衡策略

策略

Random LoadBalance
  • 随机,按权重设置随机概率
  • 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重
RoundRobin LoadBalance
  • 轮询,按公约后的权重设置轮询比率
  • 存在慢的提供者累积请求的问题,比:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上
LeastActive LoadBalance
  • 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差
  • 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大
ConsistentHash LoadBalance
  • 一致性Hash,相同参数的请求总是发到同一提供者
  • 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动

使用

@DubboReference(version="default",loadbalance="random")