3.Dubbo各种特性使用方法

准备

将producer模块复制出来三份,分别修改以下内容

  • service.port

  • dubbo.protocol.port

  • HelloWordImpl.java中的输出内容

    public class HelloWordImpl implements HelloWord {
        public String helloWord(String s) {
            return "HelloWordImpl20881:"+s;
    //        return "HelloWordImpl20882:"+s;
      //      return "HelloWordImpl20883:"+s;
        }
    }
    
  • 三个生产者全部启动

version

消费者可通过指定版本来控制要RPC调用对应版本的生产者

  • 修改生产者实现类注解

    //指定对外暴漏的服务版本为20882
    @DubboReference(version = "20882")
    
  • 修改消费者@DubboReference

    //指定请求版本为20882的生产者
    @DubboReference(version = "20882")
    

protocol通讯协议

dubbo可对协议扩展,例如:Dubbo\Rest\Http\⾃定义

配置单个协议

  protocol:
    port: 20881 #配置当前这个服务在dubbo容器中的端⼝号,每个dubbo容器内部的服务的端⼝号唯一
    name: dubbo

配置多个协议

  protocols:
    protocol_1:
      id: protocol_1
      name: rest
      port: 8080
      host: 127.0.0.1
    protocol_2:
      id: protocol_2
      name: dubbo
      port: 21881
      host: 127.0.0.1
    protocol_3:
      id: protocol_3
      name: http
      port: 8181
      host: 127.0.0.1

协议指定方式

//protocol不指定,yml声明了几个协议,就会对每个协议分别创建一份对应请求协议的服务
@DubboService(version = "20883",protocol = "配置中指定的id")
public class HelloWordImpl implements HelloWord {
    public String helloWord(String s) {
        return "HelloWordImpl20883:"+s;
    }
}

选择Rest协议

最新版本鼓捣了好久,最终通过翻官方代码找到的正确使用姿势

POM

添加GVA

        <!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo-rpc-rest -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-rpc-rest</artifactId>
            <version>3.0.6</version>
        </dependency>

YML

server:
  port: 8080
dubbo:
  application:
    name: hello-word-service #服务名称
  registry:
    address: zookeeper://192.168.0.104:2181,192.168.0.104:2182,192.168.0.104:2183,192.168.0.104:2184 #zk地址
  protocol:
    port: 6060 #rest暴漏端口
    name: rest #选择rest

修改生产者实现类

下面最终请求地址为 127.0.0.1:6066/rest/hello/helloWord?param=value

//                               path相当于在@Path前又加了一个前缀,可省略
@DubboService(protocol = "rest",path = "rest")
@Path("hello") //@RequestMapping
public class HelloWordImpl implements HelloWord {
    @GET  //发送get请求,还有put post等
    @Path("helloWord") // 方法请求路径 ,@GET和@Path = @GetMapping
    @Produces({ContentType.APPLICATION_JSON_UTF_8}) //设置ContentType,可设置多个
    public String helloWord(@QueryParam("param") String param) {
        return "HelloWordImpl20881:"+param;
    }
}

跳过注册中心,直连请求

生产者配置

假设生产者通过下面配置,并且实现类只标注了@DubboService(version = "v1"),未加protocol=‘’属性来指明到底用哪个通讯协议,则生产者启动会开启两个协议通讯端口

  protocols:
    protocol_20881:
      id: protocol_20881
      name: dubbo
      port: 20881
      host: 0.0.0.0
    protocol_20882:
      id: protocol_20882
      name: dubbo
      port: 20882
      host: 0.0.0.0

消费者直连方式

	//添加版本和直连的url/端口/接口全包名
	//输入20882和20881都可调用成功,说明开启了两个通讯协议端口
	@DubboReference(version = "v1",url ="dubbo://127.0.0.1:20882/myinterface.HelloWord")
    private HelloWord helloWord;

    @Test
    void contextLoads() {
        //执行代理方法
        System.out.println(helloWord.helloWord("RB"));
    }

超时配置

@DubboReference(version = "v1",timeout = 3000)
//消费者配置超过3秒就超时报错
@DubboService(version = "v1",timeout = 2000)
//生产者配置超过2秒就报错
  • 都不配置默认超时时间1秒

  • 如果消费者或生产者只配置一方,则超时时间消费者和生产者共享配置

  • 如果消费者配置3秒过期,生产者配置2秒过期

    1. 当方法执行超过3秒后,消费者报错

    2. 当方法执行超过2秒后,生产者会打印WARN日志,但方法还会执行完,不会终止

      [04/04/22 14:47:39:086 CST] NettyServerWorker-3-1  WARN transport.AbstractServer:  [DUBBO] All clients has disconnected from /192.168.0.105:20882. You can graceful shutdown now., dubbo version: 3.0.6, current host: 192.168.0.105
      

集群容错(高可用)机制

dubbo为集群调⽤提供了容错⽅案:

  • failover(默认):

    当出现失败时,会进⾏重试,默认重试2次,⼀共三次调⽤。但是会出现幂等性问题。 虽然会出现幂等性问题,但是依然推荐使⽤这种容错机制,在业务层⾯解决幂等性问题:

    • ⽅案⼀:把数据的业务id作为数据库的联合主键,此时业务id不能重复。

    • ⽅案⼆(推荐):使⽤分布式锁来解决重复消费问题。

  • failfast:当出现失败时。⽴即报错,不进⾏重试。

  • failsafe:失败不报错,记⼊⽇志。

  • failback:失败就失败,开启定时任务 定时重发。

  • forking:并⾏访问多个服务器,获取某⼀个结果既视为成功。

结论:使⽤dubbo,不推荐把重试关掉,⽽是在⾮幂等性操作的场景下,服务提供者⽅ 要做幂等性的解决⽅案(保证)。

服务降级

可以通过服务降级功能临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。

向注册中心写入动态配置覆盖规则:

RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&application=foo&mock=force:return+null"));

其中:

  • mock=force:return+null 表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。
  • 还可以改为 mock=fail:return+null 表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。
  • Demo
//消费者注解添加mock
@DubboReference(version = "v1",mock = "force:return timeout")

本地存根

类似熔断机制

远程服务后,客户端通常只剩下接⼝,⽽实现全在服务器端,但提供⽅有些时候想在客户端 也执⾏部分逻辑,⽐如:做 ThreadLocal 缓存,提前验证参数,调⽤失败后伪造容错数据等 等,此时就需要在 API 中带上 Stub,客户端⽣成 Proxy 实例,会把 Proxy 通过构造函数传给 Stub 1,然后把 Stub 暴露给⽤户,Stub 可以决定要不要去调 Proxy。

消费端配置

@DubboReference(version = "v1",stub = "com.rb.customer.HelloWord1Stub")
private HelloWord helloWord;
//或
@DubboReference(version = "v1",stub = "true")  
private HelloWord helloWord;

Stub类

public class HelloWordStub implements HelloWord {
    private final HelloWord helloWord;
    public HelloWord1Stub(HelloWord helloWord) {
        this.helloWord = helloWord;
    }
    public String helloWord(String parameter) {
        try {
            return helloWord.helloWord(parameter);
        } catch (Exception e) {
            return "Stub-Error:"+parameter;
        }
    }
}

流程

stub配置为true

  1. dubbo会在接口HelloWord包路径下寻找HelloWordStub.java

stub配置为自定义*Stub.java路径

  1. dubbo会在使用定义路径下的*Stub.java

    然后执行下面两步

  2. 消费端实际调用的是HelloWordStub下helloWord()方法

  3. 如果HelloWordStub类中 Dubbo提供的HelloWord类报错,则调用catch方法

参数回调

个人理解就是类似支付后回调的流程,用户支付后通过Dubbo调用支付宝,支付宝入账后再通过反向代理调用用户端提供的方法。

通过参数回调从服务器端调用客户端逻辑

参数回调方式与调用本地 callback 或 listener 相同,只需要在 Spring 的配置文件中声明哪个参数是 callback 类型即可。Dubbo 将基于长连接生成反向代理,这样就可以从服务器端调用客户端逻辑。可以参考 dubbo 项目中的示例代码

创建生产者回调接口

package myinterface;

/**
 * 创建生产者回调接⼝
 */
public interface CallbackListener {
    void callback(String msg);
}

创建哪些功能方法需要回调

package myinterface;

/**
 * 定义一个功能接口
 */
public interface HelloWord {
    String helloWord(String parameter);
    //创建带回调的方法,key用于让dubbo知道具体回调哪个方法,callbackListener为生产者调用回调的对象
    String helloWord(String parameter,String key,CallbackListener callbackListener);
}

消费者端添加实现回调接口类

package com.rb.customer;

import myinterface.CallbackListener;
import java.io.Serializable;

public class CallbackServiceImpl implements CallbackListener, Serializable {

    @Override
    public void callback(String s) {
        //如果是支付后回调,那这就是支付成功支付宝通知用户该修改订单状态了
        System.out.println("生产者回调:"+s);
    }
}

消费者发起请求

helloWord.helloWord("RB","V1",new CallbackServiceImpl())

生产者接到请求处理

package com.rb.producer.impl;


import myinterface.CallbackListener;
import myinterface.HelloWord;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.config.annotation.Argument;
import org.apache.dubbo.config.annotation.DubboService;
import org.apache.dubbo.config.annotation.Method;
import org.apache.dubbo.rpc.RpcContext;

import java.util.concurrent.TimeUnit;

/**
 * 对接口的实现
 */
//添加Dubbo注解
//name:哪个方法开启回调,arguments.index:对应方法第几个参数开启回调,可配置多个
@DubboService(version = "v1",methods = {@Method(name="helloWord",arguments = {@Argument(index = 2, callback = true)})})
public class HelloWordImpl implements HelloWord {
    public String helloWord(String param) {
        return "HelloWordImpl20882:"+param;
    }

    @Override
    public String helloWord(String s, String s1, CallbackListener callbackListener) {
        callbackListener.callback("abcd");//业务处理完,要传给消费端的参数,这个也是一个代理类,调用后消费端就可收到abcd
        return "HelloWordImpl20882:"+s;
    }
}

执行

当消费者发起调用后,消费者会打印CallbackServiceImpl.callback方法

异步调用

方法接口添加异步接口

/**
 * 定义一个功能接口
 */
public interface HelloWord {
    String helloWord(String parameter);
    String helloWord(String parameter,String key,CallbackListener callbackListener);

    //异步方法
    CompletableFuture<String> helloWordAsync(String name);
}

消费者调用

@Test
    void contextLoads() throws IOException {
        //执行代理方法
        CompletableFuture<String> rb = helloWord.helloWordAsync("RB");
        rb.whenComplete((v, e) -> {
            if (e != null) {
                e.printStackTrace();
            } else {
                System.out.println("异步结果:" + v);
            }
        });
        System.out.println("当前线程调⽤结束");
        System.in.read();//阻塞,防止线程关了,回调失败

    }

生产者实现

package com.rb.producer.impl;


import lombok.SneakyThrows;
import myinterface.CallbackListener;
import myinterface.HelloWord;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.config.annotation.Argument;
import org.apache.dubbo.config.annotation.DubboService;
import org.apache.dubbo.config.annotation.Method;
import org.apache.dubbo.rpc.RpcContext;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 * 对接口的实现
 */
//添加Dubbo注解
@DubboService(version = "v1",methods = {@Method(name="helloWord",arguments = {@Argument(index = 2, callback = true)})})
public class HelloWordImpl implements HelloWord {
    public String helloWord(String param) {
        return "HelloWordImpl20882:"+param;
    }

    @Override
    public String helloWord(String s, String s1, CallbackListener callbackListener) {
        callbackListener.callback("abcd");
        return "HelloWordImpl20882:"+s;
    }

    @SneakyThrows
    @Override
    public CompletableFuture<String> helloWordAsync(String s) {
        System.out.println("异步调⽤:" + s);
        TimeUnit.SECONDS.sleep(2);//模拟耗时业务
        return CompletableFuture.supplyAsync(() -> {
            return "HelloWordImplAsync20882:"+s;
        });
    }
}

Dubbo更多高级功能

posted @ 2022-04-05 17:06  RollBack2010  阅读(914)  评论(0编辑  收藏  举报