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,則這種情況下的微服務調用可能仍會是正常的。 ↩︎