网站首页 > java教程 正文
在之前的文章中, 介绍了搭建好的minikube环境,如果你现在还没有一个可用的minikube环境, 那么可以去该篇文章中直接下载;
在上一篇文章中, 介绍了如何开发并部署一个最简单的Node.js 应用, 一个简单的http-echo 应用, 并把这个http-echo 部署到#minikube# Kubernetes中.
下面, 我们来学习一下, 如何开发一个简单的Spring Boot 应用, 并部署到minikube中.
问题
这些问题, 您在阅读本文后希望能够回答:
- 什么是响应式编程?
- Spring WebFlux 与Spring MVC 有什么不同之处?
- Kubernetes 都有哪些容器运行时?
- 怎么样使用Springboot的maven 插件来制作Docker 镜像?
- Kubernetes 中, Service 都有哪些类型?
- ClusterIP与NodePort 分别有什么用法?
- Kubernetes中NodePort 类型的Service, 其端口范围一般是多少?
- 怎么样让kubectl 生成yaml 文件?
- kubectl create deployment 中的dry-run选项有什么作用? 都有哪些选项?
通过上一篇的学习, 我们知道, Kubernetes 是一个开源的企业级容器编排平台, 用于部署和管理各种应用程序容器以及容器所依赖的底层基础设施资源, 包括网络、存储、认证等.而minikube 则是一个单机版的Kubernetes 集群系统, 可以轻松在笔记本上运行一个单节点的Kubernetes 环境, 用于本地部署和开发; 而#kubeadm# 则是用在真正的集群部署, 另外还有很多Kubernetes 供应商, 这里就不一一介绍了, 后续有机会我们在去学习.
在把应用部署到minikube 之前, 需要先把应用打包成容器, 这个容器其实可以有很多选择, 可以选择Docker, 也有其他的一些容器引擎可供选, 这取决于你的Kubernetes 使用了哪种容器引擎, 比如Rkt, CRI等, 这得益于Kubelet 容器运行时所采用的的插件化的设计.
一、源代码--使用Spring Boot, 启用Spring WebFlux
首先, 我们还是来获取源代码, 这里我们直接用Spring的官方代码:
curl https://start.spring.io/starter.tgz -d dependencies=webflux,actuator | tar -xzvf -
注意, 这里面的依赖部分, 引入了webflux, 先来简单认识下这个框架:
Spring WebFlux 是 Spring Framework 5.0中引入的新的响应式web框架。与Spring MVC不同,它不需要Servlet API,是完全异步且非阻塞的,并且通过Reactor项目实现了Reactive Streams规范。
Spring WebFlux 用于创建基于事件循环执行模型的完全异步且非阻塞的应用程序。
Spring Boot 2.0 是基于 Spring 5 构建而成,因此 Spring Boot 2.X 将自动继承了 Webflux 组件.
这里得先了解下这个响应式编程(Reactive programming)的概念, 响应式编程是一种编写包含异步I / O的软件的方法。
响应式编程是基于异步和事件驱动的非阻塞程序,只需要在程序内启动少量线程扩展,而不是水平通过集群扩展。
用大白话讲,我们以前编写的大部分都是阻塞类的程序,当一个请求过来时任务会被阻塞,直到这个任务完成后再返回给前端;响应式编程接到请求后只是提交了一个请求给后端,后端会再安排另外的线程去执行任务,当任务执行完成后再异步通知到前端。
Java 领域的响应式编程库中,最有名的算是 Reactor 了。Reactor 也是 Spring 5 中反应式编程的基础,Webflux 依赖 Reactor 而构建。
Reactor
Reactor 是一个基于 JVM 之上的异步应用基础库。为 Java 、Groovy 和其他 JVM 语言提供了构建基于事件和数据驱动应用的抽象库。Reactor 性能相当高,在最新的硬件平台上,使用无堵塞分发器每秒钟可处理 1500 万事件。
简单说,Reactor 是一个轻量级 JVM 基础库,帮助你的服务或应用高效,异步地传递消息。Reactor 中有两个非常重要的概念 Flux 和 Mono 。
Flux 和 Mono
Flux 和 Mono 是 Reactor 中的两个基本概念。Flux 表示的是包含 0 到 N 个元素的异步序列。在该序列中可以包含三种不同类型的消息通知:正常的包含元素的消息、序列结束的消息和序列出错的消息。当消息通知产生时,订阅者中对应的方法 onNext(), onComplete()和 onError()会被调用。
Mono 表示的是包含 0 或者 1 个元素的异步序列。该序列中同样可以包含与 Flux 相同的三种类型的消息通知。Flux 和 Mono 之间可以进行转换。对一个 Flux 序列进行计数操作,得到的结果是一个 Mono对象。把两个 Mono 序列合并在一起,得到的是一个 Flux 对象。
WebFlux
WebFlux 模块的名称是 spring-webflux,名称中的 Flux 来源于 Reactor 中的类 Flux。Spring webflux 有一个全新的非堵塞的函数式 Reactive Web 框架,可以用来构建异步的、非堵塞的、事件驱动的服务,在伸缩性方面表现非常好。
从上图可以看到, Spring Boot 2现在支持两种模型:
Spring MVC
- 构建于 Servlet API 之上
- 同步阻塞 I/O 模型, 什么是同步阻塞式 I/O 模型呢?就是说,每一个请求对应一个线程去处理,应用会阻塞当前线程,所以一个 Request 对应一个 Thread,需要有一个含有大量线程的线程池, 这也是常见的Tomcat 的模式.
Spring WebFlux
- 构建于 Reactive Streams Adapters 之上
- 异步非阻塞 I/O 模型,认为应用不会阻塞当前线程,所以只是需要一个包含少数固定线程数的线程池 (event loop workers) 来处理请求
那么, 问题来了, WebFlux 能够使程序运行地更快吗?量化一点,比如说我使用 WebFlux 以后,一个接口的请求响应时间是不是就缩短了呢?
答案是否定的, WebFlux 并不能使接口的响应时间缩短,它仅仅能够提升吞吐量和伸缩性。Spring WebFlux 是一个异步非阻塞式的 Web 框架,所以,它特别适合应用在 IO 密集型的服务中,比如微服务网关这样的应用中。
PS: IO 密集型包括:磁盘IO密集型, 网络IO密集型,微服务网关就属于网络 IO 密集型,使用异步非阻塞式编程模型,能够显著地提升网关对下游服务转发的吞吐量。
关于Spring MVC和WebFlux, 我认为下面的结论还是比较中肯的:
WebFlux 不是 Spring MVC 的替代方案!,虽然 WebFlux 也可以被运行在 Servlet 容器上(需是 Servlet 3.1+ 以上的容器),但是 WebFlux 主要还是应用在异步非阻塞编程模型,而 Spring MVC 是同步阻塞的,如果你目前在 Spring MVC 框架中大量使用非同步方案,那么,WebFlux 才是你想要的,否则,使用 Spring MVC 才是你的首选。
OK, WebFlux我们就先了解到这里, 下面来看看怎么把这个简单的Spring WebFlux运行起来.
./mvnw install
我在本机运行这个mvnw是出错的, 使用mvn install 反而成功了.
中间这个安装过程确实有点慢, 需要下载很多依赖, 最后来看一下target 目录下的文件:
ls -l target/*
由于在下载项目时添加了对actuator的依赖性,因此该应用程序具有一些内置的HTTP端点。 在启动日志中看到类似于以下内容的输出:
Spring Boot Actuator可以帮助你监控和管理Spring Boot应用,比如健康检查、审计、统计和HTTP追踪等。所有的这些特性可以通过JMX或者HTTP endpoints来获得。Actuator创建了所谓的endpoint来暴露HTTP或者JMX来监控和管理应用,例如,有一个叫/health的endpoint,提供了关于应用健康的基础信息。
我们就调用一下这个Actuator暴露的HTTP endpoint (这里是/actuator)来试一下:
直接运行这个jar, 因为这是可执行的:
java -jar target/*.jar
注意这里面的这两行:
2021-03-02 14:12:09.171 INFO 9009 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 2 endpoint(s) beneath base path '/actuator'
2021-03-02 14:12:09.620 INFO 9009 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port 8080
然后调用:
curl localhost:8080/actuator | jq .
到现在为止,这个应用已经被成功调用起来了, 下一步就是把这个应用容器化(Containerize)
二、容器化(Containerize)我们的应用
在上一篇中, 我们编写了一个Dockerfile来容器化Node.js应用, 对于使用Spring Boot的Java 应用来说, 这一点更简单一点, 之前已经构建Spring Boot jar文件,因此只需要直接调用插件即可。
以下命令使用Maven:
./mvnw spring-boot:build-image
运行完之后, 就会出现一个demo的镜像.
运行一下这个容器:
docker run -p 8080:8080 demo:0.0.1-SNAPSHOT
然后调用:
curl localhost:8080/actuator/health
结果和之前的调用是一致的, 这里就不展示了.
另外, 注意到上图的镜像中的时间了吗? 41年前哈哈, 这个时间肯定是有问题的, 因为这个镜像是创建在docker.io/library这个目录下的.
Successfully built image 'docker.io/library/demo:0.0.1-SNAPSHOT'
所以我们要对这个镜像进行改名, 传到我们dockerhub 的个人空间里.
docker tag demo:0.0.1-SNAPSHOT hintcnuie/hello-springwebflux
docker push hintcnuie/hello-springwebflux
三、应用部署到minibuke
在上一篇中,我们是通过编写一个deployment 文件(yaml)的方式, 来创建一个Deployment, 然后通过kubectl apply -f deploy.yaml来部署, 现在我们用一种更简单一点的方式来部署, kubectl 可以指定--image的方式, 会自动帮你创建这个yaml 文件,来试试:
kubectl create deployment hello-springwebflux --image=hintcnuie/hello-springwebflux --dry-run=client -o=yaml > deployment.yaml
看看自动生成的yaml 文件:
回顾一下关于deployment的相关命令:
// 查看deployment
kubectl get deployments
// 删除deployment,会自动删除相应pods
kubectl delete deployment <部署名>
// 查看pods
// 查看当前namespace下的pods
kubectl get pods
// 查看所有pods
kubectl get pods --all-namespaces
// 查看pods的具体信息
kubectl describe pod <pod name>
//特定namespace下的,需要指定namespace
kubectl describe pod <pod name> --namespace=kube-system
先看看现在的deployment 情况:
kubectl get deployments
kubectl get pods
可以看出, 因为我们指定的是dry-run 的方式, 所以Kubernetes并没有产生deployment的行为.
再来回顾一下Service.
Service
我们知道, Kubernetes会根据Deployment 部署文件的需要, 来产生相应的Pod对象. Kubernetes Pod是有生命周期的,它们可以被创建,也可以被销毁,然而一旦被销毁生命就永远结束。 通过ReplicationController能够动态地创建和销毁Pod, 每个 Pod 都会获取它自己的 IP 地址,可一旦销毁后,重新创建后,IP地址会产生改变。
这会导致一个问题:在 Kubernetes 集群中,如果一组 Pod(称为 backend)为其它 Pod (称为 frontend)提供服务,一旦backend的Pod重新创建,那么frontend的Pod该如何发现,并连接到这组 Pod 中的哪些 backend 呢?
这就是Service 出现的原因.
在Kubernetes中,服务是一种抽象,定义了Pod的逻辑集和访问它们的策略(有时将此模式称为微服务)。 服务所针对的Pod集合通常由选择器确定。
例如,考虑使用3个副本运行的无状态图像处理后端。 这些副本是可替代的-前端不在乎它们使用哪个后端。 虽然组成后端集的实际Pod可能会更改,但是前端客户端不需要知道这一点,也不需要自己跟踪后端集。
服务抽象使这种解耦成为可能。
云原生服务发现
如果您能够在应用程序中使用Kubernetes API进行服务发现,则可以查询API服务器以获取端点,只要服务中的Pod集合发生更改,端点就会更新。
非云原生应用程序
对于非云原生应用程序,Kubernetes提供了在应用程序和后端Pod之间放置网络端口或负载平衡器的方法。
在minikube 环境下, Service 有下面四种类型,它们都是将集群外部流量导入到集群内的方式,只是实现方式不同:
- ClusterIP:默认值,k8s系统给service自动分配的虚拟IP,只能在集群内部访问。一个Service可能对应多个EndPoint(Pod),client访问的是Cluster IP,通过iptables规则转到Real Server,从而达到负载均衡的效果
- NodePort:将Service通过指定的Node上的端口暴露给外部,端口范围默认是30000-32767.
- LoadBalancer:在 NodePort 基础上,借助 cloud provider 创建一个外部的负载均衡器,并将请求转发到<NodeIP>:NodePort,此模式只能在云服务器上使用。
- ExternalName:将服务通过 DNS CNAME记录方式转发到指定的域名(通过 spec.externlName 设定)
在上一篇中, 我们部署的是一个Node.js 应用, 访问的端口是3000, 因此当时我们使用的Service 类型是NodePort, 也就是把3000这个端口绑定到一个给定端口. 在这里, 我们访问的是一个网址, 因此使用ClusterIP这种方式.
接下来我们再把service 也加到这个deployment 脚本中:
echo --- >> deployment.yaml
kubectl create service clusterip hello-springwebflux --tcp=8080:8080 --dry-run=client -o=yaml >> deployment.yaml
生成的deployment文件如下:
然后使用kubectl apply -f 来部署这个文件,
kubectl apply -f deployment.yaml
部署的结果是成功的:
[vagrant@control-plane hello-springwebflux]$ kubectl apply -f deployment.yaml
deployment.apps/hello-springwebflux created
service/hello-springwebflux created
使用kubectl get all查看下情况:
根据上面的输出, 我们的hello-springwebflux的service :
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/hello-springwebflux ClusterIP 10.107.17.10 <none> 8080/TCP 10m
因此, 我们可以根据这个ClusterIP 来直接访问:
curl http://10.0.2.15:8080/actuator
curl http://10.107.17.10:8080/actuator/health
在我本地, 可以看出是访问一切正常的:
注意, 上一篇中我们通过minikube service <service_name> --url的方式得到Service 的URL, 但是这种方式是针对NodePort的, 因此对于ClusterIP 是无效的.
四 知识回顾
最后, 回顾一下这篇文章的内容.
首先我们使用Spring Starter 来产生一个Spring Boot应用, 这个SpringBoot 中指明了使用Spring WebFlux, 一种异步非阻塞式IO的新的响应式编程 Web 框架, 并简单了解这个框架的不同于Spring MVC的地方;
然后我们就使用Maven的SpringBoot 插件来自动产生镜像(Container), 最后我们把这个镜像部署到Kubernetes中, 并指明Service 采用ClusterIP的方式, 同时学习了ClusterIP 和NodePort 的区别所在.
部署的时候, 使用的是通过dry-run 这种方式产生部署文件, 最后把包含Deployment 和Service 两种资源的部署文件一次性部署到Kubernetes中.
这篇文章主要是回顾了从Spring Boot 应用源代码到应用部署到Kubernetes 的全过程, 并展示了与部署Nodejs 应用的不同之处, 同时,通过实际运行的例子, 加深了对Kubernetes的理解.
源代码已经放到Github上, 供参考:github.com/hintcnuie/hello-springwebflux
希望本文能对大家的知识增长有所帮助. 任何批评建议, 也请在评论区不吝赐教.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`永远怀念达叔~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`永远怀念达叔~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
猜你喜欢
- 2024-12-03 Java,JDK11,发布订阅模式,响应式流(Reactive Streams)及背压
- 2024-12-03 有空就来学Hystrix RPC保护的原理,RPC监控之滑动窗口的实现原理
- 2024-12-03 SpringWeb服务应用响应式Web开发组件:响应式编程和SpringBoot
- 2024-12-03 Reactor响应式编程 第二篇 Spring Boot 整合 Reactor 简单例子
- 2024-12-03 反应式编程之Spring Web-Flux/Project Reactor
- 2024-12-03 即学即用Kotlin - 协程
- 2024-12-03 并发编程:CompletableFuture异步编程详解
- 2024-12-03 终于有人把安卓程序员必学知识点全整理出来了,有如醍醐灌顶
- 2024-12-03 Kotlin Flow的设计精髓:响应式编程在Android中的实践
- 2024-12-03 Reactive Programming 很简单
你 发表评论:
欢迎- 最近发表
-
- JAVA面试|为什么Spring Boot的jar可以直接运行?
- 什么情况,今年面试都不问八股文了??准备了几个月,结果一个都不问。。
- LangChain系列之如何使用LangChain4j构建RAG应用(1)
- JAVA入门教程-第2章 基本编程概念
- FTPC Pnuts语言(ftp mput put)
- 这9个工具库让我的Java开发效率提升了80%
- VS2022配置x86/x64调用32位和64位汇编语言动态库环境
- 别再裸写 parseFrom() 了!这才是 MQTT + Protobuf 消费的正确姿势
- aardio + Java + JavaScript 混合开发快速入门
- 铁打的程序,流水的语言,2018年JAVA编程还想坚挺500年?
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)