SpringCloud通過Sidecar整合異構平臺微服務(Python Flask)

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微服務版本,完成了編碼和測試。

  1. SpringCloud 整合 Python - Flask
  2. SpringCloud 整合Python
  3. SideCar模式

  1. 這種情況可能還與本地計算機的網卡數、網絡設置有關。如果Sidecar註冊到Eureka的IP地址仍然是localhost或127.0.0.1,則這種情況下的微服務調用可能仍會是正常的。 ↩︎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章