SpringCloud通过Sidecar整合异构平台微服务
背景介绍
Spring Cloud微服务架构体系中,可以通过Sidecar(边车)来实现整合Python、Go等其他语言平台的微服务。其基本原理是,通过Sidecar将第三方平台程序api注册到 Eureka等服务注册中心。这样就可以实现将第三方服务接口当Java接口一样调用,即:Spring Cloud内的程序调用Sidecar,Sidecar再将请求转发给第三方服务。
Sidecar的主要特点
- 与应用部署在同一台机器上,你的第三方平台或应用在哪里启动,Sidecar就部署在哪里
- 功能与应用独立,即Sidecar不使用应用程序的运行环境与语言
- 进程间通信,应用与Sidecar之间一般采用http接口传送json格式数据,以保证其跨语言的通用性
本文主要验证基于Spring Cloud的Sidecar组件实现整合Python Flask微服务到Spring Cloud微服务架构中。Flask api将通过Sidecar被注册到Eureka服务注册中心,并允许其他微服务访问调用。
实验过程
本文基于 Spring Cloud Hoxton.SR3 版本。
程序包含四个部分:eureka服务端、Flask微服务提供方、Sidecar服务代理方、Consumer服务消费方。首先,Flask微服务启动并正常运行,通过其/health接口,可返回其健康状况;然后,Sidecar服务为Flask微服务提供代理并注册到eureka服务端;最后,Consumer服务调用 Sidecar代理的Flask微服务。
搭建 Eureka服务端
首先,通过 Spring Initializr 快速搭建 Eureka服务端(具体过程略),搭建完毕后,工程如下图示:

其中,Eureka服务端启动程序 EurekaServerApplication:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
@EnableWebSecurity
static class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable(); // 配置屏蔽csrf过滤机制
}
}
}
需要注意的是:在新版本的security中,添加了csrf过滤。eureka开启安全策略后,可导致微服务的注册被过滤掉!因此,需要在Eureka服务端的代码中增加配置,将csrf过滤机制屏蔽掉。
pom文件内容:
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>eureka_server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka_server</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- SpringCloud Hystrix 微服务容错监控组件:断路器,依赖隔离,服务降级,服务监控 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!-- 权限控制依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Eureka服务端的应用配置文件application.yml,已包含在文件截图中。其中,spring.security.user的相关配置,用于Eureka登录验证用户名密码,以提高其安全性。
搭建 Flask 微服务提供方
Flask是Python语言中的一个轻量级Web服务框架,我们用它来模拟基于Python的微服务。
以下是已创建完毕的Flask主应用app.py代码:
import json
from flask import Flask, Response, make_response, jsonify
app = Flask(__name__)
@app.route('/health')
def health():
result = {'status': 'UP'}
return Response(json.dumps(result), mimetype='application/json')
@app.route('/getUser')
def get_user():
return Response(json.dumps({'username': 'python', 'password': 'python'}),
mimetype='application/json')
@app.errorhandler(404)
def not_found(error):
return make_response(jsonify({'error': 'Not found'}), 404)
if __name__ == '__main__':
app.run(host="0.0.0.0", port=5000)
注意:如果通过 Pycharm编写 Flask应用代码,在运行时需要通过 Terminal 窗口单独执行。若使用Pycharm的Flask模板自动创建 Flask应用,执行时需要配置命令行参数:

在 Additional options处,添加:--host=0.0.0.0 --port=5000,这是因为,使用Pycharm的Flask模板自动创建的Flask应用,在Pycharm中点运行时,会自动忽略app.py代码中写入的 host 和 port端口设置,你需要在 Run Configurations中重新设置(如上图红框所示)。如果不这样,Flask应用相当于处于本机调试模式,只允许localhost访问到,外部IP是无法访问的。这将导致后续的Consumer服务端无法访问Sidecar代理的这个Flask微服务1。
Flask微服务启动成功后,在浏览器访问http://localhost:5000/health,将得到{"status": "UP"},证明服务是正常的。
搭建 Sidecar服务代理方
Sidecar的程序比较简单,主要功能通过修改配置文件实现。主应用:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.sidecar.EnableSidecar;
@EnableSidecar
@SpringBootApplication
public class SidecarApplication {
public static void main(String[] args) {
SpringApplication.run(SidecarApplication.class, args);
}
}
其中,@EnableSidecar注解即启用Sidecar。
配置文件application.yml:
server:
port: 8326
spring:
application:
name: sidecar-server
profiles:
active: "dev"
cloud:
loadbalancer:
ribbon:
enabled: false
client:
ipAddress: localhost
sidecar:
port: 5000
health-uri: http://localhost:${sidecar.port}/health
eureka:
client:
service-url:
default-zone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ipAddress}:${spring.application.name}:${server.port}
status-page-url: http://${spring.cloud.client.ipAddress}:${server.port}/
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 10000
ribbon:
ConnectTimeout: 5000
ReadTimeout: 5000
其中,sidecar.port端口,即被代理的Flask应用所占用的端口;health-uri即Flask应用提供的/health健康检查接口地址。
搭建 Consumer服务消费方
Consumer-server的主应用:
import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@EnableFeignClients
@SpringCloudApplication
public class ConsumerServerApplication {
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerServerApplication.class, args);
}
}
其中,@LoadBananced,即启用负载均衡器Ribbon;若注释掉,则可使用 http实际地址访问 Flask服务API,以获取返回结果。
定义 Controller 服务接口
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class UserController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/java-user")
public String JavaUser() {
return "{'username': 'java', 'password': 'java'}";
}
@RequestMapping("/python-user")
public String PythonUser() {
return restTemplate.getForEntity("http://sidecar-server/getUser", String.class).getBody();
}
}
此处暴露两个url:java-user,用于返回Java服务端的用户信息;python-user,用于返回Python服务端的用户信息。关键在于http://sidecar-server/getUser,其中的sidecar-server即注册到Eureka上的 Sidecar服务名称。
Consumer服务方应用的配置文件:
spring:
application:
name: consumer-server
cloud:
client:
ipAddress: localhost
server:
port: 8325
eureka:
client:
healthcheck:
enabled: true
register-with-eureka: true
fetch-registry: true
service-url:
default-zone: http://${registry.host:localhost}:${registry.port:8761}/eureka/
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ipAddress}:${spring.application.name}:${server.port}
status-page-url: http://${spring.cloud.client.ipAddress}:${server.port}/
registry:
host: localhost
port: 8761
pom文件:
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>consumer-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>consumer-server</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--Hystrix 依赖 主要是用 @HystrixCommand-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 健康检查 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
依次启动服务
按照 Flask -> Eureka -> Sidecar -> Consumer的顺序,依次启动微服务。启动完毕后,登录 Eureka可以看到两个已成功注册的微服务:

此时,访问Consumer服务地址,请求 /java-user和 /python-user,返回正常:


后记
通过使用 Sidecar,为后续搭建混合Java和 Python开发的微服务平台提供了可能。相对于直接在Python世界中查找或重构 Eureka注册组件,这种方式可以更好地满足核心应用扩展、实现业务功能内聚、降低代码复杂度。
参考文档
本文在实验过程中,主要参考了如下文章,基于最新的Spring Cloud微服务版本,完成了编码和测试。
这种情况可能还与本地计算机的网卡数、网络设置有关。如果Sidecar注册到Eureka的IP地址仍然是localhost或127.0.0.1,则这种情况下的微服务调用可能仍会是正常的。 ↩︎

本文详细介绍如何使用SpringCloud的Sidecar组件整合Python Flask微服务,实现跨语言平台的微服务架构。通过Sidecar,SpringCloud应用能像调用Java微服务一样调用Python服务。
873





