You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@dubbo.apache.org by "chickenlj (GitHub)" <gi...@apache.org> on 2018/12/07 09:21:48 UTC

[GitHub] [incubator-dubbo] chickenlj opened issue #2911: [2.7.0-development docs] How to use Config Centre to externalize configurations and manage governance rules.

本篇主要包含两部分内容:演示如何使用ConfigCenter配置启动Dubbo应用;演示如何使用覆盖、路由等规则实现服务治理;共包含9个示例,所有示例都存储在[dubbo-samples](https://github.com/dubbo/dubbo-samples/tree/samples-for-2.7.0-SNAPSHOT)仓库,并且在详细讲解每个示例时我们都给出了链接。

## 启动配置加载

2.7版本开始,在启动阶段,Dubbo支持从ConfigCenter、XML或Annotation、dubbo.properties文件以及System Properties等读取配置,并自动根据优先级完成配置的覆盖。其中ConfigCenter配置方式是2.7中新增的,并且对各种配置的读取和覆盖关系做了系统的优化。

默认优先级为:System Properties -> ConfigCenter -> XML或Annotation -> dubbo.properties,从左到右优先级依次降低。

下面,我们将结合结合常见的几种Dubbo应用的开发启动方式,包括API、Spring(XML Annotation)等,来看一看这些不同配置模式是如何配合使用的。

### [Spring XML示例](https://github.com/dubbo/dubbo-samples/tree/samples-for-2.7.0-SNAPSHOT/dubbo-samples-configcenter/dubbo-samples-configcenter-xml)

以下是2.7之前的一个典型的XML配置使用示例

```xml
<dubbo:application name="governance-conditionrouter-provider"/>
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<dubbo:protocol name="dubbo" port="-1"/>
<dubbo:provider timeout="5000" filter="-validator"/>

<bean id="demoService" class="org.apache.dubbo.samples.governance.impl.DemoServiceImpl"/>
<dubbo:service interface="org.apache.dubbo.samples.governance.api.DemoService" ref="demoService"/>
```

```xml
<dubbo:application name="governance-conditionrouter-consumer"/>
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<dubbo:consumer timeout="5000"/>

<dubbo:reference id="demoService" interface="org.apache.dubbo.samples.governance.api.DemoService"/>
```

我们需要指定application应用基本信息、registry注册中心地址、provider/consumer默认全局配置、monitor监控中心地址、protocol协议配置、service/reference暴露服务等。

在引入ConfigCenter后,我们可以将registry、monitor、provider/consumer等全局性的配置在ConfigCenter中集中式存储,这样做对于多套环境的配置集中式管控是非常有用的;同时ConfigCenter对接的很多配置中心实现是支持分组分层次存储的,比如Apollo, Nacos等,因此对于一些特定的应用级别的配置,也可以通过ConfigCenter实现集中式的管控,如application、protocol等,而Dubbo框架会自动实现全局级配置、应用级配置的合并与覆盖。

接下来,我们通过一个实例看一下如何配合ConfigCenter来开发Dubbo应用。

首先,看一下去除全局性配置的provider和consumer端的XML配置

```xml
<dubbo:application name="configcenter-provider"/>

<dubbo:configcenter address="zookeeper://127.0.0.1:2181"/>

<bean id="demoService" class="org.apache.dubbo.samples.configcenter.impl.DemoServiceImpl"/>
<dubbo:service async="true" interface="org.apache.dubbo.samples.configcenter.api.DemoService" ref="demoService"/>
```

```xml
<dubbo:application name="configcenter-consumer"/>

<dubbo:configcenter address="zookeeper://127.0.0.1:2181"/>

<dubbo:reference id="demoService" interface="org.apache.dubbo.samples.configcenter.api.DemoService"/>
```

主要变化是增加了一条configcenter的配置,指定了configcenter的地址,删除了registry、monitor全局性的配置,而Service/reference的配置并没有变化,同时application这一个表明应用身份的配置我们也依然保留。

1. 全局配置

现在来看一下从XML移除的全局性配置,我们是怎么存储在ConfigCenter配置中心的,我们以Zookeeper为例,配置默认存储在/dubbo/config/dubbo/dubbo.properties目录下,配置内容&格式如下:

```Properties
## 默认ZK路径 /dubbo/config/dubbo/dubbo.properties
dubbo.registry.address=zookeeper://127.0.0.1:2181
#global config for consumer
dubbo.consumer.timeout=6000
#global config for provider
dubbo.protocol.port=20990
dubbo.protocol.name=dubbo
dubbo.provider.timeout=5000
dubbo.provider.filter=-validator
```

这样,开启ConfigCenter的应用将会自动从ConfigCenter读取配置,所以本地Dubbo应用不再需要关心这些配置。而如果本地已经有配置的话,ConfigCenter默认会以高优先级覆盖本地配置(我们也可以通过`-Ddubbo.configcenter.priority=false`调整ConfigCenter的优先级低于本地配置,以方便本地开发测试)。  

1. 应用级配置

   如果应用自身需要覆盖全局配置,或者也希望将某些应用自身的配置集中托管在ConfigCenter,也可以制定应用专有的配置内容。

   如下,我们在ConfigCenter为configcenter-consumer应用指定了其特有的一些配置:

   ```properties
   ## 默认ZK路径: /dubbo/config/configcenter-consumer/dubbo.properties
   #应用自身的配置
   dubbo.application.qos.port=33333
   dubbo.reference.org.apache.dubbo.samples.configcenter.api.DemoService.timeout=9999
   
   #任意覆盖全局配置
   dubbo.consumer.timeout=6666
   ```

2. [高级用法,多Protocol](https://github.com/dubbo/dubbo-samples/tree/samples-for-2.7.0-SNAPSHOT/dubbo-samples-configcenter/dubbo-samples-configcenter-multiprotocol)

   // 功能改进中。。。

   我们以多Protocol暴露服务为例来讲解在需要多个全局配置项时,该怎么配合ConfigCenter使用,对于多Registry等场景适用相同配置方式。

   Dubbo支持多协议暴露服务,本示例中我们将分别以dubbo协议和hessian协议暴露DemoService服务。

   为了从全局上统一不同协议的配置,如dubbo协议都使用20990端口,hessian协议都使用8090端口。首先,我们需要在ConfigServer配置两个protocol,以id区分,分别命名为dubbo和hessian。

   ```Properties
   #key格式: dubbo.{配置项名称}.{id}.{property}
   dubbo.protocol.dubbo.name=dubbo
   dubbo.protocol.dubbo.port=20990
   
   dubbo.protocol.hessian.name=hessian
   dubbo.protocol.hessian.port=8080
   ```

   在XML中定义bean

   ```xml
   <dubbo:application name="configcenter-provider"/>
   <dubbo:configcenter type="zookeeper" address="127.0.0.1:2181" configfile="dubbo.properties"/>
   
   <!-- This id here should match with that in the ConfigCenter 
    FIXME, still working to avoid this kind of configuration.
   -->
   <dubbo:protocol id="dubbo" />
   <dubbo:protocol id="hessian" />
   
   <bean id="demoService" class="org.apache.dubbo.samples.configcenter.impl.DemoServiceImpl"/>
   
   <dubbo:service protocol="dubbo" interface="org.apache.dubbo.samples.configcenter.api.DemoService" ref="demoService"/>
   <dubbo:service protocol="hessian" interface="org.apache.dubbo.samples.configcenter.api.DemoService" ref="demoService"/>
   ```

   理想情况下,我们期望避免<dubbo:protocol id="dubbo" />这种占位符式的配置,来实现多protocol配置在ConfigCenter的配置托管。

   ​

### [Spring Annotation示例](https://github.com/dubbo/dubbo-samples/tree/samples-for-2.7.0-SNAPSHOT/dubbo-samples-configcenter/dubbo-samples-configcenter-annotation)

在Annotation驱动的Dubbo应用中,我们的配置形式是properties+annotation。

annotation配置和之前完全一致,我们在此不做说明,唯一需要做的就是需要将之前XML示例中的<dubbo:configcenter .../>配置转化为properties形式:

```properties
dubbo.application.name=configcenter-annotation-provider
dubbo.configcenter.address=zookeeper://127.0.0.1:2181
dubbo.configcenter.configfile=dubbo.properties
```

### [应用托管配置示例](https://github.com/dubbo/dubbo-samples/tree/samples-for-2.7.0-SNAPSHOT/dubbo-samples-configcenter/dubbo-samples-configcenter-externalconfiguration)

通过前面的示例,我们发现2.7中一个明显的变化是要求提供一个配置中心地址`<dubbo:configcenter …/>`,Dubbo框架会从此配置中心加载配置。

接下来我们的示例将使用Spring boot框架开发,如果继续使用`<dubbo:configcenter address="zookeeper://127.0.0.1:2181"/>`的配置方式,则这次所需的Dubbo配置和单纯使用Spring并无多大区别。

而如果你正使用Spring Boot开发应用的话,在接触Dubbo之前,或许你的应用已经有一套配置读取、组合、覆盖的插件并且工作良好,那么你可以选择将配置加载的工作继续交给这个插件来完成,而Dubbo提供了相应的入口来让你将加载好的Dubbo配置一股脑的塞进去。而这个配置读取的中转站就是Spring的Environment,也就是说你的配置插件要将所有Dubbo相关的配置聚合并设置到Spring Environment中,Dubbo框架会自动从这里加载所需的配置。

我们以使用Apollo配置中心为例,Apollo已经和Spring Boot做了很好的整合,他会在启动阶段将配置读入Spring Environment。

假设在Apollo中已经有如下配置:

```Properties
#key: dubbo.properties
dubbo.application.name=configcenter-annotation-demo
dubbo.configcenter.address=zookeeper://127.0.0.1:2181
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
dubbo.consumer.timeout=3000
```

则已有的Apollo-spring-boot插件负责自动将此配置项注入Spring Environment。

我们只需要增加以下ConfigCenter配置,Dubbo框架就可以进一步自动从Spring Environment加载配置

```java
@Configuration
static public class ProviderConfiguration {
    @Bean
    public ConfigCenterBean configCenterBean() {
        ConfigCenterBean configCenterBean = new ConfigCenterBean();
        configCenterBean.setAuto(true);
        return configCenterBean;
    }
}
```

需要注意的是,在我们的samples示例中,我们并没有真正的对接Apollo配置中心,而是用本地文件dubbo-properties-in-configcenter.properties来模拟Apollo存储,并通过扩展MyEnvironmentPostProcessor来模拟配置载入Environment的过程。

如果你有定制化的配置管理中心,且你还想遵循之前的配置存储模式,这个功能可以很好的满足需求。

### [API使用示例](https://github.com/dubbo/dubbo-samples/tree/samples-for-2.7.0-SNAPSHOT/dubbo-samples-configcenter/dubbo-samples-configcenter-api)

和Spring使用方式一样,我们也有两种方式来通过API接口加载外部配置

1. 指定ConfigCenter地址,由Dubbo加载外部配置

```java
ConfigCenterConfig configCenter = new ConfigCenterConfig();
configCenter.setAddress("zookeeper://127.0.0.1:2181");
configCenter.init();
```

1. 或者,如果你已经能方便的拿到所有的配置项,直接设置到Dubbo的Environment上下文中

```java
/**
* 这里的map是类似dubbo.properties的格式,如:
* map.put("dubbo.registry.address", "zookeeper://127.0.0.1:2181");
* map.put("dubbo.configcenter.address", "zookeeper://127.0.0.1:2181");
* 注意,这里的configcenter仅用于服务治理
*/
Environment.getInstance().setExternalConfiguration(map);
// Must init ConfigCenter in somewhere.
ConfigCenterConfig configCenter = new ConfigCenterConfig();
configCenter.init();
```



### 特殊说明

1. 本地配置优先于ConfigCenter

   默认情况下,ConfigCenter配置优先级是高于本地XML或properties配置的,但是有些情况下,如果提高本地配置优先级,则可以通过增加以下动态参数实现:

   ```java
   -Ddubbo.configcenter.config.priority=false
   ```

   ​

2. 运行期动态参数读取

   ```java
   @Activate(group = { Constants.CONSUMER })
   public class TraceFilter implements Filter {
   
       public TraceFilter() {
       }
   
       @Override
       public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
           Result result = invoker.invoke(invocation);
           String isTraceOn = Environment.getInstance.getCompositeConfiguration().get("trace.on");
           if (Boolean.valueOf(isTraceOn)) {
            System.out.println("trace filter: " + result.getValue());   
           }
           return result;
       }
   }
   ```

   ​

## 服务治理规则推送

### 覆盖规则

覆盖规则是Dubbo设计在无需重启应用的情况下,动态调整RPC调用行为的一种能力,我们的所有示例仍将以Consumer->Provider的调用为例,说明规则如何配置以及规则能实现的能力。

在改造之后,覆盖配置规则变得相当灵活:支持应用和服务粒度的配置,在此之上还可以进一步指定对某些或全部机器生效,同时可以选择让动态配置可以工作在Consumer端或Provider端来实现不同的作用域。

#### [服务级别配置](https://github.com/dubbo/dubbo-samples/tree/samples-for-2.7.0-SNAPSHOT/dubbo-samples-governance/dubbo-samples-servicelevel-override)

示例中,我们给provider设置了3s的sleep时间,而consumer端仍然执行默认的1s的超时,因此所有consumer端的调用都将会抛出超时异常

```java
public class DemoServiceImpl implements DemoService {
    @Override
    public String sayHello(String name) {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Hello " + name + ", response from provider: " + RpcContext.getContext().getLocalAddress();
    }
}
```

```java
org.apache.dubbo.rpc.RpcException: Failed to invoke the method sayHello in the service org.apache.dubbo.samples.governance.api.DemoService. Tried 3 times of the providers [192.168.1.6:20890] (1/1) from the registry 127.0.0.1:2181 on the consumer 192.168.1.6 using the dubbo version 2.7.0-SNAPSHOT. Last error is: Invoke remote method timeout. method: sayHello, provider: dubbo://192.168.1.6:20890/org.apache.dubbo.samples.governance.api.DemoService?anyhost=true&application=governance-serviceoverride-consumer&check=false&dubbo=2.0.2&generic=false&interface=org.apache.dubbo.samples.governance.api.DemoService&methods=sayHello&pid=7364&register.ip=192.168.1.6&remote.application=governance-serviceoverride-provider&remote.timestamp=1541946438268&side=consumer&timestamp=1541946555836&valid=true, cause: Waiting server-side response timeout. start time: 2018-11-11 22:29:29.315, end time: 2018-11-11 22:29:30.319, client elapsed: 1 ms, server elapsed: 1003 ms, timeout: 1000 ms, request: Request [id=2, version=2.0.2, twoway=true, event=false, broken=false, data=RpcInvocation [methodName=sayHello, parameterTypes=[class java.lang.String], arguments=[world], attachments={path=org.apache.dubbo.samples.governance.api.DemoService, interface=org.apache.dubbo.samples.governance.api.DemoService, version=0.0.0}]], channel: /192.168.1.6:49615 -> /192.168.1.6:20890
```

接下来,我们通过下发一条覆盖规则到配置中心,来动态的增大consumer端的超时时间到6s

```yaml
# All Consumers that consume the service org.apache.dubbo.samples.governance.api.DemoService will increase the timeout value to 6000
---
apiVersion: 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
...
```

以上是一个特定于org.apache.dubbo.samples.governance.api.DemoService的服务级别的配置,这条规则将在所有消费DemoService服务的应用中生效,并且我们通过设置address为0.0.0.0解除了对消费端IP的限定,规则中唯一的配置项是rules下方的time: 6000。

当配置推送成功后,consumer端执行了6s的超时时间,超时解除,所有调用恢复正常。

#### [应用级别配置](https://github.com/dubbo/dubbo-samples/tree/samples-for-2.7.0-SNAPSHOT/dubbo-samples-governance/dubbo-samples-applevel-override)

除了服务级别配置外,Dubbo还支持应用级配置,来一次性调整应用中所有服务的运行时行为。

接下来我们将演示一个如何调整服务Provider权重的示例。为了观察Providre权重变化的效果,我们需要启动两个Provider实例,他们的Dubbo协议工作端口分别为20880、20881,默认情况下这两个实例有相同的权重weight=100,这时Consumer端的流量会均衡的分配到两个实例。

接下来,我们尝试通过下发以下规则到配置中心,将其中一个实例的权重提升10倍。

```yaml
# Execute on demo-provider only
# the governance-appoverride-provider instances on those provides who's port is 20880 will be lifted to 1000
# this will take effect on all services on governance-appoverride-provider.
---
apiVersion: v2.7
scope: application
key: governance-appoverride-provider
enabled: true
configs:
- side: provider
  addresses: ["0.0.0.0:20880"] #示例中,我们的provider实例是以端口区分的
  parameters:
    weight: 1000
...
```

通过指定scope=application,这条规则将对整个provider生效,在我们的示例中,所有Consumer消费的所有服务DemoService和DemoService2都将使用新的权重值1000。

我们可以启动两个Consumer,验证规则是否对所有消费者生效。

### 规则说明

配置模板

```yaml
---
apiVersion: v2.7
scope: application/service
key: governance-appoverride-provider/group+service+version
enabled: true
configs:
- addresses: ["0.0.0.0"] # must not contain port when works on consumer side
  providerAddresses: ["1.1.1.1:20880", "2.2.2.2:20881"] # must contain port
  side: consumer
  applications/services: []
  parameters:
    timeout: 1000
    cluster: failfase
    loadbalance: random
- addresses: ["0.0.0.0:20880"] # must contain port when works on provider side, specify multiple addresses if you want to apply to multiple ports.
  side: provider
  applications/services: []
  parameters:
    threadpool: fixed
    threads: 200
    iothreads: 4
    dispatcher: all
    queues: 0
    weight: 200
...
```

- 作用在consumer端的配置addresses地址必须不能包含port
- 作用在provider端的配置addresses地址必须包含port
- providerAddresses是为consumer端配置特有的项目,可选

注意事项

- 对于group+service+version的三元组唯一确定的一个服务,只能配置一条覆盖规则,规则可以随时更新;
- 对于一个application,只能配置一条覆盖规则,规则可以随时更新;
- Service级别的覆盖规则优先级高于application级别的覆盖规则



### 路由规则

#### [Condition条件路由](https://github.com/dubbo/dubbo-samples/tree/samples-for-2.7.0-SNAPSHOT/dubbo-samples-governance/dubbo-samples-configconditionrouter)

这里的条件路由和2.6及之前版本的路由规则是一致的,只是在2.7中,我们允许同时配置服务及应用粒度的路由规则,并且将所有的规则收敛为一条,也就是每个服务或每个应用只能有一条服务规则,这样将更利于服务治理的管控。

启动两个provider实例,分别工作在20880端口和20881端口。

现在,我们期望将DemoService服务的所有sayHello方法调用都路由到20880实例,而DemoService2服务的所有sayHello方法调用都路由到20881实例

```yaml
---
scope: application
force: true
runtime: true
enabled: true
priority: 2
key: governance-conditionrouter-consumer
conditions:
 black-white-list:
     -interface=org.apache.dubbo.samples.governance.api.DemoService&method=sayHello=>address=*:20880
     - interface=org.apache.dubbo.samples.governance.api.DemoService2&method=sayHello=>address=*:20881
   
...
```

conditions部分可以指定任意多个路由规则,并且每个子规则都和之前的条件路由规则一致:=>左边是匹配条件;=>右边是过滤条件。

#### [Tag路由](https://github.com/dubbo/dubbo-samples/tree/samples-for-2.7.0-SNAPSHOT/dubbo-samples-governance/dubbo-samples-tagrouter)

Tag路由是在2.7版本中新增的路由功能,它可以作为实现灰度、环境隔离等应用场景的基础能力。

通过下发路由规则,我们可以实现对provider实例的动态打tag标,这样当前端带有特定tag标记的流量请求到来时,我们可以确保这些流量被路由到带有特定tag标记的provider实例上。

通过下面的路由规则,我们将应用governance-tagrouter-provider的两个provider实例分别打标为tag1和tag2

```yaml
---
force: false
runtime: true
enabled: true
priority: 1
key: governance-tagrouter-provider
tags:
- name: tag1
  addresses: ["instance ip:20881"] #待支持通配符
- name: tag2
  addresses: ["instance ip:20880"] #待支持通配符
...
```

接下来,在consumer端,我们将为请求打上特定的tag标记

```java
RpcContext.getContext().setAttachment("tag", "tag1");
String hello = demoService.sayHello("world"); // call remote method
System.out.println(hello); // get result

RpcContext.getContext().setAttachment("tag", "tag2");
String hello2 = demoService2.sayHello("world again"); // call remote method
```

我们能看到,带有tag1标记的流量都到达20881节点,而带有tag2标记的流量都到达20880节点。

**容错策略:**如果带有某个tag标记的请求到来时,经过路由发现找不到任何带有对应tag标记的provider(如tag3在本利中没有provider实例),则容错策略会默认将流量导向任意一个没有tag标记的节点,我们也可以通过force=true来强制返回空地址列表。



### 路由规则说明

#### tag模板

```yaml
---
force: false
runtime: true
enabled: true
priority: 1
key: governance-tagrouter-provider # must specify a provider name
tags:
- name: tag1
  addresses: ["instance ip:20881"] # TODO, do not support wildcards
- name: tag2
  addresses: ["instance ip:20880"] # TODO, do not support wildcards
...
```

#### condition模板

```yaml
---
scope: application/service
force: true
runtime: true
enabled: true
priority: 2
key: governance-conditionrouter-consumer/group+service+version
conditions:
  - condition1 # the key can be interface, application, host, port, address, method, ...
  - condition2
```

[ Full content available at: https://github.com/apache/incubator-dubbo/issues/2911 ]
This message was relayed via gitbox.apache.org for notifications@dubbo.apache.org

[GitHub] [incubator-dubbo] chickenlj closed issue #2911: [2.7.0-development docs] How to use Config Centre to externalize configurations and manage governance rules.

Posted by "chickenlj (GitHub)" <gi...@apache.org>.
[ issue closed by chickenlj ]

[ Full content available at: https://github.com/apache/incubator-dubbo/issues/2911 ]
This message was relayed via gitbox.apache.org for notifications@dubbo.apache.org


[GitHub] [incubator-dubbo] edwardlee03 commented on issue #2911: [2.7.0-development docs] How to use Config Centre to externalize configurations and manage governance rules.

Posted by "edwardlee03 (GitHub)" <gi...@apache.org>.
**Tag路由**实现`灰度、环境隔离`等应用场景的基础能力,解决效率与资源利用率问题 👍

[ Full content available at: https://github.com/apache/incubator-dubbo/issues/2911 ]
This message was relayed via gitbox.apache.org for notifications@dubbo.apache.org