栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Dubbo的基本应用与高级应用 & 控制台构建

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Dubbo的基本应用与高级应用 & 控制台构建

内容大纲:

  1. 负载均衡、集群容错、服务降级
  2. 本地存根、本地伪装、参数回调
  3. 异步调用、泛化调用、动态配置
  4. 管理台、动态配置、服务路由

文章目录
  • 一、负载均衡
    • 1.1 负载均衡策略
    • 1.2 配置
  • 二、服务超时
  • 三、集群容错
    • 3.1 集群容错模式
    • 3.2 集群模式配置
  • 四、服务降级
  • 五、本地存根
  • 六、本地伪装
  • 七、参数回调
  • 八、异步调用
  • 九、泛化调用
  • 十、泛化服务
  • 十一、Dubbo中的REST
  • 十二、管理台
  • 十三、动态配置
    • 13.1 配置规则
    • 13.2 规则详解
    • 13.3 示例
  • 十四、服务路由
    • 14.1 路由规则
    • 14.2 条件路由
      • 14.2.1 Conditions规则体:
      • 14.2.2 Condition示例
    • 14.3 标签路由
      • 14.3.1 Provider
      • 14.3.2 Consumer
  • 十五、蓝绿发布、灰度发布
  • 十六、Zookeeper可视化客户端工具

一、负载均衡

官网地址:http://dubbo.apache.org/zh/docs/v2.7/user/examples/loadbalance/

在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random 随机调用。

可以自行扩展负载均衡策略,参见:负载均衡扩展。

1.1 负载均衡策略

Random LoadBalance

  • 随机,按权重设置随机概率。
  • 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。

RoundRobin LoadBalance

  • 轮询,按公约后的权重设置轮询比率。

  • 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。

LeastActive LoadBalance

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

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

ConsistentHash LoadBalance

  • 一致性 Hash,相同参数的请求总是发到同一提供者。

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

  • 算法参见:http://en.wikipedia.org/wiki/Consistent_hashing

  • 缺省只对第一个参数 Hash,如果要修改,请配置 value=“0,1” />

  • 缺省用 160 份虚拟节点,如果要修改,请配置 />

1、【生产者端】定义服务时配置

// 配置DefaultDemoService的负载均衡为轮训
@Service(version = "default", loadbalance = "roundrobin")
public class DefaultDemoService implements DemoService {

    @Override
    public String sayHello(String name) {
        System.out.println("执行了服务DefaultDemoService, 版本: default. Name: " + name);

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

2、【消费者端】也可以在消费者端引用服务的时候配置:

# Spring boot application
spring.application.name=dubbo-provider-demo
server.port=8081

# base packages to scan Dubbo Component: @org.apache.dubbo.config.annotation.Service
dubbo.scan.base-packages=com.jihu.provider.service
dubbo.application.name=${spring.application.name}


## Dubbo Registry
dubbo.registry.address=zookeeper://192.168.131.172:2181

# Dubbo Protocol
# dubbo.protocol.name=dubbo
# Dubbo协议默认是使用netty实现的,这里设置的是netty端口
# dubbo.protocol.port=20880


#dubbo.protocol.name=rest

dubbo.protocols.p1.id=dubbo1
dubbo.protocols.p1.name=dubbo
dubbo.protocols.p1.port=20881
dubbo.protocols.p1.host=0.0.0.0

dubbo.protocols.p2.id=dubbo2
dubbo.protocols.p2.name=dubbo
dubbo.protocols.p2.port=20882
dubbo.protocols.p2.host=0.0.0.0

dubbo.protocols.p3.id=dubbo3
dubbo.protocols.p3.name=dubbo
dubbo.protocols.p3.port=20883
dubbo.protocols.p3.host=0.0.0.0
@EnableAutoConfiguration
public class LoadBalanceDubboConsumerDemo {


    @Reference(version = "default", loadbalance = "roundrobin")
    private DemoService demoService;

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

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

        // 用来负载均衡
        for (int i = 0; i < 1000; i++) {
            System.out.println((demoService.sayHello("xiaoyan")));
            try {
                Thread.sleep(1 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
     }
 }       

如果消费者端和生产者端都配置了负载均衡, 则以消费端的配置为准! 我们使用的时候也通常是配置在消费端。

1.2 配置

服务端服务级别


客户端服务级别


服务端方法级别


    

客户端方法级别


    


如果在消费端和服务端都配置了负载均衡策略,以消费端为准。

这其中比较难理解的就是**最少活跃调用数(指的是当前服务正在处理的请求数量)**是如何进行统计的

讲道理,最少活跃数应该是在服务提供者端进行统计的,服务提供者统计有多少个请求正在执行中。但在Dubbo中,就是不讲道理,它是在消费端进行统计的,为什么能在消费端进行统计?

逻辑是这样的:

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

二、服务超时

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

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

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

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

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

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

那么消费端调用服务时,如果超过3s,消费端会收到超时异常(因为消费端超时了),服务端一切正常(服务端没有超时)。

// 服务提供方定义一个服务的时候可以设置超时时间
@Service(version = "timeout", timeout = 6000)
public class TimeoutDemoService implements DemoService {

    @Override
    public String sayHello(String name) {
        System.out.println("执行了服务TimeoutDemoService, 版本: timeout. Name: " + 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);  // 正常访问
    }
}
// 消费端定义超时时间
@EnableAutoConfiguration
public class TimeoutDubboConsumerDemo {

    // 消费端引用一个服务的时候除了可以定义负载均衡之外还可以设置超时时间
    @Reference(version = "timeout", timeout = 3000)
    private DemoService demoService;

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

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

        // 服务调用超时时间为1秒,默认为3秒
        // 如果这1秒内没有收到服务结果,则会报错
        System.out.println((demoService.sayHello("xiaoyan"))); //xxservestub
    }
}


【注意】我们在使用的时候,消费端的超时间要大于服务端这样才有意义。因为如果服务端超时时间小于服务端后,消费端抛异常了,但是服务端没办法被停下来,服务仍然会成型完毕。

并且消费端是有重试机制的,默认重试2次,加上自己第一次的调用一共是三次。所以我们看到服务端那边执行了三次!!!

三、集群容错

集群容错表示:服务消费者在调用某个服务时,这个服务有多个服务提供者,在经过负载均衡后选出其中一个服务提供者之后进行调用,但调用报错后,Dubbo所采取的后续处理策略。

官网地址:http://dubbo.apache.org/zh/docs/v2.7/user/examples/fault-tolerent-strategy/

集群调用失败时,Dubbo 提供的容错方案。

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


各节点关系:

  • 这里的 Invoker 是 Provider 的一个可调用 Service 的抽象,Invoker 封装了 Provider 地址及Service 接口信息
  • Directory 代表多个 Invoker,可以把它看成 List ,但与 List不同的是,它的值可能是动态变化的,比如注册中心推送变更
  • Cluster 将 Directory 中的多个 Invoker 伪装成一个
    Invoker,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个
  • Router 负责从多个 Invoker 中按路由规则选出子集,比如读写分离,应用隔离等
  • LoadBalance 负责从多个 Invoker 中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,需要重选
3.1 集群容错模式

可以自行扩展集群容错策略,参见:集群扩展

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

重试次数配置如下:




    

该配置为缺省配置

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

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

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

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

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

现在广播调用中,可以通过 broadcast.fail.percent 配置节点调用失败的比例,当达到这个比例后,BroadcastClusterInvoker 将不再调用其他节点,直接抛出异常。 broadcast.fail.percent 取值在 0~100 范围内。默认情况下当全部调用失败后,才会抛出异常。 broadcast.fail.percent 只是控制的当失败后是否继续调用其他节点,并不改变结果(任意一台报错则报错)。broadcast.fail.percent 参数 在 dubbo2.7.10 及以上版本生效。

Broadcast Cluster 配置 broadcast.fail.percent。

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

@reference(cluster = "broadcast", parameters = {"broadcast.fail.percent", "20"})

2.1.0 开始支持

Available Cluster
调用目前可用的实例(只调用一个),如果当前没有可用的实例,则抛出异常。通常用于不需要负载均衡的场景。

Mergeable Cluster
将集群中的调用结果聚合起来返回结果,通常和group一起配合使用。通过分组对结果进行聚合并返回聚合后的结果,比如菜单服务,用group区分同一接口的多种实现,现在消费方需从每种group中调用一次并返回结果,对结果进行合并之后返回,这样就可以实现聚合菜单项。

ZoneAware Cluster
多注册中心订阅的场景,注册中心集群间的负载均衡。对于多注册中心间的选址策略有如下四种:

  1. 指定优先级:preferred="true"注册中心的地址将被优先选择

  1. 同中心优先:检查当前请求所属的区域,优先选择具有相同区域的注册中心

  1. 权重轮询:根据每个注册中心的权重分配流量



  1. 缺省值:选择一个可用的注册中心

3.2 集群模式配置

按照以下示例在服务提供方和消费方配置集群模式:



@EnableAutoConfiguration
public class ClusterDubboConsumerDemo {

    @Reference(timeout = 1000, cluster = "failfast")
    private DemoService demoService;

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

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

        System.out.println((demoService.sayHello("xiaoyan")));

    }
}
四、服务降级

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

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

  1. 集群容错是整个集群范围内的容错
  2. 服务降级是单个服务提供者的自身容错

官网地址:http://dubbo.apache.org/zh/docs/v2.7/user/examples/service-downgrade/

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

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

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 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。

2.2.0 以上版本支持

@EnableAutoConfiguration
public class MockDubboConsumerDemo {


        @Reference(version = "timeout", timeout = 1000, mock = "fail: return 123")
//    @Reference(version = "timeout", mock = "true")
    private DemoService demoService;

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

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

        System.out.println((demoService.sayHello("xiaoyan")));
    }

}

五、本地存根

本地存根,名字很抽象,但实际上不难理解,本地存根就是一段逻辑,这段逻辑是在服务消费端执行的,这段逻辑一般都是由服务提供者提供,服务提供者可以利用这种机制在服务消费者远程调用服务提供者之前或之后再做一些其他事情,比如结果缓存,请求参数验证等等。

官网地址:http://dubbo.apache.org/zh/docs/v2.7/user/examples/local-stub/

在 Dubbo 中利用本地存根在客户端执行部分逻辑

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

在 spring 配置文件中按以下方式配置:



提供 Stub 的实现(需要实现这个接口中的方法):

package com.foo;
public class BarServiceStub implements BarService {
    private final BarService barService;
    
    // 构造函数传入真正的远程代理对象
    public BarServiceStub(BarService barService){
        this.barService = barService;
    }
 
    public String sayHello(String name) {
        // 此代码在客户端执行, 你可以在客户端做ThreadLocal本地缓存,或预先验证参数是否合法,等等
        try {
            return barService.sayHello(name);
        } catch (Exception e) {
            // 你可以容错,可以做任何AOP拦截事项
            return "容错数据";
        }
    }
}

注意:

  • Stub 必须有可传入 Proxy 的构造函数。
  • 在 interface 旁边放一个 Stub 实现,它实现 BarService 接口,并有一个传入远程 BarService
    实例的构造函数

测试:
此时我们设置消费端超时时间为1s,如果不配置Stub肯定会报超时错误。我们配置Stub后看看:

@EnableAutoConfiguration
public class StubDubboConsumerDemo {


//    @Reference(version = "timeout", timeout = 1000, stub = "com.jihu.DemoServiceStub")
	// stub="true"表示该结构由本地存根,所以调用失败后会去尝试执行:报名.接口名+Stub这个实现类中的方法 
    @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("xiaoyan")));
    }
}


我们可能会好奇,这个结果是从哪里来的呢?

其实我们有定义其他一个类实现DemoService:

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 "容错数据";
        }
    }
}

这里Stub的名称是有规则的,一般是接口名+Stub. 当然,我们也可直接配置:

@Reference(version = "timeout", timeout = 1000, stub = "com.tuling.DemoServiceStub")

个人更喜欢这种配置的方式,感觉更清楚直接。

六、本地伪装

本地伪装就是Mock,Dubbo中Mock的功能相对于本地存根更简单一点,Mock其实就是Dubbo中的服务容错的解决方案。

官网地址:http://dubbo.apache.org/zh/docs/v2.7/user/examples/local-mock/

如何在 Dubbo 中利用本地伪装实现服务降级

本地伪装通常用于服务降级,比如某验权服务,当服务提供方全部挂掉后,客户端不抛出异常,而是通过 Mock 数据返回授权失败。

在 spring 配置文件中按以下方式配置:



在工程中提供 Mock 实现:

package com.foo;
public class BarServiceMock implements BarService {
    public String sayHello(String name) {
        // 你可以伪造容错数据,此方法只在出现RpcException时被执行
        return "容错数据";
    }
}

如果服务的消费方经常需要 try-catch 捕获异常,如:

Offer offer = null;
try {
    offer = offerService.findOffer(offerId);
} catch (RpcException e) {
   logger.error(e);
}

请考虑改为 Mock 实现,并在 Mock 实现中 return null。如果只是想简单的忽略异常,在 2.0.11 以上版本可用:


七、参数回调

首先,如果当前服务支持参数回调,意思就是:对于某个服务接口中的某个方法,如果想支持消费者在调用这个方法时能设置回调逻辑,那么该方法就需要提供一个入参用来表示回调逻辑。

因为Dubbo协议是基于长连接的,所以消费端在两次调用同一个方法时想指定不同的回调逻辑,那么就需要在调用时在指定一定key进行区分。

官网地址:http://dubbo.apache.org/zh/docs/v2.7/user/examples/callback-parameter/

public interface DemoService {
    // 同步调用方法
    String sayHello(String name);

    // 异步调用方法
    default CompletableFuture sayHelloAsync(String name) {
        return null;
    };

    // 添加回调, 第三个参数是回调
    default String sayHello(String name, String key, DemoServiceListener listener) {
        return null;
    };
}
// DemoService的sayHello方法的index=1的参数是回调对象,服务消费者可以调用addListener方法来添加回调对象,服务提供者一旦执行回调对象的方法就会通知给服务消费者
// @Method(name = "sayHello", arguments = {@Argument(index = 2, callback = true)})}, callbacks = 3
// 上面配置表示sayHello方法的第三个参数参数要支持callback. 虽然这个类中有两个sayHello方法, 但是可以根据参数信息确定是调用有多个参数的那个
// callbacks = 3表示这个服务最大同时支持3个回调. 消费端如果回调超过了这个配置就会报错
@Service(version = "callback", methods = {@Method(name = "sayHello", arguments = {@Argument(index = 2, callback = true)})}, callbacks = 3)
public class CallBackDemoService implements DemoService {

    private final Map listeners = new ConcurrentHashMap();

    public CallBackDemoService() {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    for (Map.Entry entry : listeners.entrySet()) {
                        entry.getValue().changed(getChanged(entry.getKey()));
                    }
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        t.start();

    }


    private String getChanged(String key) {
        return "Changed: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
    }

    @Override
    public String sayHello(String name) {
        return null;
    }

    @Override
    public String sayHello(String name, String key, DemoServiceListener callback) {
        System.out.println("执行了服务CallBackDemoService, 版本: callback. Name: " + name + "key: " + key);

        // 执行完逻辑之后, 其实可以直接调用callback.changed方法来执行回调. 注意这个callback不是消费端传过来的, 而是一个代理对象
        // callback.changed方法调用之后会向消费端发送数据
//         callback.changed("触发回调...");

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

}
@EnableAutoConfiguration
public class CallbackDubboConsumerDemo {


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

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

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

        // 用来进行callback, 第三个参数是回调逻辑(消费端提供的)
        System.out.println(demoService.sayHello("xiaoyan", "d1", new DemoServiceListenerImpl()));
        System.out.println(demoService.sayHello("xiaoyan", "d2", new DemoServiceListenerImpl()));
        System.out.println(demoService.sayHello("xiaoyan", "d3", new DemoServiceListenerImpl()));
    }

}
八、异步调用

官网地址:http://dubbo.apache.org/zh/docs/v2.7/user/examples/async-call/

理解起来比较容易,主要要理解CompletableFuture,如果不理解,就直接把它理解为Future

其他异步调用方式:https://mp.weixin.qq.com/s/U3eyBUy6HBVy-xRw3LGbRQ

public interface DemoService {
    // 同步调用方法
    String sayHello(String name);

    // 异步调用方法
    default CompletableFuture sayHelloAsync(String name) {
        return null;
    };

    // 添加回调, 第三个参数是回调
    default String sayHello(String name, String key, DemoServiceListener listener) {
        return null;
    };
}
@Service(version = "async")
public class AsyncDemoService implements DemoService {

    @Override
    public String sayHello(String name) {
        System.out.println("执行了服务AsyncDemoService的sayHello方法, 版本: async. Name: " + name);
//        URL url = RpcContext.getContext().getUrl();
        return "sayHello执行完毕!name: " +  name;  // 正常访问
    }

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

        // 上面的逻辑会很快执行完,下面会启动一个新线程异步执行sayHello方法,并返回一个CompletableFuture对象
        return CompletableFuture.supplyAsync(() -> {
            // 异步调用sayHello方法
            return sayHello(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);

        System.out.println("异步调用开始...");
        // 调用直接返回CompletableFuture
        CompletableFuture future = demoService.sayHelloAsync("异步调用");  // 5

        // 这里也是异步的,不会阻塞主线程,等到异步任务执行完成后会触发
        future.whenComplete((v, t) -> {
            if (t != null) {
                t.printStackTrace();
            } else {
                System.out.println("回调触发. Response: " + v);
            }
        });

        System.out.println("异步调用结束...");

    }

}


九、泛化调用

官网地址:http://dubbo.apache.org/zh/docs/v2.7/user/examples/generic-reference/

泛化调用可以用来做服务测试。

在Dubbo中,如果某个服务想要支持泛化调用,就可以将该服务的generic属性设置为true,那对于服务消费者来说,就可以不用依赖该服务的接口,直接利用GenericService接口来进行服务调用。

泛化调用的好处是可以不依赖其他服务,现在并没有导入DemoService到当前类中,都是通过配置,不依赖真实接口。

package com.jihu.consumer;

import org.apache.dubbo.config.annotation.Reference;
import org.apache.dubbo.rpc.service.GenericService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;

import java.io.IOException;

@EnableAutoConfiguration
public class GenericDubboConsumerDemo {

    // 这里其实依然是在使用DemoService的default版本
    // 会将带来对象赋值给genericService这个属性
    // 泛化调用意思就是可以把任何服务转化为GenericService
    // ===》 泛化调用的好处是可以不依赖其他服务,现在并没有导入DemoService到当前类中,都是通过配置,不依赖真实接口
    @Reference(id = "demoService", version = "default", interfaceName = "com.jihu.DemoService", generic = true)
    private GenericService genericService;

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

        GenericService genericService = (GenericService) context.getBean("demoService");

        // 定义要调用的方法名、方法参数类型数组、方法值数组
        Object result = genericService.$invoke("sayHello", new String[]{"java.lang.String"}, new Object[]{"xiaoyan"});
        System.out.println(result);


    }

}
十、泛化服务

官网地址:http://dubbo.apache.org/zh/docs/v2.7/user/examples/generic-service/

实现了GenericService接口的就是泛化服务。

package com.jihu.provider.service;

import org.apache.dubbo.config.annotation.Service;
import org.apache.dubbo.rpc.service.GenericException;
import org.apache.dubbo.rpc.service.GenericService;

// 实现了GenericService接口的服务就是泛华服务

// 此时该实现类并没有实现一个具体的接口
// 调用接口的信息是通过配置的,而不是直接引入依赖
@Service(interfaceName = "com.jihu.DemoService", version = "generic")
public class GenericDemoService implements GenericService {
    @Override
    public Object $invoke(String s, String[] strings, Object[] objects) throws GenericException {
        System.out.println("执行了服务GenericDemoService, 版本: generic. s: " + s);

        return "执行的方法是" + s;
    }
}
十一、Dubbo中的REST

官网地址:http://dubbo.apache.org/zh/docs/v2.7/user/rest/

注意Dubbo的REST也是Dubbo所支持的一种协议。

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

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

此时消费端如果是springMVC就可以调用这个dubbo服务了。

配置文件可以定义只使用rest:

dubbo.protocol.name=rest

或者支持多个协议:

dubbo.protocols.p1.id=dubbo1
dubbo.protocols.p1.name=dubbo
dubbo.protocols.p1.port=20881
dubbo.protocols.p1.host=0.0.0.0

dubbo.protocols.p2.id=dubbo2
dubbo.protocols.p2.name=dubbo
dubbo.protocols.p2.port=20882
dubbo.protocols.p2.host=0.0.0.0

dubbo.protocols.p3.id=dubbo3
dubbo.protocols.p3.name=dubbo
dubbo.protocols.p3.port=20883
dubbo.protocols.p3.host=0.0.0.0

# 支持rest协议
dubbo.protocols.p4.id=rest
dubbo.protocols.p4.name=rest
dubbo.protocols.p4.port=8083
package com.jihu.provider.service;

import com.jihu.DemoService;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.config.annotation.Service;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.protocol.rest.support.ContentType;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;


// 当我们使用dubbo提供了一个服务,而消费端不是使用dubbo但是也想调用服务的时候,我们就可以使用REST协议
@Service(version = "rest")
// 如果某个服务只有REST协议可用,那么该服务必须用@Path注解定义访问路径。
@Path("/demo") // 路径
public class RestDemoService implements DemoService {

    @GET // 方法类型
    @Path("/say") // 路径
    // 定义contentType
    @Produces({ContentType.APPLICATION_JSON_UTF_8, ContentType.TEXT_XML_UTF_8})
    @Override
    public String sayHello(@QueryParam("name") String name) { // 接收参数名称
        System.out.println("执行了服务RestDemoService. Name: " + name);

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

}
十二、管理台

github地址:https://github.com/apache/dubbo-admin

我们选择master分支的项目,然后下载zip包。解压后使用IDEA打开。

然后可以看一下readme文章,里面有介绍项目和如何编译启动项目。

修改dubbo-admin-server中的application.properties文件:

# 自定义服务端口
server.port=8090

# centers in dubbo2.7, if you want to add parameters, please add them to the url
# 配置注册中心和配置中心,这里需要修改为自己的zookeeper地址,也可以是redis
admin.registry.address=zookeeper://192.168.131.172:2181
admin.config-center=zookeeper://192.168.131.172:2181
admin.metadata-report.address=zookeeper://192.168.131.172:2181

构建:

mvn clean package -Dmaven.test.skip=true

启动:

mvn --projects dubbo-admin-server spring-boot:run

或:

cd dubbo-admin-distribution/target; java -jar dubbo-admin-0.1.jar

访问:

// 默认端口8080
http://localhost:8090


默认用户名和密码是root和root,可以在配置文件中修改。


服务查询功能:
列出所有的服务,包括组、版本号等。点击进去可以查看:

还有一些服务治理的功能比较重要。

配置管理:
意思是我们可以将application.properties中的配置注册到zookeeper上去,这样就不用每个服务都去配置了。

服务治理 - 动态配置:
这的动态配置指的是每一个服务上面的配置参数,比如超时时间等。

这里的配置支持实时修改,不用再重启服务。

服务治理 - 负载均衡:

负载均衡单独抽出来一个功能。

十三、动态配置

官网地址:http://dubbo.apache.org/zh/docs/v2.7/user/examples/config-rule/

注意动态配置修改的是服务参数,并不能修改服务的协议、IP、PORT、VERSION、GROUP,因为这5个信息是服务的标识信息,是服务的身份证号,是不能修改的。

动态配置意味着修改后实时生效。

13.1 配置规则

在 Dubbo 中配置应用级治理规则和服务级治理规则。

覆盖规则是 Dubbo 设计的在无需重启应用的情况下,动态调整 RPC 调用行为的一种能力。2.7.0 版本开始,支持从服务和应用两个粒度来调整动态配置。

请在服务治理控制台查看或修改覆盖规则。

1、应用粒度

# 将应用demo(key:demo)在20880端口上提供(side:provider)的所有服务(scope:application)的权重修改为1000(weight:1000)。
---
configVersion: v2.7
scope: application
key: demo
enabled: true
configs:
- addresses: ["0.0.0.0:20880"]
  side: provider
  parameters:
  weight: 1000
  ...

2、服务粒度

# 所有消费(side:consumer)DemoService服务(key:org.apache.dubbo.samples.governance.api.DemoService)的应用实例(addresses:[0.0.0.0]),超时时间修改为6000ms
---
configVersion: v2.7
scope: service
key: org.apache.dubbo.samples.governance.api.DemoService
enabled: true
configs:
- addresses: [0.0.0.0]
  side: consumer
  parameters:
  timeout: 6000
  ...
13.2 规则详解

配置模板

---
configVersion: v2.7
scope: application/service
key: app-name/group+service+version
enabled: true
configs:
 - addresses: ["0.0.0.0"]
  providerAddresses: ["1.1.1.1:20880", "2.2.2.2:20881"]
  side: consumer
  applications/services: []
  parameters:
    timeout: 1000
    cluster: failfase
    loadbalance: random
 - addresses: ["0.0.0.0:20880"]
  side: provider
  applications/services: []
  parameters:
    threadpool: fixed
    threads: 200
    iothreads: 4
    dispatcher: all
    weight: 200
...

其中:

  • configVersion 表示 dubbo 的版本
  • scope表示配置作用范围,分别是应用(application)或服务(service)粒度。必填。
  • key 指定规则体作用在哪个服务或应用。必填。

scope=service时,key取值为[{group}:]{service}[:{version}]的组合

  • scope=application时,key取值为application名称
  • enabled=true 覆盖规则是否生效,可不填,缺省生效。
  • configs 定义具体的覆盖规则内容,可以指定n(n>=1)个规则体。必填。

side,
applications
services
parameters
addresses
providerAddresses

对于绝大多数配置场景,只需要理清楚以下问题基本就知道配置该怎么写了:

1、要修改整个应用的配置还是某个服务的配置。

  • 应用:scope: application, key: app-name(还可使用services指定某几个服务)
  • 服务:scope: service, key:group+service+version

2、修改是作用到消费者端还是提供者端。

  • 消费者:side: consumer ,作用到消费端时(你还可以进一步使用providerAddress, applications选定特定的提供者示例或应用)
  • 提供者:side: provider

3、配置是否只对某几个特定实例生效。

  • 所有实例:addresses: [“0.0.0.0”] 或addresses: [“0.0.0.0:*”] 具体由side值决定。
  • 指定实例:addersses[实例地址列表]

4、要修改的属性是哪个。

13.3 示例

1、禁用提供者:(通常用于临时踢除某台提供者机器,相似的,禁止消费者访问请使用路由规则)

---
configVersion: v2.7
scope: application
key: demo-provider
enabled: true
configs:
- addresses: ["10.20.153.10:20880"]
  side: provider
  parameters:
    disabled: true
...

2、调整权重:(通常用于容量评估,缺省权重为 200)

---
configVersion: v2.7
scope: application
key: demo-provider
enabled: true
configs:
- addresses: ["10.20.153.10:20880"]
  side: provider
  parameters:
    weight: 200
...

3、调整负载均衡策略:(缺省负载均衡策略为 random)

---
configVersion: v2.7
scope: application
key: demo-consumer
enabled: true
configs:
- side: consumer
  parameters:
    loadbalance: random
...

4、服务降级:(通常用于临时屏蔽某个出错的非关键服务)

---
configVersion: v2.7
scope: service
key: org.apache.dubbo.samples.governance.api.DemoService
enabled: true
configs:
- side: consumer
 parameters:
   force: return null
...
十四、服务路由

官网地址:http://dubbo.apache.org/zh/docs/v2.7/user/examples/routing-rule/

14.1 路由规则

通过 Dubbo 中的路由规则做服务治理。

路由规则在发起一次RPC调用前起到过滤目标服务器地址的作用,过滤后的地址列表,将作为消费端最终发起RPC调用的备选地址。

  • 条件路由。支持以服务或 Consumer 应用为粒度配置路由规则。
  • 标签路由。以 Provider 应用为粒度配置路由规则。

后续我们计划在 2.6.x 版本的基础上继续增强脚本路由功能。

14.2 条件路由

您可以随时在服务治理控制台 Dubbo-Admin 写入路由规则

1、应用粒度

# app1的消费者只能消费所有端口为20880的服务实例
# app2的消费者只能消费所有端口为20881的服务实例
---
scope: application
force: true
runtime: true
enabled: true
key: governance-conditionrouter-consumer
conditions:
  - application=app1 => address=*:20880
  - application=app2 => address=*:20881
...

2、服务粒度

# DemoService的sayHello方法只能消费所有端口为20880的服务实例
# DemoService的sayHi方法只能消费所有端口为20881的服务实例
---
scope: service
force: true
runtime: true
enabled: true
key: org.apache.dubbo.samples.governance.api.DemoService
conditions:
 - method=sayHello => address=*:20880
 - method=sayHi => address=*:20881
...

各字段含义:
1、scope表示路由规则的作用粒度,scope的取值会决定key的取值。必填。

  • service 服务粒度
  • application 应用粒度

2、Key明确规则体作用在哪个服务或应用。必填。

  • scope=service时,key取值为[{group}:]{service}[:{version}]的组合
  • scope=application时,key取值为application名称

3、enabled=true 当前路由规则是否生效,可不填,缺省生效。

4、force=false 当路由结果为空时,是否强制执行,如果不强制执行,路由结果为空的路由规则将自动失效,可不填,缺省为 false。

5、runtime=false 是否在每次调用时执行路由规则,否则只在提供者地址列表变更时预先执行并缓存结果,调用时直接从缓存中获取路由结果。如果用了参数路由,必须设为 true,需要注意设置会影响调用的性能,可不填,缺省为 false。

6、priority=1 路由规则的优先级,用于排序,优先级越大越靠前执行,可不填,缺省为 0。

7、conditions 定义具体的路由规则内容。必填。

14.2.1 Conditions规则体:

conditions部分是规则的主体,由1到任意多条规则组成,下面我们就每个规则的配置语法做详细说明:

1、格式

  • => 之前的为消费者匹配条件,所有参数和消费者的 URL 进行对比,当消费者满足匹配条件时,对该消费者执行后面的过滤规则。
  • => 之后为提供者地址列表的过滤条件,所有参数和提供者的 URL 进行对比,消费者最终只拿到过滤后的地址列表。
  • 如果匹配条件为空,表示对所有消费方应用,如:=> host != 10.20.153.11
  • 如果过滤条件为空,表示禁止访问,如:host = 10.20.153.10 =>

2、表达式

参数支持:

  • 服务调用信息,如:method, argument 等,暂不支持参数路由
  • URL 本身的字段,如:protocol, host, port 等
  • 以及 URL 上的所有参数,如:application, organization 等

条件支持:

  • 等号 = 表示"匹配",如:host = 10.20.153.10
  • 不等号 != 表示"不匹配",如:host != 10.20.153.10

值支持:

  • 以逗号 , 分隔多个值,如:host != 10.20.153.10,10.20.153.11
  • 以星号 * 结尾,表示通配,如:host != 10.20.*
  • 以美元符 $ 开头,表示引用消费者参数,如:host = $host
14.2.2 Condition示例

1、排除预发布机:

=> host != 172.22.3.91

2、白名单:

register.ip != 10.20.153.10,10.20.153.11 =>

注意:
一个服务只能有一条白名单规则,否则两条规则交叉,就都被筛选掉了

3、黑名单:

register.ip = 10.20.153.10,10.20.153.11 =>

4、服务寄宿在应用上,只暴露一部分的机器,防止整个集群挂掉:

=> host = 172.22.3.1*,172.22.3.2*

5、为重要应用提供额外的机器:

application != kylin => host != 172.22.3.95,172.22.3.96

6、读写分离:

method = find*,list*,get*,is* => host = 172.22.3.94,172.22.3.95,172.22.3.96
method != find*,list*,get*,is* => host = 172.22.3.97,172.22.3.98

7、前后台分离:

application = bops => host = 172.22.3.91,172.22.3.92,172.22.3.93
application != bops => host = 172.22.3.94,172.22.3.95,172.22.3.96

8、隔离不同机房网段:

host != 172.22.3.* => host != 172.22.3.*

9、提供者与消费者部署在同集群内,本机只访问本机的服务:

=> host = $host
14.3 标签路由

标签路由通过将某一个或多个服务的提供者划分到同一个分组,约束流量只在指定分组中流转,从而实现流量隔离的目的,可以作为蓝绿发布、灰度发布等场景的能力基础。

14.3.1 Provider

标签主要是指对Provider端应用实例的分组,目前有两种方式可以完成实例分组,分别是动态规则打标和静态规则打标,其中动态规则相较于静态规则优先级更高,而当两种规则同时存在且出现冲突时,将以动态规则为准。

1、动态规则打标,可随时在服务治理控制台下发标签归组规则

# governance-tagrouter-provider应用增加了两个标签分组tag1和tag2
# tag1包含一个实例 127.0.0.1:20880
# tag2包含一个实例 127.0.0.1:20881
---
  force: false
  runtime: true
  enabled: true
  key: governance-tagrouter-provider
  tags:
    - name: tag1
      addresses: ["127.0.0.1:20880"]
    - name: tag2
      addresses: ["127.0.0.1:20881"]
 ...

2、静态打标


or:


or:

java -jar xxx-provider.jar -Ddubbo.provider.tag={the tag you want, may come from OS ENV}
14.3.2 Consumer
RpcContext.getContext().setAttachment(Constants.REQUEST_TAG_KEY,"tag1");

请求标签的作用域为每一次 invocation,使用 attachment 来传递请求标签,注意保存在 attachment 中的值将会在一次完整的远程调用中持续传递,得益于这样的特性,我们只需要在起始调用时,通过一行代码的设置,达到标签的持续传递。

目前仅仅支持 hardcoding 的方式设置 requestTag。注意到 RpcContext 是线程绑定的,优雅的使用 TagRouter 特性,建议通过 servlet 过滤器(在 web 环境下),或者定制的 SPI 过滤器设置 requestTag。

规则详解:

格式:

  • Key明确规则体作用到哪个应用。必填。
  • enabled=true 当前路由规则是否生效,可不填,缺省生效。
  • force=false 当路由结果为空时,是否强制执行,如果不强制执行,路由结果为空的路由规则将自动失效,可不填,缺省为 false。
  • runtime=false 是否在每次调用时执行路由规则,否则只在提供者地址列表变更时预先执行并缓存结果,调用时直接从缓存中获取路由结果。如果用了参数路由,必须设为
    true,需要注意设置会影响调用的性能,可不填,缺省为 false。
  • priority=1 路由规则的优先级,用于排序,优先级越大越靠前执行,可不填,缺省为 0。
  • tags 定义具体的标签分组内容,可定义任意n(n>=1)个标签并为每个标签指定实例列表。必填。
    – name, 标签名称
  • addresses, 当前标签包含的实例列表

降级约定:

  1. request.tag=tag1 时优先选择 标记了tag=tag1 的 provider。若集群中不存在与请求标记对应的服务,默认将降级请求 tag为空的provider;如果要改变这种默认行为,即找不到匹配tag1的provider返回异常,需设置request.tag.force=true。
  2. request.tag未设置时,只会匹配tag为空的provider。即使集群中存在可用的服务,若 tag 不匹配也就无法调用,这与约定1不同,携带标签的请求可以降级访问到无标签的服务,但不携带标签/携带其他种类标签的请求永远无法访问到其他标签的服务。

提示:
2.6.x 版本以及更早的版本请使用老版本路由规则
自定义路由参考路由扩展

十五、蓝绿发布、灰度发布

https://zhuanlan.zhihu.com/p/42671353

十六、Zookeeper可视化客户端工具

Zookeeper可视化客户端:ZooInspector.zip

下载地址:https://gitee.com/tuojihu/my-dubbo

解压后进入到build目录:

启动服务:

D:下载ZooInspectorbuild>java -jar zookeeper-dev-ZooInspector.jar


点击左上角绿色按钮输入连接地址,然后点击OK.

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/345277.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号