
Spring Cloud Kubernetes微服务实战与源码分析 专栏收录该内容,点击查看专栏更多内容原创 吴就业 350 0 2020-06-20
本文为博主原创文章,未经博主允许不得转载。
本文链接:https://wujiuye.com/article/b199a2eaa1354c83972691092ea9d8d2
作者:吴就业
链接:https://wujiuye.com/article/b199a2eaa1354c83972691092ea9d8d2
来源:吴就业的网络日记
本文为博主原创文章,未经博主允许不得转载。
本篇文章写于2020年06月20日,从公众号|掘金|CSDN手工同步过来(博客搬家),本篇为原创文章。

本篇我们将从一个简单的demo上手Spring Cloud kubernetes,当然,我们只用到Spring Cloud kubernetes的服务注册与发现、配置中心模块。后续还有部分文章介绍如何将sck-demo部署到minikube以及阿里云容器服务kubernetes。
本来计划是先写源码分析再写部署的,但考虑到大家都想先看到成果再去深究,所以就根据学习的过程按顺序写了。源码分析主要是解决一些疑惑,了解用到的功能都是怎么整合到一起的,遇到问题能够快速定位。
在完成sck-demo的服务注册发现、动态配置以及项目部署之后,我们也实现一个基于Spring Cloud Gateway整合Sentinel的API网关。
从入门到源码分析,带大家一起掌握Spring Cloud kubernetes的使用,知其然并知其所以然。笔者尽量保持至少周更,也希望感兴趣的朋友帮忙点下在看。
本篇内容: * 实现服务注册与发现; * 实现动态配置并监听配置改变事件;
sck-demo代码可在github下载:
https://github.com/wujiuye/share-projects/tree/master/sck-demo。
项目框架如下图所示(后续文章会加上网关的实现代码):

common-lib:通用组件,如全局统一异常处理、自定义参数校验注解、json解析等工具类、api响应以及响应状态码的定义等;k8s:该目录存储本地部署到minikube搭建的单节点kubernetes集群的yaml文件;sck-demo-provider-api:定义对外提供的接口,只是方便java语言开发的应用直接通过依赖该jar包调用,实现rpc远程进程调用;sck-demo-provider:服务提供者,实现对外的接口;sck-demo-consimer:服务消费者,依赖sck-demo-provider-api模块,调用sck-demo-provider暴露的接口;项目pom.xml统一配置依赖的Spring Cloud kubernetes以及Spring Boot的版本。
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<spring.boot.version>2.3.0.RELEASE</spring.boot.version>
<spring.cloud.k8s.version>1.1.3.RELEASE</spring.cloud.k8s.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud Kubernetes -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-dependencies</artifactId>
<version>${spring.cloud.k8s.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 其它省略 -->
</dependencies>
</dependencyManagement>
该模块定义sck-demo-provider服务提供给其它服务调用的接口,也是sck-demo-provider需要实现的接口。但这里的实现接口不是像dubbo那样,需要在service或者controller通过关键字implements实现接口,如果非要比喻的话,倒像是go语言中的接口定义与实现。

sck-demo-provider-api模块的maven依赖配置文件中加入openfeign的starter:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
DemoService类的代码如下:
@FeignClient(
// 对应服务提供者的${spring.application.name}
name = ProviderConstant.SERVICE_NAME,
path = "/v1",
// url用于本地调试,部署到容器中时配置为null,走服务发现
url = "${fegin-client.sck-demo-provider-url}",
primary = false)
public interface DemoService {
@PostMapping("/services")
ListGenericResponse<DemoDto> getServices();
}
服务提供者需要实现/v1/services接口,服务消费者可直接通过调用DemoService的getServices方法来调用接口,不需要写http请求,这些重复的事情交由feign去实现。实际上是feign在运行时为DemoService接口编写了实现类。因此我们可以在spring项目中通过@Resource注入DemoService实例。
该模块实现sck-demo-provider-api定义的接口。

在sck-demo-provider模块的maven依赖配置文件中添加spring-cloud-starter-kubernetes的依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes</artifactId>
</dependency>
spring-cloud-starter-kubernetes会自动导入spring-cloud-kubernetes-core以及spring-cloud-kubernetes-discovery。
依赖组件模块以及sck-demo-provider-api接口定义模块:
<dependency>
<groupId>com.wujiuye</groupId>
<artifactId>common-lib</artifactId>
<version>${parent.version}</version>
</dependency>
<dependency>
<groupId>com.wujiuye</groupId>
<artifactId>sck-demo-provider-api</artifactId>
<version>${parent.version}</version>
</dependency>
commom-lib模块的依赖配置如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 参数校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
DemoController就是实现DemoService接口Controller,代码如下:
@RestController
@RequestMapping("/v1")
public class DemoController {
@Resource
private DemoProps demoProps;
@Resource
private DiscoveryClient discoveryClient;
@PostMapping("/services")
public ListGenericResponse<DemoDto> getServices() {
ListGenericResponse<DemoDto> response = new ListGenericResponse<>();
PageInfo<DemoDto> pageInfo = new PageInfo<>(discoveryClient.getServices()
.stream().map(DemoDto::new)
.collect(Collectors.toList()), discoveryClient.getServices().size(), 100, 1);
response.setData(pageInfo);
return response;
}
@GetMapping("/config")
public Object testConfig() {
return demoProps;
}
}
testConfig是我们用来测试动态配置是否生效的。
注意,我们并没有使用implements关键字实现接口,当然,你也可以使用implements关键字实现接口,这样的强约束能够让你在改动接口时想起还有其它地方要改。
实现接口之后,我们还要使用@EnableDiscoveryClient注解开启服务注册与发现功能,将服务提供者注册到注册中心。与Dubbo不同,Spring Cloud是以应用为维度注册服务与发现服务的。
@EnableDiscoveryClient
@SpringBootApplication
public class SckProviderApplication {
public static void main(String[] args) {
SpringApplication.run(SckProviderApplication.class, args);
}
}
使用Spring Cloud kubernetes实现Spring Cloud服务注册与发现接口的spring-cloud-kubernetes-discovery模块,我们不需要配置注册中心的地址,一个@EnableDiscoveryClient注解就能搞定。
实现动态配置
需要导入spring-cloud-starter-kubernetes-config的依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-config</artifactId>
</dependency>
编写props类,给类加上@ConfigurationProperties与@Component注解,当然,还需要在启动类加上@EnableConfigurationProperties注解。@RefreshScope如果不加,spring cloud也会自动给DemoProps这个Bean加上scope为refresh,即在配置改变之后,该bean会被销毁并重新初始化一个实例。
@RefreshScope
@Component
@ConfigurationProperties(prefix = "sck-demo")
public class DemoProps {
private String message;
// get、set
}
编写本地测试用的application配置文件:application-dev.yml,在配置文件中加上DemoProps的配置,用于本地Debug:
sck-demo:
message: sck-demo message!
编写bootstrap.yaml文件:
spring:
application:
name: sck-demo-provider
cloud:
kubernetes:
reload:
enabled: true
mode: polling
period: 5000
config:
sources:
- name: ${spring.application.name}-config
spring.cloud.kubernetes.reload配置启动自动拉取新配置功能,配置为主动拉取,周期为5秒。spring.cloud.kubernetes.config.sources项配置引用的ConfigMap资源的名称。
该配置文件在sck-demo项目的k8s/dev/config目录下,文件名为sck-demo-provider-config.yaml。

等到将服务部署到k8s集群时,需要使用kubectl apply -f sck-demo-provider-config.yaml将在k8s中创建该ConfigMap资源。线上的配置文件就不要放在项目中了。
apiVersion: v1
kind: ConfigMap
metadata:
name: sck-demo-provider-config
namespace: default
data:
application-dev.yml: |-
sck-demo:
message: for dev config map
怎么监听DemoProps的属性改变呢?
笔者从spring-cloud-kubernetes-config和spring-cloud-context的源码中找到了能够监听配置改变的方法。这两个模块是我们后面分析动态配置的实现原理时需要深入了解源码的两个模块。
当配置中心更新了配置文件之后,spring-cloud-kubernetes-config会拉取到新的配置,然后会先发布一个EnvironmentChangeEvent事件,再发送一个RefreshScopeRefreshedEvent事件。我们可以通过监听这两个事件得知配置改变了。在监听到EnvironmentChangeEvent事件时,DemoProps的属性还是没有改变的,而在监听到RefreshScopeRefreshedEvent事件时,DemoProps属性时已经改变了的。详细内容我们后面分析源码时再详细分析。
@Component
public class PropsListener implements ApplicationListener<RefreshScopeRefreshedEvent> {
@Resource
private DemoProps demoProps;
@Override
public void onApplicationEvent(RefreshScopeRefreshedEvent event) {
System.out.println(demoProps);
System.out.println(event.getName());
System.out.println(event.getSource());
}
}
在消费端依赖sck-demo-provider-api模块,使用sck-demo-provider-api模块调用服务提供者的API接口。

添加依赖配置:
<dependency>
<groupId>com.wujiuye</groupId>
<artifactId>common-lib</artifactId>
<version>${parent.version}</version>
</dependency>
<dependency>
<groupId>com.wujiuye</groupId>
<artifactId>sck-demo-provider-api</artifactId>
<version>${parent.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes</artifactId>
</dependency>
在启动类添加@EnableDiscoveryClient注解开启服务注册与发现功能,同时还需要添加@EnableFeignClients注解,扫描sck-demo-provider-api模块下的接口,Feign自动生成接口的实现类,并创建一个实例注册到spring bean工厂。
@EnableFeignClients(basePackages = {"com.wujiuye.sck.provider.client"})
@EnableDiscoveryClient
@SpringBootApplication
public class SckConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(SckConsumerApplication.class, args);
}
}
在Serivce中调用API接口
@Service
public class DemoInvokeServiceImpl implements DemoInvokeService {
@Resource
private DemoService demoService;
@Override
public ListGenericResponse<DemoDto> invokeDemo() {
return demoService.getServices();
}
}
在消费端也对外提供一个http接口,用于测试服务间的调用是否成功。
public class DemoTestController {
@Resource
private DemoInvokeService demoInvokeService;
@GetMapping("/demo")
public Object test2() {
return demoInvokeService.invokeDemo();
}
}
下一篇我们开始将服务部署到本地kubernetes集群,验证服务调用是否可以,配置中心改变配置之后应用程序是否能够监听到配置改变事件,是否能读取到最新配置等。
声明:公众号、CSDN、掘金的曾用名:“Java艺术”,因此您可能看到一些早期的文章的图片有“Java艺术”的水印。

如果指定了URL,那么getOptional方法不会返回null,且返回的Client是LoadBalancerFeignClient,但不会抛出异常。如果不指定URL,则走负载均衡逻辑,走的是loadBalance方法,且抛出异常。
选择Spring Cloud Kubernetes意味着我们想要将服务部署到Kubernetes集群,Spring Cloud Kubernetes为我们实现了Spring Cloud的一些接口,让我们可以快速搭建Spring Cloud微服务项目框架,并能使用Kubernetes云原生服务。
作为开发者,只有足够了解容器技术,才能做好技术选型,以及开发部署在Kubernetes容器服务之上的应用应该要注意哪些问题。如果运维不了解代码,开发也不了解Kubernetes,谁能解决将服务迁移到Kubernetes上遇到的各种问题呢?
订阅
订阅新文章发布通知吧,不错过精彩内容!
输入邮箱,提交后我们会给您发送一封邮件,您需点击邮件中的链接完成订阅设置。