异构语言接入Java微服务

前言

作为微服务体系, 应该是不限语言的, 不管是phpnodejspythonjava, 都可以是一个微服务.
本文将说明如何将phpnodejspython接入Java微服务体系下.

Sidecar

目前Spring Cloud主流使用的是Sidecar方式.

Sidecar作为一个中转服务, 和PHP服务存在同一个服务器上, 做到低延迟通信.
PHP服务启动完毕后, 启动Sidecar服务.
此时Sidecar会做三件事.

  1. Sidecar会将PHP服务所在服务器的IP和端口, 注册到注册中心, 成为一个微服务.
  2. Sidecar会定时检测PHP服务的健康状态, 也就是调用一下PHP服务的接口, 看有没有返回数据, 没有就从注册中心注销PHP服务
  3. PHP服务想要调用其他微服务接口需要通过Sidecar中转, Sidecar会将来自PHP的请求路由到其他微服务.

接下来讲一下Spring主流的具体实现.

Spring Cloud Netflix Sidecar

一代目的Spring Cloud全家桶, 也可以叫Netflix全家桶, 理所当然的, 它提供的Sidecar, 只支持了自家的Eureka.

第一步, 注册到注册中心

先来看看入口SidecarAutoConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// org.springframework.cloud.netflix.sidecar.SidecarAutoConfiguration
@Configuration(proxyBeanMethods = false)
public class SidecarAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(EurekaClientConfig.class)
protected static class EurekaInstanceConfigBeanConfiguration {
@Bean
@ConditionalOnMissingBean
public EurekaInstanceConfigBean eurekaInstanceConfigBean(ManagementMetadataProvider managementMetadataProvider) {
EurekaInstanceConfigBean config = new EurekaInstanceConfigBean(inetUtils);
int port = this.sidecarProperties.getPort();
config.setNonSecurePort(port);
String ipAddress = this.sidecarProperties.getIpAddress();
config.setIpAddress(ipAddress);
return config;
}
}
}

我们可以看到SidecarAutoConfiguration注册了一个EurekaInstanceConfigBean.
我们再看看客户端的配置EurekaClientAutoConfiguration.

1
2
3
4
5
6
7
8
// org.springframework.cloud.netflix.eureka.SidecarAutoConfiguration
@Configuration(proxyBeanMethods = false)
public class EurekaClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean(value = EurekaInstanceConfig.class, search = SearchStrategy.CURRENT)
public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils, ManagementMetadataProvider managementMetadataProvider) {
}
}

这里也有一个EurekaInstanceConfigBean.
也就是说SidecarAutoConfigurationEurekaInstanceConfigBean覆盖了EurekaClientAutoConfigurationEurekaInstanceConfigBean.
PHP服务注册到注册中心, 具体怎么注册的, 涉及Eureka的源码, 这里就不细讲.

第二步, 定时任务检查健康状态

继续看入口SidecarAutoConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// org.springframework.cloud.netflix.sidecar.SidecarAutoConfiguration
@Configuration(proxyBeanMethods = false)
public class SidecarAutoConfiguration {
@Bean
public LocalApplicationHealthIndicator localApplicationHealthIndicator() {
return new LocalApplicationHealthIndicator();
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(EurekaClientConfig.class)
protected static class EurekaInstanceConfigBeanConfiguration {
@Bean
public HealthCheckHandler healthCheckHandler(final LocalApplicationHealthIndicator healthIndicator) {
// 注入上面的 LocalApplicationHealthIndicator
return new LocalApplicationHealthCheckHandler(healthIndicator);
}
}
}
// org.springframework.cloud.netflix.sidecar.LocalApplicationHealthCheckHandler
class LocalApplicationHealthCheckHandler implements HealthCheckHandler {
// 注入进来的 LocalApplicationHealthIndicator
private final HealthIndicator healthIndicator;
LocalApplicationHealthCheckHandler(HealthIndicator healthIndicator) {
this.healthIndicator = healthIndicator;
}

@Override
public InstanceStatus getStatus(InstanceStatus currentStatus) {
// 具体的心跳检测逻辑
Status status = healthIndicator.health().getStatus();
if (status.equals(Status.UP)) { return UP; }
else if (status.equals(Status.OUT_OF_SERVICE)) { return OUT_OF_SERVICE; }
else if (status.equals(Status.DOWN)) { return DOWN; }
return UNKNOWN;
}

}

可以看到LocalApplicationHealthCheckHandler是一个LocalApplicationHealthIndicator的装饰类.
主要是调用LocalApplicationHealthIndicator来进行心跳检测. LocalApplicationHealthIndicator实现了EurekaHealthCheckHandler接口.

第三步, Sidecar会将来自PHP的请求路由到其他微服务

我们看pom.xml的依赖.
可以看到主要依赖了Zuul.
Zuul的默认转发规则是/微服务名称/具体uri.
所以如果PHP服务要调用其他微服务, 只要访问http://127.0.0.1:${Sidecar端口}/${微服务名称}/hello即可.

Spring Cloud Alibaba Sidecar

接下来是二代目的Spring Cloud全家桶, 也可以叫Alibaba全家桶, 除了支持自家的Nacos, 还支持了consul.

第一步, 注册到注册中心

这里以Nacos为例.
继续看入口SidecarNacosAutoConfiguration
这里比Netflix Sidecar的简单多了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// com.alibaba.cloud.sidecar.nacos.SidecarNacosAutoConfiguration
@Configuration
public class SidecarNacosAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public SidecarDiscoveryClient sidecarDiscoveryClient(SidecarNacosDiscoveryProperties sidecarNacosDiscoveryProperties) {
return new SidecarNacosDiscoveryClient(sidecarNacosDiscoveryProperties);
}
}
// com.alibaba.cloud.sidecar.nacos.SidecarNacosDiscoveryClient
public class SidecarNacosDiscoveryClient implements SidecarDiscoveryClient {
private final SidecarNacosDiscoveryProperties sidecarNacosDiscoveryProperties;
@Override
public void registerInstance(String applicationName, String ip, Integer port) {
// 注册逻辑
this.sidecarNacosDiscoveryProperties.namingServiceInstance().registerInstance(applicationName, ip, port);
}
@Override
public void deregisterInstance(String applicationName, String ip, Integer port) {
// 注销逻辑
this.sidecarNacosDiscoveryProperties.namingServiceInstance().deregisterInstance(applicationName, ip, port);
}
}

一目了然, 注册和注销的方法, 调用的都是Nacos提供的API.

第二步, 定时任务检查健康状态

我们看SidecarAutoConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// com.alibaba.cloud.sidecar.SidecarAutoConfiguration
@Configuration(proxyBeanMethods = false)
public class SidecarAutoConfiguration {
@Bean
public SidecarHealthChecker sidecarHealthChecker(SidecarDiscoveryClient sidecarDiscoveryClient, SidecarHealthIndicator sidecarHealthIndicator, SidecarProperties sidecarProperties, ConfigurableEnvironment environment) {
SidecarHealthChecker cleaner = new SidecarHealthChecker(sidecarDiscoveryClient, sidecarHealthIndicator, sidecarProperties, environment);
cleaner.check();
return cleaner;
}
}
// com.alibaba.cloud.sidecar.SidecarHealthChecker
public class SidecarHealthChecker {
public void check() {
// 1. 开启一个定时任务
Schedulers.single().schedulePeriodically(() -> {
String ip = sidecarProperties.getIp();
Integer port = sidecarProperties.getPort();

// 2. 调用 http 请求
Status status = healthIndicator.health().getStatus();
String applicationName = environment.getProperty("spring.application.name");

// 3. 健康则续期, 否则注销
if (status.equals(Status.UP)) {
this.sidecarDiscoveryClient.registerInstance(applicationName, ip, port);
} else {
this.sidecarDiscoveryClient.deregisterInstance(applicationName, ip, port);
}
}, 0, sidecarProperties.getHealthCheckInterval(), TimeUnit.MILLISECONDS);
}
}

还是老套路, Sidecar通过http定时检测PHP服务的健康状态.
如果健康则继续续期, 否则从注册中心注销.

第三步, Sidecar会将来自PHP的请求路由到其他微服务

我们看pom.xml
可以看到依赖了Spring Cloud Gateway.
Zuul一样. 只要访问http://127.0.0.1:${Sidecar端口}/${微服务名称}/hello即可调用其他微服务.

参考资料