本节我们主要讨论一下异构平台(比如,nodejs、python、php等提供的Rest接口服务)的服务,怎么通过spring cloud组件对这些服务注册到eureka中心以及与在微服务中怎么和异构平台的服务进行通信。这里主要是通过spring cloud的sidecar来构建异构平台的服务注册与通信。
sidecar灵感来自Netflix Prana。它可以获取注册中心的所有微服务实例的信息(例如host,端口等)的http api。也可以通过嵌入的Zuul代理来代理服务调用,该代理从Eureka获取其路由条目。 Spring Cloud配置服务器可以通过主机查找或通过嵌入的Zuul直接访问。
涉及到的服务如下:
服务名 | 端口 | 用途 |
---|---|---|
eureka-server | 8100 | 服务注册与发现 |
nodeSidecar | 8130 | 异构服务对接服务 |
node | 3000 | nodejs服务 |
consumer | 8106 | 消费者服务,与node服务存在声明式调用 |
1.1 Sidecar
1.1.1 Sidecar服务
先来看一下引入Sidecar需要做一些什么。
1. 添加 Maven 依赖,很简单,甚至不需要eureka-client的依赖,因为它已经整合至 Sidecar 的依赖中
org.springframework.cloud
spring-cloud-netflix-sidecar
2. 接下来是注解,在 Sidecar 主类上添加@EnableSidecar注解,我们来看看这个注解包含些什么
@EnableCircuitBreaker
@EnableZuulProxy
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(SidecarConfiguration.class)
public @interface EnableSidecar {
}
包含了网关 Zuul 以及微服务结构中不可或缺的熔断器 Hystrix
3. 最后是配置文件,在application.yml中添加如下配置
server:
port: 8130
spring:
application:
name: nodeSidecar
eureka:
instance:
hostname: localhost
client:
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:8100/eureka/
sidecar:
port: 3000
health-uri: http://localhost:${sidecar.port}/health
声明服务名和注册中心地址都没什么问题,最核心的就是 sidecar 的几个配置,包括
- sidecar.port 监听的 Node 应用的端口号,
- sidecar.health-uri Node 应用的健康检查接口的 uri
1.1.2 健康检查接口
需要注意的是:Node.js 的微服务应用必须实现一个/health健康检查接口,Sidecar 应用会每隔几秒访问一次该接口,并将该服务的健康状态返回给 Eureka,该接口只需要返回{ status: 'UP' }
这样一串Json即可。
var http = require('http');
var url = require("url");
var path = require('path');
// 创建server
var server = http.createServer(function(req, res) {
// 获得请求的路径
var pathname = url.parse(req.url).pathname;
res.writeHead(200, { 'Content-Type' : 'application/json; charset=utf-8' });
if (pathname === '/index') {
res.end(JSON.stringify({ "index" : "欢迎来到首页" }));
}
else if (pathname === '/health') {
res.end(JSON.stringify({ "status" : "UP" }));
}
// 其他情况返回404
else {
res.end("404");
}
});
// 创建监听,并打印日志
server.listen(3000, function() {
console.log('listening on localhost:3000');
});
1.2 服务注册
准备好eureka,nodeSidecar,node服务。按顺序启动eureka->node服务->nodeSidecar
- 访问Eureka的WebUI
http://localhost:8100/eureka-server
,nodeSidecar服务以被注册到Eureka上,并且状态为UP。
- 访问 Sidecar 的首页http://localhost:8130/,提供了三个接口
- 访问hosts/nodeSidecar可以的到 nodeSidecar 实例的一些信息
可以看到,该实例维护了 Node 应用的访问地址"uri": "http://localhost:3000",这也是接下来要说的:其他微服务可以通过 Sidecar 的服务名声明式调用 Node 服务。[ { "host": "192.168.216.1", "port": 3000, "uri": "http://192.168.216.1:3000", "metadata": { "management.port": "8130" }, "serviceId": "NODESIDECAR", "secure": false, "instanceInfo": { "instanceId": "localhost:nodeSidecar:8130", "app": "NODESIDECAR", "appGroupName": null, "ipAddr": "192.168.216.1", "sid": "na", "homePageUrl": "http://192.168.216.1:3000/", "statusPageUrl": "http://192.168.216.1:8130/actuator/info", "healthCheckUrl": "http://192.168.216.1:8130/actuator/health", "secureHealthCheckUrl": null, "vipAddress": "nodeSidecar", "secureVipAddress": "nodeSidecar", "countryId": 1, "dataCenterInfo": { "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", "name": "MyOwn" }, "hostName": "192.168.216.1", "status": "UP", "overriddenStatus": "UNKNOWN", "leaseInfo": { "renewalIntervalInSecs": 30, "durationInSecs": 90, "registrationTimestamp": 1537408212824, "lastRenewalTimestamp": 1537408361916, "evictionTimestamp": 0, "serviceUpTimestamp": 1537408212022 }, "isCoordinatingDiscoveryServer": false, "metadata": { "management.port": "8130" }, "lastUpdatedTimestamp": 1537408212824, "lastDirtyTimestamp": 1537408211890, "actionType": "ADDED", "asgName": null }, "scheme": null } ]
1.3 声明式服务调用
以上,我们已经验证了 Eureka 可以通过 Sidecar 间接的管理基于 Node 的微服务。而在微服务体系中,还有非常重要的一点,就是服务间的调用。Spring Cloud 允许我们使用服务名进行服务间的调用,摒弃了原先的固定写死的 IP 地址,便于服务集群的横向拓展及维护。那么,Non-JVM 的微服务与其他服务间是否可以通过服务名互相调用呢,答案是可以的。
1.3.1 被调用
我们假设下面一个场景,node服务提供了/index接口,返回json字符串。而 consumer服务需要访问node服务拿到返回的json数据。也就是 consumer服务需要访问 node服务 的/index接口拿到json字符串。
1. 在 node服务中实现/index接口,返回json字符串
// 创建server
var server = http.createServer(function(req, res) {
// 获得请求的路径
var pathname = url.parse(req.url).pathname;
res.writeHead(200, { 'Content-Type' : 'application/json; charset=utf-8' });
// 访问http://localhost:8060/,将会返回{"index":"欢迎来到首页"}
if (pathname === '/index') {
res.end(JSON.stringify({ "index" : "欢迎来到首页" }));
}
// 访问http://localhost:8060/health,将会返回{"status":"UP"}
else if (pathname === '/health') {
res.end(JSON.stringify({ "status" : "UP" }));
}
// 其他情况返回404
else {
res.end("404");
}
});
2. consumer作为 node 服务的调用者,需要声明 Client 接口,代码如下
@FeignClient("nodeSidecar")
public interface NodeServiceClient {
@GetMapping("/index")
String getIndex();
}
注意到@FeignClient注解中调用的服务名填写的是 nodeSidecar (大小写不敏感),因为自始至终 Eureka 中注册的是 Sidecar 的信息,而 Sidecar 实例维护了 node服务 的地址信息,所以它可以将请求转发至 node服务。
1.3.2 调用其它微服务
其他微服务可以通过 Sidecar 实例的服务名间接调用 Node 服务。同样的,node服务 也可以通过服务名调用其它微服务,这要归功于@EnableZuulProxy。
访问http://localhost:8130/consumer/index惊讶的发现,这和我们访问http://localhost:8106/index结果是一样的,这是由于 Sidecar 引入了 Zuul 网关,它可以获取 Eureka 上注册的服务的地址信息,从而进行路由跳转。因此,node访问其它微服务的地址格式为:http:localhost:8130/{serviceName}/method
另外,可以直接使用eureka的HTTP接口集成异构服务。由于eureka也是通过HTTP协议的接口暴露自身服务的,因此我们可以在node.js中手动发送HTTP请求实现服务的注册、查询和心跳功能。eureka接口描述信息可以在官方github的wiki中找到。但是这种方式存在弊端:
- 无法使用Spring Cloud组件Ribbob,Hystrix等提供的便利;
- 无法通过服务名访问其它微服务,其它服务的ip,port需暴露给node服务供http访问。