Dubbo系列|三、Dubbo高级应用

392 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情

开篇

本文主要介绍一些dubbo的高级应用。

在平时使用dubbo时,最常用的还是直接加@DubboService@DubboReference注解,dubbo还提供了更多的高级功能供我们使用。

本文采用的示例是在dubbo系列第一篇Dubbo框架介绍的基础上进行修改的,这里不再赘述。

服务降级

服务降级,是针对于某个服务提供者而言的,服务消费者在调用某个服务提供者时,如果该服务提供者报错了,所采取的措施。

比如消费者在调用提供者时,服务超时而导致调用失败了,就可以采用服务降级来进行容错。

例如,现在服务端配置超时为2s,消费端配置超时为1s,加上容错措施。

@Service
public class ConsumerService {

    @DubboReference(timeout = 1000, retries = 3, mock = "fail: return Error")
    private HelloService helloService;

    public void sayHello() {
        String result = helloService.sayHello("world");
        System.out.println(result);
    }
}

执行后,经过3次重试后,会直接返回 Error,而不是抛出异常。

mock=fail: return Error表示,在消费者调用提供者失败后,返回Error。

mock=force: return Error表示,不会执行调用,直接返回Error。

还可以创建一个Mock类来实现。

dubbo-api项目创建一个类,实现HelloService接口来做Mock。

public class HelloServiceMock implements HelloService {
    public String sayHello(String name) {
        return "Mock " + name;
    }
}

消费端调用

@EnableDubbo
@SpringBootApplication
public class ConsumerApplication {
    @DubboReference(timeout = 1000, retries = 3, mock = "true")
    private HelloService helloService;

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(ConsumerApplication.class, args);
        ConsumerApplication consumerApplication = context.getBean(ConsumerApplication.class);
        consumerApplication.sayHello("world");
    }

    public void sayHello(String name) {
        String result = helloService.sayHello(name);
        System.out.println(result);
    }
}

本地伪装

本地伪装和服务降级的功能是一样的,都是通过mock来实现,具体用法可以参考服务降级。

本地存根

本地存根是一段代码逻辑,这段代码逻辑一般是服务端提供者提供,但是在服务消费端执行。服务提供者可以利用这种机制在服务消费者远程调用服务提供者之前或之后再做一些其他事情,比如结果缓存、请求参数验证或其他逻辑。

现在我们来创建一个本地存根类HelloServiceStub,同样需要实现HelloService接口。本地存根类需要持有HelloService一个对象。当消费者调用时会先执行本地存根这个类。

public class HelloServiceStub implements HelloService {
    private final HelloService helloService;

    public HelloServiceStub(HelloService helloService) {
        this.helloService = helloService;
    }

    public String sayHello(String name) {
        System.out.println("before execute remote service, parameter: " + name);
        String result = this.helloService.sayHello(name);
        System.out.println("after execute remote service, result: " + result);
        return "Stub: " + name;
    }
}

消费者执行代码

@EnableDubbo
@SpringBootApplication
public class ConsumerApplication {
    @DubboReference(stub = "true")
    private HelloService helloService;

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(ConsumerApplication.class, args);
        ConsumerApplication consumerApplication = context.getBean(ConsumerApplication.class);
        consumerApplication.sayHello("world");
    }

    public void sayHello(String name) {
        String result = helloService.sayHello(name);
        System.out.println(result);
    }
}

查看执行结果

image.png

代码中的stub="true",代表启用本地存根,dubbo会根据调用的接口全限定名+"Stub"来充当本地存根类,当然也支持stub="某个类的全限定名",如果找不到这个类,程序就会报错。

当消费者调用sayHello方法时,是先执行的stub里面的sayHello方法,在stub类里面执行一段逻辑后,才去真正执行sayHello方法,这也相当于mock的另一种实现方式。

参数回调

参数回调是指,服务消费端在调用服务提供者的时候,调用完成后给服务消费端一个回调(再调用服务消费端一个方法)。

一般都是消费端来调用服务端,如果在调用服务端后,想让服务端再调用对应的消费端就需要用到这种机制了。

dubbo-api里增加回调接口HelloServiceListener

public interface HelloServiceListener {
    void changed(String msg);
}

dubbo-provider服务提供者增加回调配置

@DubboService(methods = {@Method(name = "sayHello", arguments = {@Argument(index = 1, callback = true)})})
public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHello(String name, HelloServiceListener listener) {
        System.out.println("provider received invoke of sayHello: " + name);
        listener.changed("Callback " + name);
        return "Hello, " + name;
    }
}

dubbo-consumer服务消费者进行调用

@EnableDubbo
@SpringBootApplication
public class ConsumerApplication {
    @DubboReference
    private HelloService helloService;

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(ConsumerApplication.class, args);
        ConsumerApplication consumerApplication = context.getBean(ConsumerApplication.class);
        consumerApplication.sayHello("world");
    }
    public void sayHello(String name) {
        String result = helloService.sayHello(name, new HelloServiceListenerImpl());
        System.out.println(result);
    }
}
public class HelloServiceListenerImpl implements HelloServiceListener {
    @Override
    public void changed(String msg) {
        System.out.println("Changed :" + msg);
    }
}

如果有多个回调的话,可以再加一个参数来区分。

异步调用

Dubbo支持多种异步调用,可以通过注解或xml将一个方法标记为异步方法,也可以将一个类的方法的返回值类型设置为CompletableFuture

注解形式

@DubboReference(methods = @Method(name = "sayHello", async = true))
private HelloService helloService;

通过这种方式,直接调用方法会得到一个null,而不是结果。需要通过RpcContext来获取返回值。

// 会返回null
String result = helloService.sayHello(name);
System.out.println(result);
CompletableFuture<String> helloFuture = RpcContext.getContext().getCompletableFuture();
helloFuture.whenComplete((v, t) -> {
    if (t == null) {
        System.out.println("result: " + v);
    } else {
        t.printStackTrace();
    }
});

或者通过asyncCall来完成异步调用。

CompletableFuture<String> f = RpcContext.getContext().asyncCall(() ->
        helloService.sayHello("async"));
System.out.println("result: " + f.get());

CompletableFuture

将方法声明为CompletableFuture类型

CompletableFuture<String> sayHello(String name);

这样消费端就不用使用RpcContext就可以完成异步调用。

CompletableFuture<String> helloFuture = helloService.sayHello(name);
helloFuture.whenComplete((v, t) -> {
    if (t == null) {
        System.out.println("result: " + v);
    } else {
        t.printStackTrace();
    }
});

泛化调用

Dubbo提供了一个类GenericService,他可以调用所有的服务,通常用来做测试。使用这个类的一个好处是,我们可以不用引入额外的jar就可以调用服务,不过书写起来不叫麻烦。需要指定generictrue

@EnableDubbo
@SpringBootApplication
public class GenericApplication {
    @DubboReference(id = "genericService", interfaceName = "cn.juejin.dubbo.api.HelloService", generic = true)
    private GenericService genericService;

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(GenericApplication.class, args);
        GenericService genericService = (GenericService) context.getBean("genericService");
        Object result = genericService.$invoke("sayHello", new String[]{"java.lang.String"}, new Object[]{"generic"});
        System.out.println(result);
    }
}

泛化服务

在服务端实现了GenericService接口的类,提供的服务称为泛化服务,是用来实现一个通用的服务测试框架。

服务端定义一个泛化服务

@DubboService(interfaceName = "cn.juejin.dubbo.api.HelloService")
public static class GenericServiceImpl implements GenericService {
    @Override
    public Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException {
        System.out.println("执行泛化服务: " + method);
        return "Hello, " + args[0];
    }
}

在消费端调用时,依然可以按之前的方式调用。

@DubboReference
private HelloService helloService;