欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

第 7 天 SpringBoot 和 SpringCloud 微服务项目交付

最编程 2024-07-16 09:52:58
...

Spring Cloud微服务项目交付

微服务扫盲篇

微服务并没有一个官方的定义,想要直接描述微服务比较困难,我们可以通过对比传统WEB应用,来理解什么是微服务。

单体应用架构

如下是传统打车软件架构图:

这种单体应用比较适合于小项目,优点是:

  • 开发简单直接,集中式管理
  • 基本不会重复开发
  • 功能都在本地,没有分布式的管理开销和调用开销

当然它的缺点也十分明显,特别对于互联网公司来说:

  • 开发效率低:所有的开发在一个项目改代码,递交代码相互等待,代码冲突不断
  • 代码维护难:代码功能耦合在一起,新人不知道何从下手
  • 部署不灵活:构建时间长,任何小修改必须重新构建整个项目,这个过程往往很长
  • 稳定性不高:一个微不足道的小问题,可以导致整个应用挂掉
  • 扩展性不够:无法满足高并发情况下的业务需求
微服务应用架构

微服务架构的设计思路不是开发一个巨大的单体式应用,而是将应用分解为小的、互相连接的微服务。一个微服务完成某个特定功能,比如乘客管理和下单管理等。每个微服务都有自己的业务逻辑和适配器。一些微服务还会提供API接口给其他微服务和应用客户端使用。

比如,前面描述的系统可被分解为:

在这里插入图片描述

每个业务逻辑都被分解为一个微服务,微服务之间通过REST API通信。一些微服务也会向终端用户或客户端开发API接口。但通常情况下,这些客户端并不能直接访问后台微服务,而是通过API Gateway来传递请求。API Gateway一般负责服务路由、负载均衡、缓存、访问控制和鉴权等任务。

微服务架构优点:

  • 解决了复杂性问题。它将单体应用分解为一组服务。虽然功能总量不变,但应用程序已被分解为可管理的模块或服务
  • 体系结构使得每个服务都可以由专注于此服务的团队独立开发。只要符合服务API契约,开发人员可以*选择开发技术。这就意味着开发人员可以采用新技术编写或重构服务,由于服务相对较小,所以这并不会对整体应用造成太大影响
  • 微服务架构可以使每个微服务独立部署。这些更改可以在测试通过后立即部署。所以微服务架构也使得CI/CD成为可能
微服务架构问题及挑战

微服务的一个主要缺点是微服务的分布式特点带来的复杂性。开发人员需要基于RPC或者消息实现微服务之间的调用和通信,而这就使得服务之间的发现、服务调用链的跟踪和质量问题变得的相当棘手。

  1. 微服务的一大挑战是跨多个服务的更改

    • 比如在传统单体应用中,若有A、B、C三个服务需要更改,A依赖B,B依赖C。我们只需更改相应的模块,然后一次性部署即可。

    • 在微服务架构中,我们需要仔细规划和协调每个服务的变更部署。我们需要先更新C,然后更新B,最后更新A。

  2. 部署基于微服务的应用也要复杂得多

    • 单体应用可以简单的部署在一组相同的服务器上,然后前端使用负载均衡即可。
    • 微服务由不同的大量服务构成。每种服务可能拥有自己的配置、应用实例数量以及基础服务地址。这里就需要不同的配置、部署、扩展和监控组件。此外,我们还需要服务发现机制,以便服务可以发现与其通信的其他服务的地址

以上问题和挑战可大体概括为:

  • API Gateway
  • 服务间调用
  • 服务发现
  • 服务容错
  • 服务部署
  • 数据调用

https://www.kancloud.cn/owenwangwen/open-capacity-platform/1480155,自助餐吃吃喝喝,竟然秒懂微服务

微服务框架

如何应对上述挑战,出现了如下微服务领域的框架:

  • Spring Cloud(各个微服务基于Spring Boot实现)
  • Dubbo
  • Service Mesh
    • Linkerd
    • Envoy
    • Conduit
    • Istio

在这里插入图片描述

了解Spring Cloud

https://spring.io

核心项目及组件

https://spring.io/projects

与Dubbo对比

做一个简单的功能对比:

核心要素 Dubbo Spring Cloud
服务注册中心 Zookeeper Spring Cloud Netflix Eureka
服务调用方式 RPC REST API
服务监控 Dubbo-monitor Spring Boot Admin
断路器 不完善 Spring Cloud Netflix Hystrix
服务网关 Spring Cloud Netflix Zuul
分布式配置 Spring Cloud Config
服务跟踪 Spring Cloud Sleuth
消息总线 Spring Cloud Bus
数据流 Spring Cloud Stream
批量任务 Spring Cloud Task
…… …… ……

从上图可以看出其实Dubbo的功能只是Spring Cloud体系的一部分。

这样对比是不够公平的,首先DubboSOA时代的产物,它的关注点主要在于服务的调用,流量分发、流量监控和熔断。而Spring Cloud诞生于微服务架构时代,考虑的是微服务治理的方方面面,另外由于依托了SpirngSpirng Boot的优势之上,两个框架在开始目标就不一致,Dubbo定位服务治理、Spirng Cloud是一个生态。

Spring Boot交付实践
从零开始创建Spring Boot项目

通过File > New > Project,新建工程,选择Spring Initializr

在这里插入图片描述

配置Project Metadata:

配置Dependencies依赖包:

选择:Web分类中的Spring web和Template Engines中的Thymeleaf

在这里插入图片描述

配置maven settings.xml:

默认使用IDE自带的maven,换成自己下载的,下载地址:

链接: https://pan.baidu.com/s/1z9dRGv_4bS1uxBtk5jsZ2Q 提取码: 3gva

解压后放到D:\software\apache-maven-3.6.3,修改D:\software\apache-maven-3.6.3\conf\settings.xml 文件:

<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
  <localRepository>D:\opt\maven-repo</localRepository>
  
  <pluginGroups>
  </pluginGroups>

  <proxies>
  </proxies>

  <servers>
  </servers>

  <mirrors>
	    <mirror>
            <id>alimaven</id>
            <mirrorOf>central</mirrorOf>
            <name>aliyun maven</name>
            <url>http://maven.aliyun.com/nexus/content/repositories/central/</url>
        </mirror>
        <mirror>
            <id>nexus-aliyun</id>
            <mirrorOf>*</mirrorOf>
            <name>Nexus aliyun</name>
            <url>http://maven.aliyun.com/nexus/content/groups/public</url>
        </mirror>
  </mirrors>

</settings>

在这里插入图片描述

在这里插入图片描述

替换springboot版本为2.3.5.RELEASE

直接启动项目并访问本地服务:localhost:8080

编写功能代码

创建controller包及HelloController.java文件

package com.luffy.demo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public String hello(String name) {
        return "Hello, " + name;
    }

保存并在浏览器中访问localhost:8080/hello?name=luffy

如果页面复杂,如何实现?

resources/templates/目录下新建index.html

<!DOCTYPE html>
<html>
<head>
    <title>Devops</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<div class="container">
    <h3 th:text="${requestname}"></h3>
    <a id="rightaway" href="#" th:href="@{/rightaway}" >立即返回</a>
    <a id="sleep" href="#" th:href="@{/sleep}">延时返回</a>
</div>
</body>
</html>

完善HelloController.java的内容:

package com.luffy.demo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

@RestController
public class HelloController {

    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public String hello(String name) {
        return "Hello, " + name;
    }

    @RequestMapping("/")
    public ModelAndView index(ModelAndView mv) {
        mv.setViewName("index");
        mv.addObject("requestname", "This is index");
        return mv;
    }

    @RequestMapping("/rightaway")
    public ModelAndView returnRightAway(ModelAndView mv) {
        mv.setViewName("index");
        mv.addObject("requestname","This request is RightawayApi");
        return mv;
    }

    @RequestMapping("/sleep")
    public ModelAndView returnSleep(ModelAndView mv) throws InterruptedException {
        Thread.sleep(2*1000);
        mv.setViewName("index");
        mv.addObject("requestname","This request is SleepApi"+",it will sleep 2s !");
        return mv;
    }
}

如何在java项目中使用maven
为什么需要maven

考虑一个常见的场景:以项目A为例,开发过程中,需要依赖B-2.0.jar的包,如果没有maven,那么正常做法是把B-2.0.jar拷贝到项目A中,但是如果B-2.0.jar还依赖C.jar,我们还需要去找到C.jar的包,因此,在开发阶段需要花费在项目依赖方面的精力会很大。

因此,开发人员需要找到一种方式,可以管理java包的依赖关系,并可以方便的引入到项目中。

maven如何工作

查看pom.xml

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

可以直接在项目中添加上dependency ,这样来指定项目的依赖包。

思考:如果spring-boot-starter-thymeleaf包依赖别的包,怎么办?

spring-boot-starter-thymeleaf同时也是一个maven项目,也有自己的pom.xml

查看一下:

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
      <version>2.3.3.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.thymeleaf</groupId>
      <artifactId>thymeleaf-spring5</artifactId>
      <version>3.0.11.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.thymeleaf.extras</groupId>
      <artifactId>thymeleaf-extras-java8time</artifactId>
      <version>3.0.4.RELEASE</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>

这样的话,使用maven的项目,只需要在自己的pom.xml中把所需的最直接的依赖包定义上,而不用关心这些被依赖的jar包自身是否还有别的依赖。剩下的都交给maven去搞定。

如何搞定?maven可以根据pom.xml中定义的依赖实现包的查找

去哪查找?maven仓库,存储jar包的地方。

当我们执行 Maven 构建命令时,Maven 开始按照以下顺序查找依赖的库:

在这里插入图片描述

本地仓库:

  • Maven 的本地仓库,在安装 Maven 后并不会创建,它是在第一次执行 maven 命令的时候才被创建。

  • 运行 Maven 的时候,Maven 所需要的任何包都是直接从本地仓库获取的。如果本地仓库没有,它会首先尝试从远程仓库下载构件至本地仓库,然后再使用本地仓库的包。

  • 默认情况下,不管Linux还是 Windows,每个用户在自己的用户目录下都有一个路径名为 .m2/respository/ 的仓库目录。

  • Maven 本地仓库默认被创建在 %USER_HOME% 目录下。要修改默认位置,在 %M2_HOME%\conf 目录中的 Maven 的 settings.xml 文件中定义另一个路径。

    <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
      <localRepository>D:\opt\maven-repo</localRepository>
    </settings>
    

*仓库:

Maven *仓库是由 Maven 社区提供的仓库,*仓库包含了绝大多数流行的开源Java构件,以及源码、作者信息、SCM、信息、许可证信息等。一般来说,简单的Java项目依赖的构件都可以在这里下载到。

*仓库的关键概念:

  • 这个仓库由 Maven 社区管理。
  • 不需要配置,maven中集成了地址 http://repo1.maven.org/maven2
  • 需要通过网络才能访问。

私服仓库:

通常使用 sonatype Nexus来搭建私服仓库。搭建完成后,需要在 setting.xml中进行配置,比如:

<profile>
    <id>localRepository</id>
    <repositories>
        <repository>
            <id>myRepository</id>
            <name>myRepository</name>
            <url>http://127.0.0.1:8081/nexus/content/repositories/myRepository/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>
</profile>

方便起见,我们直接使用国内ali提供的仓库,修改 maven 根目录下的 conf 文件夹中的 setting.xml 文件,在 mirrors 节点上,添加内容如下:

<mirrors>
    <mirror>
      <id>alimaven</id>
      <name>aliyun maven</name>
      <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
      <mirrorOf>central</mirrorOf>        
    </mirror>
</mirrors>

在执行构建的时候,maven会自动将所需的包下载到本地仓库中,所以第一次构建速度通常会慢一些,后面速度则很快。

那么maven是如何找到对应的jar包的?

我们可以访问 https://mvnrepository.com/ 查看在仓库中的jar包的样子。

<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.2.2</version>
</dependency>

刚才看到spring-boot-starter-thymeleaf的依赖同样有上述属性,因此maven就可以根据这三项属性,到对应的仓库中去查找到所需要的依赖包,并下载到本地。

其中groupId、artifactId、version共同保证了包在仓库中的唯一性,这也就是为什么maven项目的pom.xml中都先配置这几项的原因,因为项目最终发布到远程仓库中,供别人调用。

思考:我们项目的dependency中为什么没有写version ?

是因为sprintboot项目的上面有人,来看一下项目parent的写法:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

parent模块中定义过的dependencies,在子项目中引用的话,不需要指定版本,这样可以保证所有的子项目都使用相同版本的依赖包。

生命周期及mvn命令实践

Maven有三套相互独立的生命周期,分别是clean、default和site。每个生命周期包含一些阶段(phase),阶段是有顺序的,后面的阶段依赖于前面的阶段。

  • clean生命周期,清理项目
    • 清理:mvn clean  --删除target目录,也就是将class文件等删除
  • default生命周期,项目的构建等核心阶段
    • 编译:mvn compile--src/main/java目录java源码编译生成class (target目录下)
    • 测试:mvn test--src/test/java 执行目录下的测试用例
    • 打包:mvn package--生成压缩文件:java项目#jar包;web项目#war包,也是放在target目录下
    • 安装:mvn install --将压缩文件(jar或者war)上传到本地仓库
    • 部署|发布:mvn deploy--将压缩文件上传私服
  • site生命周期,建立和发布项目站点
    • 站点 : mvn site --生成项目站点文档

各个生命周期相互独立,一个生命周期的阶段前后依赖。 生命周期阶段需要绑定到某个插件的目标才能完成真正的工作,比如test阶段正是与maven-surefire-plugin的test目标相绑定了 。

举例如下:

  • mvn clean

    调用clean生命周期的clean阶段

  • mvn test

    调用default生命周期的test阶段,实际执行test以及之前所有阶段

  • mvn clean install

    调用clean生命周期的clean阶段和default的install阶段,实际执行clean,install以及之前所有阶段

在linux环境中演示:

创建gitlab组,luffy-spring-cloud,在该组下创建项目springboot-demo

  • 提交代码到git仓库

    $ git init
    $ git remote add origin http://gitlab.luffy.com/luffy-spring-cloud/springboot-demo.git
    $ git add .
    $ git commit -m "Initial commit"
    $ git push -u origin master
    
  • 使用tools容器来运行

    $ docker run --rm -ti 172.21.51.67:5000/devops/tools:v3 bash
    bash-5.0# mvn -v
    bash: mvn: command not found
    # 由于idea工具自带了maven,所以可以直接在ide中执行mvn命令。在tools容器中,需要安装mvn命令
    

    为tools镜像集成mvn:

    将本地的apache-maven-3.6.3放到tools项目中,修改settings.xml配置

    ...
    <localRepository>/opt/maven-repo</localRepository>
    ...
    

    然后修改Dockerfile,添加如下部分:

    #-----------------安装 maven--------------------#
    COPY apache-maven-3.6.3 /usr/lib/apache-maven-3.6.3
    RUN ln -s /usr/lib/apache-maven-3.6.3/bin/mvn /usr/local/bin/mvn && chmod +x /usr/local/bin/mvn
    ENV MAVEN_HOME=/usr/lib/apache-maven-3.6.3
    #------------------------------------------------#
    

去master节点拉取最新代码,构建最新的tools镜像:

  # k8s-master节点
  $ git pull
  $ docker build . -t 172.21.51.67:5000/devops/tools:v4 -f Dockerfile
  $ docker push 172.21.51.67:5000/devops/tools:v4

再次尝试mvn命令:

$ docker run --rm -ti 172.21.51.67:5000/devops/tools:v4 bash
bash-5.0# mvn -v
bash-5.0# git clone http://gitlab.luffy.com/luffy-spring-cloud/springboot-demo.git
bash-5.0# cd springboot-demo
bash-5.0# mvn clean
# 观察/opt/maven目录
bash-5.0# mvn package
# 多阶段组合
bash-5.0# mvn clean package

想系统学习maven,可以参考: https://www.runoob.com/maven/maven-pom.html

Springboot服务镜像制作

通过mvn package命令拿到服务的jar包后,我们可以使用如下命令启动服务:

$ java -jar demo-0.0.1-SNAPSHOT.jar

因此,需要准备Dockerfile来构建镜像:

FROM openjdk:8-jdk-alpine
COPY target/springboot-demo-0.0.1-SNAPSHOT.jar app.jar
CMD [ "sh", "-c", "java -jar /app.jar" ]

我们可以为构建出的镜像指定名称:

    <build>
        <finalName>${project.artifactId}</finalName><!--打jar包去掉版本号-->
    ...

Dockerfile对应修改:

FROM openjdk:8-jdk-alpine
COPY target/springboot-demo.jar app.jar
CMD [ "sh", "-c", "java -jar /app.jar" ]

执行镜像构建,验证服务启动是否正常:

$ docker build . -t springboot-demo:v1 -f Dockerfile

$ docker run -d --name springboot-demo -p 8080:8080 springboot-demo:v1

$ curl localhost:8080
接入CICD流程

之前已经实现了shared-library,并且把python项目接入到了CICD 流程中。因此,可以直接使用已有的流程,把spring boot项目接入进去。

  • Jenkinsfile
  • sonar-project.properties
  • deploy/deployment.yaml
  • deploy/service.yaml
  • deploy/ingress.yaml
  • configmap/devops-config

Jenkinsfile

@Library('luffy-devops') _

pipeline {
    agent { label 'jnlp-slave'}
    options {
		timeout(time: 20, unit: 'MINUTES')
		gitLabConnection('gitlab')
	}
    environment {
        IMAGE_REPO = "172.21.51.67:5000/demo/springboot-demo"
        IMAGE_CREDENTIAL = "credential-registry"
        DINGTALK_CREDS = credentials('dingTalk')
        PROJECT = "springboot-demo"
    }
    stages {
        stage('checkout') {
            steps {
                container('tools') {
                    checkout scm
                }
            }
        }
        stage('mvn-package') {
            steps {
                container('tools') {
                    script{
                        sh 'mvn clean package'
                    }
                }
            }
        }
        stage('CI'){
            failFast true
            parallel {
                stage('Unit Test') {
                    steps {
                        echo "Unit Test Stage Skip..."
                    }
                }
                stage('Code Scan') {
                    steps {
                        container('tools') {
                            script {
                               devops.scan().start()
                            }
                        }
                    }
                }
            }
        }

        stage('docker-image') {
            steps {
                container('tools') {
                    script{
                        devops.docker(
                            "${IMAGE_REPO}",
                            "${GIT_COMMIT}",
                            IMAGE_CREDENTIAL
                        ).build().push()
                    }
                }
            }
        
						

上一篇: 社区工具包[CommunityToolkit.Mvvm]信使信使

下一篇: Visual Basic 6.0 sp6 简体中文企业版(含 MSDN)