前言
因为客服在线模块在线上要使用到tf-serving做在线推理,所以需要把C++模块集成进来。这一篇主要讲sidecar的实验部分,会起一个Django进程嵌入SpringCloud中做非JVM微服务来做实验。
起Eureka
在 https://start.spring.io/ 勾选Eureka Server
Application加上EnableEurekaServer:
@SpringBootApplication
@EnableEurekaServer
public class CloudApplication {
public static void main(String[] args) {
SpringApplication.run(CloudApplication.class, args);
}
}
application.properties配置文件:
server.port=8761
spring.application.name=eureka-server
eureka.instance.hostname=localhost
eureka.client.registerWithEureka=false
eureka.client.fetchRegistry=false
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
完整的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.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>cloud</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.RC1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</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>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
</project>
起来后查看http://localhost:8761
准备非java服务端
用Django生成web项目
使用Django生成一个HelloWorld的web项目。
django-admin startproject HelloWorld
创建完成后可以看到下面的目录树:
$ cd HelloWorld/
$ tree
.
|-- HelloWorld
| |-- __init__.py
| |-- settings.py
| |-- urls.py
| `-- wsgi.py
`-- manage.py
说明:
- HelloWorld: 项目的容器。
- manage.py: 一个实用的命令行工具,可让你以各种方式与该 Django 项目进行交互。
- HelloWorld/init.py: 一个空文件,告诉 Python 该目录是一个 Python 包。
- HelloWorld/settings.py: 该 Django 项目的设置/配置。
- HelloWorld/urls.py: 该 Django 项目的 URL 声明; 一份由 Django 驱动的网站"目录"。
- HelloWorld/wsgi.py: 一个 WSGI 兼容的 Web 服务器的入口,以便运行你的项目。
接下来就可以启动了:
python3 manage.py runserver 0.0.0.0:8000
配置url
urls.py
"""HelloWorld URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from django.conf.urls import url
from . import view
urlpatterns = [
url(r'^health$', view.health),
url(r'^instance_info$', view.instance_info),
url(r'^query/(.+)/info$', view.query_server_info),
]
view.py
from django.http import HttpResponse,JsonResponse
import urllib
import json
# 健康检查API,用于Sidecar检查本Python服务的存活状态
def health(request):
result = {
"status":"UP"
}
print(result)
return JsonResponse(result)
# 对外服务API,用于测试其他服务调用本Python服务即查询当前实例的信息
def instance_info(request):
result = {
"status":0,
"msg":"sucess",
"data":{
"instance_name":"python_server"
}
}
print(result)
return JsonResponse(result)
# 查看指定服务的实例信息API,用于测试本Python服务调用其他已注册到Eureka上是服务
def query_server_info(request,server_name):
print(server_name)
sidecar_service_url = "http://localhost:6666/{instance_name}/instance_info".replace('{instance_name}',server_name);#sidecar获取服务信息API(实际Sidecar会将此请求转发到Sidecar代理的Python服务上)
print("request_url: "+sidecar_service_url)
req = urllib.request.urlopen(sidecar_service_url)
res_data = req.read()
print(str(res_data, encoding = "utf-8"))
return JsonResponse(json.loads(res_data))
准备sidecar应用
在pom中加入依赖spring-cloud-netflix-sidecar,完整的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.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>cloud</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.RC1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</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.cloud</groupId>
<artifactId>spring-cloud-netflix-sidecar</artifactId>
<!-- <version>1.2.4.RELEASE</version><!–具体版本可自选–>-->
</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>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
</project>
应用主类Application上加上EnableSidecar,该注解包含@EnableCircuitBreaker, @EnableDiscoveryClient以及@EnableZuulProxy。
@SpringBootApplication
@RestController
@EnableSidecar
public class CloudApplication {
public static void main(String[] args) {
SpringApplication.run(CloudApplication.class, args);
}
}
application.properties上加上注册中心的地址:
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
##Sidecar注册到Eureka注册中心的端口
server.port=8666
## 服务的名称,在Eureka注册中心上会显示此名称(在生产环境中,此名称最好与Sidecar所代理服务的名称保持一致)
spring.application.name=sidecar
##Sidecar监听的非JVM服务端口
sidecar.port=8000
##非JVM服务需要实现该接口,[响应结果](#原有服务实现健康检查API)后面会给出注册配置
sidecar.health-uri=http://localhost:8000/health
#hystrix.command.default.execution.timeout.enabled: false
hystrix.metrics.enabled=false
sidecar.port属性是非jre程序监听的端口号,以使得Sidecar将该服务正确注册到Eureka。sidecar.health-uri是非jre应用提供的一个对外暴露的可访问uri地址,在该地址对应的接口中需要实现一个模仿Spring Boot健康检查指示器的功能。它需要返回如下信息:
{
status: "UP"
}
将Sidecar和非JVM服务部署在同一台机器上。
部署起来后可以看到sidecar注册到了Eureka并且状态是UP。
java客户端,测试服务可用性
本文使用feign来调用python服务,以测试sidecar形式在微服务中的可用性。
pom中加入spring-cloud-starter-openfeign,完整的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.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>callpython</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>callpython</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud-services.version>2.1.4.RELEASE</spring-cloud-services.version>
<spring-cloud.version>Hoxton.RC1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-openfeign-dependencies</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>io.pivotal.spring.cloud</groupId>
<artifactId>spring-cloud-services-starter-service-registry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</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.batch</groupId>
<artifactId>spring-batch-test</artifactId>
<scope>test</scope>
</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>
<dependency>
<groupId>io.pivotal.spring.cloud</groupId>
<artifactId>spring-cloud-services-dependencies</artifactId>
<version>${spring-cloud-services.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>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
</project>
application.yml中加上配置eureka地址:
server:
port: 8700
spring:
application:
name: gateway
eureka:
client:
register-with-eureka: true
fetch-registry: true
serviceUrl:
defaultZone: http://localhost:8761/eureka/
添加Feign调用类:配置上sidecar在eureka上注册的应用名
@FeignClient(name = "sidecar")
public interface PythonFeign {
@RequestMapping(value = "/instance_info", method = RequestMethod.GET)
String getInstanceInfo() throws Exception;
}
添加Controller:
@RestController
public class InfoController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private PythonFeign pythonFeign;
@GetMapping(value = { "/jvm_instance_info" })
public Map getInstance() {
Map<String, Object> retVal = new HashMap<>();
retVal.put("staus", 0);
retVal.put("msg", "sucess");
Map<String, Object> instanceInfo = new HashMap<>();
instanceInfo.put("instance_name", "JVM_Gateway");
retVal.put("data", instanceInfo);
return retVal;
}
@RequestMapping("/python_user")
public String PythonUser() {
String strTmp = restTemplate.getForEntity("http://127.0.0.1:8000/instance_info", String.class).getBody();
return strTmp;
}
@GetMapping("/python_instance_info")
public String getPythonInstanceInfo(){
try {
return pythonFeign.getInstanceInfo();
} catch (Exception e) {
System.out.println(e.getMessage());
}
return "";
}
}
启动后,查看eureka可以看到服务已经注册上了:gateway那个应用。
启动后,访问http://127.0.0.1:8700/jvm_instance_info 测试本服务的可用性:
{
"msg": "sucess",
"staus": 0,
"data": {
"instance_name": "JVM_Gateway"
}
}
调用 http://127.0.0.1:8700/python_instance_info
会调用到python服务:
{
status: 0,
msg: "sucess",
data: {
instance_name: "python_server"
}
}
至此,可以看到Sidecar双向代理了python服务,把python服务纳入了基于SpringCloud的微服务中提供服务。
Troubleshooting
注册的时候机器ip变成了bogon
修改hosts文件,加上:
bogon 192.168.0.138