前面我们已经分析完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
地址: github.com/wujiuye/sha…
Spring Cloud Commons的serviceregistry与discovery
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
获取所有实例(节点)方法。
Spring Cloud Kubernetes服务注册与发现实现原理
在sck-demo
项目搭建之初,我们是跟着官方提供的demo
去实现服务注册和发现的,也就是在每个服务的Application
类上添加一个@EnableDiscoveryClient
注解,并且我们也并未配置Kubernetes
的地址,但我们使用DiscoveryClient
确能获取到服务,要解开这些疑惑我们需要了解Kubernetes
,以及了解Spring Cloud Kubernetes Discovery
的源码。
前面我们在分析Ribbon
的源码时也了解到,Ribbon
并非通过DiscoveryClient
去获取服务提供者的,Ribbon
通过提供一个ServerList
接口让使用者自己去实现来完成Ribbon
的服务发现,Spring Cloud Kubernetes Ribbon
的作用就是实现Ribbon
的ServerList
接口,从Kubernetes
获取可用的服务提供者,Ribbon
定时调用ServerList
更新自身缓存的服务提供者列表,默认30
秒更新一次。
实际上,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
获取服务提供者,
Spring Cloud Kubernetes Discovery
实现的服务注册接口也并非真正的去注册服务,可以这么说,在Spring Cloud Kubernetes
项目中Spring Cloud Kubernetes Discovery
是一个多余的存在。
如果去掉Spring Cloud Kubernetes Discovery
后,我们想要获取某个服务的当前可用服务提供者怎么获取呢?我们可以通过使用Ribbon
的ServerList
去获取,由Spring Cloud Kubernetes Ribbon
实现。
我们来做个实验:
第一步:排除spring-cloud-kubernetes-discovery
依赖,并且注释掉@EnableDiscoveryClient
注解;
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-discovery</artifactId>
</exclusion>
</exclusions>
</dependency>
第二步:从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<Server> getUpdatedListOfServers() {
List<Server> 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<ServiceInstance> getInstances(String serviceId) {
// this.client.endpoints().withName(serviceId).get()
List<Endpoints> 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
可以看下这个文档:kubernetes.io/docs/refere…。
对应本例的API
如下图所示。
想要试一下API
?本地使用kubectl proxy
命令可运行一个Kubernetes API
代理服务。
例如:
MacBook-Pro:sck-demo 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 [服务名]
命令。
例如:
MacBook-Pro:sck-demo 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 Core源码分析
该模块是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
相关的),却能启动服务,且能调用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 Kubernetes Discovery源码分析
该模块实现Spring Cloud
的服务发现接口DiscoveryClient
,实现类为KubernetesDiscoveryClient
。KubernetesDiscoveryClient
使用KubernetesClient
请求Kubernetes API
获取Endpoints
。
同时也实现了服务注册接口ServiceRegistry
,有发现就有注册,ServiceRegistry
的实现类为KubernetesServiceRegistry
,该类实现接口的注册、注销方法只打印日记,什么也不做。
public class KubernetesServiceRegistry
implements ServiceRegistry<KubernetesRegistration> {
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
的动态配置。