dubbo多个生产者一个消费者 ,一对多 dubbo 异步调用需求处理

2.7.0基于CompletableFuture的增强

了解Java中Future演进历史的同学应该知道,Dubbo 2.6.x及之前版本中使用的Future是在java 5中引入的,所以存在以上一些功能设计上的问题,而在java 8中引入的CompletableFuture进一步丰富了Future接口,很好的解决了这些问题。

Dubbo在2.7.0版本已经升级了对Java 8的支持,同时基于CompletableFuture对当前的异步功能进行了增强。

  1. 支持直接定义返回CompletableFuture的服务接口。通过这种类型的接口,我们可以更自然的实现Consumer、Provider端的异步编程。

    public interface AsyncService {
        CompletableFuture<String> sayHello(String name);
    }
    
  2. 如果你不想将接口的返回值定义为Future类型,或者存在定义好的同步类型接口,则可以选择重载原始方法并为新方法定义CompletableFuture类型返回值。

    public interface GreetingsService {
        String sayHi(String name);
    }
    
    public interface GreetingsService {
        String sayHi(String name);
        // 为了保证方法级服务治理规则依然有效,建议保持方法名不变: sayHi
        // 使用default实现,避免给服务端提供者带来额外实现成本
        // boolean placeHoler只是为了实现重载而增加,只要Java语法规则允许,你可以使用任何方法重载手段
        default CompletableFuture<String> sayHi(String name, boolean placeHolder) {
          return CompletableFuture.completedFuture(sayHello(name));
        }
    }
    

    这样,Provider依然可以只实现sayHi方法;而Consumer通过直接调用新增的sayHi重载方法可以拿到一个Future实例。

  3. 如果你的原始接口定义是同步的,这时要实现Provider端异步,则可以使用AsyncContext(类似Servlet 3.0里的AsyncContext的编程接口)。

注意:在已有CompletabeFuture返回类型的接口上,不建议再使用AsyncContext,请直接利用CompletableFuture带来的异步能力。

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.write("Hello " + name + ", response from provider.");
        }).start();
        return null;
    }
}

在方法体的开始RpcContext.startAsync()启动异步,并开启新线程异步的执行业务逻辑,在耗时操作完成后通过asyncContext.write将结果写回。

  1. RpcContext直接返回CompletableFuture

    CompletableFuture<String> f = RpcContext.getContext().getCompletableFuture();
    

以上所有的增强,是在兼容已有异步编程的基础上进行的,因此基于2.6.x版本编写的异步程序不用做任何改造即可顺利编译通过。

接下来,我们通过几个示例看一下如何实现一个全异步的Dubbo服务调用链。

示例1:CompletableFuture类型接口

CompletableFuture类型的接口既可以用作同步调用,也可以实现Consumer或Provider的异步调用。本示例实现了Consumer和Provider端异步调用,代码参见dubbo-samples-async-original-future

  1. 定义接口

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

    注意接口的返回类型是CompletableFuture<String>

  2. Provider端

    • 实现

      public class AsyncServiceImpl implements AsyncService {
          public CompletableFuture<String> sayHello(String name) {
              return CompletableFuture.supplyAsync(() -> {
                  try {
                      Thread.sleep(5000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  return "async response from provider.";
              });
          }
      }
      

      可以看到这里通过supplyAsync将业务代码切换到了新的线程执行,因此实现了Provider端异步。

    • 配置

      <bean id="asyncService" class="com.alibaba.dubbo.samples.async.impl.AsyncServiceImpl"/>
      <dubbo:service interface="com.alibaba.dubbo.samples.async.api.AsyncService" ref="asyncService"/>
      

      配置方式和普通接口是一样的。

  3. Consumer端

    • 配置
    <dubbo:reference id="asyncService" timeout="10000" interface="com.alibaba.dubbo.samples.async.api.AsyncService"/>
    

    ​ 配置方式和普通接口是一样的。

    • 调用远程服务
    public static void main(String[] args) throws Exception {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/async-consumer.xml"});
            context.start();
            final AsyncService asyncService = (AsyncService) context.getBean("asyncService");
        
            CompletableFuture<String> future = asyncService.sayHello("async call request");
            future.whenComplete((v, t) -> {
                if (t != null) {
                    t.printStackTrace();
                } else {
                    System.out.println("Response: " + v);
                }
            });
            System.out.println("Executed before response return.");
            System.in.read();
        }
    

    CompletableFuture<String> future = asyncService.sayHello("async call request");很自然的返回了Future示例,这样就实现了Consumer端的异步服务调用。

示例2:重载同步接口

这个示例演示了如何在同步接口的基础上,通过增加重载方法实现消费端的异步调用,具体代码参见地址dubbo-samples-async-generated-future

  1. 定义接口

    @DubboAsync
    public interface GreetingsService {
        String sayHi(String name);
    }
    

    修改接口,增加重载方法

    public interface GreetingsService {
        String sayHi(String name);
      
        default CompletableFuture<String> sayHi(String name, boolean isAsync) {
          return CompletableFuture.completedFuture(sayHello(name));
        }
    }
    
  2. Provider端

    • 配置
    <bean id="greetingsService" class="com.alibaba.dubbo.samples.async.impl.GreetingsServiceImpl"/>
    <dubbo:service interface="com.alibaba.dubbo.samples.api.GreetingsService" ref="greetingsService"/>
    
    • 服务实现
    public class GreetingsServiceImpl implements GreetingsService {
        @Override
        public String sayHi(String name) {
            return "hi, " + name;
        }
    }
    
  3. Consumer端

    • 配置
     <dubbo:reference id="greetingsService" interface="com.alibaba.dubbo.samples.api.GreetingsService"/>
    
    • 调用服务
     public static void main(String[] args) throws Exception {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/async-consumer.xml"});
            context.start();
    
            GreetingsService greetingsService = (GreetingsService) context.getBean("greetingsService");
            CompletableFuture<String> future = greetingsService.sayHi("async call reqeust", true);
            System.out.println("async call ret :" + future.get());
         
            System.in.read();
        }
    

    这样,我们就可以直接使用CompletableFuture<String> future = greetingsService.sayHi("async call reqeust", true);,直接返回CompletableFuture。

示例3:使用AsyncContext

本示例演示了如何在同步接口的基础上,通过AsyncContext实现Provider端异步执行,示例代码参见dubbo-samples-async-provider

之前已经提到过,已经是CompletableFuture签名的接口,要实现Provider端异步没必要再用AsyncContext。

  1. 定义接口

    public interface AsyncService {
        String sayHello(String name);
    }
    
  2. Provider端,和普通provider端配置完全一致

    • 配置
    <bean id="asyncService" class="com.alibaba.dubbo.samples.async.impl.AsyncServiceImpl"/>
    <dubbo:service async="true" interface="com.alibaba.dubbo.samples.async.api.AsyncService" ref="asyncService"/>
    
    • 异步执行实现
    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;
        }
    }
    
  3. Consumer端

    • 配置
    <dubbo:reference id="asyncService" interface="com.alibaba.dubbo.samples.async.api.AsyncService"/>
    
    • 服务调用
     public static void main(String[] args) throws Exception {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/async-consumer.xml"});
            context.start();
    
            AsyncService asyncService = (AsyncService) context.getBean("asyncService");
            System.out.println(asyncService.sayHello("async call request"));
         
            System.in.read();
        }
    

异步引入的新问题

Filter链

以下是一次普通Dubbo调用的完整Filter链(Filter链路图待补充)。

而采用异步调用后,由于异步结果在异步线程中单独执行,所以流经后半段Filter链的Result是空值,当真正的结果返回时已无法被Filter链处理。

为了解决这个问题,2.7.0中为Filter增加了回调接口onResponse。

以下是一个扩展Filter并支持异步Filter链的例子

@Activate(group = {Constants.PROVIDER, Constants.CONSUMER})
public class AsyncPostprocessFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        return invoker.invoke(invoker, invocation);
    }

    @Override
    public Result onResponse(Result result, Invoker<?> invoker, Invocation invocation) {
        System.out.println("Filter get the return value: " + result.getValue());
        return result;
    }
}

上下文传递

这里的上下文问题主要是指在提供端异步的场景。

当前我们考虑的上下文主要是指保存在RpcContext中的数据,大多数场景是需要用户在切换业务线程前自己完成Context的传递。

public class AsyncServiceImpl implements AsyncService {
    // 保存当前线程的上下文
    RpcContext context = RpcContext.getContext();
    public CompletableFuture<String> sayHello(String name) {
        return CompletableFuture.supplyAsync(() -> {
            // 设置到新线程中
            RpcContext.setContext(context);
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "async response from provider.";
        });
    }
}

不过AsyncContext也提供了signalContextSwitch()的方法来实现方便的Context切换。

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;
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值