diff --git a/pom.xml b/pom.xml index dbf0860d..8315ee40 100644 --- a/pom.xml +++ b/pom.xml @@ -79,6 +79,7 @@ 2.2.0.BUILD-SNAPSHOT 2.2.0.BUILD-SNAPSHOT Horsham.BUILD-SNAPSHOT + 2.2.0.BUILD-SNAPSHOT 4.12 3.0 @@ -122,6 +123,7 @@ spring-cloud-alicloud-schedulerx spring-cloud-alicloud-sms spring-cloud-alibaba-coverage + spring-cloud-alibaba-sidecar @@ -199,6 +201,14 @@ import + + org.springframework.cloud + spring-cloud-consul-dependencies + ${spring-cloud-consul.version} + pom + import + + org.apache.dubbo dubbo-spring-boot-starter diff --git a/spring-cloud-alibaba-dependencies/pom.xml b/spring-cloud-alibaba-dependencies/pom.xml index a15bdd26..6af3af71 100644 --- a/spring-cloud-alibaba-dependencies/pom.xml +++ b/spring-cloud-alibaba-dependencies/pom.xml @@ -295,6 +295,11 @@ spring-cloud-alibaba-dubbo ${project.version} + + com.alibaba.cloud + spring-cloud-alibaba-sidecar + ${project.version} + @@ -360,6 +365,11 @@ spring-cloud-starter-bus-rocketmq ${project.version} + + com.alibaba.cloud + spring-cloud-starter-alibaba-sidecar + ${project.version} + diff --git a/spring-cloud-alibaba-docs/src/main/asciidoc-zh/sidecar.adoc b/spring-cloud-alibaba-docs/src/main/asciidoc-zh/sidecar.adoc new file mode 100644 index 00000000..2a8832f5 --- /dev/null +++ b/spring-cloud-alibaba-docs/src/main/asciidoc-zh/sidecar.adoc @@ -0,0 +1,194 @@ +== Spring Cloud Alibaba Sidecar + +`Spring Cloud Alibaba Sidecar` 是一个用来快速**完美整合** Spring Cloud +与 *异构微服务* 的框架,灵感来自 +https://github.com/spring-cloud/spring-cloud-netflix/tree/master/spring-cloud-netflix-sidecar[Spring +Cloud Netflix Sidecar] 。目前支持的服务发现组件: + +* Nacos +* Consul + +=== 术语 + +==== 异构微服务 + +非Spring Cloud应用,统称异构微服务。比如你的遗留项目,或者非JVM应用。 + +==== ``完美整合''的三层含义 + +* 享受服务发现的优势 +* 有负载均衡 +* 有断路器 + +=== Why or Why not? + +==== 为什么要编写Alibaba Sidecar? + +原因有两点: + +* Spring Cloud子项目 `Spring Cloud Netflix Sidecar` +是可以快速整合异构微服务的。然而,Sidecar只支持使用Eureka作为服务发现,*如果使用其他服务发现组件就抓瞎了*。 +* *Sidecar是基于Zuul 1.x的*,Spring +Cloud官方明确声明,未来将会逐步淘汰Zuul。今年早些时候,我有给Spring +Cloud官方提出需求,希望官方实现一个基于Spring Cloud +Gateway的新一代Sidecar,然而官方表示并没有该计划。详见:https://github.com/spring-cloud/spring-cloud-gateway/issues/735 + +既然没有,索性自己写了。 + +==== 为什么不用Service Mesh? + +* 目前Mesh主要使用场景在Kubernetes领域(Istio、Linkerd +2等,大多将Kubernetes作为First +Class支持,虽然Istio也可部署在非Kubernetes环境),而目前业界,Spring +Cloud应用未必有试试Mesh的环境; +* 使用Alibaba +Sidecar一个小组件就能解决问题了(核心代码不超过200行),引入整套Mesh方案,颇有点屠龙刀杀黄鳝的意思。 + +=== 原理 + +* Alibaba +Sidecar根据配置的异构微服务的IP、端口等信息,*将异构微服务的IP/端口注册到服务发现组件上* +。 +* Alibaba Sidecar实现了 *健康检查* ,Alibaba +Sidecar会定时检测异构微服务是否健康。如果发现异构微服务不健康,Alibaba +Sidecar会自动将代表异构微服务的Alibaba +Sidecar实例下线;如果异构微服务恢复正常,则会自动上线。最长延迟是30秒,详见 +`Alibaba SidecarChecker#check` 。 + +=== 要求 + +* 【必须】你的异构微服务需使用HTTP通信。这一点严格来说不算要求,因为Spring +Cloud本身就是基于HTTP的; +* 【可选】如果微服务配置了 `sidecar.health-check-url` +,则表示开启健康检查,此时,你的异构微服务需实现健康检查(可以是空实现,只要暴露一个端点,返回类似 +`{"status": "UP"}` 的字符串即可)。 + +=== 使用示例 + +* 如使用Nacos作为服务发现组件,详见`spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-nacos-example` +* 如使用Consul作为服务发现组件,详见`spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-nacos-example` + +==== 示例代码(以Nacos服务发现为例) + +* 加依赖: ++ +[source,xml] +---- + + com.alibaba.cloud + spring-cloud-starter-alibaba-sidecar + +---- +* 写配置: ++ +[source,yaml] +---- +server: + port: 8070 +spring: + cloud: + nacos: + discovery: + server-addr: localhost:8848 + gateway: + discovery: + locator: + enabled: true + application: + name: node-service +sidecar: + # 异构微服务的IP + ip: 127.0.0.1 + # 异构微服务的端口 + port: 8060 + # 异构微服务的健康检查URL + health-check-url: http://localhost:8060/health.json +management: +endpoint: + health: + show-details: always +---- ++ +配置比较简单,就是把Alibaba Sidecar注册到Nacos上,然后添加了几行Alibaba +Sidecar的配置。 + +==== 异构微服务 + +我准备了一个NodeJS编写的简单微服务。 + +[source,javascript] +---- +var http = require('http'); +var url = require("url"); +var path = require('path'); + +// 创建server +var server = http.createServer(function(req, res) { + // 获得请求的路径 + var pathname = url.parse(req.url).pathname; + res.writeHead(200, { 'Content-Type' : 'application/json; charset=utf-8' }); + // 访问http://localhost:8060/,将会返回{"index":"欢迎来到首页"} + if (pathname === '/') { + res.end(JSON.stringify({ "index" : "欢迎来到首页" })); + } + // 访问http://localhost:8060/health,将会返回{"status":"UP"} + else if (pathname === '/health.json') { + res.end(JSON.stringify({ "status" : "UP" })); + } + // 其他情况返回404 + else { + res.end("404"); + } +}); +// 创建监听,并打印日志 +server.listen(8060, function() { + console.log('listening on localhost:8060'); +}); +---- + +==== 测试 + +===== 测试1:Spring Cloud微服务完美调用异构微服务 + +为你的Spring Cloud微服务整合Ribbon,然后构建 `http://node-service/**` +,就可以请求到异构微服务的 `/**` 了。 + +示例: + +Ribbon请求 `http://node-service/` 会请求到 `http://localhost:8060/` +,以此类推。 + +至于断路器,正常为你的Spring +Cloud微服务整合Sentinel或者Hystirx、Resilience4J即可 。 + +===== 测试2:异构微服务完美调用Spring Cloud微服务 + +由于Alibaba Sidecar基于Spring Cloud Gateway,而网关自带转发能力。 + +示例: + +如果你有一个Spring Cloud微服务叫做 `spring-cloud-microservice` +,那么NodeJS应用只需构建 +`http://localhost:8070/spring-cloud-microservice/**` ,Alibaba +Sidecar就会把请求转发到 `spring-cloud-microservice` 的 `/**` 。 + +而Spring Cloud Gateway是整合了Ribbon的,所以实现了负载均衡;Spring Cloud +Gateway还可以整合Sentinel或者Hystirx、Resilience4J,所以也带有了断路器。 + +=== Alibaba Sidecar优缺点分析 + +Alibaba +Sidecar的设计和Sidecar基本一致,优缺点和Sidecar的优缺点也是一样的。 + +优点: + +* 接入简单,几行代码就可以将异构微服务整合到Spring Cloud生态 +* 不侵入原代码 + +缺点: + +* 每接入一个异构微服务实例,都需要额外部署一个Alibaba +Sidecar实例,增加了部署成本(虽然这个成本在Kubernetes环境中几乎可以忽略不计(只需将Alibaba +Sidecar实例和异构微服务作为一个Pod部署即可)); +* 异构微服务调用Spring Cloud微服务时,本质是把Alibaba +Sidecar当网关在使用,经过了一层转发,性能有一定下降。 diff --git a/spring-cloud-alibaba-docs/src/main/asciidoc-zh/spring-cloud-alibaba.adoc b/spring-cloud-alibaba-docs/src/main/asciidoc-zh/spring-cloud-alibaba.adoc index c38e2134..13e0e76e 100644 --- a/spring-cloud-alibaba-docs/src/main/asciidoc-zh/spring-cloud-alibaba.adoc +++ b/spring-cloud-alibaba-docs/src/main/asciidoc-zh/spring-cloud-alibaba.adoc @@ -31,4 +31,6 @@ include::schedulerx.adoc[] include::sms.adoc[] +include::sidecar.adoc[] + diff --git a/spring-cloud-alibaba-examples/pom.xml b/spring-cloud-alibaba-examples/pom.xml index 6f30e338..b4cac1f9 100644 --- a/spring-cloud-alibaba-examples/pom.xml +++ b/spring-cloud-alibaba-examples/pom.xml @@ -43,6 +43,8 @@ spring-cloud-bus-rocketmq-example schedulerx-example/schedulerx-simple-task-example spring-cloud-alibaba-dubbo-examples + spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-nacos-example + spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-consul-example diff --git a/spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/node-service.js b/spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/node-service.js new file mode 100644 index 00000000..61e07ab6 --- /dev/null +++ b/spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/node-service.js @@ -0,0 +1,26 @@ +var http = require('http'); +var url = require("url"); +var path = require('path'); + +// 创建server +var server = http.createServer(function(req, res) { + // 获得请求的路径 + var pathname = url.parse(req.url).pathname; + res.writeHead(200, { 'Content-Type' : 'application/json; charset=utf-8' }); + // 访问http://localhost:8060/,将会返回{"index":"欢迎来到首页"} + if (pathname === '/') { + res.end(JSON.stringify({ "index" : "欢迎来到首页" })); + } + // 访问http://localhost:8060/health,将会返回{"status":"UP"} + else if (pathname === '/health.json') { + res.end(JSON.stringify({ "status" : "UP" })); + } + // 其他情况返回404 + else { + res.end("404"); + } +}); +// 创建监听,并打印日志 +server.listen(8060, function() { + console.log('listening on localhost:8060'); +}); \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-consul-example/pom.xml b/spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-consul-example/pom.xml new file mode 100644 index 00000000..3583a0fc --- /dev/null +++ b/spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-consul-example/pom.xml @@ -0,0 +1,59 @@ + + + + com.alibaba.cloud + spring-cloud-alibaba-examples + 2.2.0.BUILD-SNAPSHOT + ../../pom.xml + + 4.0.0 + + spring-cloud-alibaba-sidecar-consul-example + Example demonstrating how to use Spring Cloud Alibaba Sidecar with consul + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-sidecar + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + + org.springframework.cloud + spring-cloud-starter-consul-discovery + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-deploy-plugin + ${maven-deploy-plugin.version} + + true + + + + + diff --git a/spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-consul-example/src/main/java/com/alibaba/cloud/sidecar/DemoApplication.java b/spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-consul-example/src/main/java/com/alibaba/cloud/sidecar/DemoApplication.java new file mode 100644 index 00000000..d97b1807 --- /dev/null +++ b/spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-consul-example/src/main/java/com/alibaba/cloud/sidecar/DemoApplication.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2018 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 + * + * http://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.sidecar; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class DemoApplication { + public static void main(String[] args) { + SpringApplication.run(DemoApplication.class, args); + } +} diff --git a/spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-consul-example/src/main/resources/application.yml b/spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-consul-example/src/main/resources/application.yml new file mode 100644 index 00000000..325fb38a --- /dev/null +++ b/spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-consul-example/src/main/resources/application.yml @@ -0,0 +1,24 @@ +server: + port: 8070 +spring: + cloud: + gateway: + discovery: + locator: + enabled: true + consul: + host: localhost + port: 8500 + application: + name: node-service +sidecar: + # 异构微服务的IP + ip: 127.0.0.1 + # 异构微服务的端口 + port: 8060 + # 异构微服务的健康检查URL + health-check-url: http://localhost:8060/health.json +management: + endpoint: + health: + show-details: always \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-nacos-example/pom.xml b/spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-nacos-example/pom.xml new file mode 100644 index 00000000..59a8fb1a --- /dev/null +++ b/spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-nacos-example/pom.xml @@ -0,0 +1,44 @@ + + + + com.alibaba.cloud + spring-cloud-alibaba-examples + 2.2.0.BUILD-SNAPSHOT + ../../pom.xml + + 4.0.0 + + spring-cloud-alibaba-sidecar-nacos-example + Example demonstrating how to use Spring Cloud Alibaba Sidecar with nacos + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-sidecar + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-deploy-plugin + ${maven-deploy-plugin.version} + + true + + + + + diff --git a/spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-nacos-example/src/main/java/com/alibaba/cloud/sidecar/DemoApplication.java b/spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-nacos-example/src/main/java/com/alibaba/cloud/sidecar/DemoApplication.java new file mode 100644 index 00000000..d449ac26 --- /dev/null +++ b/spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-nacos-example/src/main/java/com/alibaba/cloud/sidecar/DemoApplication.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018 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 + * + * http://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.sidecar; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class DemoApplication { + + public static void main(String[] args) { + SpringApplication.run(DemoApplication.class, args); + } + +} diff --git a/spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-nacos-example/src/main/resources/application.yml b/spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-nacos-example/src/main/resources/application.yml new file mode 100644 index 00000000..7f059ef3 --- /dev/null +++ b/spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-nacos-example/src/main/resources/application.yml @@ -0,0 +1,24 @@ +server: + port: 8070 +spring: + cloud: + nacos: + discovery: + server-addr: localhost:8848 + gateway: + discovery: + locator: + enabled: true + application: + name: node-service +sidecar: + # 异构微服务的IP + ip: 127.0.0.1 + # 异构微服务的端口 + port: 8060 + # 异构微服务的健康检查URL + health-check-url: http://localhost:8060/health.json +management: + endpoint: + health: + show-details: always \ No newline at end of file diff --git a/spring-cloud-alibaba-sidecar/pom.xml b/spring-cloud-alibaba-sidecar/pom.xml new file mode 100644 index 00000000..f7833b4d --- /dev/null +++ b/spring-cloud-alibaba-sidecar/pom.xml @@ -0,0 +1,49 @@ + + + + com.alibaba.cloud + spring-cloud-alibaba + 2.2.0.BUILD-SNAPSHOT + + 4.0.0 + + spring-cloud-alibaba-sidecar + spring-cloud-alibaba-sidecar + An easy way to integrate polyglot apps for Spring Cloud Alibaba. + + + + org.springframework.cloud + spring-cloud-starter-gateway + + + + org.springframework.boot + spring-boot-starter-actuator + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + org.springframework.cloud + spring-cloud-starter-consul-discovery + true + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + diff --git a/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/SidecarAutoConfiguration.java b/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/SidecarAutoConfiguration.java new file mode 100644 index 00000000..ee2a5d78 --- /dev/null +++ b/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/SidecarAutoConfiguration.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2018 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 + * + * http://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.sidecar; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.web.client.RestTemplate; + + +/** + * @author www.itmuch.com + */ +@Configuration +@EnableConfigurationProperties(SidecarProperties.class) +public class SidecarAutoConfiguration { + @Bean + @ConditionalOnMissingBean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + + @Bean + public SidecarHealthIndicator sidecarHealthIndicator(SidecarProperties sidecarProperties, RestTemplate restTemplate) { + return new SidecarHealthIndicator(sidecarProperties, restTemplate); + } + + @Bean + public SidecarHealthChecker sidecarHealthChecker(SidecarDiscoveryClient sidecarDiscoveryClient, SidecarHealthIndicator sidecarHealthIndicator, SidecarProperties sidecarProperties, ConfigurableEnvironment environment) { + SidecarHealthChecker cleaner = new SidecarHealthChecker(sidecarDiscoveryClient, sidecarHealthIndicator, sidecarProperties, environment); + cleaner.check(); + return cleaner; + } +} \ No newline at end of file diff --git a/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/SidecarDiscoveryClient.java b/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/SidecarDiscoveryClient.java new file mode 100644 index 00000000..d492e142 --- /dev/null +++ b/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/SidecarDiscoveryClient.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2018 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 + * + * http://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.sidecar; + +/** + * @author www.itmuch.com + */ +public interface SidecarDiscoveryClient { + /** + * register instance + * + * @param applicationName applicationName + * @param ip ip + * @param port port + */ + void registerInstance(String applicationName, String ip, Integer port); + + /** + * deregister instance + * + * @param applicationName applicationName + * @param ip ip + * @param port port + */ + void deregisterInstance(String applicationName, String ip, Integer port); +} diff --git a/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/SidecarHealthChecker.java b/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/SidecarHealthChecker.java new file mode 100644 index 00000000..a302a1e2 --- /dev/null +++ b/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/SidecarHealthChecker.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2018 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 + * + * http://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.sidecar; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.actuate.health.Status; +import org.springframework.core.env.ConfigurableEnvironment; +import reactor.core.scheduler.Schedulers; + +import java.util.concurrent.TimeUnit; + +/** + * @author www.itmuch.com + */ +@Slf4j +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class SidecarHealthChecker { + private final SidecarDiscoveryClient sidecarDiscoveryClient; + private final HealthIndicator healthIndicator; + private final SidecarProperties sidecarProperties; + private final ConfigurableEnvironment environment; + + public void check() { + Schedulers.single() + .schedulePeriodically( + () -> { + String ip = sidecarProperties.getIp(); + Integer port = sidecarProperties.getPort(); + + Status status = healthIndicator.health().getStatus(); + String applicationName = environment.getProperty("spring.application.name"); + + if (status.equals(Status.UP)) { + this.sidecarDiscoveryClient.registerInstance(applicationName, ip, port); + log.debug("Health check success. register this instance. applicationName = {}, ip = {}, port = {}, status = {}", + applicationName, ip, port, status + ); + } else { + log.warn("Health check failed. unregister this instance. applicationName = {}, ip = {}, port = {}, status = {}", + applicationName, ip, port, status + ); + this.sidecarDiscoveryClient.deregisterInstance(applicationName, ip, port); + } + + }, + 0, + 30, + TimeUnit.SECONDS + ); + } +} diff --git a/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/SidecarHealthIndicator.java b/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/SidecarHealthIndicator.java new file mode 100644 index 00000000..6a2b5eb8 --- /dev/null +++ b/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/SidecarHealthIndicator.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2018 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 + * + * http://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.sidecar; + +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.health.AbstractHealthIndicator; +import org.springframework.boot.actuate.health.Health; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; + +import java.net.URI; +import java.util.Map; + +/** + * @author www.itmuch.com + */ +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class SidecarHealthIndicator extends AbstractHealthIndicator { + private final SidecarProperties sidecarProperties; + private final RestTemplate restTemplate; + + @Override + protected void doHealthCheck(Health.Builder builder) throws Exception { + try { + URI uri = this.sidecarProperties.getHealthCheckUrl(); + if (uri == null) { + builder.up(); + return; + } + + ResponseEntity> exchange = this.restTemplate.exchange( + uri, + HttpMethod.GET, + null, + new ParameterizedTypeReference>() { + } + ); + + Map map = exchange.getBody(); + + if (map == null) { + this.getWarning(builder); + return; + } + Object status = map.get("status"); + if (status instanceof String) { + builder.status(status.toString()); + } else { + this.getWarning(builder); + } + } catch (Exception e) { + builder.down().withDetail("error", e.getMessage()); + } + } + + private void getWarning(Health.Builder builder) { + builder.unknown().withDetail("warning", "no status field in response"); + } +} diff --git a/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/SidecarProperties.java b/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/SidecarProperties.java new file mode 100644 index 00000000..075db5db --- /dev/null +++ b/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/SidecarProperties.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018 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 + * + * http://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.sidecar; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; +import java.net.URI; + +/** + * @author www.itmuch.com + */ +@ConfigurationProperties("sidecar") +@Data +@Validated +public class SidecarProperties { + /** + * polyglot service's ip + */ + private String ip; + + /** + * polyglot service's port + */ + @NotNull + @Max(65535) + @Min(1) + private Integer port; + + /** + * polyglot service's health check url. + * this endpoint must return json and the format must follow spring boot actuator's health endpoint. + * eg. {"status": "UP"} + */ + private URI healthCheckUrl; +} diff --git a/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/consul/SidecarConsulAutoConfiguration.java b/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/consul/SidecarConsulAutoConfiguration.java new file mode 100644 index 00000000..777dd509 --- /dev/null +++ b/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/consul/SidecarConsulAutoConfiguration.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2018 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 + * + * http://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.sidecar.consul; + +import com.alibaba.cloud.sidecar.SidecarAutoConfiguration; +import com.alibaba.cloud.sidecar.SidecarDiscoveryClient; +import com.alibaba.cloud.sidecar.SidecarProperties; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties; +import org.springframework.cloud.consul.discovery.ConsulDiscoveryProperties; +import org.springframework.cloud.consul.discovery.HeartbeatProperties; +import org.springframework.cloud.consul.serviceregistry.*; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +/** + * @author www.itmuch.com + */ +@Configuration +@ConditionalOnClass(ConsulServiceRegistryAutoConfiguration.class) +@AutoConfigureBefore({ConsulAutoServiceRegistrationAutoConfiguration.class, SidecarAutoConfiguration.class}) +public class SidecarConsulAutoConfiguration { + @Bean + public ConsulAutoRegistration consulRegistration( + AutoServiceRegistrationProperties autoServiceRegistrationProperties, + ConsulDiscoveryProperties properties, + ApplicationContext applicationContext, + ObjectProvider> registrationCustomizers, + ObjectProvider> managementRegistrationCustomizers, + HeartbeatProperties heartbeatProperties, + SidecarProperties sidecarProperties) { + return SidecarConsulAutoRegistration.registration(autoServiceRegistrationProperties, + properties, applicationContext, registrationCustomizers.getIfAvailable(), + managementRegistrationCustomizers.getIfAvailable(), heartbeatProperties, sidecarProperties); + } + + @Bean + public SidecarDiscoveryClient sidecarDiscoveryClient( + ConsulDiscoveryProperties properties, + ConsulServiceRegistry serviceRegistry, + ConsulAutoRegistration registration) { + return new SidecarConsulDiscoveryClient(properties, serviceRegistry, registration); + } +} diff --git a/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/consul/SidecarConsulAutoRegistration.java b/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/consul/SidecarConsulAutoRegistration.java new file mode 100644 index 00000000..49e64ab0 --- /dev/null +++ b/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/consul/SidecarConsulAutoRegistration.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2018 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 + * + * http://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.sidecar.consul; + +import com.ecwid.consul.v1.agent.model.NewService; +import com.alibaba.cloud.sidecar.SidecarProperties; +import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties; +import org.springframework.cloud.consul.discovery.ConsulDiscoveryProperties; +import org.springframework.cloud.consul.discovery.HeartbeatProperties; +import org.springframework.cloud.consul.serviceregistry.ConsulAutoRegistration; +import org.springframework.cloud.consul.serviceregistry.ConsulManagementRegistrationCustomizer; +import org.springframework.cloud.consul.serviceregistry.ConsulRegistrationCustomizer; +import org.springframework.context.ApplicationContext; +import org.springframework.core.env.Environment; + +import java.util.List; + +/** + * @author www.itmuch.com + */ +public class SidecarConsulAutoRegistration extends ConsulAutoRegistration { + public SidecarConsulAutoRegistration(NewService service, AutoServiceRegistrationProperties autoServiceRegistrationProperties, ConsulDiscoveryProperties properties, ApplicationContext context, HeartbeatProperties heartbeatProperties, List managementRegistrationCustomizers) { + super(service, autoServiceRegistrationProperties, properties, context, heartbeatProperties, managementRegistrationCustomizers); + } + + public static ConsulAutoRegistration registration( + AutoServiceRegistrationProperties autoServiceRegistrationProperties, + ConsulDiscoveryProperties properties, ApplicationContext context, + List registrationCustomizers, + List managementRegistrationCustomizers, + HeartbeatProperties heartbeatProperties, + SidecarProperties sidecarProperties) { + + NewService service = new NewService(); + String appName = getAppName(properties, context.getEnvironment()); + service.setId(getInstanceId(sidecarProperties, context.getEnvironment())); + if (!properties.isPreferAgentAddress()) { + service.setAddress(sidecarProperties.getIp()); + } + service.setName(normalizeForDns(appName)); + service.setTags(createTags(properties)); + + // set health check, use alibaba sidecar self's port rather than polyglot app's port. + service.setPort(Integer.valueOf(context.getEnvironment().getProperty("server.port"))); + setCheck(service, autoServiceRegistrationProperties, properties, context, + heartbeatProperties); + + service.setPort(sidecarProperties.getPort()); + + ConsulAutoRegistration registration = new ConsulAutoRegistration(service, + autoServiceRegistrationProperties, properties, context, + heartbeatProperties, managementRegistrationCustomizers); + customize(registrationCustomizers, registration); + return registration; + } + + public static String getInstanceId(SidecarProperties sidecarProperties, + Environment environment) { + return String.format("%s-%s-%s", + environment.getProperty("spring.application.name"), + sidecarProperties.getIp(), + sidecarProperties.getPort()); + } +} diff --git a/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/consul/SidecarConsulDiscoveryClient.java b/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/consul/SidecarConsulDiscoveryClient.java new file mode 100644 index 00000000..689e5d9b --- /dev/null +++ b/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/consul/SidecarConsulDiscoveryClient.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2018 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 + * + * http://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.sidecar.consul; + +import com.alibaba.cloud.sidecar.SidecarDiscoveryClient; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.consul.discovery.ConsulDiscoveryProperties; +import org.springframework.cloud.consul.serviceregistry.ConsulAutoRegistration; +import org.springframework.cloud.consul.serviceregistry.ConsulServiceRegistry; + +/** + * @author www.itmuch.com + */ +@Slf4j +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class SidecarConsulDiscoveryClient implements SidecarDiscoveryClient { + private final ConsulDiscoveryProperties properties; + private final ConsulServiceRegistry serviceRegistry; + private final ConsulAutoRegistration registration; + + @Override + public void registerInstance(String applicationName, String ip, Integer port) { + + if (!this.properties.isRegister()) { + log.debug("Registration disabled."); + return; + } + + serviceRegistry.register(registration); + + } + + @Override + public void deregisterInstance(String applicationName, String ip, Integer port) { + if (!this.properties.isRegister() || !this.properties.isDeregister()) { + return; + } + serviceRegistry.deregister(registration); + } +} diff --git a/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/nacos/SidecarNacosAutoConfiguration.java b/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/nacos/SidecarNacosAutoConfiguration.java new file mode 100644 index 00000000..cb03c735 --- /dev/null +++ b/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/nacos/SidecarNacosAutoConfiguration.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2018 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 + * + * http://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.sidecar.nacos; + +import com.alibaba.cloud.nacos.NacosDiscoveryProperties; +import com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientAutoConfiguration; +import com.alibaba.cloud.sidecar.SidecarAutoConfiguration; +import com.alibaba.cloud.sidecar.SidecarDiscoveryClient; +import com.alibaba.cloud.sidecar.SidecarProperties; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author www.itmuch.com + */ +@Configuration +@AutoConfigureBefore({NacosDiscoveryClientAutoConfiguration.class, SidecarAutoConfiguration.class}) +@ConditionalOnClass(NacosDiscoveryProperties.class) +public class SidecarNacosAutoConfiguration { + @Bean + @ConditionalOnMissingBean + public SidecarNacosDiscoveryProperties sidecarNacosDiscoveryProperties(SidecarProperties sidecarProperties) { + return new SidecarNacosDiscoveryProperties(sidecarProperties); + } + + @Bean + @ConditionalOnMissingBean + public SidecarDiscoveryClient sidecarDiscoveryClient(SidecarNacosDiscoveryProperties sidecarNacosDiscoveryProperties) { + return new SidecarNacosDiscoveryClient(sidecarNacosDiscoveryProperties); + } +} diff --git a/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/nacos/SidecarNacosDiscoveryClient.java b/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/nacos/SidecarNacosDiscoveryClient.java new file mode 100644 index 00000000..e5184db0 --- /dev/null +++ b/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/nacos/SidecarNacosDiscoveryClient.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018 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 + * + * http://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.sidecar.nacos; + +import com.alibaba.cloud.sidecar.SidecarDiscoveryClient; +import com.alibaba.nacos.api.exception.NacosException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * @author www.itmuch.com + */ +@Slf4j +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class SidecarNacosDiscoveryClient implements SidecarDiscoveryClient { + private final SidecarNacosDiscoveryProperties sidecarNacosDiscoveryProperties; + + @Override + public void registerInstance(String applicationName, String ip, Integer port) { + try { + this.sidecarNacosDiscoveryProperties.namingServiceInstance() + .registerInstance(applicationName, ip, port); + } catch (NacosException e) { + log.warn("nacos exception happens", e); + } + } + + @Override + public void deregisterInstance(String applicationName, String ip, Integer port) { + try { + this.sidecarNacosDiscoveryProperties.namingServiceInstance() + .deregisterInstance(applicationName, ip, port); + } catch (NacosException e) { + log.warn("nacos exception happens", e); + } + } +} diff --git a/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/nacos/SidecarNacosDiscoveryProperties.java b/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/nacos/SidecarNacosDiscoveryProperties.java new file mode 100644 index 00000000..8d49e708 --- /dev/null +++ b/spring-cloud-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/nacos/SidecarNacosDiscoveryProperties.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018 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 + * + * http://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.sidecar.nacos; + +import com.alibaba.cloud.nacos.NacosDiscoveryProperties; +import com.alibaba.cloud.sidecar.SidecarProperties; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; + +import java.net.SocketException; + +/** + * @author itmuch.com + */ +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class SidecarNacosDiscoveryProperties extends NacosDiscoveryProperties { + private final SidecarProperties sidecarProperties; + + @Override + public void init() throws SocketException { + super.init(); + + String ip = sidecarProperties.getIp(); + if (StringUtils.isNotBlank(ip)) { + this.setIp(ip); + } + + Integer port = sidecarProperties.getPort(); + this.setPort(port); + } +} diff --git a/spring-cloud-alibaba-sidecar/src/main/resources/META-INF/spring.factories b/spring-cloud-alibaba-sidecar/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..726e9382 --- /dev/null +++ b/spring-cloud-alibaba-sidecar/src/main/resources/META-INF/spring.factories @@ -0,0 +1,4 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.alibaba.cloud.sidecar.nacos.SidecarNacosAutoConfiguration,\ + com.alibaba.cloud.sidecar.SidecarAutoConfiguration,\ + com.alibaba.cloud.sidecar.consul.SidecarConsulAutoConfiguration \ No newline at end of file diff --git a/spring-cloud-starter-alibaba/pom.xml b/spring-cloud-starter-alibaba/pom.xml index 47f3f65e..bf367999 100644 --- a/spring-cloud-starter-alibaba/pom.xml +++ b/spring-cloud-starter-alibaba/pom.xml @@ -20,5 +20,6 @@ spring-cloud-starter-stream-rocketmq spring-cloud-starter-bus-rocketmq spring-cloud-starter-dubbo + spring-cloud-starter-alibaba-sidecar \ No newline at end of file diff --git a/spring-cloud-starter-alibaba/spring-cloud-starter-alibaba-sidecar/pom.xml b/spring-cloud-starter-alibaba/spring-cloud-starter-alibaba-sidecar/pom.xml new file mode 100644 index 00000000..ea93952c --- /dev/null +++ b/spring-cloud-starter-alibaba/spring-cloud-starter-alibaba-sidecar/pom.xml @@ -0,0 +1,22 @@ + + + + com.alibaba.cloud + spring-cloud-starter-alibaba + 2.2.0.BUILD-SNAPSHOT + ../pom.xml + + 4.0.0 + + spring-cloud-starter-alibaba-sidecar + Spring Cloud Starter Alibaba Sidecar + + + + com.alibaba.cloud + spring-cloud-alibaba-sidecar + + + \ No newline at end of file