spring cloud通过sidecar支持异构语言微服务
1 背景
spring cloud是java应用世界中微服务的事实标准,它提供了非常丰富且完整的微服务组件,且非常方便与java应用程序进行集成。但是,由于spring cloud很多功能是通过java jar包以SDK调用的形式集成到应用程序中的(如eureka client,spring cloud config client等),使用其他语言(如go、python、php、js等)开发的应用程序并不能直接使用这些jar包集成到spring cloud微服务系统中。但是,由于各种现实的原因,要求业务系统所有的组件都采用java技术栈不是很现实。同时,采用微服务技术的一个重要出发点是系统的不同组件可以独立选择技术栈,进行独立设计、开发、测试和部署升级,如果约束微服务系统的所有组件都必须采用单一的java技术栈,也违背了微服务设计思想的初衷。如果要让非java应用集成到spring cloud系统中,一种方式是为每种语言开发一系列相应的客户端组件,嵌入到应用程序中,仍然通过SDK调用的方式进行集成,与当前java应用程序采取的方式一样,这种方式是侵入式的;另一种方式是通过sidecar的方式,引入一个单独的中间组件,非java应用程序只要做极少的适配,就可以复用spring cloud大部分微服务基础能力,这种方式(基本上可以说)是非侵入式的。这里通过介绍一个sidecar的实现,即netflix公司提供的spring cloud netflix sidecar,来看看spring cloud如何通过sidecar集成非java应用程序。
2 spring cloud netflix sidecar工作原理
2.1 java应用程序与非java应用程序通过sidecar相互调用过程
2.1.1 java应用程序调用非java应用程序服务过程
image.png1)sidecar向注册中心注册自己
2)java-app从注册中心获取sidecar的实例列表信息
3)java-app从sidecar的实例列表中基于一定的负载均衡算法选择一个具体的实例,并将请求发送给选中的实例
4)sidecar接收到请求后,发现是调用自己代理的non-java-app服务,然后将请求转发给相应的non-java-app
5)non-java-app处理请求,将响应发送给sidecar
6)sidecar将响应转发给java-app
2.1.2 非java应用程序调用java应用程序服务过程
image.png1)java-app向注册中心注册自己
2)non-java-app将请求发送给sidecar,请求url path的第一段值为java-app的服务名
3)sidecar从注册中心获取java-app的实例列表信息
4)sidecar从java-app的实例列表中基于一定的负载均衡算法选择一个具体的实例,并将请求发送给选中的实例
5)java-app处理请求,将响应发送给sidecar
6)sidecar将响应转发给non-java-app
2.2 spring cloud netflix sidecar内部原理
spring cloud netflix sidecar内部的核心工作是由netflix开源的微服务网关组件zuul提供的,sidecar其实是在zuul基础上进行的二次开发。
1)sidecar与non-java-app必须部署在同一个节点上,且一个sidecar唯一对应一个non-java-app
2)sidecar相当于non-java-app的代理,由sidecar向注册中心注册服务,其他微服务看到的non-java-app的服务名是由sidecar注册的,服务端口号是sidecar的端口号
3)注册中心如何检测non-java-app的健康状态
非java应用程序如果希望通过sidecar集成到spring cloud系统中,需要提供一个health检测接口,当非java应用程序状态正常时,这个health接口返回一个json字符串,内容如下所示:
{
"status":"UP"
}
sidecar在向注册中心反馈自己的健康状态时,会调用相应非java应用程序提供的health检测接口,这样只有非java应用程序状态正常时,sidecar在注册中心注册的相应服务实例的状态才是正常的。
4)sidecar如何确定请求是来自于被代理的non-java-app还是发送给non-java-app的
当sidecar接收到请求时,会判断http url的path路径的第一段是不是一个服务名。如果是,则会根据注册中心注册的实例信息,通过一定的负载均衡算法选择一个具体的实例,将请求转发给选择的实例。否则,sidecar则认为是调用自己代理的非java应用程序服务,将请求的host改为127.0.0.1,port改为相应非java应用程序的port,然后将请求转发给相应的非java应用程序
3 spring cloud netflix sidecar应用举例
下面通过一个java应用程序与一个go应用程序通过spring cloud微服务相互调用来举例说明sidecar是如何支持多语言微服务集成的,本例中使用的注册中心为eureka。
3.1 go应用程序实例
go应用程序的代码片段如下所示:
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
)
// 提供的健康检测接口
func health(w http.ResponseWriter, r *http.Request) {
m := map[string]string{"status": "UP"}
mjson, _ := json.Marshal(m)
w.Header().Set("Content-Type", "application/json")
w.Write(mjson)
}
func hellogo(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hellogo")
}
func remoteHellojava(w http.ResponseWriter, r *http.Request) {
// 通过sidecar调用其它微服务的接口,8004是sidecar的服务端口,java-app-service是服务名,/hellojava是服务路径
resp, _ := http.Get("http://172.18.182.27:8004/java-app-service/hellojava")
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Fprintf(w, string(body))
}
func main() {
http.HandleFunc("/health.json", health)
http.HandleFunc("/hellogo", hellogo)
http.HandleFunc("/remoteHellojava", remoteHellojava)
// 监听的端口是8093
err := http.ListenAndServe(":8093", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
该应用监听的port为8093,提供了三个http接口:
1)/health.json
用于sidecar检测应用的健康状态,eureka则通过sidecar间接确定应用的健康状态
2)/hellogo
应用提供的服务接口,被调用时返回字符串"hellogo"
3)/remoteHellojava
该接口会通过调用sidecar的接口来间接调用其他微服务的接口。url格式为:
http://{sidecar-host}:{sidecar-port}/{目的服务的服务名}/{目的服务的path}
3.2 sidecar实例
sidecar代码片段如下:
@EnableSidecar
@EnableEurekaClient
@SpringBootApplication
public class SidecarApplication {
public static void main(String[] args) {
SpringApplication.run(SidecarApplication.class, args);
}
}
可见,sidecar的基础代码非常简单,只要添加几个注解即可。@EnableSidecar注解用来开启sidecar功能
基础配置如下:
# 服务端口号
server.port: 8004
# 服务名
spring:
application:
name: go-app-service
# 服务注册和发现
eureka:
client:
healthcheck:
enabled: true
serviceUrl:
defaultZone: http://127.0.0.1:8002/eureka/
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ipAddress}:${server.port}
# sidecar配置
sidecar:
port: 8093
health-uri: http://localhost:8093/health.json
从上述的配置可见,除了跟sidecar相关的两个配置项:sidecar.port和sidecar.health-uri,其余的配置跟一个普通的spring cloud应用程序相同,更准确的说,跟配置微服务网关zuul是一样的。
sidecar.port用来告诉sidecar go应用程序监听的服务端口(不提供go应用程序host的原因是sidecar调用go应用程序服务时固定使用127.0.0.1地址),sidecar.health-uri用来告诉sidecar go应用程序提供的健康检测接口
分别启动go应用程序和sidecar,可以在eureka中看到sidecar注册的服务实例,如下:
GO-APP-SERVICE n/a (1) (1) UP (1) - 172.18.182.27:8004
3.3 java应用程序实例
java应用的代码片段如下:
@FeignClient(value = "go-app-service")
@RequestMapping(value = "/")
public interface GoAppServiceFeignClient {
@GetMapping(value = "/hellogo")
String hello();
}
通过feign调用远端微服务,@FeignClient(value = "go-app-service")指定微服务的名字,从前面可知,这个服务名即sidecar向eureka注册的服务名
@RestController
@RequestMapping(value = "/")
public class Controller {
@Autowired
private GoAppServiceFeignClient feignClient;
@GetMapping(value = "/hellojava")
public String hellojava() {
return "hellojava";
}
@GetMapping(value = "/remoteHellogo")
public String hellogo() {
return feignClient.hello();
}
}
@EnableFeignClients
@EnableEurekaClient
@SpringBootApplication
public class EurekaClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class, args);
}
}
配置文件内容如下:
server:
port: 8003
spring:
application:
name: java-app-service
eureka:
client:
healthcheck:
enabled: true
serviceUrl:
defaultZone: http://127.0.0.1:8002/eureka/
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ipAddress}:${server.port}
启动程序,可以在eureka中看到应用程序注册的服务实例,如下:
JAVA-APP-SERVICE n/a (1) (1) UP (1) - 172.18.182.27:8003
3.4 相互调用测试
调用go应用程序的/remoteHellojava
接口,返回hellojava
,说明调用成功
% curl http://localhost:8093/remoteHellojava
hellojava
调用java应用程序的/remoteHellogo
接口,返回hellogo
,说明调用成功
% curl http://localhost:8003/remoteHellogo
hellogo