[WIP] [OSPP] [2.2.x] feat: Add regional affinity routing and adapt clients and gateways (#3449)

feat: Add regional affinity routing and adapt clients and gateways
2.2.x-ospp2023
YuLuo 1 year ago committed by GitHub
parent f42f8b8915
commit 7da2ae9eec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,24 @@
# app home
APP_HOME=/sca-service/sca-routing
# image and container prefix
DOCKER_PREFIX=sca-label-routing
# Routing service provider port
PROVIDER_PORT_A1 = 19091
PROVIDER_PORT_A2 = 19092
PROVIDER_PORT_A3 = 19093
PROVIDER_PORT_A4 = 19094
# Routing web client consumer port
FEIGN_CONSUMER_PORT=19095
REACTIVE_CONSUMER_PORT=19096
REST_CONSUMER_PORT=19097
# Routing gateway consumer port
GATEWAY_CONSUMER_PORT=19098
ZUUL_CONSUMER_PORT=19099
# Nacos server port
NACOS_PORT=8848
NACOS_GRPC_PORT=9848

@ -0,0 +1,589 @@
# Label Routing Docker-Compose Quickstart
本文章将会说明如何使用 Docker 来完成 Spring-Cloud-Alibaba (以下简称 SCA) 服务治理功能中标签路由功能的使用示例。
## 1. 环境准备
### 1.1 准备服务 Jar 包
1. 进入 `spring-cloud-alibaba\spring-cloud-alibaba-examples` 目录下,执行 `mvn clean package` 打包 example 服务 jar 包;
2. 进入 `spring-cloud-alibaba-examples/governance-example/docker-compose-example-quickstart/label-routing-quickstart` 目录下,执行以下脚本移动 jar 包到指定目录下;
```shell
# linux/mac 直接执行
./move-example-jar.sh
# windows 下使用 github bash 执行
,/move-example-jar.sh
```
3. 分别进入 `label-routing-gateway-consumer-example` `label-routing-service-provider-example` `label-routing-webClient-consumer-example`label-routing-quickstart 子级目录) 目录下查看 `app.jar` 是否存在。如果不存在手动移动即可;
4. 检查 `application-docker.yml` 配置文件是否准确。
### 1.2 安装 Docker
如果本机机器中没有 Docker 和 Docker-Compose 请参照如下链接进行安装
- Dockerhttps://docs.docker.com/engine/install/
- Docker-Composehttps://docs.docker.com/compose/install/
### 1.3 目录说明
```markdown
└─label-routing-quickstart
│ .env # Docker env 设置
│ .gitignore
│ docker-compose-service-provider.yml # 服务提供者和 nacos server
│ docker-compose-gateway-consumer.yml # 网关消费者 example
│ docker-compose-web-client-consumer.yml # 三种请求客户端消费者
│ move-example-jar.sh # 移动 jar 包脚本
│ README.md # 参考 README 文件
│ README-zh.md
|
├─assets
│ └─img
├─label-routing-gateway-consumer-example
│ │ DockerFile # 启动服务消费者的 DockerFile 文件
│ │
│ ├─label-routing-gateway-consumer-example
│ │ app.jar # spring boot 应用 jar 包
│ │ application-docker.yml # spring boot 应用在 docker 容器中启动时的配置文件
│ │
│ └─label-routing-zuul-consumer-example
│ app.jar
│ application-docker.yml
├─label-routing-service-provider-example # 服务提供者文件夹
│ app.jar
│ application-dockerA1.properties
│ application-dockerA2.properties
│ application-dockerA3.properties
│ application-dockerA4.properties
│ DockerFile
│ start-label-routing-service-provider.sh # 服务提供者启动脚本docker内
└─label-routing-web-client-consumer-example # 三种请求客户端消费者文件夹
│ DockerFile
├─label-routing-feign-consumer-example
│ app.jar
│ application-docker.yml
├─label-routing-reactive-consumer-example
│ app.jar
│ application-docker.yml
└─label-routing-rest-consumer-example
app.jar
application-docker.yml
```
### 1.4 Postman 测试脚本
1. 进入 `spring-cloud-alibaba-examples/governance-example/label-routing-example/gateway-consumer-example/resources` 文件夹下,将网关消费者请求脚本导入 postman 中;
2. 进入 `spring-cloud-alibaba-examples/governance-example/label-routing-example/web-client-consumer-example/resources` 文件夹下,将客户端消费者请求脚本导入 postman 中。
## 2. 启动服务提供者和 nacos-server
1. 进入 `spring-cloud-alibaba-examples/governance-example/docker-compose-example-quickstart/label-routing-quickstart` 文件夹下,在 terminal 中输入以下命令以启动 label-routing-service-provider-example 和 nacos-server
```shell
# 构建 docker 镜像
docker-compose -f docker-compose-service-provider.yml build
# 启动 docker 容器
docker-compose -f docker-compose-service-provider.yml up -d
# 可以去掉最后的 -d 参数,查看服务启动过程中的日志输出
docker-compose -f docker-compose-service-provider.yml up
```
2. 间隔一段时间之后(需要等待 docker 容器启动完成)本地机器访问 nacos 的控制台地址 (http://127.0.0.1:8848/nacos) 查看已经注册上线的 service-provider其中包含四个服务实例。
<img src="./assets/img/nacos-service-provider.png" style="zoom: 80%;" >
3. 如果出现以上服务注册信息,即证明 docker 容器启动成功!点击详情可以看到服务提供者实例的元数据信息。
<img src="./assets/img/service-provider-metadata.png" style="zoom: 80%;" >
## 3. 客户端服务消费者示例
### 3.1 启动服务消费者容器
1. 进入 `spring-cloud-alibaba-examples/governance-example/docker-compose-example-quickstart/label-routing-quickstart` 文件夹下,执行以下命令启动请求客户端 example 应用容器;
```shell
# 构建 docker 镜像
docker-compose -f docker-compose-web-client-consumer.yml build
# 启动 docker 容器
docker-compose -f docker-compose-web-client-consumer.yml up -d
# 可以去掉最后的 -d 参数,查看服务启动过程中的日志输出
docker-compose -f docker-compose-web-client-consumer.yml up
```
2. 去往 nacos 查看服务注册情况,等待服务上线之后,出现以下信息证明容器启动成功;
<img src="./assets/img/nacos-webClient-consumer.jpg" style="zoom: 80%;" >
3. 进入 postman 中,发起请求,查看服务之间是否可以正常通信**(不添加任务路由规则,只依靠 ribbon 进行负载均衡)**
分别进入 `feign` `rest` `webcleint` 文件中点击 `v1版本测试``v2版本测试` 在不添加路由配置信息时发送请求,查看四种服务提供者是否均可以被消费者正常消费
<img src="./assets/img/postman-webclient-consumer.jpg" style="zoom:67%;" >
分别点击查看响应值是否四种实例均被调用到,确认成功之后进入下一步。**(默认的 ribbon 负载均衡规则为 ZoneAvoidanceRule**
### 3.2 发布路由规则测试标签路由
> 以 feign 客户端调用为例演示Rest 和 WebClient 相同
#### 3.2.1 发布路由规则
> **确保四个服务提供者均能被消费者正常调用!**
服务消费者openfeign-consumer-examplerestTemplate-consumer-examplewebClient-consumer-example实例中设置的路由规则如下
```json
[
{
"targetService": "routing-service-provider",
"labelRouteRule": {
"matchRouteList": [
{
"ruleList": [
{
"type": "header",
"condition": "=",
"key": "tag",
"value": "v2"
},
{
"type": "parameter",
"condition": ">",
"key": "id",
"value": "10"
},
{
"type": "path",
"condition": "=",
"value": "/router-test",
"key": null
}
],
"version": "v2",
"weight": 100,
"fallback": null
}
],
"defaultRouteVersion": "v1"
}
}
]
```
代码对应的路由规则如下:
> 若同时满足请求参数中含有`tag=v2`,请求头中含有 id 且值小于10uri 为`/router-test`则流量全部路由到 v2 版本中,若有一条不满足,则流量路由到 v1 版本中。
规则也支持动态修改,测试动态修改的规则如下:
```json
[
{
"targetService": "routing-service-provider",
"labelRouteRule": {
"matchRouteList": [
{
"ruleList": [
{
"type": "header",
"condition": "=",
"key": "tag",
"value": "v2"
},
{
"type": "parameter",
"condition": ">",
"key": "id",
"value": "10"
},
{
"type": "path",
"condition": "=",
"value": "/router-test",
"key": null
}
],
"version": "v2",
"weight": 50,
"fallback": null
}
],
"defaultRouteVersion": "v1"
}
}
]
```
代码对应的规则如下:
> 若同时满足请求参数中含有`tag=v2`,请 求头中含有 id 且值小于10uri 为`/router-test`,则 50% 流量路由到 v2 版本中,剩下的流量路由到 v1 版本中,若有一条不满足,则流量路由到 v1 版本中。
区域亲和性路由规则如下:
```yml
# label routing configuration
governance:
routing:
region: dev
zone: zone1
# rule: RandomRule
```
> 当服务实例满足所设置的 `region=dev`, `zone=zone1` 规则时,路由到指定服务实例。
#### 3.2.2 区域亲和性路由规则存在时
1. 添加路由规则
```shell
# 预期结果:
# v1Route in 172.18.0.3:18083, region: dev, zone: zone1, version: v1
# v2观察 service-provider 的元数据发现,没有 region=devzone=zone1version=v2 的服务实例,因此区域亲和性路由会退化为标签路由效果,预期为以下结果:
# Route in 172.18.0.3:18082, region: qa, zone: zone2, version: v2
# Route in 172.18.0.3:18084, region: dev, zone: zone2, version: v2
# 测试发现和预期结果匹配!
```
2. 更新路由规则
```shell
# 预期结果:
# v1Route in 172.18.0.3:18083, region: dev, zone: zone1, version: v1
# v2因为设置了区域亲和性路由规则所以即使 v1 和 v2 版本各自 50 的权重,但是还是会根据区域亲和性路由规则选取服务实例, 预期结果为:
# Route in 192.168.2.9:18083, region: dev, zone: zone1, version: v1
# 测试发现和预期结果匹配!
```
#### 3.2.3 区域亲和性路由不存在时
> Note: 因为 docker 镜像有缓存性,可能注释掉之后重新打包存在不生效的情况,重启 docker 服务再次尝试!
为了验证此场景,需要更改配置文件,取消区域亲和性路由配置。**执行 `docker-compose -f docker-compose-web-client-consumer.yml stop` 停止消费者容器。**
`spring-cloud-alibaba-examples/governance-example/docker-compose-example-quickstart/label-routing-quickstart/label-routing-webClient-consumer-example/label-routing-feign-consumer-example/application-docker.yml` 文件中注释区域亲和性路由配置Rest 和 WebClient 消费者同理)
```properties
# label routing configuration
governance:
routing:
# region: dev
# zone: zone1
# rule: RandomRule
```
重新 **构建** 和 **启动** 容器。
1. 添加路由规则
```shell
# 预期结果:
# v1因为没有区域亲和性路由限制所以会在实例之间按照 ribbon 的规则进行负载均衡
# Route in 172.18.0.3:19093, region: dev, zone: zone1, version: v1
# Route in 172.18.0.3:19091, region: qa, zone: zone1, version: v1
# v2因为没有区域亲和性路由限制所以会在实例之间按照 ribbon 的规则进行负载均衡
# Route in 172.18.0.3:19094, region: qa, zone: zone2, version: v2
# Route in 172.18.0.3:19092, region: dev, zone: zone2, version: v2
# 测试发现,符合预期结果
```
2. 更新路由规则
```properties
# 预期结果
# v1因为没有区域亲和性路由限制路由结果按照标签路由选择服务实例所以会在两个实例之间按照 ribbon 的规则进行负载均衡
# Route in 172.18.0.3:19091, region: qa, zone: zone1, version: v1
# Route in 172.18.0.3:19093, region: dev, zone: zone1, version: v1
# v2v1 和 v2 权重各占 50%,所以四种服务实例的调用结果都会出现
# Route in 172.18.0.3:19092, region: qa, zone: zone2, version: v2
# Route in 172.18.0.3:19094, region: dev, zone: zone2, version: v2
# Route in 172.18.0.3:19091, region: qa, zone: zone1, version: v1
# Route in 172.18.0.3:19093, region: dev, zone: zone1, version: v1
# 测试发现,符合预期结果
```
### 3.3 关闭容器
执行以下命令关闭服务消费者容器:
```shell
docker-compose -f docker-compose-web-client-consumer.yml stop
```
## 4. 网关服务消费者示例
### 4.1 启动网关服务消费者容器
1. 进入 `spring-cloud-alibaba\spring-cloud-alibaba-examples\governance-example\label-routing-example\docker-compose-quickstart\label-routing-quickstart` 文件夹下,执行以下命令启动请求客户端 example 应用容器;
```shell
# 构建 docker 镜像
docker-compose -f docker-compose-gateway-consumer.yml build
# 启动 docker 容器
docker-compose -f docker-compose-gateway-consumer.yml up -d
# 可以去掉最后的 -d 参数,查看服务启动过程中的日志输出
docker-compose -f docker-compose-gateway-consumer.yml up
```
2. 去往 nacos 查看服务注册情况,等待服务上线之后,出现以下信息证明容器启动成功:
<img src="./assets/img/nacos-gateway-consumer.png">
3. 进入 postman 中,发起请求,查看服务之间是否可以正常通信**(不添加任务路由规则,只依靠 ribbon 进行负载均衡)**
分别进入 `sc-gw` `zuul` 文件中点击 `v1版本测试``v2版本测试` 在不添加路由配置信息时发送请求,查看四种服务提供者是否均可以被消费者正常消费
<img src="./assets/img/postman-gateway-consumer.png" style="zoom:67%;" >
分别点击请求查看响应值,服务提供者的四个实例均被调用到,确认成功之后进入下一步。**(默认的 ribbon 负载均衡规则为 RandomRule **
### 4.2 发布路由规则测试标签路由
> **以 gateway 为例zuul 网关相同!**
#### 4.1.1 发布路由规则
网关消费者中的标签路由规则如下:
```json
[
{
"targetService": "routing-service-provider",
"labelRouteRule": {
"matchRouteList": [
{
"ruleList": [
{
"type": "header",
"condition": "=",
"key": "tag",
"value": "v2"
},
{
"type": "parameter",
"condition": ">",
"key": "id",
"value": "10"
},
{
"type": "path",
"condition": "=",
"value": "/test-a1",
"key": null
}
],
"version": "v2",
"weight": 100,
"fallback": null
}
],
"defaultRouteVersion": "v1"
}
}
]
```
代码对应的路由规则如下:
> 若同时满足请求参数中含有`tag=v2`,请求头中含有 id 且值小于10uri 为`/test-a1`则流量全部路由到 v2 版本中,若有一条不满足,则流量路由到 v1 版本中。
更新路由规则:
```json
[
{
"targetService": "routing-service-provider",
"labelRouteRule": {
"matchRouteList": [
{
"ruleList": [
{
"type": "header",
"condition": "=",
"key": "tag",
"value": "v2"
},
{
"type": "parameter",
"condition": ">",
"key": "id",
"value": "10"
},
{
"type": "path",
"condition": "=",
"value": "/test-a1",
"key": null
}
],
"version": "v2",
"weight": 50,
"fallback": null
}
],
"defaultRouteVersion": "v1"
}
}
]
```
代码对应的规则如下:
> 若同时满足请求参数中含有`tag=v2`,请 求头中含有 id 且值小于10uri 为`/test-a1`,则 50% 流量路由到 v2 版本中,剩下的流量路由到 v1 版本中,若有一条不满足,则流量路由到 v1 版本中。
区域亲和性路由规则如下:
```yml
# label routing configuration
governance:
routing:
region: dev
zone: zone1
# rule: RandomRule
```
> 当服务实例满足所设置的 `region=dev`, `zone=zone1` 规则时,路由到指定服务实例。
#### 4.1.2 区域亲和性路由规则存在时
1. 添加路由规则
```shell
# 预期结果:
# v1Route in 172.18.0.3:18083, region: dev, zone: zone1, version: v1
# v2观察 service-provider 的元数据发现,没有 region=devzone=zone1version=v2 的服务实例,因此区域亲和性路由会退化为标签路由效果,预期为以下结果:
# Route in 172.18.0.3:19092, region: qa, zone: zone2, version: v2
# Route in 172.18.0.3:19094, region: dev, zone: zone2, version: v2
# 测试发现和预期结果匹配!
```
2. 更新路由规则
```shell
# 预期结果:
# v1Route in 172.18.0.3:19093, region: dev, zone: zone1, version: v1
# v2因为设置了区域亲和性路由规则所以即使 v1 和 v2 版本各自 50 的权重,但是还是会根据区域亲和性路由规则选取服务实例, 预期结果为:
# Route in 172.18.0.3:19093, region: dev, zone: zone1, version: v1
# 测试发现和预期结果匹配!
```
#### 4.1.3 区域亲和性路由不存在时
> Note: 因为 docker 镜像有缓存性,可能注释掉之后存在不生效的情况,重启 docker 服务再次尝试!
为了验证此场景,需要更改配置文件,取消区域亲和性路由配置。**执行 `docker-compose -f docker-compose-gateway-consumer.yml stop` 停止消费者容器。**
`spring-cloud-alibaba-examples/governance-example/docker-compose-example-quickstart/label-routing-quickstart/label-routing-gateway-consumer-example/label-routing-gateway-consumer-example\application-docker.yml` 文件中注释区域亲和性路由配置Zuul 网关消费者同理)
```properties
# Regional affinity routing configuration
governance:
routing:
# region: dev
# zone: zone1
# rule: RandomRule
```
重新 **构建** 和 **启动** 容器。
1. 添加路由规则
```shell
# 预期结果:
# v1因为没有区域亲和性路由限制所以会在实例之间按照 ribbon 的规则进行负载均衡
# Route in 172.18.0.3:19093, region: dev, zone: zone1, version: v1
# Route in 172.18.0.3:19091, region: qa, zone: zone1, version: v1
# v2因为没有区域亲和性路由限制所以会在实例之间按照 ribbon 的规则进行负载均衡
# Route in 172.18.0.3:19094, region: qa, zone: zone2, version: v2
# Route in 172.18.0.3:19092, region: dev, zone: zone2, version: v2
# 测试发现,符合预期结果
```
2. 更新路由规则
```properties
# 预期结果
# v1因为没有区域亲和性路由限制路由结果按照标签路由选择服务实例所以会在两个实例之间按照 ribbon 的规则进行负载均衡
# Route in 172.18.0.3:19091, region: qa, zone: zone1, version: v1
# Route in 172.18.0.3:19093, region: dev, zone: zone1, version: v1
# v2v1 和 v2 权重各占 50所以四种服务实例的调用结果都会出现
# Route in 172.18.0.3:19092, region: qa, zone: zone2, version: v2
# Route in 172.18.0.3:19094, region: dev, zone: zone2, version: v2
# Route in 172.18.0.3:19091, region: qa, zone: zone1, version: v1
# Route in 172.18.0.3:19093, region: dev, zone: zone1, version: v1
# 测试发现,符合预期结果
```
### 4.3 关闭容器
执行以下命令关闭服务消费者容器:
```shell
docker-compose -f docker-compose-gateway-consumer.yml stop
```
## 5. 服务容器移除
1. 执行以下命令停止正在运行的服务提供者和 nacos server 容器
```shell
docker-compose -f docker-compose-service-provider.yml stop
```
2. 执行以下命令删除所有容器和镜像
```shell
# 按条件筛选之后删除镜像
docker rmi `docker images | grep sca-routing-** | awk {print $1}` -f
# 按条件筛选之后删除容器
docker rm `docker ps -a | grep sca-routing-** | awk '{print $1}'`
```

@ -0,0 +1,627 @@
# Label Routing Docker-Compose Quickstart
This article will show an example of how Docker can be used to implement the label routing functionality of the Spring-Cloud-Alibaba (SCA) service governance functionality.
## 1. Environmental preparation
### 1.1 Prepare the service Jar package
1. Enter `spring-cloud-alibaba\spring-cloud-alibaba-examples` the directory and execute the `mvn clean package` packaged example service jar package
2. Enter the `spring-cloud-alibaba-examples/governance-example/docker-compose-example-quickstart/label-routing-quickstart` directory and execute the following script to move the jar package to the specified directory;
```shell
# linux/mac execute directly
./move-example-jar.sh
# Use githu bash to execute under windows
,/move-example-jar.sh
```
3. Respectively enter `label-routing-gateway-consumer-example` `label-routing-service-provider-example` `label-routing-webClient-consumer-example` the (routing-quickstart subdirectory) directory to check `app.jar` whether it exists. If there is no manual movement;
4. Check that `application-docker.yml` the configuration file is accurate.
### 1.2 Install Docker
If there is no Docker and Docker-Compose in the local machine, please refer to the following link for installation.
- Dockerhttps://docs.docker.com/engine/install/
- Docker-Composehttps://docs.docker.com/compose/install/
### 1.3 Description of contents
```md
└─spring-cloud-alibaba-examples/governance-example/docker-compose-example-quickstart/label-routing-quickstartrouting-quickstart
│ .env # Docker env settings
│ .gitignore
│ docker-compose-service-provider.yml # service provider and nacos server
│ docker-compose-gateway-consumer.yml # gateway consumer example
│ docker-compose-web-client-consumer.yml # client consumer
│ move-example-jar.sh # move jar script
│ README.md # readme file
│ README-zh.md
|
├─assets
│ └─img
├─label-routing-gateway-consumer-example
│ │ DockerFile # DockerFile file that starts the service consumer
│ │
│ ├─label-routing-gateway-consumer-example
│ │ app.jar # spring boot application jar
│ │ application-docker.yml # Configuration file when the spring boot application is started in the docker container
│ │
│ └─label-routing-zuul-consumer-example
│ app.jar
│ application-docker.yml
├─label-routing-service-provider-example # service provider folder
│ app.jar
│ application-dockerA1.yml
│ application-dockerA2.yml
│ application-dockerA3.yml
│ application-dockerA4.yml
│ DockerFile
│ start-label-routing-service-provider.sh
└─label-routing-webClient-consumer-example # Three types of request client consumer folders
│ DockerFile
├─label-routing-feign-consumer-example
│ app.jar
│ application-docker.yml
├─label-routing-reactive-consumer-example
│ app.jar
│ application-docker.yml
└─label-routing-rest-consumer-example
app.jar
application-docker.yml
```
### 1.4 Postman Test Script
1. Enter `spring-cloud-alibaba-examples/governance-example/label-routing-example/gateway-consumer-example/resources` the folder and import the gateway consumer request script into postman;
2. Go to `spring-cloud-alibaba-examples/governance-example/label-routing-example/web-client-consumer-example/resources` the folder and import the client consumer request script into postman.
## 2. Start the service provider and the nacos server
1. Enter `spring-cloud-alibaba-examples/governance-example/docker-compose-example-quickstart/label-routing-quickstart` the folder and enter the following command in the terminal to start the service-provider and nacos-server;
```shell
# Build docker image
docker-compose -f docker-compose-service-provider.yml build
# Start docker container
docker-compose -f docker-compose-service-provider.yml up -d
# You can remove the last -d parameter and view the log output during service startup.
docker-compose -f docker-compose-service-provider.yml up
```
2. After a certain period of time (need to wait for the start of the docker container to be completed), the local machine accesses the nacos console address (http://127.0.0.1:8848/nacos) to view the service-provider that has been registered and launched, which contains four service instances.
<img src="./assets/img/nacos-service-provider.png" style="zoom: 80%;" >
3. If the above service registration information appears, it proves that the docker container is started successfully! Click Details to view the metadata information of the service provider instance.
<img src="./assets/img/service-provider-metadata.png" style="zoom: 80%;" >
## 3. Client service consumer examples
### 3.1Start the Service Consumer Container
1. Enter `spring-cloud-alibaba-examples/governance-example/docker-compose-example-quickstart/label-routing-quickstart` the folder and execute the following command to start the example application container of the request client;
```shell
# Build docker image
docker-compose -f docker-compose-web-client-consumer.yml build
# Start docker container
docker-compose -f docker-compose-web-client-consumer.yml up -d
# You can remove the last -d parameter and view the log output during service startup.
docker-compose -f docker-compose-web-client-consumer.yml up
```
2. Go to nacos to check the service registration. After the service is online, the following message appears to prove that the container is started successfully;
<img src="./assets/img/nacos-webClient-consumer.jpg" style="zoom: 80%;" >
3. Enter postman, initiate a request, and check whether the services can communicate **(No task routing rules are added, only ribbon is used for load balancing) ** normally;
Click `v1版本测试` in the file and `v2版本测试` send the request without adding the routing configuration information respectively `feign` `rest` `webcleint` to check whether the four service providers can be consumed normally by the consumer:
<img src="./assets/img/postman-webclient-consumer.jpg" style="zoom:67%;" >
Click respectively to check whether all the four instances are called according to the response value, and enter the next step after confirming the success. **(The default ribbon load balancing rule is ZoneAvoidanceRule) **
### 3.2 Publish routing rules to test label routing
> Take the feign client call as an example. Rest and WebClient are the same.
#### 3.2.1 Publish Routing Rule
> **Ensure that all four service providers can be called normally by consumers! **
1. Analysis of label route rule
```java
// add routing rule
@GetMapping("/add")
public void getDataFromControlPlaneTest() {
List<Rule> routeRules = new ArrayList<>();
List<MatchService> matchServices = new ArrayList<>();
UnifiedRoutingDataStructure unifiedRouteDataStructure = new UnifiedRoutingDataStructure();
unifiedRouteDataStructure.setTargetService(WebClientConsumerConstants.SERVICE_PROVIDER_NAME); # service-providerThe set rules only take effect for the service of this application name.
RoutingRule labelRouteData = new RoutingRule();
labelRouteData.setDefaultRouteVersion("v1"); # Set the default service version for routing
Rule routeRule = new HeaderRoutingRule(); # Set the request header routing rule tag=v2, and route to the v2 service instance when such a request header exists
routeRule.setCondition("=");
routeRule.setKey("tag");
routeRule.setValue("v2");
Rule routeRule1 = new UrlRoutingRule.ParameterRoutingRule(); # Request parameter routing rules, when id>10, route to v2 service instance
routeRule1.setCondition(">");
routeRule1.setKey("id");
routeRule1.setValue("10");
Rule routeRule2 = new UrlRoutingRule.PathRoutingRule(); # Route to v2 service instance when /router-test is requested
routeRule2.setCondition("=");
routeRule2.setValue("/router-test");
routeRules.add(routeRule);
routeRules.add(routeRule1);
routeRules.add(routeRule2);
MatchService matchService = new MatchService(); # Matching rules, set when the above conditions are met, route to v2 version, service weight is 100
matchService.setVersion("v2");
matchService.setWeight(100);
matchService.setRuleList(routeRules);
matchServices.add(matchService);
labelRouteData.setMatchRouteList(matchServices);
unifiedRouteDataStructure.setLabelRouteRule(labelRouteData);
List<UnifiedRoutingDataStructure> unifiedRouteDataStructureList = new ArrayList<>();
unifiedRouteDataStructureList.add(unifiedRouteDataStructure);
applicationContext.publishEvent(
new RoutingDataChangedEvent(this, unifiedRouteDataStructureList));
}
// update routing rule
@GetMapping("/update")
public void updateDataFromControlPlaneTest() {
List<Rule> routeRules = new ArrayList<>();
List<MatchService> matchServices = new ArrayList<>();
UnifiedRoutingDataStructure unifiedRouteDataStructure = new UnifiedRoutingDataStructure();
unifiedRouteDataStructure.setTargetService(WebClientConsumerConstants.SERVICE_PROVIDER_NAME);
RoutingRule labelRouteData = new RoutingRule();
labelRouteData.setDefaultRouteVersion("v1");
Rule routeRule = new HeaderRoutingRule();
routeRule.setCondition("=");
routeRule.setKey("tag");
routeRule.setValue("v2");
Rule routeRule1 = new UrlRoutingRule.ParameterRoutingRule();
routeRule1.setCondition(">");
routeRule1.setKey("id");
routeRule1.setValue("10");
Rule routeRule2 = new UrlRoutingRule.PathRoutingRule();
routeRule2.setCondition("=");
routeRule2.setValue("/router-test");
routeRules.add(routeRule);
routeRules.add(routeRule1);
routeRules.add(routeRule2);
MatchService matchService = new MatchService(); # The other routing rules are the same, except that the weight of v2 is adjusted to 50, that is, half of the routes are routed to v1 and half are routed to the v2 version
matchService.setVersion("v2");
matchService.setWeight(50);
matchService.setRuleList(routeRules);
matchServices.add(matchService);
labelRouteData.setMatchRouteList(matchServices);
unifiedRouteDataStructure.setLabelRouteRule(labelRouteData);
List<UnifiedRoutingDataStructure> unifiedRouteDataStructureList = new ArrayList<>();
unifiedRouteDataStructureList.add(unifiedRouteDataStructure);
applicationContext.publishEvent(
new RoutingDataChangedEvent(this, unifiedRouteDataStructureList));
}
```
2. Area Affinity Routing Resolution
```yml
server:
port: 19095
spring:
application:
name: openfeign-service-consumer
cloud:
# register center configuration
nacos:
discovery:
fail-fast: true
server-addr: 127.0.0.1:8848
# label routing configuration
governance:
routing:
region: dev
zone: zone1
# rule: RandomRule
```
#### 3.2.2 When Area Affinity Routing Rules Exist
1. Add a routing rule
```shell
# expected outcome:
# v1: Route in 172.18.0.3:18083, region: dev, zone: zone1, version: v1
# v2: Observing the metadata of service-provider, it is found that there is no service instance with region=dev, zone=zone1, and version=v2. Therefore, regional affinity routing will degenerate into label routing effect. The following results are expected:
# Route in 172.18.0.3:18082, region: qa, zone: zone2, version: v2
# Route in 172.18.0.3:18084, region: dev, zone: zone2, version: v2
# The test results match the expected results!
```
2. Update Routing Rule
```shell
# expected outcome:
# v1: Route in 172.18.0.3:18083, region: dev, zone: zone1, version: v1
# v2: Because the regional affinity routing rules are set, even if the v1 and v2 versions have a weight of 50 each, the service instance will still be selected based on the regional affinity routing rules. The expected result is:
# Route in 192.168.2.9:18083, region: dev, zone: zone1, version: v1
# The test results match the expected results!
```
#### 3.2.3 When Area Affinity Routing Does Not Exist
> Note: Because the docker image is cached, it may not work if you repack it after commenting it out. Restart the docker service and try again!
To verify this scenario, you need to change the configuration file to unconfigure zone affinity routing. ** Execution `docker-compose -f docker-compose-web-client-consumer.yml stop` stops the consumer container. **
In the `spring-cloud-alibaba-examples/governance-example/docker-compose-example-quickstart/label-routing-quickstart/label-routing-webClient-consumer-example/label-routing-feign-consumer-example/application-docker.yml` file, comment the zone affinity routing configuration: (Same for Rest and WebClient consumers)
```properties
# label routing configuration
governance:
routing:
# region: dev
# zone: zone1
# rule: RandomRule
```
Rebuild and start the container.
1. Add a routing rule
```shell
# expected outcome:
# v1: Because there are no regional affinity routing restrictions, load balancing will be performed between instances according to ribbon rules.
# Route in 172.18.0.3:18083, region: dev, zone: zone1, version: v1
# Route in 172.18.0.3:18081, region: qa, zone: zone1, version: v1
# v2: Because there are no regional affinity routing restrictions, load balancing will be performed between instances according to ribbon rules.
# Route in 172.18.0.3:18084, region: qa, zone: zone2, version: v2
# Route in 172.18.0.3:18082, region: dev, zone: zone2, version: v2
# The test found that it met the expected results
```
2. Update Routing Rule
```shell
# expected outcome
# v1: Because there are no regional affinity routing restrictions, the routing results select service instances based on label routing, so load balancing is performed between the two instances according to ribbon rules.
# Route in 172.18.0.3:18081, region: qa, zone: zone1, version: v1
# Route in 172.18.0.3:18083, region: dev, zone: zone1, version: v1
# v2: The weights of v1 and v2 each account for 50, so the calling results of the four service instances will appear.
# Route in 172.18.0.3:18082, region: qa, zone: zone2, version: v2
# Route in 172.18.0.3:18084, region: dev, zone: zone2, version: v2
# Route in 172.18.0.3:18081, region: qa, zone: zone1, version: v1
# Route in 172.18.0.3:18083, region: dev, zone: zone1, version: v1
# The test found that it met the expected results
```
### 3.3 Close the container
Execute the following command to close the service consumer container:
```shell
docker-compose -f docker-compose-web-client-consumer.yml stop
```
## 4. Gateway Service Consumer Example
### 4.1Start the Gateway Service Consumer Container
1. Enter `spring-cloud-alibaba\spring-cloud-alibaba-examples\governance-example\label-routing-example\docker-compose-quickstart\label-routing-quickstart` the folder and execute the following command to start the example application container of the request client;
```shell
# Build docker image
docker-compose -f docker-compose-gateway-consumer.yml build
# Start docker container
docker-compose -f docker-compose-gateway-consumer.yml up -d
# You can remove the last -d parameter and view the log output during service startup.
docker-compose -f docker-compose-gateway-consumer.yml up
```
2. Go to nacos to check the service registration. After waiting for the service to go online, the following message appears to prove that the container is started successfully:
<img src="./assets/img/nacos-gateway-consumer.png">
3. Enter postman, initiate a request, and check whether the services can communicate **(No task routing rules are added, only ribbon is used for load balancing) ** normally;
Click `v1版本测试` in the file and `v2版本测试` send the request without adding the routing configuration information respectively `sc-gw` `zuul` to check whether the four service providers can be consumed normally by the consumer
<img src="./assets/img/postman-gateway-consumer.png" style="zoom:67%;" >
Click respectively to check whether all the four instances are called according to the response value, and enter the next step after confirming the success. **(The default ribbon load balancing rule is RandomRule)**
### 4.2 Publish routing rules to test label routing
> **Take gateway as an example, the zuul gateway is the same!**
#### 4.1.1 Publish Routing Rule
1. Analysis of label route rule
```java
@Override
public void getDataFromControlPlaneTest() {
log.info("请求 /add 接口,发布路由规则");
List<Rule> routeRules = new ArrayList<>();
List<MatchService> matchServices = new ArrayList<>();
UnifiedRoutingDataStructure unifiedRouteDataStructure = new UnifiedRoutingDataStructure();
// set target service
unifiedRouteDataStructure.setTargetService(GatewayConstants.SERVICE_PROVIDER_NAME);
RoutingRule labelRouteData = new RoutingRule();
// set default service version
labelRouteData.setDefaultRouteVersion("v1");
// set request header routing rule
Rule routeRule = new HeaderRoutingRule();
routeRule.setCondition("=");
routeRule.setKey("tag");
routeRule.setValue("v2");
// set request url routing rule
Rule routeRule1 = new UrlRoutingRule.ParameterRoutingRule();
routeRule1.setCondition(">");
routeRule1.setKey("id");
routeRule1.setValue("10");
// set request url routing rule
Rule routeRule2 = new UrlRoutingRule.PathRoutingRule();
routeRule2.setCondition("=");
routeRule2.setValue("/test-a1"); # The rules are the same as before, but because the service is called according to the gateway for consumption. So you need to specify the resource uri of the service provider
// add routing rule to routeRules#List<Rule>
routeRules.add(routeRule);
routeRules.add(routeRule1);
routeRules.add(routeRule2);
// If the preceding conditions are met, the route is routed to the v2 instance and
// the weight is set to 100
MatchService matchService = new MatchService();
matchService.setVersion("v2");
matchService.setWeight(100);
matchService.setRuleList(routeRules);
matchServices.add(matchService);
labelRouteData.setMatchRouteList(matchServices);
unifiedRouteDataStructure.setLabelRouteRule(labelRouteData);
List<UnifiedRoutingDataStructure> unifiedRouteDataStructureList = new ArrayList<>();
unifiedRouteDataStructureList.add(unifiedRouteDataStructure);
RoutingDataChangedEvent routingDataChangedEvent = new RoutingDataChangedEvent(
this, unifiedRouteDataStructureList);
// Publish routing rules
applicationContext.publishEvent(routingDataChangedEvent);
log.info("请求 /add 接口,发布路由规则完成!");
}
// The update rule is the same as the request client routing rule definition.
public void updateDataFromControlPlaneTest() {
log.info("请求 /update 接口,更新路由规则");
List<Rule> routeRules = new ArrayList<>();
List<MatchService> matchServices = new ArrayList<>();
UnifiedRoutingDataStructure unifiedRouteDataStructure = new UnifiedRoutingDataStructure();
unifiedRouteDataStructure.setTargetService(GatewayConstants.SERVICE_PROVIDER_NAME);
RoutingRule labelRouteData = new RoutingRule();
labelRouteData.setDefaultRouteVersion("v1");
Rule routeRule = new HeaderRoutingRule();
routeRule.setCondition("=");
routeRule.setKey("tag");
routeRule.setValue("v2");
Rule routeRule1 = new UrlRoutingRule.ParameterRoutingRule();
routeRule1.setCondition(">");
routeRule1.setKey("id");
routeRule1.setValue("10");
Rule routeRule2 = new UrlRoutingRule.PathRoutingRule();
routeRule2.setCondition("=");
routeRule2.setValue("/test-a1");
routeRules.add(routeRule);
routeRules.add(routeRule1);
routeRules.add(routeRule2);
// set weight 50
MatchService matchService = new MatchService();
matchService.setVersion("v2");
matchService.setWeight(50);
matchService.setRuleList(routeRules);
matchServices.add(matchService);
labelRouteData.setMatchRouteList(matchServices);
unifiedRouteDataStructure.setLabelRouteRule(labelRouteData);
List<UnifiedRoutingDataStructure> unifiedRouteDataStructureList = new ArrayList<>();
unifiedRouteDataStructureList.add(unifiedRouteDataStructure);
applicationContext.publishEvent(
new RoutingDataChangedEvent(this, unifiedRouteDataStructureList));
log.info("请求 /update 接口,更新路由规则完成!");
}
```
2. Area Affinity Routing Resolution
```yml
# Regional affinity routing configuration
governance:
routing:
# Set the region parameter called by the service consumer to dev and the available zone to zone1
region: dev
zone: zone1
# rule: RandomRule
```
#### 4.1.2 When Area Affinity Routing Rules Exist
1. Add a routing rule
```shell
# expected outcome:
# v1: Route in 172.18.0.3:18083, region: dev, zone: zone1, version: v1
# v2: Observing the metadata of service-provider, it is found that there is no service instance with region=dev, zone=zone1, and version=v2. Therefore, regional affinity routing will degenerate into label routing effect. The following results are expected:
# Route in 172.18.0.3:18082, region: qa, zone: zone2, version: v2
# Route in 172.18.0.3:18084, region: dev, zone: zone2, version: v2
# The test results match the expected results!
```
2. Update Routing Rule
```shell
# expected outcome:
# v1: Route in 172.18.0.3:18083, region: dev, zone: zone1, version: v1
# v2: Because the regional affinity routing rules are set, even if the v1 and v2 versions have a weight of 50 each, the service instance will still be selected based on the regional affinity routing rules. The expected result is:
# Route in 192.168.2.9:18083, region: dev, zone: zone1, version: v1
# The test results match the expected results!
```
#### 4.1.3 When Area Affinity Routing Does Not Exist
> Note: Because the docker image is cached, it may not take effect after being commented out. Restart the docker service and try again!
To verify this scenario, you need to change the configuration file to unconfigure zone affinity routing. ** Execution `docker-compose -f docker-compose-webClient.yml stop` stops the consumer container. **
In the `spring-cloud-alibaba-examples/governance-example/docker-compose-example-quickstart/label-routing-quickstart/label-routing-gateway-consumer-example/label-routing-gateway-consumer-example\application-docker.yml` file, comment the zone affinity routing configuration: (the same goes for the Zuul gateway consumer)
```properties
# Regional affinity routing configuration
governance:
routing:
# region: dev
# zone: zone1
# rule: RandomRule
```
Rebuild and start the container.
1. Add a routing rule
```shell
# expected outcome:
# v1: Because there are no regional affinity routing restrictions, load balancing will be performed between instances according to ribbon rules.
# Route in 172.18.0.3:18083, region: dev, zone: zone1, version: v1
# Route in 172.18.0.3:18081, region: qa, zone: zone1, version: v1
# v2: Because there are no regional affinity routing restrictions, load balancing will be performed between instances according to ribbon rules.
# Route in 172.18.0.3:18084, region: qa, zone: zone2, version: v2
# Route in 172.18.0.3:18082, region: dev, zone: zone2, version: v2
# The test found that it met the expected results
```
2. Update Routing Rule
```shell
# expected outcome
# v1: Because there are no regional affinity routing restrictions, the routing results select service instances based on label routing, so load balancing is performed between the two instances according to ribbon rules.
# Route in 172.18.0.3:18081, region: qa, zone: zone1, version: v1
# Route in 172.18.0.3:18083, region: dev, zone: zone1, version: v1
# v2: The weights of v1 and v2 each account for 50, so the calling results of the four service instances will appear.
# Route in 172.18.0.3:18082, region: qa, zone: zone2, version: v2
# Route in 172.18.0.3:18084, region: dev, zone: zone2, version: v2
# Route in 172.18.0.3:18081, region: qa, zone: zone1, version: v1
# Route in 172.18.0.3:18083, region: dev, zone: zone1, version: v1
# The test found that it met the expected results
```
### 4.3 Close the container
Execute the following command to close the service consumer container:
```shell
docker-compose -f docker-compose-gateway-consumer.yml stop
```
## 5. Service container removal
1. Execute the following command to stop the running service provider and nacos server container
```shell
docker-compose -f docker-compose-service-provider.yml stop
```
2. Execute the following command to remove all containers and mirror
```shell
# Delete images after filtering by conditions
docker rmi `docker images | grep sca-routing-** | awk {print $1}` -f
# Delete containers after filtering by conditions
docker rm `docker ps -a | grep sca-routing-** | awk '{print $1}'`
```

@ -0,0 +1,46 @@
version: "3"
services:
# routing gateway consumer example
routing-gateway-consumer:
image: ${DOCKER_PREFIX}-gateway-consumer
env_file:
- .env
build:
dockerfile: label-routing-gateway-consumer-example/DockerFile
context: .
args:
APP_HOME: ${APP_HOME}
container_name: ${DOCKER_PREFIX}-gateway-consumer
restart: on-failure
environment:
- SERVER_PORT=${GATEWAY_CONSUMER_PORT}
volumes:
- ./label-routing-gateway-consumer-example/label-routing-gateway-consumer-example:${APP_HOME}
- /etc/localtime:/etc/localtime
expose:
- ${GATEWAY_CONSUMER_PORT}
ports:
- ${GATEWAY_CONSUMER_PORT}:${GATEWAY_CONSUMER_PORT}
# routing provider example
routing-zuul-consumer:
image: ${DOCKER_PREFIX}-zuul-consumer
env_file:
- .env
build:
dockerfile: label-routing-gateway-consumer-example/DockerFile
context: .
args:
APP_HOME: ${APP_HOME}
container_name: ${DOCKER_PREFIX}-zuul-consumer
restart: on-failure
environment:
- SERVER_PORT=${ZUUL_CONSUMER_PORT}
volumes:
- ./label-routing-gateway-consumer-example/label-routing-zuul-consumer-example:${APP_HOME}
- /etc/localtime:/etc/localtime
expose:
- ${ZUUL_CONSUMER_PORT}
ports:
- ${ZUUL_CONSUMER_PORT}:${ZUUL_CONSUMER_PORT}

@ -0,0 +1,43 @@
version: "3"
services:
# nacos
label-routing-service-nacos:
image: nacos/nacos-server:v2.1.0
restart: always
container_name: ${DOCKER_PREFIX}-nacos-server
env_file:
- .env
environment:
- MODE=standalone
ports:
- ${NACOS_PORT}:${NACOS_PORT}
- ${NACOS_GRPC_PORT}:${NACOS_GRPC_PORT}
# routing provider example
label-routing-service-provider:
image: ${DOCKER_PREFIX}-service-provider
env_file:
- .env
build:
dockerfile: label-routing-service-provider-example/DockerFile
context: .
args:
APP_HOME: ${APP_HOME}
container_name: ${DOCKER_PREFIX}-service-provider
restart: on-failure
volumes:
- ./label-routing-service-provider-example:${APP_HOME}
- /etc/localtime:/etc/localtime
expose:
- ${PROVIDER_PORT_A1}
- ${PROVIDER_PORT_A2}
- ${PROVIDER_PORT_A3}
- ${PROVIDER_PORT_A4}
ports:
- ${PROVIDER_PORT_A1}:${PROVIDER_PORT_A1}
- ${PROVIDER_PORT_A2}:${PROVIDER_PORT_A3}
- ${PROVIDER_PORT_A3}:${PROVIDER_PORT_A3}
- ${PROVIDER_PORT_A4}:${PROVIDER_PORT_A4}
depends_on:
- label-routing-service-nacos

@ -0,0 +1,62 @@
version: "3"
services:
# routing web client consumer example
routing-feign-consumer:
image: ${DOCKER_PREFIX}-feign-consumer
env_file:
- .env
build:
dockerfile: label-routing-webClient-consumer-example/DockerFile
context: .
args:
APP_HOME: ${APP_HOME}
container_name: ${DOCKER_PREFIX}-feign-consumer
restart: on-failure
volumes:
- ./label-routing-webClient-consumer-example/label-routing-feign-consumer-example:${APP_HOME}
- /etc/localtime:/etc/localtime
expose:
- ${FEIGN_CONSUMER_PORT}
ports:
- ${FEIGN_CONSUMER_PORT}:${FEIGN_CONSUMER_PORT}
# routing provider example
routing-rest-consumer:
image: ${DOCKER_PREFIX}-rest-consumer
env_file:
- .env
build:
dockerfile: label-routing-webClient-consumer-example/DockerFile
context: .
args:
APP_HOME: ${APP_HOME}
container_name: ${DOCKER_PREFIX}-rest-consumer
restart: on-failure
volumes:
- ./label-routing-webClient-consumer-example/label-routing-rest-consumer-example:${APP_HOME}
- /etc/localtime:/etc/localtime
expose:
- ${REST_CONSUMER_PORT}
ports:
- ${REST_CONSUMER_PORT}:${REST_CONSUMER_PORT}
# routing provider example
routing-reactive-consumer:
image: ${DOCKER_PREFIX}-reactive-consumer
env_file:
- .env
build:
dockerfile: label-routing-webClient-consumer-example/DockerFile
context: .
args:
APP_HOME: ${APP_HOME}
container_name: ${DOCKER_PREFIX}-reactive-consumer
restart: on-failure
volumes:
- ./label-routing-webClient-consumer-example/label-routing-reactive-consumer-example:${APP_HOME}
- /etc/localtime:/etc/localtime
expose:
- ${REACTIVE_CONSUMER_PORT}
ports:
- ${REACTIVE_CONSUMER_PORT}:${REACTIVE_CONSUMER_PORT}

@ -0,0 +1,12 @@
FROM openjdk:8-jdk-alpine as builder
LABEL author="yuluo" \
email="1481556636@qq.com"
ARG APP_HOME
VOLUME ${APP_HOME}
WORKDIR ${APP_HOME}
ENTRYPOINT ["java", "-jar", "app.jar", "-Dspring.profiles.active=docker"]

@ -0,0 +1,28 @@
server:
port: 19098
spring:
application:
name: gateway-consumer-example
cloud:
nacos:
discovery:
server-addr: sca-label-routing-nacos-server:8848
# Regional affinity routing configuration
governance:
routing:
region: dev
zone: zone1
# rule: RandomRule
# Gateway route config
gateway:
routes:
- id: label-routing-test-route
uri: lb://routing-service-provider
predicates:
- Path=/routing-service-provider/**
filters:
- StripPrefix=1

@ -0,0 +1,25 @@
server:
port: 19099
spring:
application:
name: zuul-consumer-example
cloud:
nacos:
discovery:
server-addr: sca-label-routing-nacos-server:8848
# Regional affinity routing configuration
governance:
routing:
region: dev
zone: zone1
# rule: RandomRule
# Zuul route config
zuul:
routes:
my-service:
serviceId: routing-service-provider
path: /routing-service-provider/**

@ -0,0 +1,12 @@
FROM openjdk:8-jdk-alpine as builder
LABEL author="yuluo" \
email="1481556636@qq.com"
ARG APP_HOME
VOLUME ${APP_HOME}
WORKDIR ${APP_HOME}
ENTRYPOINT ["sh", "start-label-routing-service-provider.sh"]

@ -0,0 +1,17 @@
server:
port: 19091
spring:
# spring application configuration
application:
name: routing-service-provider
# register center configuration
cloud:
nacos:
discovery:
server-addr: sca-label-routing-nacos-server:8848
metadata:
version: v1
region: qa
zone: zone1

@ -0,0 +1,18 @@
server:
port: 19092
spring:
# spring application configuration
application:
name: routing-service-provider
# register center configuration
cloud:
nacos:
discovery:
server-addr: sca-label-routing-nacos-server:8848
metadata:
version: v2
region: qa
zone: zone2

@ -0,0 +1,17 @@
server:
port: 19093
spring:
# spring application configuration
application:
name: routing-service-provider
# register center configuration
cloud:
nacos:
discovery:
server-addr: sca-label-routing-nacos-server:8848
metadata:
version: v1
region: dev
zone: zone1

@ -0,0 +1,17 @@
server:
port: 19094
spring:
# spring application configuration
application:
name: routing-service-provider
# register center configuration
cloud:
nacos:
discovery:
server-addr: sca-label-routing-nacos-server:8848
metadata:
version: v2
region: dev
zone: zone2

@ -0,0 +1,9 @@
#!/bin/sh
java -jar app.jar --spring.profiles.active=dockerA1 &
java -jar app.jar --spring.profiles.active=dockerA2 &
java -jar app.jar --spring.profiles.active=dockerA3 &
java -jar app.jar --spring.profiles.active=dockerA4

@ -0,0 +1,12 @@
FROM openjdk:8-jdk-alpine as builder
LABEL author="yuluo" \
email="1481556636@qq.com"
ARG APP_HOME
VOLUME ${APP_HOME}
WORKDIR ${APP_HOME}
ENTRYPOINT ["java", "-jar", "app.jar", "--spring.profiles.active=docker"]

@ -0,0 +1,21 @@
server:
port: 19095
spring:
application:
name: openfeign-service-consumer
cloud:
# register center configuration
nacos:
discovery:
fail-fast: true
server-addr: sca-label-routing-nacos-server:8848
# label routing configuration
governance:
routing:
region: dev
zone: zone1
# rule: RandomRule

@ -0,0 +1,18 @@
server:
port: 19096
spring:
application:
name: webClient-service-consumer
cloud:
nacos:
discovery:
server-addr: sca-label-routing-nacos-server:8848
# label routing configuration
governance:
routing:
region: dev
zone: zone1
# rule: RandomRule

@ -0,0 +1,17 @@
server:
port: 19097
spring:
application:
name: restTemplate-service-consumer
cloud:
nacos:
discovery:
server-addr: sca-label-routing-nacos-server:8848
# label routing configuration
governance:
routing:
region: dev
zone: zone1
# rule: RandomRule

@ -0,0 +1,22 @@
#!/bin/sh
gateway_example_project_path="label-routing-example/gateway-consumer-example/gateway-consumer-example/target"
gateway_example_docker_path="label-routing-gateway-consumer-example/label-routing-gateway-consumer-example"
zuul_example_project_path="label-routing-example/gateway-consumer-example/zuul-consumer-example/target"
zuul_example_docker_path="label-routing-gateway-consumer-example/label-routing-zuul-consumer-example"
feign_example_project_path="label-routing-example/web-client-consumer-example/openfeign-consumer-example/target"
feign_example_docker_path="label-routing-webClient-consumer-example/label-routing-feign-consumer-example"
rest_example_project_path="label-routing-example/web-client-consumer-example/restTemplate-consumer-example/target"
rest_example_docker_path="label-routing-webClient-consumer-example/label-routing-rest-consumer-example"
reactive_example_project_path="label-routing-example/web-client-consumer-example/webClient-consumer-example/target"
reactive_example_docker_path="label-routing-webClient-consumer-example/label-routing-reactive-consumer-example"
provider_example_project_path="label-routing-example/routing-service-provider-example/target"
provider_example_docker_path="label-routing-service-provider-example"
cp ../../${provider_example_project_path}/routing-service-provider-example-*.jar ./${provider_example_docker_path}/app.jar
cp ../../${gateway_example_project_path}/gateway-consumer-example-*.jar ./${gateway_example_docker_path}/app.jar
cp ../../${zuul_example_project_path}/zuul-consumer-example-*.jar ./${zuul_example_docker_path}/app.jar
cp ../../${feign_example_project_path}/openfeign-consumer-example-*.jar ./${feign_example_docker_path}/app.jar
cp ../../${rest_example_project_path}/restTemplate-consumer-example-*.jar ./${rest_example_docker_path}/app.jar
cp ../../${reactive_example_project_path}/webClient-consumer-example-*.jar ./${reactive_example_docker_path}/app.jar

@ -1,172 +0,0 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.examples;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.cloud.commons.governance.event.RoutingDataChangedEvent;
import com.alibaba.cloud.commons.governance.routing.MatchService;
import com.alibaba.cloud.commons.governance.routing.RoutingRule;
import com.alibaba.cloud.commons.governance.routing.UnifiedRoutingDataStructure;
import com.alibaba.cloud.commons.governance.routing.rule.HeaderRoutingRule;
import com.alibaba.cloud.commons.governance.routing.rule.Rule;
import com.alibaba.cloud.commons.governance.routing.rule.UrlRoutingRule;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author HH
*/
@SpringBootApplication
@EnableDiscoveryClient(autoRegister = true)
@EnableFeignClients
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@FeignClient(name = "service-provider")
public interface FeignService {
/**
* Feign test.
* @return String
*/
@GetMapping("/test")
String test();
}
@RestController
public class Controller implements ApplicationContextAware {
@Autowired
private ApplicationContext applicationContext;
@Autowired
private ConsumerApplication.FeignService feignService;
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
@GetMapping("/router-test")
public String notFound() {
return feignService.test();
}
@GetMapping("/add")
public void getDataFromControlPlaneTest() {
List<Rule> routeRules = new ArrayList<>();
List<MatchService> matchServices = new ArrayList<>();
UnifiedRoutingDataStructure unifiedRouteDataStructure = new UnifiedRoutingDataStructure();
unifiedRouteDataStructure.setTargetService("service-provider");
RoutingRule labelRouteData = new RoutingRule();
labelRouteData.setDefaultRouteVersion("v1");
Rule routeRule = new HeaderRoutingRule();
routeRule.setCondition("=");
routeRule.setKey("tag");
routeRule.setValue("v2");
Rule routeRule1 = new UrlRoutingRule.ParameterRoutingRule();
routeRule1.setCondition(">");
routeRule1.setKey("id");
routeRule1.setValue("10");
Rule routeRule2 = new UrlRoutingRule.PathRoutingRule();
routeRule2.setCondition("=");
routeRule2.setValue("/router-test");
routeRules.add(routeRule);
routeRules.add(routeRule1);
routeRules.add(routeRule2);
MatchService matchService = new MatchService();
matchService.setVersion("v2");
matchService.setWeight(100);
matchService.setRuleList(routeRules);
matchServices.add(matchService);
labelRouteData.setMatchRouteList(matchServices);
unifiedRouteDataStructure.setLabelRouteRule(labelRouteData);
List<UnifiedRoutingDataStructure> unifiedRouteDataStructureList = new ArrayList<>();
unifiedRouteDataStructureList.add(unifiedRouteDataStructure);
applicationContext.publishEvent(
new RoutingDataChangedEvent(this, unifiedRouteDataStructureList));
}
@GetMapping("/update")
public void updateDataFromControlPlaneTest() {
List<Rule> routeRules = new ArrayList<>();
List<MatchService> matchServices = new ArrayList<>();
UnifiedRoutingDataStructure unifiedRouteDataStructure = new UnifiedRoutingDataStructure();
unifiedRouteDataStructure.setTargetService("service-provider");
RoutingRule labelRouteData = new RoutingRule();
labelRouteData.setDefaultRouteVersion("v1");
Rule routeRule = new HeaderRoutingRule();
routeRule.setCondition("=");
routeRule.setKey("tag");
routeRule.setValue("v2");
Rule routeRule1 = new UrlRoutingRule.ParameterRoutingRule();
routeRule1.setCondition(">");
routeRule1.setKey("id");
routeRule1.setValue("10");
Rule routeRule2 = new UrlRoutingRule.PathRoutingRule();
routeRule2.setCondition("=");
routeRule2.setValue("/router-test");
routeRules.add(routeRule);
routeRules.add(routeRule1);
routeRules.add(routeRule2);
MatchService matchService = new MatchService();
matchService.setVersion("v2");
matchService.setWeight(50);
matchService.setRuleList(routeRules);
matchServices.add(matchService);
labelRouteData.setMatchRouteList(matchServices);
unifiedRouteDataStructure.setLabelRouteRule(labelRouteData);
List<UnifiedRoutingDataStructure> unifiedRouteDataStructureList = new ArrayList<>();
unifiedRouteDataStructureList.add(unifiedRouteDataStructure);
applicationContext.publishEvent(
new RoutingDataChangedEvent(this, unifiedRouteDataStructureList));
}
}
}

@ -1,8 +0,0 @@
spring.application.name=service-consumer
server.port=18083
management.endpoints.web.exposure.include=*
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.cloud.nacos.discovery.fail-fast=true
spring.cloud.nacos.username=nacos
spring.cloud.nacos.password=nacos
#spring.cloud.governance.routing.rule=RandomRule

@ -1,45 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<version>${revision}</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>default-provider-version-example</artifactId>
<name>Spring Cloud Starter Alibaba Label Routing Provider Example</name>
<description>Example demonstrating how to use label routing provider</description>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

@ -1,12 +0,0 @@
server.port=18081
spring.application.name=service-provider
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.cloud.nacos.discovery.enabled=true
spring.cloud.nacos.discovery.metadata.version=v1
spring.cloud.nacos.username=nacos
spring.cloud.nacos.password=nacos
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<version>${revision}</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<artifactId>gateway-consumer-example</artifactId>
<name>Label Routing Gateway Consumer Example </name>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-governance-routing</artifactId>
<exclusions>
<exclusion>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-commons</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>label-routing-example-common</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,37 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*/
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayConsumerApplication.class);
}
}

@ -0,0 +1,62 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.gateway.configuration;
import com.alibaba.cloud.example.gateway.handler.AddGatewayRoutingRuleHandler;
import com.alibaba.cloud.example.gateway.handler.GatewayServerListHandler;
import com.alibaba.cloud.example.gateway.handler.UpdateGatewayRoutingRuleHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*/
@Configuration
public class GatewayRouterFunctionConfiguration {
@Autowired
private AddGatewayRoutingRuleHandler addGatewayRoutingRuleHandler;
@Autowired
private UpdateGatewayRoutingRuleHandler updateGatewayRoutingRuleHandler;
@Autowired
private GatewayServerListHandler gatewayServerListHandler;
@SuppressWarnings("rawtypes")
@Bean
public RouterFunction gatewayRouterFunction() {
return RouterFunctions.route()
.GET("/addRule", RequestPredicates.accept(MediaType.TEXT_PLAIN),
addGatewayRoutingRuleHandler)
.GET("/updateRule", RequestPredicates.accept(MediaType.TEXT_PLAIN),
updateGatewayRoutingRuleHandler)
.GET("/services", RequestPredicates.accept(MediaType.TEXT_PLAIN),
gatewayServerListHandler)
.build();
}
}

@ -0,0 +1,51 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.gateway.handler;
import com.alibaba.cloud.example.gateway.service.AddGatewayRoutingRuleService;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*/
@Service
public class AddGatewayRoutingRuleHandler implements HandlerFunction<ServerResponse> {
@Autowired
private AddGatewayRoutingRuleService routingRuleService;
@Override
public Mono<ServerResponse> handle(ServerRequest request) {
routingRuleService.getDataFromControlPlaneTest();
return ServerResponse.status(HttpStatus.OK)
.body(BodyInserters.fromValue("Add routing rule success"));
}
}

@ -0,0 +1,62 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.gateway.handler;
import java.util.HashMap;
import java.util.List;
import reactor.core.publisher.Mono;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*/
@Service
public class GatewayServerListHandler implements HandlerFunction<ServerResponse> {
private final DiscoveryClient discoveryClient;
public GatewayServerListHandler(DiscoveryClient discoveryClient) {
this.discoveryClient = discoveryClient;
}
@Override
public Mono<ServerResponse> handle(ServerRequest serverRequest) {
HashMap<String, List<ServiceInstance>> map = new HashMap<>();
List<String> services = discoveryClient.getServices();
for (String service : services) {
List<ServiceInstance> instances = discoveryClient.getInstances(service);
map.put(service, instances);
}
return ServerResponse.status(HttpStatus.OK).body(BodyInserters.fromValue(map));
}
}

@ -0,0 +1,51 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.gateway.handler;
import com.alibaba.cloud.example.gateway.service.UpdateGatewayRoutingRuleService;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*/
@Service
public class UpdateGatewayRoutingRuleHandler implements HandlerFunction<ServerResponse> {
@Autowired
private UpdateGatewayRoutingRuleService routingRuleService;
@Override
public Mono<ServerResponse> handle(ServerRequest serverRequest) {
routingRuleService.updateDataFromControlPlaneTest();
return ServerResponse.status(HttpStatus.OK)
.body(BodyInserters.fromValue("Updating routing rule success"));
}
}

@ -0,0 +1,28 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.gateway.service;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*/
public interface AddGatewayRoutingRuleService {
void getDataFromControlPlaneTest();
}

@ -0,0 +1,28 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.gateway.service;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*/
public interface UpdateGatewayRoutingRuleService {
void updateDataFromControlPlaneTest();
}

@ -0,0 +1,95 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.gateway.service.impl;
import java.io.IOException;
import java.util.List;
import com.alibaba.cloud.commons.governance.event.RoutingDataChangedEvent;
import com.alibaba.cloud.commons.governance.routing.UnifiedRoutingDataStructure;
import com.alibaba.cloud.example.gateway.service.AddGatewayRoutingRuleService;
import com.alibaba.cloud.routing.consumer.constants.ConsumerConstants;
import com.alibaba.cloud.routing.consumer.converter.Converter;
import com.alibaba.cloud.routing.consumer.util.ReadJsonFileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*/
@Service
public class AddGatewayRoutingRuleServiceImpl
implements AddGatewayRoutingRuleService, ApplicationContextAware {
private static final Logger log = LoggerFactory
.getLogger(AddGatewayRoutingRuleServiceImpl.class);
@Autowired
private ApplicationContext applicationContext;
@Autowired
private Converter<String, List<UnifiedRoutingDataStructure>> jsonConverter;
private static String addRoutingRulePath;
static {
org.springframework.core.io.Resource resource = new ClassPathResource(
"add-routing-rule.json");
try {
addRoutingRulePath = resource.getFile().getPath();
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void getDataFromControlPlaneTest() {
log.info("Access /addRule interface, publish routing rule..." + "\n"
+ ConsumerConstants.ADD_RULE_DESCRIPTION);
String content = ReadJsonFileUtils.convertFile2String(addRoutingRulePath);
List<UnifiedRoutingDataStructure> unifiedRouteDataStructureList = jsonConverter
.convert(content);
applicationContext.publishEvent(
new RoutingDataChangedEvent(this, unifiedRouteDataStructureList));
log.info("Add routing rule success!");
}
}

@ -0,0 +1,94 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.gateway.service.impl;
import java.io.IOException;
import java.util.List;
import com.alibaba.cloud.commons.governance.event.RoutingDataChangedEvent;
import com.alibaba.cloud.commons.governance.routing.UnifiedRoutingDataStructure;
import com.alibaba.cloud.example.gateway.service.UpdateGatewayRoutingRuleService;
import com.alibaba.cloud.routing.consumer.constants.ConsumerConstants;
import com.alibaba.cloud.routing.consumer.converter.Converter;
import com.alibaba.cloud.routing.consumer.util.ReadJsonFileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*/
@Service
public class UpdateGatewayRoutingRuleServiceImpl
implements UpdateGatewayRoutingRuleService, ApplicationContextAware {
private static final Logger log = LoggerFactory
.getLogger(UpdateGatewayRoutingRuleServiceImpl.class);
@Autowired
private ApplicationContext applicationContext;
@Autowired
private Converter<String, List<UnifiedRoutingDataStructure>> jsonConverter;
private static String updateRoutingRulePath;
static {
org.springframework.core.io.Resource resource = new ClassPathResource(
"update-routing-rule.json");
try {
updateRoutingRulePath = resource.getFile().getPath();
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
public void updateDataFromControlPlaneTest() {
log.info("Access /updateRule interfaceupdate routing rule success..." + "\n"
+ ConsumerConstants.UPDATE_RULE_DESCRIPTION);
String content = ReadJsonFileUtils.convertFile2String(updateRoutingRulePath);
List<UnifiedRoutingDataStructure> unifiedRouteDataStructureList = jsonConverter
.convert(content);
applicationContext.publishEvent(
new RoutingDataChangedEvent(this, unifiedRouteDataStructureList));
log.info("Update routing rule success!");
}
}

@ -0,0 +1,35 @@
[
{
"targetService": "routing-service-provider",
"labelRouteRule": {
"matchRouteList": [
{
"ruleList": [
{
"type": "header",
"condition": "=",
"key": "tag",
"value": "v2"
},
{
"type": "parameter",
"condition": ">",
"key": "id",
"value": "10"
},
{
"type": "path",
"condition": "=",
"value": "/test-a1",
"key": null
}
],
"version": "v2",
"weight": 100,
"fallback": null
}
],
"defaultRouteVersion": "v1"
}
}
]

@ -0,0 +1,28 @@
server:
port: 19098
spring:
application:
name: gateway-consumer-example
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
# Regional affinity routing configuration
governance:
routing:
region: dev
zone: zone1
# rule: RandomRule
# Gateway route config
gateway:
routes:
- id: label-routing-test-route
uri: lb://routing-service-provider
predicates:
- Path=/routing-service-provider/**
filters:
- StripPrefix=1

@ -0,0 +1,35 @@
[
{
"targetService": "routing-service-provider",
"labelRouteRule": {
"matchRouteList": [
{
"ruleList": [
{
"type": "header",
"condition": "=",
"key": "tag",
"value": "v2"
},
{
"type": "parameter",
"condition": ">",
"key": "id",
"value": "10"
},
{
"type": "path",
"condition": "=",
"value": "/test-a1",
"key": null
}
],
"version": "v2",
"weight": 50,
"fallback": null
}
],
"defaultRouteVersion": "v1"
}
}
]

@ -0,0 +1,224 @@
{
"info": {
"_postman_id": "5b1fe8e3-9334-40b1-978c-bf7ba146b3dc",
"name": "网关消费者",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "16212773"
},
"item": [
{
"name": "sc-gw",
"item": [
{
"name": "添加路由规则",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "http://localhost:19098/addRule",
"protocol": "http",
"host": [
"localhost"
],
"port": "19098",
"path": [
"addRule"
]
}
},
"response": []
},
{
"name": "更新路由规则",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "http://localhost:19098/updateRule",
"protocol": "http",
"host": [
"localhost"
],
"port": "19098",
"path": [
"updateRule"
]
}
},
"response": []
},
{
"name": "V1版本测试",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "http://localhost:19098/routing-service-provider/test-a1",
"protocol": "http",
"host": [
"localhost"
],
"port": "19098",
"path": [
"routing-service-provider",
"test-a1"
]
}
},
"response": []
},
{
"name": "V2版本测试",
"request": {
"method": "GET",
"header": [
{
"key": "tag",
"value": "v2",
"type": "text"
}
],
"url": {
"raw": "http://localhost:19098/routing-service-provider/test-a1?id=11",
"protocol": "http",
"host": [
"localhost"
],
"port": "19098",
"path": [
"routing-service-provider",
"test-a1"
],
"query": [
{
"key": "id",
"value": "11"
},
{
"key": "tag",
"value": "v2",
"disabled": true
}
]
}
},
"response": []
},
{
"name": "获取服务列表",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "http://localhost:19098/services",
"protocol": "http",
"host": [
"localhost"
],
"port": "19098",
"path": [
"services"
]
}
},
"response": []
}
]
},
{
"name": "zuul",
"item": [
{
"name": "V1版本测试",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "http://localhost:19099/routing-service-provider/test-a1",
"protocol": "http",
"host": [
"localhost"
],
"port": "19099",
"path": [
"routing-service-provider",
"test-a1"
]
}
},
"response": []
},
{
"name": "V2版本测试",
"request": {
"method": "GET",
"header": [
{
"key": "tag",
"value": "v2",
"type": "text"
}
],
"url": {
"raw": "http://localhost:19099/routing-service-provider/test-a1?id=11",
"protocol": "http",
"host": [
"localhost"
],
"port": "19099",
"path": [
"routing-service-provider",
"test-a1"
],
"query": [
{
"key": "id",
"value": "11"
}
]
}
},
"response": []
},
{
"name": "添加路由规则",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "http://localhost:19099/addRule",
"protocol": "http",
"host": [
"localhost"
],
"port": "19099",
"path": [
"addRule"
]
}
},
"response": []
},
{
"name": "更新路由规则",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "http://localhost:19099/updateRule",
"protocol": "http",
"host": [
"localhost"
],
"port": "19099",
"path": [
"updateRule"
]
}
},
"response": []
}
]
}
]
}

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<version>${revision}</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<artifactId>zuul-consumer-example</artifactId>
<name>Label Routing Zuul Consumer Example</name>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-governance-routing</artifactId>
<exclusions>
<exclusion>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-commons</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>label-routing-example-common</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,39 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.example.zuul;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*/
@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
public class ZuulConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulConsumerApplication.class);
}
}

@ -0,0 +1,53 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.example.zuul.controller;
import javax.annotation.Resource;
import com.alibaba.example.zuul.service.AddZuulRoutingRuleService;
import com.alibaba.example.zuul.service.UpdateZuulRoutingRuleService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*/
@RestController
public class ZuulConsumerController {
@Resource
private AddZuulRoutingRuleService addZuulRoutingRuleService;
@Resource
private UpdateZuulRoutingRuleService updateZuulRoutingRuleService;
@GetMapping("/addRule")
public void getDataFromControlPlaneTest() {
addZuulRoutingRuleService.getDataFromControlPlaneTest();
}
@GetMapping("/updateRule")
public void updateDataFromControlPlaneTest() {
updateZuulRoutingRuleService.updateDataFromControlPlaneTest();
}
}

@ -0,0 +1,28 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.example.zuul.service;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*/
public interface AddZuulRoutingRuleService {
void getDataFromControlPlaneTest();
}

@ -0,0 +1,28 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.example.zuul.service;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*/
public interface UpdateZuulRoutingRuleService {
void updateDataFromControlPlaneTest();
}

@ -0,0 +1,95 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.example.zuul.service.impl;
import java.io.IOException;
import java.util.List;
import com.alibaba.cloud.commons.governance.event.RoutingDataChangedEvent;
import com.alibaba.cloud.commons.governance.routing.UnifiedRoutingDataStructure;
import com.alibaba.cloud.routing.consumer.constants.ConsumerConstants;
import com.alibaba.cloud.routing.consumer.converter.Converter;
import com.alibaba.cloud.routing.consumer.util.ReadJsonFileUtils;
import com.alibaba.example.zuul.service.AddZuulRoutingRuleService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*/
@Service
public class AddZuulRoutingRuleServiceImpl
implements AddZuulRoutingRuleService, ApplicationContextAware {
private static final Logger log = LoggerFactory
.getLogger(AddZuulRoutingRuleServiceImpl.class);
@Autowired
private ApplicationContext applicationContext;
@Autowired
private Converter<String, List<UnifiedRoutingDataStructure>> jsonConverter;
private static String addRoutingRulePath;
static {
org.springframework.core.io.Resource resource = new ClassPathResource(
"add-routing-rule.json");
try {
addRoutingRulePath = resource.getFile().getPath();
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void getDataFromControlPlaneTest() {
log.info("Access /addRule interface, publish routing rule..." + "\n"
+ ConsumerConstants.ADD_RULE_DESCRIPTION);
String content = ReadJsonFileUtils.convertFile2String(addRoutingRulePath);
List<UnifiedRoutingDataStructure> unifiedRouteDataStructureList = jsonConverter
.convert(content);
applicationContext.publishEvent(
new RoutingDataChangedEvent(this, unifiedRouteDataStructureList));
log.info("Add routing rule success!");
}
}

@ -0,0 +1,95 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.example.zuul.service.impl;
import java.io.IOException;
import java.util.List;
import com.alibaba.cloud.commons.governance.event.RoutingDataChangedEvent;
import com.alibaba.cloud.commons.governance.routing.UnifiedRoutingDataStructure;
import com.alibaba.cloud.routing.consumer.constants.ConsumerConstants;
import com.alibaba.cloud.routing.consumer.converter.Converter;
import com.alibaba.cloud.routing.consumer.util.ReadJsonFileUtils;
import com.alibaba.example.zuul.service.UpdateZuulRoutingRuleService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*/
@Service
public class UpdateZuulRoutingRuleServiceImpl
implements UpdateZuulRoutingRuleService, ApplicationContextAware {
private static final Logger log = LoggerFactory
.getLogger(UpdateZuulRoutingRuleServiceImpl.class);
@Autowired
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
@Autowired
private Converter<String, List<UnifiedRoutingDataStructure>> jsonConverter;
private static String updateRoutingRulePath;
static {
org.springframework.core.io.Resource resource = new ClassPathResource(
"update-routing-rule.json");
try {
updateRoutingRulePath = resource.getFile().getPath();
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void updateDataFromControlPlaneTest() {
log.info("Access /updateRule interfaceupdate routing rule success..." + "\n"
+ ConsumerConstants.UPDATE_RULE_DESCRIPTION);
String content = ReadJsonFileUtils.convertFile2String(updateRoutingRulePath);
List<UnifiedRoutingDataStructure> unifiedRouteDataStructureList = jsonConverter
.convert(content);
applicationContext.publishEvent(
new RoutingDataChangedEvent(this, unifiedRouteDataStructureList));
log.info("Update routing rule success!");
}
}

@ -0,0 +1,35 @@
[
{
"targetService": "routing-service-provider",
"labelRouteRule": {
"matchRouteList": [
{
"ruleList": [
{
"type": "header",
"condition": "=",
"key": "tag",
"value": "v2"
},
{
"type": "parameter",
"condition": ">",
"key": "id",
"value": "10"
},
{
"type": "path",
"condition": "=",
"value": "/routing-service-provider/test-a1",
"key": null
}
],
"version": "v2",
"weight": 100,
"fallback": null
}
],
"defaultRouteVersion": "v1"
}
}
]

@ -0,0 +1,25 @@
server:
port: 19099
spring:
application:
name: zuul-consumer-example
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
# Regional affinity routing configuration
governance:
routing:
region: dev
zone: zone1
# rule: RandomRule
# Zuul route config
zuul:
routes:
my-service:
serviceId: routing-service-provider
path: /routing-service-provider/**

@ -0,0 +1,35 @@
[
{
"targetService": "routing-service-provider",
"labelRouteRule": {
"matchRouteList": [
{
"ruleList": [
{
"type": "header",
"condition": "=",
"key": "tag",
"value": "v2"
},
{
"type": "parameter",
"condition": ">",
"key": "id",
"value": "10"
},
{
"type": "path",
"condition": "=",
"value": "/routing-service-provider/test-a1",
"key": null
}
],
"version": "v2",
"weight": 50,
"fallback": null
}
],
"defaultRouteVersion": "v1"
}
}
]

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<version>${revision}</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<version>${revision}</version>
<name> Label Routing Example Common Module </name>
<packaging>jar</packaging>
<artifactId>label-routing-example-common</artifactId>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.26</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-commons</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

@ -1,5 +1,5 @@
/*
* Copyright 2022-2023 the original author or authors.
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,23 +14,26 @@
* limitations under the License.
*/
package com.alibaba.cloud.routing.ribbon;
package com.alibaba.cloud.routing.consumer.config;
import com.alibaba.cloud.routing.consumer.converter.Converter;
import com.alibaba.cloud.routing.consumer.converter.JsonConverter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author HH
* @since 2.2.10-RC1
* @author yuluo
* @author 1481556636@qq.com
*/
@Configuration(proxyBeanMethods = false)
public class RoutingLoadBalanceRuleAutoConfiguration {
public class ConsumerCommonConfig {
@Bean
@ConditionalOnMissingBean
public RoutingLoadBalanceRule routingLoadBalanceRule() {
return new RoutingLoadBalanceRule();
public Converter jsonConverter() {
return new JsonConverter();
}
}

@ -0,0 +1,104 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.routing.consumer.constants;
/**
* @author yuluo
* @author <a href="1481556636@qq.com"></a>
*/
public final class ConsumerConstants {
private ConsumerConstants() {
}
/**
* Service port.
*/
public static final String PORT = "port";
/**
* Service host.
*/
public static final String HOST = "host";
/**
* Service instance id.
*/
public static final String INSTANCE_ID = "instanceId";
/**
* Service provider name.
*/
public static final String SERVICE_PROVIDER_NAME = "routing-service-provider";
/**
* Access service provider url.
*/
public static final String SERVICE_PROVIDER_ADDRESS = "http://"
+ SERVICE_PROVIDER_NAME;
/**
* Routing rule description.
*/
public static final String ADD_RULE_DESCRIPTION = "If the request parameters contain `tag=v2`, the request header contains id and the value is less than 10, and the uri is `/test-a1`, then all traffic will be routed to the v2 version. If one of them is not satisfied, the traffic will be routed to In v1 version.";
/**
* Routing rule description.
*/
public static final String UPDATE_RULE_DESCRIPTION = "If the request parameters contain `tag=v2`, the request header contains id and the value is less than 10, and the uri is `/test-a1`, then 50% of the traffic is routed to the v2 version, and the remaining traffic is routed to the v1 version. If any one of them is not satisfied, the traffic will be routed to version v1.";
/**
* Gateway Consumer Constants.
*/
public class GatewayConsumerConstants {
/**
* Gateway consumer example name.
*/
public static final String GATEWAY_CONSUMER_EXAMPLE = "gateway-consumer-example";
/**
* Zuul consumer example name.
*/
public static final String ZUUL_CONSUMER_EXAMPLE = "zuul-consumer-example";
}
/**
* WebClient Consumer Constants.
*/
public class WebClientConsumerConstants {
/**
* RestTemplate consumer Application name.
*/
public static final String REST_APPLICATION_NAME = "routing-rest-consumer";
/**
* Openfeign consumer Application name.
*/
public static final String FEIGN_APPLICATION_NAME = "routing-feign-consumer";
/**
* WebClient consumer Application name.
*/
public static final String WEBCLIENT_APPLICATION_NAME = "routing-reactive-consumer";
}
}

@ -0,0 +1,28 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.routing.consumer.converter;
/**
* @author yuluo
* @author 1481556636@qq.com
*/
public interface Converter<S, T> {
T convert(S val);
}

@ -0,0 +1,41 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.routing.consumer.converter;
import java.util.List;
import com.alibaba.cloud.commons.governance.routing.UnifiedRoutingDataStructure;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
/**
* @author yuluo
* @author 1481556636@qq.com
*/
public class JsonConverter
implements Converter<String, List<UnifiedRoutingDataStructure>> {
@Override
public List<UnifiedRoutingDataStructure> convert(String val) {
return JSON.parseObject(val,
new TypeReference<List<UnifiedRoutingDataStructure>>() {
});
}
}

@ -0,0 +1,53 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.routing.consumer.entity;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.stereotype.Component;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*/
@Component
public final class ConsumerNodeInfo {
private static Map<String, List<Map<String, List<String>>>> nodeIno = new ConcurrentHashMap<>();
private ConsumerNodeInfo() {
}
public static void set(String var1, List<Map<String, List<String>>> var2) {
nodeIno.put(var1, var2);
}
public static List<Map<String, List<String>>> get(String var) {
return nodeIno.get(var);
}
public static Map<String, List<Map<String, List<String>>>> getNodeIno() {
return nodeIno;
}
}

@ -0,0 +1,56 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.routing.consumer.util;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author yuluo
* @author 1481556636@qq.com
*/
public final class ReadJsonFileUtils {
private static final Logger log = LoggerFactory.getLogger(ReadJsonFileUtils.class);
private ReadJsonFileUtils() {
}
public static String convertFile2String(String filePath) {
File file = new File(filePath);
StringBuilder content = new StringBuilder();
try (Scanner scanner = new Scanner(file)) {
while (scanner.hasNextLine()) {
content.append(scanner.nextLine()).append("\n");
}
}
catch (FileNotFoundException e) {
log.warn("File not found: " + filePath);
e.printStackTrace();
}
return content.toString();
}
}

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.cloud.routing.consumer.config.ConsumerCommonConfig

@ -0,0 +1,82 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.example.convert;
import java.io.IOException;
import java.util.List;
import com.alibaba.cloud.commons.governance.routing.UnifiedRoutingDataStructure;
import com.alibaba.cloud.routing.consumer.config.ConsumerCommonConfig;
import com.alibaba.cloud.routing.consumer.converter.Converter;
import com.alibaba.cloud.routing.consumer.converter.JsonConverter;
import com.alibaba.cloud.routing.consumer.util.ReadJsonFileUtils;
import com.fasterxml.jackson.databind.json.JsonMapper;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author yuluo
* @author 1481556636@qq.com
*/
@SpringBootTest(classes = TestConverter.TestConfig.class)
@RunWith(SpringRunner.class)
public class TestConverter {
Resource resource = new ClassPathResource("add-routing-rule.json");
@Autowired
private Converter<String, List<UnifiedRoutingDataStructure>> jsonConverter;
private static final String data = "[{\"targetService\":\"routing-service-provider\",\"labelRouteRule\":{\"matchRouteList\":[{\"ruleList\":[{\"value\":\"v2\",\"key\":\"tag\",\"type\":\"header\",\"condition\":\"=\"},{\"value\":\"10\",\"key\":\"id\",\"type\":\"parameter\",\"condition\":\">\"},{\"value\":\"/router-test\",\"key\":null,\"type\":\"path\",\"condition\":\"=\"}],\"version\":\"v2\",\"weight\":100,\"fallback\":null}],\"defaultRouteVersion\":\"v1\"}}]";
@Test
public void test_json_converter() throws IOException {
String content = ReadJsonFileUtils
.convertFile2String(resource.getFile().getPath());
Assert.assertEquals(data,
new JsonMapper().writeValueAsString(jsonConverter.convert(content)));
}
@Configuration
@EnableAutoConfiguration
@ImportAutoConfiguration({ ConsumerCommonConfig.class })
public static class TestConfig {
@Bean
public Converter<String, List<UnifiedRoutingDataStructure>> jonConverter() {
return new JsonConverter();
}
}
}

@ -0,0 +1,35 @@
[
{
"targetService": "routing-service-provider",
"labelRouteRule": {
"matchRouteList": [
{
"ruleList": [
{
"type": "header",
"condition": "=",
"key": "tag",
"value": "v2"
},
{
"type": "parameter",
"condition": ">",
"key": "id",
"value": "10"
},
{
"type": "path",
"condition": "=",
"value": "/router-test",
"key": null
}
],
"version": "v2",
"weight": 100,
"fallback": null
}
],
"defaultRouteVersion": "v1"
}
}
]

@ -6,157 +6,451 @@
## 模块结构
本模块包括一个消费者实例和一个提供者集群,该集群包含着两个实例。
本模块包括一个消费者实例和一个提供者集群该集群包含着4个实例。
- 本文中提到的示例模块都在 `spring-cloud-alibaba/spring-cloud-alibaba-examples/governance-example/label-routing-example/` 目录下;
- starter 起步依赖模块在 `spring-cloud-alibaba/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-governance-routing` 目录下。
- 如无特殊说明,下文中提到的所有路径都以其上为父路径。
## 组件支持说明
目前,路由模块只支持了部分组件:
远程调用组件Spring Cloud OpenFeign
远程调用组件Spring Cloud OpenFeign, RestTemplate, WebClient
负载均衡组件Ribbon
负载均衡组件Ribbon
网关组件Spring Netflix ZuulSpring Cloud Gateway
未来会支持更多的比如RestTemplateSpring Cloud LoadBalancer等组件
未来会支持更多组件,例如 Spring Cloud LoadBalancer 等
## 示例
### 如何接入
**注意 本章节只是为了便于您理解接入方式,本示例代码中已经完成接入工作,您无需再进行修改。**
1. 首先,修改需要进行路由服务的`pom.xml` 文件,引入 `spring-cloud-starter-alibaba-governance-routing` 依赖。
```xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-governance-routing</artifactId>
</dependency>
```
2.配置当没有路由规则时的负载均衡算法(以随机负载均衡算法为例)
如果没有配置使用ribbon默认的负载均衡算法ZoneAvoidanceRule
```yaml
spring.cloud.governance.routing.rule=RandomRule
```
1. 首先,修改需要进行路由服务的 `pom.xml` 文件,引入 `spring-cloud-starter-alibaba-governance-routing` 依赖。
```xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-governance-routing</artifactId>
</dependency>
```
2. 配置当没有路由规则时的负载均衡算法(以随机负载均衡算法为例),如果没有配置,使用 ribbon 默认的负载均衡算法 ZoneAvoidanceRule 。
```properties
spring.cloud.governance.routing.rule=RandomRule
```
### 应用启动
启动一个三个模块的启动类分别为ConsumerApplication两个ProviderApplication将其注入到Nacos注册中心中。
进入 `routing-service-provider-example` 文件夹,启动四个服务实例,分别为 A1ProviderApplication到 A4ProviderApplication 将其注入到 Nacos 注册中心中。
### 效果演示
### 客户端消费者效果演示(以 feign 为例)
> Note: 本章节演示提供了 Docker-Compose 快速启动版本,点击此处查看 (Docker-Compose QuickStart)[./docker-compose-example-quickstart/label-routing-quickstart/README-zh.md]
1. 进入 `web-client-consumer-example/resources` 文件夹中将请求客户端需要的脚本导入到 postman 中,在验证时发送请求使用;
1. `feign` 消费者客户端在 postman 中的请求脚本位置在 `客户端消费者/feign` 目录下RestTemplate 和 WebClient 相同)
2. 进入 `web-client-consumer-example` 文件夹分别启动三个模块的启动类,分别为 ConsumerFeignApplicationConsumerReactiveApplication 和 ConsumerRestApplication
3. 逐个点击 v1 和 v2 版本请求,查看四个服务实例是否可以被正常消费**(不设置任何路由规则)**。
#### 服务提供者预期结果说明
进入 `routing-service-provider-example` 路径下,查看启动类文件,可以发现如下代码:
#### 规则说明
实例中设置的规则如下:
```java
@GetMapping("/add")
public void getDataFromControlPlaneTest() {
List<RoutingRule> routingRules = new ArrayList<>();
List<MatchService> matchServices = new ArrayList<>();
UnifiedRouteDataStructure unifiedRouteDataStructure = new UntiedRouteDataStructure();
unifiedRouteDataStructure.setTargetService("service-provider");
LabelRouteRule labelRouteData = new LabelRouteRule();
labelRouteData.setDefaultRouteVersion("v1");
RoutingRule routingRule = new HeaderRule();
routingRule.setType("header");
routingRule.setCondition("=");
routingRule.setKey("tag");
routingRule.setValue("v2");
RoutingRule routingRule1 = new UrlRule.Parameter();
routingRule1.setType("parameter");
routingRule1.setCondition(">");
routingRule1.setKey("id");
routingRule1.setValue("10");
RoutingRule routingRule2 = new UrlRule.Path();
routingRule2.setType("path");
routingRule2.setCondition("=");
routingRule2.setValue("/router-test");
routingRules.add(routingRule);
routingRules.add(routingRule1);
routingRules.add(routingRule2);
MatchService matchService = new MatchService();
matchService.setVersion("v2");
matchService.setWeight(100);
matchService.setRuleList(routingRules);
matchServices.add(matchService);
labelRouteData.setMatchRouteList(matchServices);
unifiedRouteDataStructure.setLabelRouteRule(labelRouteData);
List<UntiedRouteDataStructure> unifiedRouteDataStructureList = new ArrayList<>();
unifiedRouteDataStructureList.add(unifiedRouteDataStructure);
controlPlaneConnection.pushRouteData(unifiedRouteDataStructureList);
@RestController
class A1Controller {
@GetMapping("/test-a1")
public String testA1() {
String host = nacosRegistration.getHost();
int port = nacosRegistration.getPort();
String zone = nacosRegistration.getMetadata().get("zone");
String region = nacosRegistration.getMetadata().get("region");
String version = nacosRegistration.getMetadata().get("version");
return "Route in " + host + ":" + port + ", region: " + region + ", zone: "
+ zone + ", version: " + version;
}
}
```
代码对应的规则如下:
若同时满足请求参数中含有`tag=v2`请求头中含有id且值小于10uri为`/router-test`则流量全部路由到v2版本中若有一条不满足则流量路由到v1版本中。
从中可以看出服务提供者返回的数据如下:
- 返回服务实例的 hostip 地址;
- 返回服务实例的 port
- 在进行区域亲和性路由时,在服务中设置的 region 和 zone 标签;
- 在服务中设置的 nacos 元数据标签 version
服务提供者返回样例:
```shell
Route in 192.168.2.9:19093, region: dev, zone: zone1, version: v1
```
#### 规则说明
服务消费者openfeign-consumer-examplerestTemplate-consumer-examplereactive-consumer-example实例中设置的路由规则如下
```json
[
{
"targetService": "routing-service-provider",
"labelRouteRule": {
"matchRouteList": [
{
"ruleList": [
{
"type": "header",
"condition": "=",
"key": "tag",
"value": "v2"
},
{
"type": "parameter",
"condition": ">",
"key": "id",
"value": "10"
},
{
"type": "path",
"condition": "=",
"value": "/router-test",
"key": null
}
],
"version": "v2",
"weight": 100,
"fallback": null
}
],
"defaultRouteVersion": "v1"
}
}
]
```
代码对应的路由规则如下:
> 若同时满足请求参数中含有`tag=v2`,请求头中含有 id 且值小于10uri 为 `/router-test` 则流量全部路由到 v2 版本中,若有一条不满足,则流量路由到 v1 版本中。
规则也支持动态修改,测试动态修改的规则如下:
```java
@GetMapping("/add")
public void getDataFromControlPlaneTest() {
List<RoutingRule> routingRules = new ArrayList<>();
List<MatchService> matchServices = new ArrayList<>();
UntiedRouteDataStructure unifiedRouteDataStructure = new UntiedRouteDataStructure();
unifiedRouteDataStructure.setTargetService("service-provider");
LabelRouteRule labelRouteData = new LabelRouteRule();
labelRouteData.setDefaultRouteVersion("v1");
RoutingRule routingRule = new HeaderRule();
routingRule.setType("header");
routingRule.setCondition("=");
routingRule.setKey("tag");
routingRule.setValue("v2");
RoutingRule routingRule1 = new UrlRule.Parameter();
routingRule1.setType("parameter");
routingRule1.setCondition(">");
routingRule1.setKey("id");
routingRule1.setValue("10");
RoutingRule routingRule2 = new UrlRule.Path();
routingRule2.setType("path");
routingRule2.setCondition("=");
routingRule2.setValue("/router-test");
routingRules.add(routingRule);
routingRules.add(routingRule1);
routingRules.add(routingRule2);
MatchService matchService = new MatchService();
matchService.setVersion("v2");
matchService.setWeight(50);
matchService.setRuleList(routingRules);
matchServices.add(matchService);
labelRouteData.setMatchRouteList(matchServices);
unifiedRouteDataStructure.setLabelRouteRule(labelRouteData);
List<UntiedRouteDataStructure> unifiedRouteDataStructureList = new ArrayList<>();
unifiedRouteDataStructureList.add(unifiedRouteDataStructure);
controlPlaneConnection.pushRouteData(unifiedRouteDataStructureList);
}
```json
[
{
"targetService": "routing-service-provider",
"labelRouteRule": {
"matchRouteList": [
{
"ruleList": [
{
"type": "header",
"condition": "=",
"key": "tag",
"value": "v2"
},
{
"type": "parameter",
"condition": ">",
"key": "id",
"value": "10"
},
{
"type": "path",
"condition": "=",
"value": "/router-test",
"key": null
}
],
"version": "v2",
"weight": 50,
"fallback": null
}
],
"defaultRouteVersion": "v1"
}
}
]
```
代码对应的规则如下:
若同时满足请求参数中含有`tag=v2`请求头中含有id且值小于10uri为`/router-test`则50%流量路由到v2版本中剩下的流量路由到v1版本中若有一条不满足则流量路由到v1版本中。
##### 演示步骤
1. 访问 http://localhost:18083/add 将路由规则由控制面接口推入路由规则仓库中。
访问 http://localhost:18083/router-test 不满足路由规则路由到v1版本中v1版本实例打印返回如下结果
```
Route in 30.221.132.228: 18081,version is v1.
```
访问 http://localhost:18083/router-test?id=11 且请求头设置tag值为v2 满足路由规则路由到v2版本中v2版本实例打印返回如下结果
```
Route in 30.221.132.228: 18082,version is v2.
> 若同时满足请求参数中含有 `tag=v2`,请 求头中含有 id 且值小于10uri 为 `/router-test`,则 50% 流量路由到 v2 版本中,剩下的流量路由到 v1 版本中,若有一条不满足,则流量路由到 v1 版本中。
区域亲和性路由规则如下:
```yml
# label routing configuration
governance:
routing:
region: dev
zone: zone1
# rule: RandomRule
```
> 当服务实例满足所设置的 `region=dev`, `zone=zone1` 规则时,路由到指定服务实例。
#### 当区域亲和性路由存在时
1. 添加路由规则,将路由规则由控制面接口推入路由规则仓库中。
```shell
# 预期结果:
# v1不满足路由规则路由到v1版本中且区域亲和性路由规则为 regnion=devzone=zone1预期结果为
# Route in 192.168.2.9:19093, region: dev, zone: zone1, version: v1
# v2观察 service-provider 的元数据发现,没有 region=devzone=zone1version=v2 的服务实例,因此区域亲和性路由会退化为标签路由效果,预期为以下结果:
# Route in 192.168.2.9:19092, region: qa, zone: zone2, version: v2
# Route in 192.168.2.9:19094, region: dev, zone: zone2, version: v2
# 测试发现和预期结果匹配!
```
2. 访问 http://localhost:18083/update 模拟动态修改路由规则。
访问 http://localhost:18083/router-test 不满足路由规则路由到v1版本中v1版本实例打印返回如下结果
2. 更新路由规则,模拟动态修改路由规则。
```shell
# 预期结果:
# v1不满足路由规则路由到 v1 版本中,且区域亲和性路由规则为 region=devzone=zone1实例打印返回如下结果
# Route in 172.18.0.3:19093, region: dev, zone: zone1, version: v1
# v2因为设置了区域亲和性路由规则所以即使 v1 和 v2 版本各自 50% 的权重,但是还是会根据区域亲和性路由规则选取服务实例, 预期结果为:
# Route in 192.168.2.9:19093, region: dev, zone: zone1, version: v1
# 测试发现和预期结果匹配!
```
Route in 30.221.132.228: 18081,version is v1.
#### 当区域亲和性路由不存在时
进入 `web-client-consumer-example/openfeign-consumer-example/src/main/resources/application.yml` 文件中,注释以下配置,再次启动 ConsumerFeignApplication
```yml
# label routing configuration
governance:
routing:
# region: dev
# zone: zone1
# rule: RandomRule
```
1. 添加路由规则,将路由规则由控制面接口推入路由规则仓库中。
```shell
# 预期结果:
# v1因为没有区域亲和性路由限制所以会在 v1 实例之间按照 ribbon 的规则进行负载均衡
# Route in 192.168.2.9:19091, region: qa, zone: zone1, version: v1
# Route in 192.168.2.9:19093, region: dev, zone: zone1, version: v1
# v2因为没有区域亲和性路由限制所以会在 v2 实例之间按照 ribbon 的规则进行负载均衡
# Route in 192.168.2.9:19094, region: dev, zone: zone2, version: v2
# Route in 192.168.2.9:19092, region: qa, zone: zone2, version: v2
# 测试发现,符合预期结果
```
访问 http://localhost:18083/router-test?id=11 且请求头设置tag值为v2 满足路由规则50%路由到v2版本中v2版本实例打印返回如下结果
2. 更新路由规则,模拟动态修改路由规则。
```shell
# 预期结果
# v1因为没有区域亲和性路由限制路由结果按照标签路由选择服务实例所以会在两个 v1 实例之间按照 ribbon 的规则进行负载均衡
# Route in 192.168.2.9:19093, region: dev, zone: zone1, version: v1
# Route in 192.168.2.9:19091, region: qa, zone: zone1, version: v1
# v2v1 和 v2 权重各占 50所以四种服务实例的调用结果都会出现
# Route in 192.168.2.9:19093, region: dev, zone: zone1, version: v1
# Route in 192.168.2.9:19092, region: qa, zone: zone2, version: v2
# Route in 192.168.2.9:19094, region: dev, zone: zone2, version: v2
# Route in 192.168.2.9:19091, region: qa, zone: zone1, version: v1
# 测试发现,符合预期结果
```
Route in 30.221.132.228: 18082,version is v2.
### 网关消费者效果演示 (以 gateway 为例)
1. 进入 `gateway-consumer-example` 文件夹分别启动两个网关模块的启动类,分别为 ConsumerZuulApplication和ConsumerGatewayApplication
2. 进入 `gateway-consumer-example/resources` 文件夹将网关示例需要的请求脚本导入到 postman 中;
3. 逐个点击 v1 和 v2 版本请求,查看四个服务实例是否可以被正常消费**(不设置任何路由规则)**。
#### 规则说明
网关消费者中的标签路由规则如下:
```json
[
{
"targetService": "routing-service-provider",
"labelRouteRule": {
"matchRouteList": [
{
"ruleList": [
{
"type": "header",
"condition": "=",
"key": "tag",
"value": "v2"
},
{
"type": "parameter",
"condition": ">",
"key": "id",
"value": "10"
},
{
"type": "path",
"condition": "=",
"value": "/test-a1",
"key": null
}
],
"version": "v2",
"weight": 100,
"fallback": null
}
],
"defaultRouteVersion": "v1"
}
}
]
```
代码对应的路由规则如下:
> 若同时满足请求参数中含有 `tag=v2`,请求头中含有 id 且值小于10uri 为 `/test-a1` 则流量全部路由到 v2 版本中,若有一条不满足,则流量路由到 v1 版本中。
更新路由规则:
```json
[
{
"targetService": "routing-service-provider",
"labelRouteRule": {
"matchRouteList": [
{
"ruleList": [
{
"type": "header",
"condition": "=",
"key": "tag",
"value": "v2"
},
{
"type": "parameter",
"condition": ">",
"key": "id",
"value": "10"
},
{
"type": "path",
"condition": "=",
"value": "/test-a1",
"key": null
}
],
"version": "v2",
"weight": 50,
"fallback": null
}
],
"defaultRouteVersion": "v1"
}
}
]
```
代码对应的规则如下:
> 若同时满足请求参数中含有 `tag=v2`,请 求头中含有 id 且值小于10uri 为 `/test-a1`,则 50% 流量路由到 v2 版本中,剩下的流量路由到 v1 版本中,若有一条不满足,则流量路由到 v1 版本中。
区域亲和性路由规则如下:
```yml
# label routing configuration
governance:
routing:
region: dev
zone: zone1
# rule: RandomRule
```
> 当服务实例满足所设置的 `region=dev`, `zone=zone1` 规则时,路由到指定服务实例。
#### 当区域亲和性路由存在时
1. 添加路由规则,将路由规则由控制面接口推入路由规则仓库中。
```shell
# 预期结果:
# v1不满足路由规则路由到v1版本中且区域亲和性路由规则为 region=devzone=zone1预期结果为
# Route in 192.168.2.9:19093, region: dev, zone: zone1, version: v1
# v2观察 service-provider 的元数据发现,没有 region=devzone=zone1version=v2 的服务实例,因此区域亲和性路由会退化为标签路由效果,预期为以下结果:
# Route in 192.168.2.9:19092, region: qa, zone: zone2, version: v2
# Route in 192.168.2.9:19094, region: dev, zone: zone2, version: v2
# 测试发现和预期结果匹配!
```
50%路由到v1版本中v1版本实例打印返回如下结果
2. 更新路由规则,模拟动态修改路由规则。
```shell
# 预期结果:
# v1不满足标签路由规则路由到v1版本中从两个 v1 版本实例中根据区域亲和性标签选择服务实例,实例打印返回如下结果:
# Route in 172.18.0.3:19093, region: dev, zone: zone1, version: v1
# v2因为设置了区域亲和性路由规则所以即使 v1 和 v2 版本各自 50% 的权重,但是还是会根据区域亲和性路由规则选取服务实例, 预期结果为:
# Route in 192.168.2.9:19093, region: dev, zone: zone1, version: v1
# 测试发现和预期结果匹配!
```
Route in 30.221.132.228: 18081,version is v1.
#### 当区域亲和性路由不存在时
进入 `gateway-consumer-example/gateway-consumer-example/src/main/resources/application.yml` 文件中,注释以下配置,再次启动 GatewayConsumerApplication
```yml
# Regional affinity routing configuration
governance:
routing:
# region: dev
# zone: zone1
# rule: RandomRule
```
1. 添加路由规则,将路由规则由控制面接口推入路由规则仓库中。
```shell
# 预期结果:
# v1因为没有区域亲和性路由限制所以会在实例之间按照 ribbon 的规则进行负载均衡
# Route in 192.168.2.9:19091, region: qa, zone: zone1, version: v1
# Route in 192.168.2.9:19093, region: dev, zone: zone1, version: v1
# v2因为没有区域亲和性路由限制所以会在实例之间按照 ribbon 的规则进行负载均衡
# Route in 192.168.2.9:19094, region: dev, zone: zone2, version: v2
# Route in 192.168.2.9:19092, region: qa, zone: zone2, version: v2
# 测试发现,符合预期结果
```
3. 如果不推送规则,走正常路由
2. 更新路由规则,模拟动态修改路由规则。
```shell
# 预期结果
# v1因为没有区域亲和性路由限制路由结果按照标签路由选择服务实例所以会在两个实例之间按照 ribbon 的规则进行负载均衡
# Route in 192.168.2.9:19093, region: dev, zone: zone1, version: v1
# Route in 192.168.2.9:19091, region: qa, zone: zone1, version: v1
# v2v1 和 v2 权重各占 50所以四种服务实例的调用结果都会出现
# Route in 192.168.2.9:19093, region: dev, zone: zone1, version: v1
# Route in 192.168.2.9:19092, region: qa, zone: zone2, version: v2
# Route in 192.168.2.9:19094, region: dev, zone: zone2, version: v2
# Route in 192.168.2.9:19091, region: qa, zone: zone1, version: v1
# 测试发现,符合预期结果
```
## 集成Istio
**注意 本章节只是为了便于您理解接入方式,本示例代码中已经完成接入工作,您无需再进行修改。**
### 安装K8s环境
请参考K8s的[安装工具](https://kubernetes.io/zh-cn/docs/tasks/tools/)小节。
@ -166,7 +460,9 @@ public void getDataFromControlPlaneTest() {
- [VirtualService](https://istio.io/latest/zh/docs/reference/config/networking/virtual-service/)
- [DestinationRule](https://istio.io/latest/zh/docs/reference/config/networking/destination-rule/)
### 配置
1. 首先修改pom.xml 文件,引入`spring-cloud-starter-alibaba-governance-routing`依赖。同时引入Spring Cloud Alibaba的`spring-cloud-starter-xds-adapter`模块:
```xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
@ -177,6 +473,7 @@ public void getDataFromControlPlaneTest() {
<artifactId>spring-cloud-starter-xds-adapter</artifactId>
</dependency>
```
2. 参照[文档](https://github.com/alibaba/spring-cloud-alibaba/blob/2.2.x/spring-cloud-alibaba-docs/src/main/asciidoc-zh/governance.adoc),实现与`Istio`控制面的对接
并在`application.yml`中配置默认路由规则:
```yml
@ -188,11 +485,15 @@ spring:
routing:
rule: ${ROUTING_RULE:RandomRule}
```
### 应用启动
启动三个模块的启动类分别为IstioConsumerApplication两个ProviderApplication将其注入到Nacos注册中心中。
### 下发配置
通过Istio控制面下发标签路由规则首先下发DestinationRule规则:
```YAML
kubectl apply -f - << EOF
apiVersion: networking.istio.io/v1alpha3
@ -210,8 +511,9 @@ spec:
version: v2
EOF
```
此规则将后端服务拆分为两个版本label为v1的pod被分到v1版本label为v2的pod被分到v2版本
之后下发VirtualService规则:
此规则将后端服务拆分为两个版本label为v1的pod被分到v1版本label为v2的pod被分到v2版本之后下发 VirtualService 规则:
```YAML
kubectl apply -f - << EOF
apiVersion: networking.istio.io/v1alpha3
@ -238,9 +540,12 @@ spec:
subset: v1
EOF
```
这条VirtualService指定了一条最简单的标签路由规则将请求头tag为v2请求路径为`/istio-label-routing`的HTTP请求路由到v2版本其余的流量都路由到v1版本:
这条 VirtualService 指定了一条最简单的标签路由规则将请求头tag为v2请求路径为`/istio-label-routing`的HTTP请求路由到v2版本其余的流量都路由到v1版本:
### 效果演示
发送一条不带请求头的HTTP请求至IstioConsumerApplication:
```
curl --location --request GET '127.0.0.1:18084/istio-label-routing'
```

@ -1,159 +1,468 @@
# Routing Example
## Project Description
## Project description
This project demonstrates how to use the Spring Cloud Alibaba Governance Routing module to complete the routing capacity.
This project demonstrates how to use the Spring Cloud Alibaba Governance Routing module to complete the label routing function.
## Module structure
This module includes a consumer instance and a provider cluster, which contains two instances.
This module includes a consumer instance and a provider cluster, which contains four instances.
- The sample modules mentioned in this article are in the `spring-cloud-alibaba/spring-cloud-alibaba-examples/governance-example/label-routing-example/` directory;
- The starter dependent module is in the `spring-cloud-alibaba/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-governance-routing` directory.
- Unless otherwise specified, all paths mentioned below are based on the parent path above.
## Component support description
Currently, the routing module supports only a few components:
Currently, the routing module only supports some components:
Remote call component: Spring Cloud OpenFeign, RestTemplate, WebClient;
Remote call Component: Spring Cloud OpenFeign
Load balancing component: Ribbon;
Load balance Component: Ribbon
Gateway components: Spring Netflix Zuul, Spring Cloud Gateway;
In the future, more components such as RestTemplate, Spring Cloud LoadBalancer and so on will be supported.
More components, such as Spring Cloud LoadBalancer, will be supported in the future.
## Example
## Examples
### How to access
**Note that this section is only for your convenience in understanding the access method. The access work has been completed in this sample code, and you do not need to modify it.**
1. First, modify the pom XML file, which introduces the Spring Cloud Alibaba governance routing dependency.
```xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-governance-routing</artifactId>
</dependency>
```
**Note that this section is only for your convenience to understand the access method. The access work has been completed in this sample code, and you do not need to modify it.**
2.Configure a load balance algorithm when there are no routing rules (RandomRule algorithm as an example)
If no configuration,use default ribbon load balance algorithm ZoneAvoidanceRule.
```yaml
spring.cloud.governance.routing.rule=RandomRule
```
1. First, modify the `pom.xml` file that needs routing service and introduce `spring-cloud-starter-alibaba-governance-routing` dependencies.
### Application Start
Start a startup class of three modules, ConsumerApplication and two ProviderApplications, and inject them into the Nacos registry.
```xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-governance-routing</artifactId>
</dependency>
```
### Effect demonstration
2. Configure the load balancing algorithm when there is no routing rule (take the random load balancing algorithm as an example). If there is no configuration, use the default load balancing algorithm Zone AvoidanceRule.
```properties
spring.cloud.governance.routing.rule=RandomRule
```
### The application starts
Enter the `routing-service-provider-example` folder, start four service instances, namely A1Provider Application, and inject them into the Nacos registry at A4Provider Application.
### Client consumer effect demonstration (take feign as an example)
> Note: This chapter demo provides a quick start version of Docker-Compose. Click here to view (docker-compose QuickStart) [./docker-composition-example-quickstart/label-routing-quickstart/README-zh. MD]
1. Enter `web-client-consumer-example/resources` the folder to import the script required by the request client into postman, and send the request for use during verification;
1. The location of the request script for the `feign` consumer client in postman is in the `客户端消费者/feign` directory; (RestTemplate is the same as WebClient)
2. Enter the `web-client-consumer-example` folder to start the startup classes of the three modules respectively, which are ConsumerFeign Application, ConsumerReactive Application and ConsumerRestApplication;
3. Click the v1 and v2 version requests one by one to see if the four service instances can be consumed **(No routing rules are set)** normally.
#### Description of expected results of service provider
Enter `routing-service-provider-example` the path, view the startup class file, and you can find the following code:
#### Rule Description
The rules set in the instance are as follows:
```java
@GetMapping("/add")
public void getDataFromControlPlaneTest() {
List<RoutingRule> routingRules = new ArrayList<>();
List<MatchService> matchServices = new ArrayList<>();
UnifiedRouteDataStructure unifiedRouteDataStructure = new UntiedRouteDataStructure();
unifiedRouteDataStructure.setTargetService("service-provider");
LabelRouteRule labelRouteData = new LabelRouteRule();
labelRouteData.setDefaultRouteVersion("v1");
RoutingRule routingRule = new HeaderRule();
routingRule.setType("header");
routingRule.setCondition("=");
routingRule.setKey("tag");
routingRule.setValue("v2");
RoutingRule routingRule1 = new UrlRule.Parameter();
routingRule1.setType("parameter");
routingRule1.setCondition(">");
routingRule1.setKey("id");
routingRule1.setValue("10");
RoutingRule routingRule2 = new UrlRule.Path();
routingRule2.setType("path");
routingRule2.setCondition("=");
routingRule2.setValue("/router-test");
routingRules.add(routingRule);
routingRules.add(routingRule1);
routingRules.add(routingRule2);
MatchService matchService = new MatchService();
matchService.setVersion("v2");
matchService.setWeight(100);
matchService.setRuleList(routingRules);
matchServices.add(matchService);
labelRouteData.setMatchRouteList(matchServices);
unifiedRouteDataStructure.setLabelRouteRule(labelRouteData);
List<UntiedRouteDataStructure> unifiedRouteDataStructureList = new ArrayList<>();
unifiedRouteDataStructureList.add(unifiedRouteDataStructure);
controlPlaneConnection.pushRouteData(unifiedRouteDataStructureList);
@RestController
class A1Controller {
@GetMapping("/test-a1")
public String testA1() {
String host = nacosRegistration.getHost();
int port = nacosRegistration.getPort();
String zone = nacosRegistration.getMetadata().get("zone");
String region = nacosRegistration.getMetadata().get("region");
String version = nacosRegistration.getMetadata().get("version");
return "Route in " + host + ":" + port + ", region: " + region + ", zone: "
+ zone + ", version: " + version;
}
}
```
The rules corresponding to the code are as follows:
If the request parameter contains tag=v2 and the request header contains id and the value is greater than 10, uri is `/router-test` at the same time, the traffic is routed to the v2 version. If one of the request parameters does not meet the requirement, the traffic is routed to the v1 version.
Rules also support dynamic modification. The rules for testing dynamic modification are as follows:
```java
@GetMapping("/add")
public void getDataFromControlPlaneTest() {
List<RoutingRule> routingRules = new ArrayList<>();
List<MatchService> matchServices = new ArrayList<>();
UntiedRouteDataStructure unifiedRouteDataStructure = new UntiedRouteDataStructure();
unifiedRouteDataStructure.setTargetService("service-provider");
LabelRouteRule labelRouteData = new LabelRouteRule();
labelRouteData.setDefaultRouteVersion("v1");
RoutingRule routingRule = new HeaderRule();
routingRule.setType("header");
routingRule.setCondition("=");
routingRule.setKey("tag");
routingRule.setValue("v2");
RoutingRule routingRule1 = new UrlRule.Parameter();
routingRule1.setType("parameter");
routingRule1.setCondition(">");
routingRule1.setKey("id");
routingRule1.setValue("10");
RoutingRule routingRule2 = new UrlRule.Path();
routingRule2.setType("path");
routingRule2.setCondition("=");
routingRule2.setValue("/router-test");
routingRules.add(routingRule);
routingRules.add(routingRule1);
routingRules.add(routingRule2);
MatchService matchService = new MatchService();
matchService.setVersion("v2");
matchService.setWeight(50);
matchService.setRuleList(routingRules);
matchServices.add(matchService);
labelRouteData.setMatchRouteList(matchServices);
unifiedRouteDataStructure.setLabelRouteRule(labelRouteData);
List<UntiedRouteDataStructure> unifiedRouteDataStructureList = new ArrayList<>();
unifiedRouteDataStructureList.add(unifiedRouteDataStructure);
controlPlaneConnection.pushRouteData(unifiedRouteDataStructureList);
}
It can be seen that the data returned by the service provider is as follows:
- Returns the host, IP address of the service instance;
- Returns the port of the service instance;
- The region and zone labels set in the service during the region affinity routing;
- The nacos metadata tag version set in the service
Service Provider Return Sample:
```shell
Route in 192.168.2.9:19093, region: dev, zone: zone1, version: v1
```
The rules corresponding to the code are as follows:
If the request parameter contains tag=v2, and the request header contains id and the value is greater than 10, URL is `/router-test`, 50% of the traffic is routed to the v2 version, and the rest is routed to the v1 version. If one of the traffic does not meet the requirements, the traffic is routed to the v1 version.
##### demonstration Steps
1. visit http://localhost:18083/add Push the routing rules from the control surface interface to the routing rule warehouse
visit http://localhost:18083/router -The test does not meet the routing rules. When the test is routed to the v1 version, the v1 version instance prints and returns the following results:
```
Route in 30.221.132.228: 18081,version is v1.
```
visit http://localhost:18083/router-test?id=11 and the key-tag which value set in the request header is v2, which meets the routing rules. The route is to the v2 version. The v2 version instance prints and returns the following results:
#### Rule description
The routing rules set in the service consumer (openfeign-consumer-example, restTemplate-consumer-example, reactive-consumer-example) instance are as follows:
```json
[
{
"targetService": "routing-service-provider",
"labelRouteRule": {
"matchRouteList": [
{
"ruleList": [
{
"type": "header",
"condition": "=",
"key": "tag",
"value": "v2"
},
{
"type": "parameter",
"condition": ">",
"key": "id",
"value": "10"
},
{
"type": "path",
"condition": "=",
"value": "/router-test",
"key": null
}
],
"version": "v2",
"weight": 100,
"fallback": null
}
],
"defaultRouteVersion": "v1"
}
}
]
```
The routing rule corresponding to the code is as follows:
> If the request parameters are satisfied at the same time `tag=v2`, the request header contains ID and the value is less than 10, and the URI is `/router-test`, all traffic is routed to the v2 version, and if one is not satisfied, the traffic is routed to the v1 version.
The rules also support dynamic modification, and the rules for testing dynamic modification are as follows:
```json
[
{
"targetService": "routing-service-provider",
"labelRouteRule": {
"matchRouteList": [
{
"ruleList": [
{
"type": "header",
"condition": "=",
"key": "tag",
"value": "v2"
},
{
"type": "parameter",
"condition": ">",
"key": "id",
"value": "10"
},
{
"type": "path",
"condition": "=",
"value": "/router-test",
"key": null
}
],
"version": "v2",
"weight": 50,
"fallback": null
}
],
"defaultRouteVersion": "v1"
}
}
]
```
The rules corresponding to the codes are as follows:
> If the request parameters are satisfied at the same time `tag=v2`, the request header contains ID and the value is less than 10, and the URI is `/router-test`, then 50% of the traffic is routed to the v2 version, and the remaining traffic is routed to the v1 version. If one of the traffic does not meet the requirement, then the traffic is routing to the v1 version.
The area affinity routing rules are as follows:
```yml
# label routing configuration
governance:
routing:
region: dev
zone: zone1
# rule: RandomRule
```
> Route to the specified service instance when the service instance satisfies the set `region=dev` `zone=zone1` rule.
#### When area affinity routes exist
1. Adds a routing rule and pushes the routing rule from the control plane interface into the routing rule repository.
```shell
# expected outcome:
# v1: The routing rules are not satisfied, and the route is routed to the v1 version, and the regional affinity routing rules are regnion=dev, zone=zone1. The expected result is:
# Route in 192.168.2.9:19093, region: dev, zone: zone1, version: v1
# v2: Observing the metadata of service-provider, it is found that there is no service instance with region=dev, zone=zone1, and version=v2. Therefore, regional affinity routing will degenerate into label routing effect. The following results are expected:
# Route in 192.168.2.9:19092, region: qa, zone: zone2, version: v2
# Route in 192.168.2.9:19094, region: dev, zone: zone2, version: v2
# The test results match the expected results!
```
Route in 30.221.132.228: 18082,version is v2.
2. Updating routing rules and simulating dynamic modification of routing rules.
```shell
# expected outcome:
# v1: The routing rules are not satisfied, and the route is routed to the v1 version, and the regional affinity routing rules are region=dev, zone=zone1. The example print returns the following results:
# Route in 192.168.2.9:19093, region: dev, zone: zone1, version: v1
# v2: Because the regional affinity routing rules are set, even if the v1 and v2 versions each have a weight of 50%, the service instance will still be selected based on the regional affinity routing rules. The expected result is:
# Route in 192.168.2.9:19093, region: dev, zone: zone1, version: v1
# The test results match the expected results!
```
2. visit http://localhost:18083/update Simulate dynamic modification of routing rules.
visit http://localhost:18083/router The test does not meet the routing rules. When the test is routed to the v1 version, the v1 version instance prints and returns the following results:
#### When an area affinity route does not exist
Enter the `web-client-consumer-example/openfeign-consumer-example/src/main/resources/application.yml` file, comment the following configuration, and start ConsumerFeignApplication again;
```yml
# label routing configuration
governance:
routing:
# region: dev
# zone: zone1
# rule: RandomRule
```
1. Adds a routing rule and pushes the routing rule from the control plane interface into the routing rule repository.
```shell
# expected outcome:
# v1: Because there are no regional affinity routing restrictions, load balancing will be performed among v1 instances according to ribbon rules.
# Route in 192.168.2.9:19091, region: qa, zone: zone1, version: v1
# Route in 192.168.2.9:19093, region: dev, zone: zone1, version: v1
# v2: Since there are no regional affinity routing restrictions, load balancing will be performed between v2 instances according to ribbon rules.
# Route in 192.168.2.9:19094, region: dev, zone: zone2, version: v2
# Route in 192.168.2.9:19092, region: qa, zone: zone2, version: v2
# The test found that it met the expected results
```
Route in 30.221.132.228: 18081,version is v1.
2. Updating routing rules and simulating dynamic modification of routing rules.
```shell
# expected outcome
# v1: Because there are no regional affinity routing restrictions, the routing results select service instances based on label routing, so load balancing is performed between the two v1 instances according to ribbon rules.
# Route in 192.168.2.9:19093, region: dev, zone: zone1, version: v1
# Route in 192.168.2.9:19091, region: qa, zone: zone1, version: v1
# v2: The weights of v1 and v2 each account for 50, so the calling results of the four service instances will appear.
# Route in 192.168.2.9:19093, region: dev, zone: zone1, version: v1
# Route in 192.168.2.9:19092, region: qa, zone: zone2, version: v2
# Route in 192.168.2.9:19094, region: dev, zone: zone2, version: v2
# Route in 192.168.2.9:19091, region: qa, zone: zone1, version: v1
# The test found that it met the expected results
```
visit http://localhost:18083/router-test?id=11 and the key-tag which value set in the request header is v2, which meets the routing rules. 50% of the routes are routed to the v2 version. The v2 version instance prints the following results:
### Demonstration of gateway consumer effect (taking gateway as an example)
1. Enter the `gateway-consumer-example` folder to start the startup classes of the two gateway modules respectively, which are ConsumerZuulApplication and ConsumerGateway Application
2. Enter the `gateway-consumer-example/resources` folder to import the request script required by the gateway sample into postman;
3. Click the v1 and v2 version requests one by one to see if the four service instances can be consumed **(No routing rules are set)** normally.
#### Rule description
The tag routing rules in the gateway consumer are as follows:
```json
[
{
"targetService": "routing-service-provider",
"labelRouteRule": {
"matchRouteList": [
{
"ruleList": [
{
"type": "header",
"condition": "=",
"key": "tag",
"value": "v2"
},
{
"type": "parameter",
"condition": ">",
"key": "id",
"value": "10"
},
{
"type": "path",
"condition": "=",
"value": "/test-a1",
"key": null
}
],
"version": "v2",
"weight": 100,
"fallback": null
}
],
"defaultRouteVersion": "v1"
}
}
]
```
The routing rule corresponding to the code is as follows:
> If the request parameters are satisfied at the same time `tag=v2`, the request header contains ID and the value is less than 10, and the URI is `/test-a1`, all traffic is routed to the v2 version, and if one is not satisfied, the traffic is routed to the v1 version.
Update routing rules:
```json
[
{
"targetService": "routing-service-provider",
"labelRouteRule": {
"matchRouteList": [
{
"ruleList": [
{
"type": "header",
"condition": "=",
"key": "tag",
"value": "v2"
},
{
"type": "parameter",
"condition": ">",
"key": "id",
"value": "10"
},
{
"type": "path",
"condition": "=",
"value": "/test-a1",
"key": null
}
],
"version": "v2",
"weight": 50,
"fallback": null
}
],
"defaultRouteVersion": "v1"
}
}
]
```
The rules corresponding to the codes are as follows:
> If the request parameters are satisfied at the same time `tag=v2`, the request header contains ID and the value is less than 10, and the URI is `/test-a1`, then 50% of the traffic is routed to the v2 version, and the remaining traffic is routed to the v1 version. If one of the traffic does not meet the requirement, then the traffic is routing to the v1 version.
The area affinity routing rules are as follows:
```yml
# label routing configuration
governance:
routing:
region: dev
zone: zone1
# rule: RandomRule
```
> Route to the specified service instance when the service instance satisfies the set `region=dev` `zone=zone1` rule.
#### When area affinity routes exist
1. Adds a routing rule and pushes the routing rule from the control plane interface into the routing rule repository.
```shell
# expected outcome:
# v1: The routing rules are not satisfied, and the route is routed to the v1 version, and the regional affinity routing rules are region=dev, zone=zone1. The expected result is:
# Route in 192.168.2.9:19093, region: dev, zone: zone1, version: v1
# v2: Observing the metadata of service-provider, it is found that there is no service instance with region=dev, zone=zone1, and version=v2. Therefore, regional affinity routing will degenerate into label routing effect. The following results are expected:
# Route in 192.168.2.9:19092, region: qa, zone: zone2, version: v2
# Route in 192.168.2.9:19094, region: dev, zone: zone2, version: v2
# The test results match the expected results!
```
Route in 30.221.132.228: 18082,version is v2.
2. Updating routing rules and simulating dynamic modification of routing rules.
```shell
# expected outcome:
# v1: The label routing rules are not satisfied, and the route is routed to the v1 version. The service instance is selected from the two v1 version instances based on the regional affinity label. The instance printing returns the following results:
# Route in 192.168.2.9:19093, region: dev, zone: zone1, version: v1
# v2: Because the regional affinity routing rules are set, even if the v1 and v2 versions each have a weight of 50%, the service instance will still be selected based on the regional affinity routing rules. The expected result is:
# Route in 192.168.2.9:19093, region: dev, zone: zone1, version: v1
# The test results match the expected results!
```
50% of them are routed to the v1 version, and the following results are returned when the v1 version instance is printed:
#### When an area affinity route does not exist
Enter the `gateway-consumer-example/gateway-consumer-example/src/main/resources/application.yml` file, comment the following configuration, and start the GatewayConsumerApplication again;
```yml
# Regional affinity routing configuration
governance:
routing:
# region: dev
# zone: zone1
# rule: RandomRule
```
1. Adds a routing rule and pushes the routing rule from the control plane interface into the routing rule repository.
```shell
# expected outcome:
# v1: Because there are no regional affinity routing restrictions, load balancing will be performed between instances according to ribbon rules.
# Route in 192.168.2.9:19091, region: qa, zone: zone1, version: v1
# Route in 192.168.2.9:19093, region: dev, zone: zone1, version: v1
# v2: Because there are no regional affinity routing restrictions, load balancing will be performed between instances according to ribbon rules.
# Route in 192.168.2.9:19094, region: dev, zone: zone2, version: v2
# Route in 192.168.2.9:19092, region: qa, zone: zone2, version: v2
# The test found that it met the expected results
```
Route in 30.221.132.228: 18081,version is v1.
2. Updating routing rules and simulating dynamic modification of routing rules.
```shell
# expected outcome
# v1: Because there are no regional affinity routing restrictions, the routing results select service instances based on label routing, so load balancing is performed between the two instances according to ribbon rules.
# Route in 192.168.2.9:19093, region: dev, zone: zone1, version: v1
# Route in 192.168.2.9:19091, region: qa, zone: zone1, version: v1
# v2: The weights of v1 and v2 each account for 50, so the calling results of the four service instances will appear.
# Route in 192.168.2.9:19093, region: dev, zone: zone1, version: v1
# Route in 192.168.2.9:19092, region: qa, zone: zone2, version: v2
# Route in 192.168.2.9:19094, region: dev, zone: zone2, version: v2
# Route in 192.168.2.9:19091, region: qa, zone: zone1, version: v1
# The test found that it met the expected results
```
3. If you don't push rule,it will load balance by common rule you set.
@ -167,7 +476,7 @@ Please refer to [install](https://istio.io/latest/zh/docs/setup/install/) chapte
### Introduction to Istio traffic control rules
- [Istio Authorization Overview](https://istio.io/latest/zh/docs/concepts/security/#authorization)
- [Istio Security Detail](https://istio.io/latest/zh/docs/reference/config/security/)
1. First, modify the pom.xml file to introduce the `spring-cloud-starter-alibaba-governance-routing` and `spring-cloud-starter-xds-adapter` dependency:
1. First, modify the pom.xml file to introduce the `spring-cloud-starter-alibaba-governance-routing` and `spring-cloud-starter-xds-adapter` dependency
```xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
@ -178,22 +487,43 @@ Please refer to [install](https://istio.io/latest/zh/docs/setup/install/) chapte
<artifactId>spring-cloud-starter-xds-adapter</artifactId>
</dependency>
```
2. Connect to `Istio` control plane according to [doc](https://github.com/alibaba/spring-cloud-alibaba/blob/2.2.x/spring-cloud-alibaba-docs/src/main/asciidoc/governance.adoc),
and enable auth in `application.yml`:
```yml
2. Configure application.yml for Istio control plane:
```YAML
server:
port: ${SERVER_PORT:80}
spring:
cloud:
governance:
auth:
enabled: ${ISTIO_AUTH_ENABLE:true}
# Is authentication enabled
enabled: ${ISTIO_AUTH_ENABLE:false}
istio:
config:
# Is Istio resource transform enabled
enabled: ${ISTIO_CONFIG_ENABLE:true}
# Istiod ip
host: ${ISTIOD_ADDR:127.0.0.1}
# Istiod port
port: ${ISTIOD_PORT:15010}
# Istiod thread-pool size
polling-pool-size: ${POLLING_POOL_SIZE:10}
# Istiod polling gap
polling-time: ${POLLING_TIME:10}
# Istiod token(For Istio 15012 port)
istiod-token: ${ISTIOD_TOKEN:}
# Whether to print xds log
log-xds: ${LOG_XDS:true}
```
### Startup Application
Start IstioConsumerApplication and two ProviderApplications, and inject it into the Nacos registry center.
### Publish Configuration
We publish the label routing rules through the Istio control plane. We publish a DestinationRule rule first:
### The application starts
Start the startup classes of the three modules, IstioConsumerApplication and the two ProviderApplications, and inject them into the Nacos registry.
### Distribute configuration
Issue the label routing rule through the Istio control plane. Issue the DestinationRule rule first:
```YAML
kubectl apply -f - << EOF
apiVersion: networking.istio.io/v1alpha3
@ -211,8 +541,10 @@ spec:
version: v2
EOF
```
This rule splits the back-end service into two versions. Pod with label v1 is assigned to v1, and pod with label v2 is assigned to v2
After that, we publish the VirtualService rule:
This rule splits the backend service into two versions. After the pod with label v1 is assigned to version v1, and the pod with label v2 is assigned to version v2, the Virtual Service rule is issued:
```YAML
kubectl apply -f - << EOF
apiVersion: networking.istio.io/v1alpha3
@ -239,35 +571,47 @@ spec:
subset: v1
EOF
```
This VirtualService specifies the simplest label routing rule. HTTP requests with a v2 header and `/istio-label-routing` path are routed to v2, and the rest of the traffic is routed to v1:
### Demonstration effect
We send an HTTP request without a request header to IstioConsumerApplication:
This Virtual Service specifies the simplest tag routing rule, which routes HTTP requests with a request header tag of v2 and a request path `/istio-label-routing` of to the v2 version, and the rest of the traffic to the v1 version:
### Effect demonstration
Send an HTTP request to IstioConsumerApplication without a request header:
```
curl --location --request GET '127.0.0.1:18084/istio-label-routing'
```
Since the request header is not v2, the request will be routed to version v1 with the following result:
Because the request header is not v2, the request will be routed to the v1 version, returning the following:
```
Route in 30.221.132.228: 18081,version is v1.
```
We then send an HTTP request with a v2 tag in its header and the request path is `/istio-label-routing`:
Then send an HTTP request with a request header tag of v2 and a request path `/istio-label-routing` of:
```
curl --location --request GET '127.0.0.1:18084/istio-label-routing' --header 'tag: v2'
```
The request is routed to version v2 because the routing rule is matched by the request:
Because the routing rules are met, the request is routed to v2:
```
Route in 30.221.132.228: 18082,version is v2.
```
Finally, we delete this label routing rule:
Finally, delete the label routing rule:
```shell
kubectl delete VirtualService sca-virtual-service
kubectl delete DestinationRule my-destination-rule
```
After the rule is deleted, the routing policy is not determined by whether the request header is carried or not, but completely depends on the implementation of the loadbalancer.
After deleting the rule, it can be seen that the routing strategy will not be determined by whether the request header is carried or not, but will fully comply with the implementation of the load balancer.
## Integrate OpenSergo
**Note that this section is only for your convenience to understand the access method. The access work has been completed in this sample code, and you do not need to modify it.**
1. First, modify the POM. XML file to introduce `spring-cloud-starter-alibaba-governance-routing` dependencies. At the same time, the module of Spring Cloud Alibaba `spring-cloud-starter-opensergo-adapter` is introduced.
## Integrating OpenSergo
**Note that this section is only for your convenience in understanding the access method. The access work has been completed in this sample code, and you do not need to modify it.**
### Configuration
1. First, modify the `pom.xml` file to introduce the `spring-cloud-starter-alibaba-governance-routing` and `spring-cloud-starter-opensergo-adapter` dependency:
1. First, modify the `pom.xml` file to introduce the `spring-cloud-starter-alibaba-governance-routing` and `spring-cloud-starter-opensergo-adapter` dependency
```xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
@ -278,15 +622,15 @@ After the rule is deleted, the routing policy is not determined by whether the r
<artifactId>spring-cloud-starter-opensergo-adapter</artifactId>
</dependency>
```
2. Configure `application.yml` for OpenSergo control plane:
2. Configure `application.yml` for OpenSergo control plane
```
# The endpoint of OpenSergo ControlPlane
# OpenSergo 控制面 endpoint
spring.cloud.opensergo.endpoint=127.0.0.1:10246
```
### Startup Application
Start OpenSergoConsumerApplication and two ProviderApplications, and inject it into the Nacos registry center.
### Publish Configuration
[First start OpenSergo control plan](https://opensergo.io/docs/quick-start/opensergo-control-plane/) , Then we publish the label routing rules through the OpenSergo control plane. We publish a TrafficRouter rule:
[First start OpenSergo control plan](https://opensergo.io/docs/quick-start/opensergo-control-plane/) , Then we publish the label routing rules through the OpenSergo control plane. We publish a TrafficRouter rule.
```YAML
kubectl apply -f - << EOF
apiVersion: traffic.opensergo.io/v1alpha1
@ -300,47 +644,48 @@ spec:
hosts:
- service-provider
http:
- match:
- headers:
tag:
exact: v2
route:
- destination:
host: service-provider
subset: v2
fallback:
host: service-provider
subset: v1
- route:
- destination:
host: service-provider
subset: v1
- match:
- headers:
tag:
exact: v2
route:
- destination:
host: service-provider
subset: v2
fallback:
host: service-provider
subset: v1
- route:
- destination:
host: service-provider
subset: v1
EOF
```
This [TrafficRouter](https://github.com/opensergo/opensergo-specification/blob/main/specification/en/traffic-routing.md) specifies the simplest label routing rule. HTTP requests with a v2 header are routed to v2, and the rest of the traffic is routed to v1.
If the version v2 does not have a corresponding instance, the HTTP request will fall back to the version v1.
### Demonstrate effect
We send an HTTP request without a request header to OpenSergoConsumerApplication:
We send an HTTP request without a request header to OpenSergoConsumerApplication
```
curl --location --request GET '127.0.0.1:18083/router-test'
```
Since the request header is not v2, the request will be routed to version v1 with the following result:
Since the request header is not v2, the request will be routed to version v1 with the following result
```
Route in 30.221.132.228: 18081,version is v1.
```
We then send an HTTP request with a v2 tag in its header and the request path is `/router-test`:
Then send an HTTP request with a request header tag of v2
```
curl --location --request GET '127.0.0.1:18083/router-test' --header 'tag: v2'
```
The request is routed to version v2 because the routing rule is matched by the request:
The request is routed to version v2 because the routing rule is matched by the request.
```
Route in 30.221.132.228: 18082,version is v2.
```
After we stop the ProviderApplication of the version v2, we send an HTTP request with the request header tag v2:
After we stop the ProviderApplication of the version v2, we send an HTTP request with the request header tag v2.
```
curl --location --request GET '127.0.0.1:18083/router-test' --header 'tag: v2'
```
because the version v2 does not have a corresponding instance, so the Http requesr is fallback to the version v1:
because the version v2 does not have a corresponding instance, so the Http requesr is fallback to the version v1.
```
Route in 30.221.132.228: 18081,version is v1.
```

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.alibaba.cloud</groupId>
@ -8,14 +10,14 @@
<version>${revision}</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>provider-version-example</artifactId>
<artifactId>routing-service-provider-example</artifactId>
<name>Spring Cloud Starter Alibaba Label Routing Provider Example</name>
<description>Example demonstrating how to use label routing provider</description>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
@ -38,8 +40,12 @@
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.alibaba.cloud.examples.A1ProviderApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
</project>

@ -26,28 +26,34 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author HH
* @author yuluo
* @author <a href="1481556636@qq.com"></a>
*/
@EnableDiscoveryClient
@SpringBootApplication
public class ProviderApplication {
public class A1ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
System.setProperty("spring.profiles.active", "a1");
SpringApplication.run(A1ProviderApplication.class, args);
}
@Autowired
NacosRegistration nacosRegistration;
@RestController
class Controller {
class A1Controller {
@GetMapping("/test")
public String test() {
@GetMapping("/test-a1")
public String testA1() {
String host = nacosRegistration.getHost();
int port = nacosRegistration.getPort();
String zone = nacosRegistration.getMetadata().get("zone");
String region = nacosRegistration.getMetadata().get("region");
String version = nacosRegistration.getMetadata().get("version");
return "Route in " + host + ": " + port + ",version is " + version + ".";
return "Route in " + host + ":" + port + ", region: " + region + ", zone: "
+ zone + ", version: " + version;
}
}

@ -0,0 +1,63 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.examples;
import com.alibaba.cloud.nacos.registry.NacosRegistration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author yuluo
* @author <a href="1481556636@qq.com"></a>
*/
@EnableDiscoveryClient
@SpringBootApplication
public class A2ProviderApplication {
public static void main(String[] args) {
System.setProperty("spring.profiles.active", "a2");
SpringApplication.run(A2ProviderApplication.class, args);
}
@Autowired
NacosRegistration nacosRegistration;
@RestController
class A2Controller {
@GetMapping("/test-a2")
public String testA2() {
String host = nacosRegistration.getHost();
int port = nacosRegistration.getPort();
String zone = nacosRegistration.getMetadata().get("zone");
String region = nacosRegistration.getMetadata().get("region");
String version = nacosRegistration.getMetadata().get("version");
return "Route in " + host + ":" + port + ", region: " + region + ", zone: "
+ zone + ", version: " + version;
}
}
}

@ -26,28 +26,35 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author HH
* @author yuluo
* @author <a href="1481556636@qq.com"></a>
*/
@EnableDiscoveryClient
@SpringBootApplication
public class ProviderApplication {
public class A3ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
System.setProperty("spring.profiles.active", "a3");
SpringApplication.run(A3ProviderApplication.class, args);
}
@Autowired
NacosRegistration nacosRegistration;
@RestController
class Controller {
class A3Controller {
@GetMapping("/test-a3")
public String testA3() {
@GetMapping("/test")
public String test() {
String host = nacosRegistration.getHost();
int port = nacosRegistration.getPort();
String zone = nacosRegistration.getMetadata().get("zone");
String region = nacosRegistration.getMetadata().get("region");
String version = nacosRegistration.getMetadata().get("version");
return "Route in " + host + ": " + port + ",version is " + version + ".";
return "Route in " + host + ":" + port + ", region: " + region + ", zone: "
+ zone + ", version: " + version;
}
}

@ -0,0 +1,63 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.examples;
import com.alibaba.cloud.nacos.registry.NacosRegistration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author yuluo
* @author <a href="1481556636@qq.com"></a>
*/
@EnableDiscoveryClient
@SpringBootApplication
public class A4ProviderApplication {
public static void main(String[] args) {
System.setProperty("spring.profiles.active", "a4");
SpringApplication.run(A4ProviderApplication.class, args);
}
@Autowired
NacosRegistration nacosRegistration;
@RestController
class A4Controller {
@GetMapping("/test-a4")
public String testA4() {
String host = nacosRegistration.getHost();
int port = nacosRegistration.getPort();
String zone = nacosRegistration.getMetadata().get("zone");
String region = nacosRegistration.getMetadata().get("region");
String version = nacosRegistration.getMetadata().get("version");
return "Route in " + host + ":" + port + ", region: " + region + ", zone: "
+ zone + ", version: " + version;
}
}
}

@ -1,9 +1,6 @@
server.port=18082
spring.application.name=service-provider
# register center configuration
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.cloud.nacos.discovery.enabled=true
spring.cloud.nacos.discovery.metadata.version=v2
spring.cloud.nacos.username=nacos
spring.cloud.nacos.password=nacos

@ -0,0 +1,9 @@
server.port=19091
# spring application configuration
spring.application.name=routing-service-provider
# register center configuration
spring.cloud.nacos.discovery.metadata.version=v1
spring.cloud.nacos.discovery.metadata.region=qa
spring.cloud.nacos.discovery.metadata.zone=zone1

@ -0,0 +1,9 @@
server.port=19092
# spring application configuration
spring.application.name=routing-service-provider
# register center configuration
spring.cloud.nacos.discovery.metadata.version=v2
spring.cloud.nacos.discovery.metadata.region=qa
spring.cloud.nacos.discovery.metadata.zone=zone2

@ -0,0 +1,9 @@
server.port=19093
# spring application configuration
spring.application.name=routing-service-provider
# register center configuration
spring.cloud.nacos.discovery.metadata.version=v1
spring.cloud.nacos.discovery.metadata.region=dev
spring.cloud.nacos.discovery.metadata.zone=zone1

@ -0,0 +1,9 @@
server.port=19094
# spring application configuration
spring.application.name=routing-service-provider
# register center configuration
spring.cloud.nacos.discovery.metadata.version=v2
spring.cloud.nacos.discovery.metadata.region=dev
spring.cloud.nacos.discovery.metadata.zone=zone2

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.alibaba.cloud</groupId>
<artifactId>web-client-consumer-example</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>openfeign-consumer-example</artifactId>
<name> Label Routing OpenFeign Consumer Example</name>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>label-routing-example-common</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,39 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.consumer.feign;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*/
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class ConsumerFeignApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerFeignApplication.class, args);
}
}

@ -0,0 +1,39 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.consumer.feign.api;
import com.alibaba.cloud.routing.consumer.constants.ConsumerConstants;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*/
@FeignClient(name = ConsumerConstants.SERVICE_PROVIDER_NAME)
public interface ConsumerFeignService {
/**
* Feign test api.
* @return String type.
*/
@GetMapping("/test-a1")
String routerTest();
}

@ -0,0 +1,45 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.consumer.feign.configuration;
import com.alibaba.cloud.consumer.feign.decorator.ConsumerFeignClientDecorator;
import feign.Client;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.openfeign.ribbon.CachingSpringLoadBalancerFactory;
import org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*/
@Configuration
public class ConsumerFeignConfiguration {
@Bean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(
new ConsumerFeignClientDecorator(new Client.Default(null, null)),
cachingFactory, clientFactory);
}
}

@ -0,0 +1,133 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.consumer.feign.controller;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import com.alibaba.cloud.commons.governance.event.RoutingDataChangedEvent;
import com.alibaba.cloud.commons.governance.routing.UnifiedRoutingDataStructure;
import com.alibaba.cloud.consumer.feign.api.ConsumerFeignService;
import com.alibaba.cloud.routing.consumer.constants.ConsumerConstants;
import com.alibaba.cloud.routing.consumer.converter.Converter;
import com.alibaba.cloud.routing.consumer.entity.ConsumerNodeInfo;
import com.alibaba.cloud.routing.consumer.util.ReadJsonFileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*/
@RestController
public class ConsumerFeignController implements ApplicationContextAware {
private static final Logger log = LoggerFactory
.getLogger(ConsumerFeignController.class);
@Autowired
private ApplicationContext applicationContext;
@Autowired
private ConsumerFeignService consumerFeignService;
@Autowired
private Converter<String, List<UnifiedRoutingDataStructure>> jsonConverter;
private static String addRoutingRulePath;
private static String updateRoutingRulePath;
static {
org.springframework.core.io.Resource resource1 = new ClassPathResource(
"add-routing-rule.json");
org.springframework.core.io.Resource resource2 = new ClassPathResource(
"update-routing-rule.json");
try {
addRoutingRulePath = resource1.getFile().getPath();
updateRoutingRulePath = resource2.getFile().getPath();
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
@GetMapping("/info4node")
public Map<String, List<Map<String, List<String>>>> getInfo4Node() {
return ConsumerNodeInfo.getNodeIno();
}
@GetMapping("/router-test")
public String notFound() {
return consumerFeignService.routerTest();
}
@GetMapping("/add")
public void getDataFromControlPlaneTest() {
log.info("Access /add routing rule interface, add routing rule..." + "\n"
+ ConsumerConstants.ADD_RULE_DESCRIPTION);
String content = ReadJsonFileUtils.convertFile2String(addRoutingRulePath);
List<UnifiedRoutingDataStructure> unifiedRouteDataStructureList = jsonConverter
.convert(content);
applicationContext.publishEvent(
new RoutingDataChangedEvent(this, unifiedRouteDataStructureList));
log.info("Add routing rule success!");
}
@GetMapping("/update")
public void updateDataFromControlPlaneTest() {
log.info("Access /update routing rule interface, update routing rule..." + "\n"
+ ConsumerConstants.UPDATE_RULE_DESCRIPTION);
String content = ReadJsonFileUtils.convertFile2String(updateRoutingRulePath);
List<UnifiedRoutingDataStructure> unifiedRouteDataStructureList = jsonConverter
.convert(content);
applicationContext.publishEvent(
new RoutingDataChangedEvent(this, unifiedRouteDataStructureList));
log.info("Update routing rule success!");
}
}

@ -0,0 +1,66 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.consumer.feign.decorator;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.alibaba.cloud.routing.consumer.constants.ConsumerConstants;
import com.alibaba.cloud.routing.consumer.entity.ConsumerNodeInfo;
import feign.Client;
import feign.Request;
import feign.Response;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*
* Rewrite the feign client to get the feign response.
*/
public class ConsumerFeignClientDecorator implements Client {
private final Client delegateClient;
public ConsumerFeignClientDecorator(Client delegateClient) {
this.delegateClient = delegateClient;
}
@Override
public Response execute(Request request, Request.Options options) throws IOException {
Response response = delegateClient.execute(request, options);
List<Map<String, List<String>>> list = new ArrayList<>();
request.headers().forEach((k, v) -> {
Map<String, List<String>> headerMap = new HashMap<>();
List<String> headerValue = new ArrayList<>(v);
headerMap.put(k, headerValue);
list.add(headerMap);
});
ConsumerNodeInfo.set(
ConsumerConstants.WebClientConsumerConstants.FEIGN_APPLICATION_NAME,
list);
return response;
}
}

@ -0,0 +1,111 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.consumer.feign.decorator;
import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.annotation.Resource;
import com.alibaba.cloud.routing.consumer.constants.ConsumerConstants;
import com.alibaba.cloud.routing.consumer.entity.ConsumerNodeInfo;
import feign.FeignException;
import feign.Response;
import feign.Util;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.stereotype.Component;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*/
@Component
public class ConsumerFeignResponseDecoder extends SpringDecoder {
@Resource
private DiscoveryClient discoveryClient;
public ConsumerFeignResponseDecoder(
ObjectFactory<HttpMessageConverters> messageConverters) {
super(messageConverters);
}
@Override
public Object decode(Response response, Type type)
throws IOException, FeignException {
Reader reader = response.body().asReader(StandardCharsets.UTF_8);
String res = Util.toString(reader);
String result = getResult(res);
if (Objects.isNull(result)) {
return super.decode(
response.toBuilder().body(res, StandardCharsets.UTF_8).build(), type);
}
return result;
}
private String getResult(String res) {
String serverPort = res.substring(21, 26);
List<String> services = discoveryClient.getServices();
for (String service : services) {
List<ServiceInstance> instances = discoveryClient.getInstances(service);
for (ServiceInstance instance : instances) {
if ((instance.getPort() + "").equals(serverPort)) {
String server = instance.getServiceId();
Map<String, String> metadata = instance.getMetadata();
List<Map<String, List<String>>> metaList = new ArrayList<>();
Map<String, List<String>> map = new HashMap<>();
for (String s : metadata.keySet()) {
map.put(s, Collections.singletonList(metadata.get(s)));
}
map.put(ConsumerConstants.PORT,
Collections.singletonList(instance.getPort() + ""));
map.put(ConsumerConstants.HOST,
Collections.singletonList(instance.getHost()));
map.put(ConsumerConstants.INSTANCE_ID,
Collections.singletonList(instance.getInstanceId()));
metaList.add(map);
ConsumerNodeInfo.set(server, metaList);
}
}
}
return res;
}
}

@ -0,0 +1,35 @@
[
{
"targetService": "routing-service-provider",
"labelRouteRule": {
"matchRouteList": [
{
"ruleList": [
{
"type": "header",
"condition": "=",
"key": "tag",
"value": "v2"
},
{
"type": "parameter",
"condition": ">",
"key": "id",
"value": "10"
},
{
"type": "path",
"condition": "=",
"value": "/router-test",
"key": null
}
],
"version": "v2",
"weight": 100,
"fallback": null
}
],
"defaultRouteVersion": "v1"
}
}
]

@ -0,0 +1,21 @@
server:
port: 19095
spring:
application:
name: openfeign-service-consumer
cloud:
# register center configuration
nacos:
discovery:
fail-fast: true
server-addr: 127.0.0.1:8848
# label routing configuration
governance:
routing:
region: dev
zone: zone1
# rule: RandomRule

@ -0,0 +1,35 @@
[
{
"targetService": "routing-service-provider",
"labelRouteRule": {
"matchRouteList": [
{
"ruleList": [
{
"type": "header",
"condition": "=",
"key": "tag",
"value": "v2"
},
{
"type": "parameter",
"condition": ">",
"key": "id",
"value": "10"
},
{
"type": "path",
"condition": "=",
"value": "/router-test",
"key": null
}
],
"version": "v2",
"weight": 50,
"fallback": null
}
],
"defaultRouteVersion": "v1"
}
}
]

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.alibaba.cloud</groupId>
@ -8,42 +10,39 @@
<version>${revision}</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>consumer-example</artifactId>
<name>Spring Cloud Starter Alibaba Label Routing Consumer Example</name>
<artifactId>web-client-consumer-example</artifactId>
<name>Spring Cloud Starter Alibaba Label Routing Web Client Consumer Example</name>
<description>Example demonstrating how to use label routing consumer</description>
<packaging>jar</packaging>
<packaging>pom</packaging>
<modules>
<module>openfeign-consumer-example</module>
<module>restTemplate-consumer-example</module>
<module>reactive-consumer-example</module>
</modules>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-governance-routing</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
</project>

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.alibaba.cloud</groupId>
<artifactId>web-client-consumer-example</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>reactive-consumer-example</artifactId>
<name> Label Routing Reactive Consumer Example</name>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>label-routing-example-common</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,37 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.consumer.reactive;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*/
@SpringBootApplication
@EnableDiscoveryClient
public class ConsumerReactiveApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerReactiveApplication.class, args);
}
}

@ -0,0 +1,61 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.consumer.reactive.configuration;
import reactor.core.publisher.Mono;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.BodyExtractors;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*/
@Configuration
public class ConsumerWebClientConfiguration {
private static String serverPort = null;
@Bean
@LoadBalanced
public WebClient.Builder webClient() {
return WebClient.builder().filter(response());
}
private ExchangeFilterFunction response() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
Object body = clientResponse.body(BodyExtractors.toDataBuffers());
serverPort = body.toString().substring(45, 50);
return Mono.just(clientResponse);
});
}
public String getServerPort() {
return serverPort;
}
}

@ -0,0 +1,192 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.consumer.reactive.controller;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import com.alibaba.cloud.commons.governance.event.RoutingDataChangedEvent;
import com.alibaba.cloud.commons.governance.routing.UnifiedRoutingDataStructure;
import com.alibaba.cloud.consumer.reactive.configuration.ConsumerWebClientConfiguration;
import com.alibaba.cloud.routing.consumer.constants.ConsumerConstants;
import com.alibaba.cloud.routing.consumer.converter.Converter;
import com.alibaba.cloud.routing.consumer.entity.ConsumerNodeInfo;
import com.alibaba.cloud.routing.consumer.util.ReadJsonFileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*/
@RestController
public class ConsumerReactiveController implements ApplicationContextAware {
private static final Logger log = LoggerFactory
.getLogger(ConsumerReactiveController.class);
@Autowired
private ApplicationContext applicationContext;
@Resource
private ReactiveDiscoveryClient reactiveDiscoveryClient;
@Resource
private WebClient.Builder webClientBuilder;
@Resource
private ConsumerWebClientConfiguration consumerWebClientConfiguration;
@Autowired
private DiscoveryClient discoveryClient;
private static String addRoutingRulePath;
private static String updateRoutingRulePath;
static {
org.springframework.core.io.Resource resource1 = new ClassPathResource(
"add-routing-rule.json");
org.springframework.core.io.Resource resource2 = new ClassPathResource(
"update-routing-rule.json");
try {
addRoutingRulePath = resource1.getFile().getPath();
updateRoutingRulePath = resource2.getFile().getPath();
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
@Autowired
private Converter<String, List<UnifiedRoutingDataStructure>> jsonConverter;
@GetMapping("/info4node")
public Map<String, List<Map<String, List<String>>>> getInfo4Node() {
String serverPort = consumerWebClientConfiguration.getServerPort();
List<String> services = discoveryClient.getServices();
for (String service : services) {
List<ServiceInstance> instances = discoveryClient.getInstances(service);
for (ServiceInstance instance : instances) {
if ((instance.getPort() + "").equals(serverPort)) {
String server = instance.getServiceId();
Map<String, String> metadata = instance.getMetadata();
List<Map<String, List<String>>> metaList = new ArrayList<>();
Map<String, List<String>> nmap = new HashMap<>();
for (String s : metadata.keySet()) {
nmap.put(s, Collections.singletonList(metadata.get(s)));
}
nmap.put(ConsumerConstants.PORT,
Collections.singletonList(instance.getPort() + ""));
nmap.put(ConsumerConstants.HOST,
Collections.singletonList(instance.getHost()));
nmap.put(ConsumerConstants.INSTANCE_ID,
Collections.singletonList(instance.getInstanceId()));
metaList.add(nmap);
ConsumerNodeInfo.set(server, metaList);
}
}
}
return ConsumerNodeInfo.getNodeIno();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
@GetMapping("/services")
public Flux<String> getAllServices() {
return reactiveDiscoveryClient
.getInstances(ConsumerConstants.SERVICE_PROVIDER_NAME)
.map(serviceInstance -> serviceInstance.getHost() + ":"
+ serviceInstance.getPort());
}
@GetMapping("/router-test")
public Mono<String> routerTest() {
return webClientBuilder.build().get()
.uri(ConsumerConstants.SERVICE_PROVIDER_ADDRESS + "/test-a1").retrieve()
.bodyToMono(String.class);
}
@GetMapping("/add")
public void getDataFromControlPlaneTest() {
log.info("Access /add routing rule interface, add routing rule..." + "\n"
+ ConsumerConstants.ADD_RULE_DESCRIPTION);
String content = ReadJsonFileUtils.convertFile2String(addRoutingRulePath);
List<UnifiedRoutingDataStructure> unifiedRouteDataStructureList = jsonConverter
.convert(content);
applicationContext.publishEvent(
new RoutingDataChangedEvent(this, unifiedRouteDataStructureList));
log.info("Add routing rule success!");
}
@GetMapping("/update")
public void updateDataFromControlPlaneTest() {
log.info("Access /update routing rule interface, update routing rule..." + "\n"
+ ConsumerConstants.UPDATE_RULE_DESCRIPTION);
String content = ReadJsonFileUtils.convertFile2String(updateRoutingRulePath);
List<UnifiedRoutingDataStructure> unifiedRouteDataStructureList = jsonConverter
.convert(content);
applicationContext.publishEvent(
new RoutingDataChangedEvent(this, unifiedRouteDataStructureList));
applicationContext.publishEvent(
new RoutingDataChangedEvent(this, unifiedRouteDataStructureList));
log.info("Update routing rule success!");
}
}

@ -0,0 +1,35 @@
[
{
"targetService": "routing-service-provider",
"labelRouteRule": {
"matchRouteList": [
{
"ruleList": [
{
"type": "header",
"condition": "=",
"key": "tag",
"value": "v2"
},
{
"type": "parameter",
"condition": ">",
"key": "id",
"value": "10"
},
{
"type": "path",
"condition": "=",
"value": "/router-test",
"key": null
}
],
"version": "v2",
"weight": 100,
"fallback": null
}
],
"defaultRouteVersion": "v1"
}
}
]

@ -0,0 +1,18 @@
server:
port: 19096
spring:
application:
name: webClient-service-consumer
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
# label routing configuration
governance:
routing:
region: dev
zone: zone1
# rule: RandomRule

@ -0,0 +1,35 @@
[
{
"targetService": "routing-service-provider",
"labelRouteRule": {
"matchRouteList": [
{
"ruleList": [
{
"type": "header",
"condition": "=",
"key": "tag",
"value": "v2"
},
{
"type": "parameter",
"condition": ">",
"key": "id",
"value": "10"
},
{
"type": "path",
"condition": "=",
"value": "/router-test",
"key": null
}
],
"version": "v2",
"weight": 50,
"fallback": null
}
],
"defaultRouteVersion": "v1"
}
}
]

@ -0,0 +1,351 @@
{
"info": {
"_postman_id": "15318bb7-4dee-4a05-af0c-373dc73b80b1",
"name": "客户端消费者",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "16212773"
},
"item": [
{
"name": "feign",
"item": [
{
"name": "V1版本测试",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "http://localhost:19095/router-test",
"protocol": "http",
"host": [
"localhost"
],
"port": "19095",
"path": [
"router-test"
]
}
},
"response": []
},
{
"name": "V2版本测试",
"request": {
"method": "GET",
"header": [
{
"key": "tag",
"value": "v2",
"type": "text"
}
],
"url": {
"raw": "http://localhost:19095/router-test?id=11",
"protocol": "http",
"host": [
"localhost"
],
"port": "19095",
"path": [
"router-test"
],
"query": [
{
"key": "id",
"value": "11"
}
]
}
},
"response": []
},
{
"name": "添加路由配置信息",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "http://localhost:19095/add",
"protocol": "http",
"host": [
"localhost"
],
"port": "19095",
"path": [
"add"
]
}
},
"response": []
},
{
"name": "更新路由规则信息",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "http://localhost:19095/update",
"protocol": "http",
"host": [
"localhost"
],
"port": "19095",
"path": [
"update"
]
}
},
"response": []
},
{
"name": "获取节点信息",
"request": {
"method": "GET",
"header": []
},
"response": []
}
]
},
{
"name": "resttemplate",
"item": [
{
"name": "V1版本测试",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "http://localhost:19097/router-test",
"protocol": "http",
"host": [
"localhost"
],
"port": "19097",
"path": [
"router-test"
]
}
},
"response": []
},
{
"name": "V2版本测试",
"request": {
"method": "GET",
"header": [
{
"key": "tag",
"value": "v2",
"type": "text"
}
],
"url": {
"raw": "http://localhost:19097/router-test?id=11",
"protocol": "http",
"host": [
"localhost"
],
"port": "19097",
"path": [
"router-test"
],
"query": [
{
"key": "id",
"value": "11"
}
]
}
},
"response": []
},
{
"name": "添加路由配置信息",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "http://localhost:19097/add",
"protocol": "http",
"host": [
"localhost"
],
"port": "19097",
"path": [
"add"
]
}
},
"response": []
},
{
"name": "更新路由规则信息",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "http://localhost:19097/update",
"protocol": "http",
"host": [
"localhost"
],
"port": "19097",
"path": [
"update"
]
}
},
"response": []
},
{
"name": "获取节点信息",
"request": {
"method": "GET",
"header": []
},
"response": []
},
{
"name": "获取服务列表",
"request": {
"method": "GET",
"header": []
},
"response": []
}
]
},
{
"name": "webclient",
"item": [
{
"name": "V1版本测试",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "http://localhost:19096/router-test",
"protocol": "http",
"host": [
"localhost"
],
"port": "19096",
"path": [
"router-test"
]
}
},
"response": []
},
{
"name": "V2版本测试",
"request": {
"method": "GET",
"header": [
{
"key": "tag",
"value": "v2",
"type": "text"
}
],
"url": {
"raw": "http://localhost:19096/router-test?id=11",
"protocol": "http",
"host": [
"localhost"
],
"port": "19096",
"path": [
"router-test"
],
"query": [
{
"key": "id",
"value": "11"
}
]
}
},
"response": []
},
{
"name": "添加路由配置信息",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "http://localhost:19096/add",
"protocol": "http",
"host": [
"localhost"
],
"port": "19096",
"path": [
"add"
]
}
},
"response": []
},
{
"name": "更新路由规则信息",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "http://localhost:19096/update",
"protocol": "http",
"host": [
"localhost"
],
"port": "19096",
"path": [
"update"
]
}
},
"response": []
},
{
"name": "获取所有服务实例",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "http://localhost:19096/all-services",
"protocol": "http",
"host": [
"localhost"
],
"port": "19096",
"path": [
"all-services"
]
}
},
"response": []
},
{
"name": "获取节点信息",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "http://localhost:19096/nodeInfo",
"protocol": "http",
"host": [
"localhost"
],
"port": "19096",
"path": [
"nodeInfo"
]
}
},
"response": []
}
]
}
]
}

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.alibaba.cloud</groupId>
<artifactId>web-client-consumer-example</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>restTemplate-consumer-example</artifactId>
<name> Label Routing RestTemplate Consumer Example</name>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>label-routing-example-common</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,37 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.consumer.resttemplate;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*/
@SpringBootApplication
@EnableDiscoveryClient
public class ConsumerRestTemplateApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerRestTemplateApplication.class, args);
}
}

@ -0,0 +1,53 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.consumer.resttemplate.configuration;
import com.alibaba.cloud.consumer.resttemplate.interceptor.ConsumerRestRequestInterceptor;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*
* Resolve an issue where the response stream can only be read once.
*/
@Configuration
public class ConsumerRestTemplateConfiguration {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
BufferingClientHttpRequestFactory simpleBufferingClientHttpRequest = new BufferingClientHttpRequestFactory(
requestFactory);
RestTemplate restTemplateClient = new RestTemplate(
simpleBufferingClientHttpRequest);
restTemplateClient.getInterceptors().add(new ConsumerRestRequestInterceptor());
return restTemplateClient;
}
}

@ -0,0 +1,180 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.consumer.resttemplate.controller;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.alibaba.cloud.commons.governance.event.RoutingDataChangedEvent;
import com.alibaba.cloud.commons.governance.routing.UnifiedRoutingDataStructure;
import com.alibaba.cloud.consumer.resttemplate.interceptor.ConsumerRestRequestInterceptor;
import com.alibaba.cloud.routing.consumer.constants.ConsumerConstants;
import com.alibaba.cloud.routing.consumer.converter.Converter;
import com.alibaba.cloud.routing.consumer.entity.ConsumerNodeInfo;
import com.alibaba.cloud.routing.consumer.util.ReadJsonFileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* @author yuluo-yx
* @author <a href="1481556636@qq.com"></a>
*/
@RestController
public class ConsumerRestTemplateController implements ApplicationContextAware {
private static final Logger log = LoggerFactory
.getLogger(ConsumerRestTemplateController.class);
@Autowired
private ApplicationContext applicationContext;
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private ConsumerRestRequestInterceptor consumerRestRequestInterceptor;
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
@Autowired
private Converter<String, List<UnifiedRoutingDataStructure>> jsonConverter;
private static String addRoutingRulePath;
private static String updateRoutingRulePath;
static {
org.springframework.core.io.Resource resource1 = new ClassPathResource(
"add-routing-rule.json");
org.springframework.core.io.Resource resource2 = new ClassPathResource(
"update-routing-rule.json");
try {
addRoutingRulePath = resource1.getFile().getPath();
updateRoutingRulePath = resource2.getFile().getPath();
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
@GetMapping("/info4node")
public Map<String, List<Map<String, List<String>>>> getNodeInfo() {
String serverPort = consumerRestRequestInterceptor.getServerPort();
List<String> services = discoveryClient.getServices();
for (String service : services) {
List<ServiceInstance> instances = discoveryClient.getInstances(service);
for (ServiceInstance instance : instances) {
if ((instance.getPort() + "").equals(serverPort)) {
String server = instance.getServiceId();
Map<String, String> metadata = instance.getMetadata();
List<Map<String, List<String>>> metaList = new ArrayList<>();
Map<String, List<String>> nmap = new HashMap<>();
for (String s : metadata.keySet()) {
nmap.put(s, Collections.singletonList(metadata.get(s)));
}
nmap.put(ConsumerConstants.PORT,
Collections.singletonList(instance.getPort() + ""));
nmap.put(ConsumerConstants.HOST,
Collections.singletonList(instance.getHost()));
nmap.put(ConsumerConstants.INSTANCE_ID,
Collections.singletonList(instance.getInstanceId()));
metaList.add(nmap);
ConsumerNodeInfo.set(server, metaList);
}
}
}
return ConsumerNodeInfo.getNodeIno();
}
@GetMapping("/services")
public Object getAllServices() {
return discoveryClient.getServices();
}
@GetMapping("/router-test")
public String routerTest() {
return restTemplate.getForObject(
ConsumerConstants.SERVICE_PROVIDER_ADDRESS + "/test-a1", String.class);
}
@GetMapping("/add")
public void getDataFromControlPlaneTest() {
log.info("Access /add routing rule interface, add routing rule..." + "\n"
+ ConsumerConstants.ADD_RULE_DESCRIPTION);
String content = ReadJsonFileUtils.convertFile2String(addRoutingRulePath);
List<UnifiedRoutingDataStructure> unifiedRouteDataStructureList = jsonConverter
.convert(content);
applicationContext.publishEvent(
new RoutingDataChangedEvent(this, unifiedRouteDataStructureList));
log.info("Add routing rule success!");
}
@GetMapping("/update")
public void updateDataFromControlPlaneTest() {
log.info("Access /update routing rule interface, update routing rule..." + "\n"
+ ConsumerConstants.UPDATE_RULE_DESCRIPTION);
String content = ReadJsonFileUtils.convertFile2String(updateRoutingRulePath);
List<UnifiedRoutingDataStructure> unifiedRouteDataStructureList = jsonConverter
.convert(content);
applicationContext.publishEvent(
new RoutingDataChangedEvent(this, unifiedRouteDataStructureList));
log.info("Update routing rule success!");
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save