关注 “Java艺术” 我们一起成长!
前面我们已经分析完OpenFeign
与Ribbon
的源码,包括两者的整合使用,以及Ribbon
的重试机制,从最顶层调用接口开始到负载均衡的实现。今天我们分析更底层的实现,即服务注册与发现。
本篇内容包括:
Spring Cloud Commons
的serviceregistry
与discovery
Spring Cloud Kubernetes
服务注册与发现实现原理
Spring Cloud Kubernetes Core
源码分析
Spring Cloud Kubernetes Discovery
源码分析
使用Spring Cloud Kubernetes
搭建的demo
级微服务项目sck-demo
的Github
地址:
https://github.com/wujiuye/share-projects/tree/master/sck-demo
Spring Cloud Commons
是Spring Cloud
的核心组件,提供各种接口。其实就是通过定义接口来定义规范,方便将各种框架整合到Spring Cloud
微服务项目中,这些功能或必须或可选。
Spring Cloud
定义的服务注册和发现接口,我们可以自己实现服务注册和发现以便接入新的服务注册中心,如
Nacos
、
Apollo
。
Spring Cloud Commons
只定义接口,不提供实现。
Ribbon
源码时就接触了其中一个接口,负载均衡接口:
LoadBalancerClient
,由
spring-cloud-netflix-ribbon
提供该接口的实现。
Spring Cloud Commons
的serviceregistry
包与discovery
包中分别定义了服务注册接口、服务发现接口。
服务注册接口ServiceRegistry
:定义注册方法和注销方法;
服务发现接口DiscoveryClient
:定义获取所有服务ID
方法以及根据服务ID
获取所有实例(节点)方法。
在sck-demo
项目搭建之初,我们是跟着官方提供的demo
去实现服务注册和发现的,也就是在每个服务的Application
类上添加一个@EnableDiscoveryClient
注解。
Kubernetes
的地址,但我们使用
DiscoveryClient
却能获取到服务节点。要解开这个疑惑我们需要了解
Kubernetes
,以及了解
Spring Cloud Kubernetes Discovery
的源码。
前面我们在分析Ribbon
的源码时也了解到,Ribbon
并非通过DiscoveryClient
去获取服务提供者的。
Ribbon
通过提供一个
ServerList
接口让使用者自己去实现来完成
Ribbon
的服务发现。Ribbon定时调用ServerList更新自身缓存的服务提供者列表,默认30秒更新一次。
Spring Cloud Kubernetes Ribbon
的作用就是实现
Ribbon
的
ServerList
接口,从
Kubernetes
获取可用的服务提供者。
实际上,Spring Cloud Kubernetes Ribbon
也并未使用到Spring Cloud Kubernetes Discovery
提供的DiscoveryClient
接口的实现来获取服务列表,而是直接调用接口从Kubernetes
中获取。
@EnableDiscoveryClient
注解后,以及去掉
Spring Cloud Kubernetes Discovery
的依赖后,项目依然能正常运作。
需要注意,Spring Cloud Kubernetes Ribbon
依赖Spring Cloud Kubernetes Core
,如果去掉Spring Cloud Kubernetes Discovery
,可能就要自动手动添加Spring Cloud Kubernetes Core
的依赖,否则服务启动失败。
Spring Cloud Kubernetes Discovery
实现DiscoveryClient
接口只是能够让我们通过DiscoveryClient
获取服务提供者。
SpringCloud Kubernetes Discovery
实现的服务注册接口也并未真正的去注册服务。
Spring Cloud Kubernetes
项目中,
Spring Cloud Kubernetes Discovery
是一个多余的存在。
如果去掉Spring Cloud Kubernetes Discovery
后,我们想要获取某个服务的当前可用服务提供者怎么获取呢?
Ribbon
的
ServerList
去获取,由
Spring Cloud Kubernetes Ribbon
实现。
我们来做个实验:
第一步:排除spring-cloud-kubernetes-discovery
依赖,并且去掉@EnableDiscoveryClient
注解;
org.springframework.cloud
spring-cloud-starter-kubernetes
org.springframework.cloud
spring-cloud-kubernetes-discovery
第二步:从ServerList
获取某个服务的当前可用服务提供者;
@RestController
@Slf4j
@RequestMapping
public class DemoTestController {
@Resource
private SpringClientFactory factory;
@GetMapping("/ServerList")
public Object test3() {
return factory.getInstance(ProviderConstant.SERVICE_NAME, ServerList.class)
.getUpdatedListOfServers();
}
}
如上代码所示,想要取得ServerList
就必须要通过SpringClientFactory
去拿。
Ribbon
源码分析的文章我们知道,
Ribbon
会为每个
Client
创建一个
ApplicationContext
,目的是实现环境隔离,让我们能够为调用每个服务提供者使用不同的配置。
因此,我们需要通过
SpringClientFactory
拿到对应
Client
的
ApplicationContext
,再从该
ApplicationContext
中获取
ServerList
。
最后调用
ServerList
的
getUpdatedListOfServers
方法从注册中心获取当前可以的服务列表。
测试结果如下图所示。
那么问题来了,服务是怎么注册到注册中心的,以及Spring Cloud Kubernetes Ribbon
和Spring Cloud Kubernetes Discovery
又是怎么从注册中心拿到服务提供者列表的?
第一个问题:服务怎么注册到注册中心的?
服务不需要注册,使用Spring Cloud Kubernetes
服务不需要注册!
Pod
启动起来时,就已经“注册”到
Kubernetes
的
etcd
了,这便是使用
Spring Cloud Kubernetes
的好处,直接使用
Kubernetes
的原生服务实现服务发现和注册。
第二个问题:怎么从注册中心拿到服务提供者列表的?
我们从minikube
(使用minikube
搭建的本地单节点Kubernetes
集群)的dashboard
就能找到服务的节点信息,如下图所示。
这些数据都是存储在Kubernetes
的etcd
服务上的,Spring Cloud Kubernetes Ribbon
和Spring Cloud Kubernetes Discovery
都是通过KubernetesClient
与Kubernetes
交互,调用Kubernetes
的API
读取服务信息的。
Spring Cloud Kubernetes Ribbon
的实现(以KubernetesEndpointsServerList
为例)
public class KubernetesEndpointsServerList extends KubernetesServerList {
KubernetesEndpointsServerList(KubernetesClient client,
KubernetesRibbonProperties properties) {
super(client, properties);
}
@Override
public List getUpdatedListOfServers() {
List result = new ArrayList<>();
// this.getClient().endpoints().withName(this.getServiceId()).get()
Endpoints endpoints = StringUtils.isNotBlank(this.getNamespace())
? this.getClient().endpoints().inNamespace(this.getNamespace())
.withName(this.getServiceId()).get()
: this.getClient().endpoints().withName(this.getServiceId()).get();
// ....
}
}
Spring Cloud Kubernetes Discovery
的实现
public class KubernetesDiscoveryClient implements DiscoveryClient {
private final KubernetesDiscoveryProperties properties;
private KubernetesClient client;
//......
@Override
public List getInstances(String serviceId) {
// this.client.endpoints().withName(serviceId).get()
List endpointsList = this.properties.isAllNamespaces()
? this.client.endpoints().inAnyNamespace()
.withField("metadata.name", serviceId).list().getItems()
: Collections.singletonList(this.client.endpoints().withName(serviceId).get());
// ......
return instances;
}
}
所以两者都是调用KubernetesClient
的endpoints
方法获取某个服务的节点信息。
KubernetesClient
是怎么获取服务节点信息的,我们并不需要去看
KubernetesClient
的源码,也没必要,
KubernetesClient
不过是封装了
http
请求。
想要了解
Kubernetes
提供的
API
可以看下这个文档:
https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#。
对应本例的API
如下图所示。
想要试一下API
?可以!
kubectl proxy
命令就会运行一个
Kubernetes API
代理服务。
例如:
wjy$ kubectl proxy --port=8004
Starting to serve on 127.0.0.1:8004
使用kubectl proxy --port=8004
开启Kubernetes API
代理服务,监听请求的端口为8004
。代理服务启动成功后,我们就可以使用127.0.0.1:8004
访问Kubernetes
的API
了。
例如,获取sck-demo-provider
这个服务的所有endpoints
,在浏览器输入:
127.0.0.1:8004/api/v1/namespaces/default/endpoints/sck-demo-provider
其中default
为名称空间,sck-demo-provider
为服务名。响应结果如下图所示。
调用这个接口就相当于使用kubectl get endpoints [服务名]
命令。
wjy$ kubectl get endpoints sck-demo-provider
NAME ENDPOINTS AGE
sck-demo-provider 172.17.0.2:8080,172.17.0.4:8080,172.17.0.6:8080 8d
Endpoints
和Pod
是什么关系?Endpoints
是个什么概念?
Pod
是
Kubernetes
调度的最小单位,一个
Pod
中可以运行多个
Docker
容器,即运行多个应用,但一般只会运行一个容器。
Endpoints
与
Pod
的关系是什么?
Endpoints
是Kubernetes
集群中的一个资源对象,存储在etcd
中,用来记录一个Service
对应的所有Pod
的访问地址。
Kubernetes
集群中创建一个名为
sck-demo-provider
的
Service
,就会生成一个同名的
Endpoint
对象,
Endpoint
就是
Service
关联的
Pod
的
IP
地址和端口(
Service
配置
selector
,否则不会生成
Endpoint
对象)。
该模块是Spring Cloud Kubernetes
项目的核心模块,Spring Cloud Kubernetes
的其它模块都依赖它实现与Kubernetes
交互。
Spring Cloud Kubernetes Core
主要实现自动配置KubernetesClient
,KubernetesClient
封装与Kubernetes
交互的API
请求。
KubernetesClient
的方法就是向
Kubernetes
的
API
服务发起一个请求。
该模块的spring.factories
文件配置了两个配置类:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.kubernetes.KubernetesAutoConfiguration\
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.cloud.kubernetes.profile.KubernetesProfileEnvironmentPostProcessor
KubernetesAutoConfiguration
读取前缀为
spring.cloud.kubernetes.client
的配置项,根据配置创建KubernetesClient
。
你是否有疑问,为什么我们在sck-demo
项目中什么也没有配置(如kubernetes的 MasterUrl
),却能启动服务,且能调用kubernetes
的API
?
当我们不配置Kubernetes
的MasterUrl
时,默认使用的是:
https://kubernetes.default.svc
,且默认API
版本为V1
、默认不配置名称空间。
KubernetesClientProperties
或者
io.fabric8.kubernetes.client.Config
的源码。
Spring Cloud Kubernetes Core
还实现spring-boot-actuate
健康监控的HealthIndicator
接口。
KubernetesHealthIndicator
实现HealthIndicator
接口的health
方法,在方法中判断,如果能够获取当前Pod
信息则说明服务正常。
KubernetesHealthIndicator
在KubernetesAutoConfiguration
中实例化并注册到Spring
容器。
KubernetesProfileEnvironmentPostProcessor
这是一个EnvironmentPostProcessor。
KubernetesProfileEnvironmentPostProcessor
的作用是拦截Environment
的初始化,如果当前应用是运行在容器内,则会调用Environment
的addActiveProfile
方法追加"kubernetes"
。服务启动时会打印如下日记:
INFO c.w.s.p.SckProviderApplication - The following profiles are active: kubernetes,dev
该模块实现Spring Cloud
的服务发现接口DiscoveryClient
,实现类为KubernetesDiscoveryClient
。
KubernetesDiscoveryClient
使用KubernetesClient
调用Kubernetes API
获取Endpoints
。
同时也实现了服务注册接口ServiceRegistry
,有发现就有注册。
ServiceRegistry
的实现类为KubernetesServiceRegistry
,该类实现接口的注册、注销方法,但仅仅只是打印个日记,什么也不做。
public class KubernetesServiceRegistry
implements ServiceRegistry {
private static final Log log = LogFactory.getLog(KubernetesServiceRegistry.class);
@Override
public void register(KubernetesRegistration registration) {
log.info("Registering : " + registration);
}
@Override
public void deregister(KubernetesRegistration registration) {
log.info("DeRegistering : " + registration);
}
//.......
}
所以,Spring Cloud Kubernetes Discovery
模块没什么好分析的。
Spring Cloud Kubernetes
动态配置的实现源码与源码分析。
▼
往期原创精选
▼
Ribbon重试策略RetryHandler的配置与源码分析
OpenFeign与Ribbon源码分析总结(面试题)
Spring Cloud Ribbon源码分析(Spring Cloud Kubernetes)
Spring Cloud OpenFeign源码分析
Spring Cloud kubernetes入门项目sck-demo
为什么要选择Spring Cloud Kubernetes?
[Java艺术] 微信号:javaskill
一个只推送原创文章的技术公众号,分享Java后端相关技术。