当前位置:   article > 正文

DEVOPS架构师 -- 07SpringBoot与SpringCloud微服务项目交付_spring cloud 提交微服务

spring cloud 提交微服务

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对比

做一个简单的功能对比:

核心要素DubboSpring Cloud
服务注册中心ZookeeperSpring Cloud Netflix Eureka
服务调用方式RPCREST API
服务监控Dubbo-monitorSpring 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
在这里插入图片描述
在这里插入图片描述

配置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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

在这里插入图片描述
在这里插入图片描述

替换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;
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

保存并在浏览器中访问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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

完善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;
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
如何在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>
  • 1
  • 2
  • 3
  • 4

可以直接在项目中添加上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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

这样的话,使用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>
    
    • 1
    • 2
    • 3
    • 4
    • 5

中央仓库:

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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

方便起见,我们直接使用国内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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在执行构建的时候,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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

刚才看到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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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
    
    • 1
    • 2
    • 3
    • 4
    • 5
  • 使用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命令
    
    • 1
    • 2
    • 3
    • 4

    为tools镜像集成mvn:

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


    /opt/maven-repo

    
    
    
    然后修改Dockerfile,添加如下部分:
    
    ```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
    #------------------------------------------------#
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    去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
    
    • 1
    • 2
    • 3
    • 4

    再次尝试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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

    Springboot服务镜像制作

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

    $ java -jar demo-0.0.1-SNAPSHOT.jar
    
    • 1

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

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

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

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

    Dockerfile对应修改:

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

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

    $ docker build . -t springboot-demo:v1 -f Dockerfile
    
    $ docker run -d --name springboot-demo -p 8080:8080 springboot-demo:v1
    
    $ curl localhost:8080
    
    • 1
    • 2
    • 3
    • 4
    • 5
    接入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()
                        }
                    }
                }
            }
            stage('deploy') {
                steps {
                    container('tools') {
                        script{
                        	devops.deploy("deploy",true,"deploy/deployment.yaml").start()
                        }
                    }
                }
            }
        }
        post {
            success {
                script{
                    devops.notificationSuccess(PROJECT,"dingTalk")
                }
            }
            failure {
                script{
                    devops.notificationFailure(PROJECT,"dingTalk")
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

    sonar-project.properties

    sonar.projectKey=springboot-demo
    sonar.projectName=springboot-demo
    # if you want disabled the DTD verification for a proxy problem for example, true by default
    # JUnit like test report, default value is test.xml
    sonar.sources=src/main/java
    sonar.language=java
    sonar.tests=src/test/java
    sonar.java.binaries=target/classes
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    deploy/deployment.yaml

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: springboot-demo
      namespace: {{NAMESPACE}}
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: springboot-demo
      template:
        metadata:
          labels:
            app: springboot-demo
        spec:
          containers:
            - name: springboot-demo
              image: {{IMAGE_URL}}
              imagePullPolicy: IfNotPresent
              ports:
                - containerPort: 8080
              resources:
                requests:
                  memory: 100Mi
                  cpu: 50m
                limits:
                  memory: 500Mi
                  cpu: 100m
              livenessProbe:
                httpGet:
                  path: /
                  port: 8080
                  scheme: HTTP
                initialDelaySeconds: 120
                periodSeconds: 15
                timeoutSeconds: 3
              readinessProbe:
                httpGet:
                  path: /
                  port: 8080
                  scheme: HTTP
                initialDelaySeconds: 120
                timeoutSeconds: 2
                periodSeconds: 15
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    deploy/service.yaml

    apiVersion: v1
    kind: Service
    metadata:
      name: springboot-demo
      namespace: {{NAMESPACE}}
    spec:
      ports:
        - port: 8080
          protocol: TCP
          targetPort: 8080
      selector:
        app: springboot-demo
      sessionAffinity: None
      type: ClusterIP
    status:
      loadBalancer: {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    deploy/ingress.yaml

    apiVersion: extensions/v1beta1
    kind: Ingress
    metadata:
      name: springboot-demo
      namespace: {{NAMESPACE}}
    spec:
      rules:
        - host: {{INGRESS_SPRINGBOOTDEMO}}
          http:
            paths:
              - backend:
                  serviceName: springboot-demo
                  servicePort: 8080
                path: /
    status:
      loadBalancer: {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    维护devops-configconfigmap,添加INGRESS_SPRINGBOOTDEMO配置项:

    $ kubectl -n dev edit cm devops-config
    ...
    data:
      INGRESS_MYBLOG: blog-dev.luffy.com
      INGRESS_SPRINGBOOTDEMO: springboot-dev.luffy.com
      NAMESPACE: dev
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    更新Jenkins中的jnlp-slave-pod模板镜像:

    172.21.51.67:5000/devops/tools:v4
    
    • 1

    由于镜像中maven的目录是/opt/maven-repo,而slave-pod是执行完任务后会销毁,因此需要将maven的数据目录挂载出来,不然每次构建都会重新拉取所有依赖的jar包:

    在这里插入图片描述

    配置Jenkins流水线:

    添加单元测试覆盖率

    单元测试这块内容一直没有把覆盖率统计到sonarqube端,本节看下怎么样将单元测试的结果及覆盖率展示到Jenkins及sonarqube平台中。

    为了展示效果,我们先添加一个单元测试文件HelloControllerTest

    package com.luffy.demo;
    
    import org.junit.jupiter.api.BeforeEach;
    import org.junit.jupiter.api.Test;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.http.MediaType;
    import org.springframework.test.context.web.WebAppConfiguration;
    import org.springframework.test.web.servlet.MockMvc;
    import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
    import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
    import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
    import org.springframework.test.web.servlet.setup.MockMvcBuilders;
    import org.springframework.web.context.WebApplicationContext;
    
    @SpringBootTest
    @WebAppConfiguration
    public class HelloControllerTests {
    
        private static final Logger logger = LoggerFactory.getLogger(HelloControllerTests.class);
        @Autowired
        private WebApplicationContext webApplicationContext;
    
        private MockMvc mockMvc;
    
        @BeforeEach
        public void setMockMvc() {
            mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
        }
    
        @Test
        public void index(){
            try {
                mockMvc.perform(MockMvcRequestBuilders.post("/")
                        .contentType(MediaType.APPLICATION_JSON)
                ).andExpect(MockMvcResultMatchers.status().isOk())
                        .andDo(MockMvcResultHandlers.print());
            }catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    
        @Test
        public void rightaway(){
            try {
                mockMvc.perform(MockMvcRequestBuilders.post("/rightaway")
                        .contentType(MediaType.APPLICATION_JSON)
                ).andExpect(MockMvcResultMatchers.status().isOk())
                        .andDo(MockMvcResultHandlers.print());
            }catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    
        @Test
        public void sleep(){
            try {
                mockMvc.perform(MockMvcRequestBuilders.post("/sleep")
                        .contentType(MediaType.APPLICATION_JSON)
                ).andExpect(MockMvcResultMatchers.status().isOk())
                        .andDo(MockMvcResultHandlers.print());
            }catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    jacoco:监控JVM中的调用,生成监控结果(默认保存在jacoco.exec文件中),然后分析此结果,配合源代码生成覆盖率报告。

    如何引入jacoco测试:

                <plugin>
                    <groupId>org.jacoco</groupId>
                    <artifactId>jacoco-maven-plugin</artifactId>
                    <version>0.7.8</version>
                    <executions>
                        <execution>
                            <goals>
                                <goal>prepare-agent</goal>
                            </goals>
                            <configuration>
                                <destFile>${project.build.directory}/coverage-reports/jacoco.exec</destFile>
                            </configuration>
                        </execution>
                        <execution>
                            <id>default-report</id>
                            <phase>test</phase>
                            <goals>
                                <goal>report</goal>
                            </goals>
                            <configuration>
                                <dataFile>${project.build.directory}/coverage-reports/jacoco.exec</dataFile>
                                <outputDirectory>${project.reporting.outputDirectory}/jacoco</outputDirectory>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    其中:

    • prepare-agent,会把agent准备好,这样在执行用例的时候,就会使用agent检测到代码执行的过程,通常将结果保存在jacoco.exec
    • report,分析保存的jacoco.exec文件,生成报告

    在IDE中添加,观察插件的goal,执行mvn test,观察执行过程。

    有了上述内容后,如何将结果发布到sonarqube中?

    提交最新代码,查看sonarqube的分析结果。

    在这里插入图片描述

    Spring Cloud开发、交付实践

    https://spring.io/projects/spring-cloud#overview

    1、Netflix是一家做视频的网站,可以这么说该网站上的美剧应该是最火的。

    2、Netflix是一家没有CTO的公司,正是这样的组织架构能使产品与技术无缝的沟通,从而能快速迭代出更优秀的产品。在当时软件敏捷开发中,Netflix的更新速度不亚于当年的微信后台变更,虽然微信比Netflix迟发展,但是当年微信的灰度发布和敏捷开发应该算是业界最猛的。

    3、Netflix由于做视频的原因,访问量非常的大,从而促使其技术快速的发展在背后支撑着,也正是如此,Netflix开始把整体的系统往微服务上迁移。

    4、Netflix的微服务做的不是最早的,但是确是最大规模的在生产级别微服务的尝试。也正是这种大规模的生产级别尝试,在服务器运维上依托AWS云。当然AWS云同样受益于Netflix的大规模业务不断的壮大。

    5、Netflix的微服务大规模的应用,在技术上毫无保留的把一整套微服务架构核心技术栈开源了出来,叫做Netflix OSS,也正是如此,在技术上依靠开源社区的力量不断的壮大。

    6、Spring Cloud是构建微服务的核心,而Spring Cloud是基于Spring Boot来开发的。

    7、Pivotal在Netflix开源的一整套核心技术产品线的同时,做了一系列的封装,就变成了Spring Cloud;虽然Spring Cloud到现在为止不只有Netflix提供的方案可以集成,还有很多方案,但Netflix是最成熟的。

    本课程基于SpringBoot 2.3.6.RELEASE 和Spring Cloud Hoxton.SR9版本

    微服务场景

    开发APP,提供个人的花呗账单管理。

    • 注册、登录、账单查询
    • 用户服务,账单管理服务

    在这里插入图片描述

    在这里插入图片描述

    Eureka服务注册中心

    SpringCloud体系中,我们知道服务之间的调用是通过http协议进行调用的。而注册中心的主要目的就是维护这些服务的服务列表。

    https://docs.spring.io/spring-cloud-netflix/docs/2.2.5.RELEASE/reference/html/

    新建项目

    在这里插入图片描述

    pom中引入spring-cloud的依赖:

    https://spring.io/projects/spring-cloud#overview

    <properties>
        <spring.cloud-version>Hoxton.SR9</spring.cloud-version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring.cloud-version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    引入eureka-server的依赖:

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
            </dependency>
    
    • 1
    • 2
    • 3
    • 4
    启动eureka服务

    https://docs.spring.io/spring-cloud-netflix/docs/2.2.5.RELEASE/reference/html/#spring-cloud-eureka-server-standalone-mode

    application.yml

    server:
      port: 8761
      
    eureka:
      client:
        service-url:
          defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
        register-with-eureka: false
        fetch-registry: false
      instance:
        hostname: localhost
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    启动类:

    package com.luffy.eureka;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
    
    @SpringBootApplication
    @EnableEurekaServer
    public class EurekaServerApplication {
        public static void main(String[] args) {
            SpringApplication.run(EurekaServerApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    启动访问localhost:8761测试

    创建spring cloud项目三部曲:

    • 引入依赖包
    • 修改application.yml配置文件
    • 启动类添加注解
    eureka认证

    没有认证,不安全,添加认证:

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
    
    • 1
    • 2
    • 3
    • 4

    application.yml

    server:
      port: 8761
    eureka:
      client:
        service-url:
          defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@${eureka.instance.hostname}:${server.port}/eureka/
        register-with-eureka: false
        fetch-registry: false
      instance:
        hostname: localhost
    spring:
      security:
        user:
          name: ${EUREKA_USER:admin}
          password: ${EUREKA_PASS:admin}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    注册服务到eureka

    新建项目,user-service(选择Spring Cloud依赖和SpringBoot Web依赖),用来提供用户查询功能。

    三部曲:

    • pom.xml,并添加依赖
    • 创建application.yml配置文件
    • 创建Springboot启动类,并配置注解

    pom.xml添加:

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    application.yml

    server:
      port: 7000
    eureka:
      client:
        serviceUrl:
          defaultZone: http://${EUREKA_USER:admin}:${EUREKA_PASS:admin}@localhost:8761/eureka/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    启动类:

    package com.luffy.user;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    
    //注意这里也可使用@EnableEurekaClient
    //但由于springcloud是灵活的,注册中心支持eureka、consul、zookeeper等
    //若写了具体的注册中心注解,则当替换成其他注册中心时,又需要替换成对应的注解了。
    //所以 直接使用@EnableDiscoveryClient 启动发现。
    //这样在替换注册中心时,只需要替换相关依赖即可。
    @EnableDiscoveryClient
    @SpringBootApplication
    public class UserServiceApplication {
        public static void main(String[] args) {
            SpringApplication.run(UserServiceApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    报错:

    c.n.d.s.t.d.RetryableEurekaHttpClient    : Request execution failed with message: com.fasterxml.jackson.databind.exc.MismatchedInputException: Root name 'timestamp' does not match expected ('instance') for type [simple type, class com.netflix.appinfo.InstanceInfo]
    
    
    • 1
    • 2

    新版本的security默认开启csrf了,关掉,在注册中心新建一个类,继承WebSecurityConfigurerAdapter来关闭 ,> 注意,是在eureka server端关闭。

    package com.luffy.eureka;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    
    @EnableWebSecurity
    @Configuration
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable(); //关闭csrf
            http.authorizeRequests().anyRequest().authenticated().and().httpBasic(); //开启认证
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    再次启动发现可以注册,但是地址是

    application.yaml

    server:
      port: 7000
    eureka:
      client:
        serviceUrl:
          defaultZone: http://${EUREKA_USER:admin}:${EUREKA_PASS:admin}@localhost:8761/eureka/
      instance:
        instance-id: ${eureka.instance.hostname}:${server.port}
        prefer-ip-address: true
        hostname: user-service
    spring:
      application:
        name: user-service
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    Eurake有一个配置参数eureka.server.renewalPercentThreshold,定义了renews 和renews threshold的比值,默认值为0.85。当server在15分钟内,比值低于percent,即少了15%的微服务心跳,server会进入自我保护状态

    默认情况下,如果Eureka Server在一定时间内没有接收到某个微服务实例的心跳,Eureka Server将会注销该实例(默认90秒)。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,这就可能变得非常危险了,因为微服务本身是健康的,此时本不应该注销这个微服务。

    Eureka Server通过“自我保护模式”来解决这个问题,当Eureka Server节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式,Eureka Server就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该Eureka Server节点会自动退出自我保护模式。

    自我保护模式是一种对网络异常的安全保护措施。使用自我保护模式,而让Eureka集群更加的健壮、稳定。

    开发阶段可以通过配置:eureka.server.enable-self-preservation=false关闭自我保护模式。

    生产阶段,理应以默认值进行配置。

    至于具体具体的配置参数,可至官网查看:http://cloud.spring.io/spring-cloud-static/Finchley.RELEASE/single/spring-cloud.html#_appendix_compendium_of_configuration_properties

    高可用

    高可用:

    • 优先保证可用性
    • 各个节点都是平等的,1个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务
    • 在向某个Eureka注册时如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性)

    注意点:

    • 多实例的话eureka.instance.instance-id需要保持不一样,否则会当成同一个
    • eureka.instance.hostname要与defaultZone里的地址保持一致
    • 各个eureka的spring.application.name相同

    在这里插入图片描述

    拷贝eureka服务,分别命名eureka-ha-peer1eureka-ha-peer2

    修改模块的pom.xml

    <artifactId>eureka-ha-peer1</artifactId>
    
    • 1

    修改配置文件application.yml,注意集群服务,需要各个eureka的spring.application.name相同

    server:
      port: ${EUREKA_PORT:8762}
    eureka:
      client:
        service-url:
          defaultZone: ${EUREKA_SERVER:http://${spring.security.user.name}:${spring.security.user.password}@peer1:8762/eureka/,http://${spring.security.user.name}:${spring.security.user.password}@peer2:8763/eureka/}
        fetch-registry: true
      instance:
        instance-id: ${eureka.instance.hostname}:${server.port}
        hostname: peer1
    spring:
      security:
        user:
          name: ${EUREKA_USER:admin}
          password: ${EUREKA_PASS:admin}
      application:
        name: eureka-cluster
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    设置hosts文件

    127.0.0.1 peer1 peer2
    
    • 1

    服务提供者若想连接高可用的eureka,需要修改:

          defaultZone: http://${EUREKA_USER:admin}:${EUREKA_PASS:admin}@peer1:8762/eureka/,http://${EUREKA_USER:admin}:${EUREKA_PASS:admin}@peer2:8763/eureka/
    
    
    • 1
    • 2
    k8s交付

    分析:

    高可用互相注册,但是需要知道对方节点的地址。k8s中pod ip是不固定的,如何将高可用的eureka服务使用k8s交付?

    • 方案一:创建三个Deployment+三个Service

    在这里插入图片描述

    • 方案二:使用statefulset管理
      在这里插入图片描述

    eureka-statefulset.yaml

    # eureka-statefulset.yaml
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: eureka-cluster
      namespace: dev
    spec:
      serviceName: "eureka"
      replicas: 3
      selector:
        matchLabels:
          app: eureka-cluster
      template:
        metadata:
          labels:
            app: eureka-cluster
        spec:
          containers:
            - name: eureka
              image: 172.21.51.67:5000/spring-cloud/eureka-cluster:v1
              ports:
                - containerPort: 8761
              resources:
                requests:
                  memory: 400Mi
                  cpu: 50m
                limits:
                  memory: 2Gi
                  cpu: 2000m
              env:
                - name: MY_POD_NAME
                  valueFrom:
                    fieldRef:
                      fieldPath: metadata.name
                - name: JAVA_OPTS
                  value: -XX:+UnlockExperimentalVMOptions
                    -XX:+UseCGroupMemoryLimitForHeap
                    -XX:MaxRAMFraction=2
                    -XX:CICompilerCount=8
                    -XX:ActiveProcessorCount=8
                    -XX:+UseG1GC
                    -XX:+AggressiveOpts
                    -XX:+UseFastAccessorMethods
                    -XX:+UseStringDeduplication
                    -XX:+UseCompressedOops
                    -XX:+OptimizeStringConcat
                - name: EUREKA_SERVER
                  value: "http://admin:admin@eureka-cluster-0.eureka:8761/eureka/,http://admin:admin@eureka-cluster-1.eureka:8761/eureka/,http://admin:admin@eureka-cluster-2.eureka:8761/eureka/"
                - name: EUREKA_INSTANCE_HOSTNAME
                  value: ${MY_POD_NAME}.eureka
                - name: EUREKA_PORT
                  value: "8761"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    eureka-headless-service.yaml

    apiVersion: v1
    kind: Service
    metadata:
      name: eureka
      namespace: dev
      labels:
        app: eureka
    spec:
      ports:
        - port: 8761
          name: eureka
      clusterIP: None
      selector:
        app: eureka-cluster
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    想通过ingress访问eureka,需要使用有头服务

    apiVersion: v1
    kind: Service
    metadata:
      name: eureka-ingress
      namespace: dev
      labels:
        app: eureka-cluster
    spec:
      ports:
        - port: 8761
          name: eureka-cluster
      selector:
        app: eureka-cluster
    ---
    apiVersion: extensions/v1beta1
    kind: Ingress
    metadata:
      name: eureka-cluster
      namespace: dev
    spec:
      rules:
        - host: eureka-cluster.luffy.com
          http:
            paths:
              - backend:
                  serviceName: eureka-ingress
                  servicePort: 8761
                path: /
    status:
      loadBalancer: {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    使用StatefulSet管理有状态服务

    使用StatefulSet创建多副本pod的情况:

    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: nginx-statefulset
      labels:
        app: nginx-sts
    spec:
      replicas: 3
      serviceName: "nginx"
      selector:
        matchLabels:
          app: nginx-sts
      template:
        metadata:
          labels:
            app: nginx-sts
        spec:
          containers:
          - name: nginx
            image: nginx:alpine
            ports:
            - containerPort: 80
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    无头服务Headless Service

    kind: Service
    apiVersion: v1
    metadata:
      name: nginx
    spec:
      selector:
        app: nginx-sts
      ports:
      - protocol: TCP
        port: 80
        targetPort: 80
      clusterIP: None
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    $ kubectl -n spring exec  -ti nginx-statefulset-0 sh
    / # curl nginx-statefulset-2.nginx
    
    • 1
    • 2
    接入CICD流程

    所需的文件:

    在pom.xml中重写jar包名称:

    <finalName>${project.artifactId}</finalName>
    
    • 1

    Dockerfile

    FROM openjdk:8-jdk-alpine
    ADD target/eureka.jar app.jar
    ENV JAVA_OPTS=""
    CMD [ "sh", "-c", "java $JAVA_OPTS -jar /app.jar" ]
    
    • 1
    • 2
    • 3
    • 4

    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/spring-cloud/eureka-cluster"
            IMAGE_CREDENTIAL = "credential-registry"
            DINGTALK_CREDS = credentials('dingTalk')
            PROJECT = "eureka-cluster"
        }
        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()
                        }
                    }
                }
            }
            stage('deploy') {
                steps {
                    container('tools') {
                        script{
                        	devops.deploy("deploy",false,"deploy/statefulset.yaml").start()
                        }
                    }
                }
            }
        }
        post {
            success {
                script{
                    devops.notificationSuccess(PROJECT,"dingTalk")
                }
            }
            failure {
                script{
                    devops.notificationFailure(PROJECT,"dingTalk")
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

    sonar-project.properties

    sonar.projectKey=eureka-cluster
    sonar.projectName=eureka-cluster
    # if you want disabled the DTD verification for a proxy problem for example, true by default
    # JUnit like test report, default value is test.xml
    sonar.sources=src/main/java
    sonar.language=java
    sonar.tests=src/test/java
    sonar.java.binaries=target/classes
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    模板化k8s资源清单:

    # eureka-statefulset.yaml
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: eureka-cluster
      namespace: {{NAMESPACE}}
    spec:
      serviceName: "eureka"
      replicas: 3
      selector:
        matchLabels:
          app: eureka-cluster
      template:
        metadata:
          labels:
            app: eureka-cluster
        spec:
          containers:
            - name: eureka
              image: {{IMAGE_URL}}
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    维护新组件的ingress:

    $ kubectl -n dev edit configmap devops-config
    ...
      INGRESS_EUREKA: eureka.luffy.com
    ...
    
    • 1
    • 2
    • 3
    • 4

    部署k8s集群时,将eureka的集群地址通过参数的形式传递到pod内部,因此本地开发时,直接按照单点模式进行:

    server:
      port: ${EUREKA_PORT:8761}
    eureka:
      client:
        service-url:
          defaultZone: ${EUREKA_SERVER:http://${spring.security.user.name}:${spring.security.user.password}@localhost:8761/eureka/}
        fetch-registry: true
        register-with-eureka: true
      instance:
        instance-id: ${eureka.instance.hostname}:${server.port}
        hostname: ${EUREKA_INSTANCE_HOSTNAME:localhost}
        prefer-ip-address: true
    spring:
      security:
        user:
          name: ${EUREKA_USER:admin}
          password: ${EUREKA_PASS:admin}
      application:
        name: eureka-cluster
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    提交项目:

    创建develop分支,CICD部署开发环境

    停掉eureka-ha

    微服务间调用
    服务提供者

    前面已经将用户服务注册到了eureka注册中心,但是还没有暴漏任何API给服务消费者调用。

    新建controller类:

    package com.luffy.userservice.controller;
    
    
    import com.luffy.userservice.entity.User;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.Random;
    
    @RestController
    public class UserController {
    
        @GetMapping("/user")
        public String getUserService(){
            return "this is user-service";
        }
    
        @GetMapping("/user-nums")
        public Integer getUserNums(){
            return new Random().nextInt(100);
        }
    
        //{"id": 123, "name": "张三", "age": 20, "sex": "male"}
        @GetMapping("/user/{id}")
        public User getUserInfo(@PathVariable("id") int id){
            User user = new User();
            user.setId(id);
            user.setAge(20);
            user.setName("zhangsan");
            user.setSex("male");
            return user;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    实体类User.java

    package com.luffy.userservice.entity;
    
    public class User {
        private int id;
        private String name;
        private int age;
        private String sex;
    
        public int getAge() {
            return age;
        }
    
        public int getId() {
            return id;
        }
    
        public String getName() {
            return name;
        }
    
        public String getSex() {
            return sex;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void setSex(String sex) {
            this.sex = sex;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    application.yml

    增加从环境变量中读取EUREKA_SERVEREUREKA_INSTANCE_HOSTNAME配置

    server:
      port: 7000
    eureka:
      client:
        serviceUrl:
          defaultZone: ${EUREKA_SERVER:http://admin:admin@localhost:8761/eureka/}
      instance:
        instance-id: ${eureka.instance.hostname}:${server.port}
        prefer-ip-address: true
        hostname: ${INSTANCE_HOSTNAME:user-service}
    spring:
      application:
        name: user-service
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    CICD持续交付服务提供者

    deployment.yaml

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: user-service
      namespace: {{NAMESPACE}}
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: user-service
      template:
        metadata:
          labels:
            app: user-service
        spec:
          containers:
            - name: user-service
              image: {{IMAGE_URL}}
              imagePullPolicy: IfNotPresent
              ports:
                - containerPort: 7000
              resources:
                requests:
                  memory: 400Mi
                  cpu: 50m
                limits:
                  memory: 2Gi
                  cpu: 2000m
              env:
                - name: JAVA_OPTS
                  value: -XX:+UnlockExperimentalVMOptions
                    -XX:+UseCGroupMemoryLimitForHeap
                    -XX:MaxRAMFraction=2
                    -XX:CICompilerCount=8
                    -XX:ActiveProcessorCount=8
                    -XX:+UseG1GC
                    -XX:+AggressiveOpts
                    -XX:+UseFastAccessorMethods
                    -XX:+UseStringDeduplication
                    -XX:+UseCompressedOops
                    -XX:+OptimizeStringConcat
                - name: EUREKA_SERVER
                  value: "http://admin:admin@eureka-cluster-0.eureka:8761/eureka/,http://admin:admin@eureka-cluster-1.eureka:8761/eureka/,http://admin:admin@eureka-cluster-2.eureka:8761/eureka/"
                - name: INSTANCE_HOSTNAME
                  valueFrom:
                    fieldRef:
                      fieldPath: metadata.name
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    service.yaml

    apiVersion: v1
    kind: Service
    metadata:
      name: user-service
      namespace: {{NAMESPACE}}
    spec:
      ports:
        - port: 7000
          protocol: TCP
          targetPort: 7000
      selector:
        app: user-service
      sessionAffinity: None
      type: ClusterIP
    status:
      loadBalancer: {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    ingress.yaml

    apiVersion: extensions/v1beta1
    kind: Ingress
    metadata:
      name: user-service
      namespace: {{NAMESPACE}}
    spec:
      rules:
        - host: {{INGRESS_USER_SERVICE}}
          http:
            paths:
              - backend:
                  serviceName: user-service
                  servicePort: 7000
                path: /
    status:
      loadBalancer: {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    ingress配置:

    $ kubectl -n dev edit configmap devops-config
    ...
    data:
      INGRESS_MYBLOG: blog-dev.luffy.com
      INGRESS_SPRINGBOOTDEMO: springboot-dev.luffy.com
      INGRESS_USER_SERVICE: user-service-dev.luffy.com
      NAMESPACE: dev
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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/spring-cloud/user-service"
            IMAGE_CREDENTIAL = "credential-registry"
            DINGTALK_CREDS = credentials('dingTalk')
            PROJECT = "user-service"
        }
        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()
                        }
                    }
                }
            }
            stage('deploy') {
                steps {
                    container('tools') {
                        script{
                        	devops.deploy("deploy",true,"deploy/deployment.yaml").start()
                        }
                    }
                }
            }
        }
        post {
            success {
                script{
                    devops.notificationSuccess(PROJECT,"dingTalk")
                }
            }
            failure {
                script{
                    devops.notificationFailure(PROJECT,"dingTalk")
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

    pom.xml

    <finalName>${project.artifactId}</finalName>
    
    • 1

    Dockerfile

    FROM openjdk:8-jdk-alpine
    COPY target/user-service.jar app.jar
    ENV JAVA_OPTS=""
    CMD [ "sh", "-c", "java $JAVA_OPTS -jar /app.jar" ]
    
    • 1
    • 2
    • 3
    • 4

    sonar-project.properties

    sonar.projectKey=user-service
    sonar.projectName=user-service
    # if you want disabled the DTD verification for a proxy problem for example, true by default
    # JUnit like test report, default value is test.xml
    sonar.sources=src/main/java
    sonar.language=java
    sonar.tests=src/test/java
    sonar.java.binaries=target/classes
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    创建user-service项目,提交代码:

    git init
    git remote add origin http://gitlab.luffy.com/luffy-spring-cloud/user-service.git
    git add .
    git commit -m "Initial commit"
    git push -u origin master
    
    # 提交到develop分支
    git checkout -b develop
    git push -u origin develop
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    创建Jenkins任务,测试自动部署

    访问http://user-service-dev.luffy.com/ 验证

    服务消费者
    RestTemplate

    Spring中,提供了RestTemplateRestTemplateSpring提供的用于访问Rest服务的客户端。而在SpringCloud中也是使用此服务进行服务调用的。

    创建bill-service模块

    新的模块初始化三部曲:

    • pom.xml
    • 启动类
    • 配置文件

    pom.xml 添加如下内容:

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
    
    • 1
    • 2
    • 3
    • 4

    全量内容如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.3.6.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.luffy</groupId>
        <artifactId>bill-service</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>bill-service</name>
        <description>bill-service</description>
    
        <properties>
            <java.version>1.8</java.version>
            <spring-cloud.version>Hoxton.SR9</spring-cloud.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
        </dependencies>
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>${spring-cloud.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <build>
            <finalName>${project.artifactId}</finalName><!--打jar包去掉版本号-->
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    BillServiceApplication

    package com.luffy.billservice;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    
    @SpringBootApplication
    @EnableDiscoveryClient
    public class BillServiceApplication {
        public static void main(String[] args) {
            SpringApplication.run(BillService.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    application.yml

    server:
      port: 7001
    eureka:
      client:
        serviceUrl:
          defaultZone: ${EUREKA_SERVER:http://admin:admin@localhost:8761/eureka/}
      instance:
        instance-id: ${eureka.instance.hostname}:${server.port}
        prefer-ip-address: true
        hostname: ${INSTANCE_HOSTNAME:bill-service}
    spring:
      application:
        name: bill-service
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    BillController

    package com.luffy.billservice.controller;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.client.RestTemplate;
    
    @RestController
    public class BillController {
    
        @Bean
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    
        @Autowired
        private RestTemplate restTemplate;
    
        @GetMapping("/bill/user")
        public String getUserInfo(){
            return restTemplate.getForObject("http://localhost:7000/user", String.class);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    问题:

    • 服务调用采用指定IP+Port方式,注册中心未使用
    • 多个服务负载均衡
    使用注册中心实现服务调用

    修改BillController

    package com.luffy.billservice.controller;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.client.RestTemplate;
    
    @RestController
    public class BillController {
    
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    
        @Autowired
        private RestTemplate restTemplate;
    
        @GetMapping("/bill/user")
        public String getUserInfo(){
            return restTemplate.getForObject("http://user-service/user", String.class);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    访问测试

    总体来说,就是通过为加入@LoadBalanced注解的RestTemplate添加一个请求拦截器,在请求前通过拦截器获取真正的请求地址,最后进行服务调用。

    友情提醒:若被@LoadBalanced注解的RestTemplate访问正常的服务地址,如http://127.0.0.1:8080/hello时,是会提示无法找到此服务的。

    具体原因:serverid必须是我们访问的服务名称 ,当我们直接输入ip的时候获取的servernull,就会抛出异常。

    如果想继续调用,可以通过如下方式:

    package com.luffy.billservice.controller;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.client.RestTemplate;
    
    @RestController
    public class BillController {
    
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    
        @Autowired
        private RestTemplate restTemplate;
    
        @Bean("normalRestTemplate")
        public RestTemplate normalRestTemplate() {
            return new RestTemplate();
        }
    
        @Autowired
        @Qualifier("normalRestTemplate")
        RestTemplate normalRestTemplate;
    
    
        @GetMapping("/service/user")
        public String getUserInfo(){
            return restTemplate.getForObject("http://user-service/user", String.class);
        }
    
        @GetMapping("/normal")
        public String normal() {
            return normalRestTemplate.getForObject("http://localhost:7000/user", String.class);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    Ribbon 负载均衡

    再启动一个user-service-instance2,复制user-service项目

    修改user-service-instance2的application.yml的server.port

    server:
      port: 7002
    eureka:
      client:
        serviceUrl:
          defaultZone: ${EUREKA_SERVER:http://admin:admin@peer1:8761/eureka/}
      instance:
        instance-id: ${eureka.instance.hostname}:${server.port}
        prefer-ip-address: true
        hostname: ${INSTANCE_HOSTNAME:user-service}
    spring:
      application:
        name: user-service
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    修改user-service-instance2的UserController.java,为了可以区分是哪个服务提供者的实例提供的服务

    package com.luffy.userservice.controller;
    
    
    import com.luffy.userservice.entity.User;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.Random;
    
    @RestController
    public class UserController {
    
        @GetMapping("/user")
        public String getUserService(){
            return "this is user-service-instance2";
        }
    
        @GetMapping("/user-nums")
        public Integer getUserNums(){
            return new Random().nextInt(100);
        }
    
        //{"id": 123, "name": "张三", "age": 20, "sex": "male"}
        @GetMapping("/user/{id}")
        public User getUserInfo(@PathVariable("id") int id){
            User user = new User();
            user.setId(id);
            user.setAge(20);
            user.setName("zhangsan");
            user.setSex("male");
            return user;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    访问bill-service,查看调用结果(默认是轮询策略)

    Spring Cloud Ribbon是一个基于Http和TCP的客服端负载均衡工具,它是基于Netflix Ribbon实现的。与Eureka配合使用时,Ribbon可自动从Eureka Server (注册中心)获取服务提供者地址列表,并基于负载均衡算法,通过在客户端中配置ribbonServerList来设置服务端列表去轮询访问以达到均衡负载的作用。

    eureka-client中包含了ribbon的包,所以不需要单独引入

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    如何修改调用策略?

    • 代码中指定rule的规则
    • 配置文件配置

    在bill-service中新建package,com.luffy.rule,注意不能被springboot扫描到,不然规则就成了全局规则,所有的ribbonclient都会应用到该规则。

    package com.luffy.rule;
    
    import com.netflix.loadbalancer.*;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class RandomConfiguration {
    
        @Bean
        public IRule ribbonRule() {
            // new BestAvailableRule();
            // new WeightedResponseTimeRule();
            return new RandomRule();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    修改BillController

    import com.luffy.rule.RandomConfiguration;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.netflix.ribbon.RibbonClient;
    
    @SpringBootApplication
    @EnableDiscoveryClient
    @RibbonClient(name = "user-service", configuration = RandomConfiguration.class)
    public class BillServiceApplication {
        public static void main(String[] args) {
            SpringApplication.run(BillServiceApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    配置文件方式: https://docs.spring.io/spring-cloud-netflix/docs/2.2.5.RELEASE/reference/html/#customizing-the-ribbon-client-by-setting-properties

    注释掉代码:

    package com.luffy.ticket;
    
    import com.luffy.rule.RandomConfiguration;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.netflix.ribbon.RibbonClient;
    
    @SpringBootApplication
    @EnableDiscoveryClient
    //@RibbonClient(name = "USER-SERVICE", configuration = RandomConfiguration.class)
    public class TicketApplication {
        public static void main(String[] args) {
            SpringApplication.run(TicketApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    修改配置文件:

    server:
      port: ${SERVER_PORT:9000}
    
    spring:
      application:
        name: bill-service
    
    eureka:
      client:
        service-url:
          defaultZone: ${EUREKA_SERVER:http://admin:admin@peer1:8762/eureka/,http://admin:admin@peer2:8763/eureka/}
      instance:
        prefer-ip-address: true
        instance-id: ${spring.cloud.client.ip-address}:${server.port}
    user-service:
      ribbon:
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    声明式服务Feign

    从上一章节,我们知道,当我们要调用一个服务时,需要知道服务名和api地址,这样才能进行服务调用,服务少时,这样写觉得没有什么问题,但当服务一多,接口参数很多时,上面的写法就显得不够优雅了。所以,接下来,来说说一种更好更优雅的调用服务的方式:Feign

    FeignNetflix开发的声明式、模块化的HTTP客户端。Feign可帮助我们更好更快的便捷、优雅地调用HTTP API

    Spring Cloud中,使用Feign非常简单——创建一个接口,并在接口上添加一些注解。Feign支持多种注释,例如Feign自带的注解或者JAX-RS注解等 Spring Cloud对Feign进行了增强,使Feign支持了Spring MVC注解,并整合了Ribbon和 Eureka,从而让Feign 的使用更加方便。只需要通过创建接口并用注解来配置它既可完成对Web服务接口的绑定。

    https://github.com/OpenFeign/feign

    对bill-service项目添加openfeign的依赖引入:

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
    
    • 1
    • 2
    • 3
    • 4

    启动类中引入Feign注解:

    package com.luffy.billservice;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableFeignClients
    public class BillServiceApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(BillServiceApplication.class, args);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    建立interface

    package com.luffy.billservice.interfaces;
    
    import com.luffy.billservice.entity.User;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    
    @FeignClient(name="user-service")
    public interface UserServiceCli {
    
        @GetMapping("/user")
        public String getUserService();
    
        @GetMapping("/user/{id}")
        public User getUserInfo(@PathVariable("id") int id);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    拷贝User类到当前项目:

    package com.luffy.billservice.entity;
    
    public class User {
        private int id;
        private String name;
        private int age;
        private String sex;
    
        public int getAge() {
            return age;
        }
    
        public int getId() {
            return id;
        }
    
        public String getName() {
            return name;
        }
    
        public String getSex() {
            return sex;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void setSex(String sex) {
            this.sex = sex;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    修改BillController

    package com.luffy.billservice.controller;
    
    import com.luffy.billservice.entity.User;
    import com.luffy.billservice.interfaces.UserServiceCli;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class BillController {
    
        @Autowired
        private UserServiceCli userServiceCli;
    
    
        @GetMapping("/bill/user")
        public String getUserInfo(){
            return userServiceCli.getUserService();
        }
    
        @GetMapping("/bill/user/{id}")
        public User getUserInfo(@PathVariable("id") int id){
            return userServiceCli.getUserInfo(id);
            //return restTemplate.getForObject("http://USER-SERVICE/user/" + id, String.class);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    CICD持续交付服务消费者

    拷贝user-service的交付文件,替换如下:

    • user-service -> bill-service
    • 7000 -> 7001
    • INGRESS_USER_SERVICE -> INGRESS_BILL_SERVICE
    $ kubectl -n dev edit configmap devops-config
    ...
    data:
      INGRESS_MYBLOG: blog-dev.luffy.com
      INGRESS_SPRINGBOOTDEMO: springboot-dev.luffy.com
      INGRESS_USER_SERVICE: user-service-dev.luffy.com
      INGRESS_BILL_SERVICE: user-service-dev.luffy.com
      NAMESPACE: dev
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    创建develop分支,提交代码到gitlab仓库,验证持续交付

    前面主要讲解了下服务消费者如何利用原生、ribbon、fegin三种方式进行服务调用的,其实每种调用方式都是使用restTemplate来进行调用的,只是有些进行了增强,目的是使用起来更简单高效。

    Hystrix 断路器

    为什么需要断路器?

    在这里插入图片描述

    A作为服务提供者,B为A的服务消费者,C和D是B的服务消费者。A不可用引起了B的不可用,并将不可用像滚雪球一样放大到C和D时,雪崩效应就形成了。

    因此,需要实现一种机制,可以做到自动监控服务状态并根据调用情况进行自动处理。

    • 记录时间周期内服务调用失败次数
    • 维护断路器的打开、关闭、半开三种状态
    • 提供fallback机制

    在这里插入图片描述

    修改bill-service项目:

    pom.xml

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            </dependency>
    
    • 1
    • 2
    • 3
    • 4

    application.xml

    feign:
      hystrix:
        enabled: true
    
    • 1
    • 2
    • 3

    启动类添加注解@EnableCircuitBreaker

    package com.luffy.billservice;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableFeignClients
    @EnableCircuitBreaker
    public class BillServiceApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(BillServiceApplication.class, args);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    UserServiceCli.java

    package com.luffy.bill.interfaces;
    
    import com.luffy.bill.entity.User;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    
    @FeignClient(name="user-service", fallback = UserServiceFallbackImpl.class)
    public interface UserServiceCli {
    
        @GetMapping("/user")
        public String getUserService();
    
        @GetMapping("/user/{id}")
        public User getUserInfo(@PathVariable("id") int id);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    UserServiceFallbackImpl.java

    package com.luffy.billservice.interfaces;
    
    import com.luffy.billservice.entity.User;
    import org.springframework.stereotype.Component;
    
    @Component("fallback")
    public class UserServiceFallbackImpl implements UserServiceCli{
    
        @Override
        public String getUserService() {
            return "fallback user service";
        }
    
        @Override
        public User getUserInfo(int id) {
            User user = new User();
            user.setId(1);
            user.setName("feign-fallback");
            return user;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    停止user-service测试熔断及fallback。

    Hystrix Dashboard

    前面一章,我们讲解了如何整合Hystrix。而在实际情况下,使用了Hystrix的同时,还会对其进行实时的数据监控,反馈各类指标数据。今天我们就将讲解下Hystrix DashboardTurbine.其中Hystrix Dashboard是一款针对Hystrix进行实时监控的工具,通过Hystrix Dashboard我们可以在直观地看到各Hystrix Command请求响应时间, 请求成功率等数据,监控单个实例内的指标情况。后者Turbine,能够将多个实例指标数据进行聚合的工具。

    在eureka注册中心处访问bill-service的服务actuator地址: http://192.168.136.1:7001/actuator/info

    若访问不了,需要添加如下内容:

    • 为服务消费者bill-service的pom.xml添加依赖:

              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-actuator</artifactId>
              </dependency>
      
      • 1
      • 2
      • 3
      • 4
    • 修改application.yml配置:

      management:
        endpoints:
          web:
            exposure:
              include: "*"
      
      • 1
      • 2
      • 3
      • 4
      • 5

    访问http://localhost:9000/actuator/hystrix.stream 即可访问到断路器的执行状态,但是显示不太友好,因此需要dashboard。

    新建项目,hystrix-dashboard

    Hystrix-dashboard(仪表盘)是一款针对Hystrix进行实时监控的工具,通过Hystrix Dashboard我们可以在直观地看到各Hystrix Command的请求响应时间, 请求成功率等数据。

    pom.xml引入依赖包:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.3.6.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.luffy</groupId>
        <artifactId>hystrix-dashboard</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>hystrix-dashboard</name>
        <description>hystrxi dashboard</description>
    
        <properties>
            <java.version>1.8</java.version>
            <spring.cloud-version>Hoxton.SR9</spring.cloud-version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
        </dependencies>
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>${spring.cloud-version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    启动类加上@EnableHystrixDashboard注解:

    package com.luffy.hystrixdashboard;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
    
    @SpringBootApplication
    @EnableHystrixDashboard
    public class HystrixDashboardApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(HystrixDashboardApplication.class, args);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    application.yml

    #应用名称
    server:
      port: 9696
    
    spring:
      application:
        name: hystrix-dashboard
    hystrix:
      dashboard:
        proxy-stream-allow-list: "*"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    访问localhost:9696/hystrix

    • 实心圆:它有颜色和大小之分,分别代表实例的监控程度和流量大小。如上图所示,它的健康度从绿色、黄色、橙色、红色递减。通过该实心圆的展示,我们就可以在大量的实例中快速的发现故障实例和高压力实例。

    • 曲线:用来记录 2 分钟内流量的相对变化,我们可以通过它来观察到流量的上升和下降趋势。

    • 其他一些数量指标如下图所示

    在这里插入图片描述

    提交代码到gitlab仓库

    Turbine

    hystrix只能实现单个微服务的监控,可是一般项目中是微服务是以集群的形式搭建,一个一个的监控不现实。而Turbine的原理是,建立一个turbine服务,并注册到eureka中,并发现eureka上的hystrix服务。通过配置turbine会自动收集所需hystrix的监控信息,最后通过dashboard展现,以达到集群监控的效果。

    简单来说,就是通过注册到注册中心,发现其他服务的hystrix服务,然后进行聚合数据,最后通过自身的端点输出到仪表盘上进行个性化展示。这我们就监控一个turbine应用即可,当有新增的应用加入时,我们只需要配置下turbine参数即可。

    微服务网关
    为什么需要网关

    在微服务框架中,每个对外服务都是独立部署的,对外的api或者服务地址都不是不尽相同的。对于内部而言,很简单,通过注册中心自动感知即可。但我们大部分情况下,服务都是提供给外部系统进行调用的,不可能同享一个注册中心。同时一般上内部的微服务都是在内网的,和外界是不连通的。而且,就算我们每个微服务对外开放,对于调用者而言,调用不同的服务的地址或者参数也是不尽相同的,这样就会造成消费者客户端的复杂性,同时想想,可能微服务可能是不同的技术栈实现的,有的是httprpc或者websocket等等,也会进一步加大客户端的调用难度。所以,一般上都有会有个api网关,根据请求的url不同,路由到不同的服务上去,同时入口统一了,还能进行统一的身份鉴权、日志记录、分流等操作
    在这里插入图片描述

    网关的功能
    • 减少api请求次数
    • 限流
    • 缓存
    • 统一认证
    • 降低微服务的复杂度
    • 支持混合通信协议(前端只和api通信,其他的由网关调用)

    在这里插入图片描述

    Zuul实践

    新建模块,gateway-zuul,(spring cloud)

    pom.xml中需要引入zuul和eureka服务发现的依赖

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    启动类添加注解

    package com.luffy.gateway;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
    
    @SpringBootApplication
    @EnableZuulProxy
    @EnableDiscoveryClient
    public class ZuulGatewayApplication {
        public static void main(String[] args) {
            SpringApplication.run(ZuulGatewayApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    配置文件:

    server:
      port: 10000
    
    spring:
      application:
        name: gateway-zuul
    
    eureka:
      client:
        serviceUrl:
          defaultZone: ${EUREKA_SERVER:http://admin:admin@localhost:8761/eureka/}
      instance:
        instance-id: ${eureka.instance.hostname}:${server.port}
        prefer-ip-address: true
        hostname: ${INSTANCE_HOSTNAME:gateway-zuul}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    启动后,访问:

    http://localhost:10000/bill-service/bill/user/1

    http://localhost:10000/user-service/user

    通过如下方式,配置短路径:

    zuul:
      routes:
        user-service: /users/**
        bill-service:
          path: /bill/**
          service-id: bill-service
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    http://localhost:10000/users/user/1
                                                     --->  http://localhost:7000/user/2
    http://localhost:10000/user-service/user/1
    
    
    
    http://localhost:10000/bill/service/user/2 
                                                     --->http://localhost:7001/service/user/2
    http://localhost:10000/bill-service/service/user/2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    zuul如何指定对外暴漏api的path,如:

    所有的api都是这样:http://zuul-host:zuul-port/apis/,可以添加zuul.prefix:/apis

    配置一下配置文件

    management:
      endpoints:
        web:
          exposure:
            include: "*"
    
    • 1
    • 2
    • 3
    • 4
    • 5

    可以访问到zuul的route列表, http://localhost:10000/actuator/routes/ ,添加details可以访问到详细信息

    {
    "/apis/users/**": "user-service",
    "/apis/bill/**": "bill-service",
    "/apis/bill-service/**": "bill-service",
    "/apis/user-service/**": "user-service"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    提交代码到代码仓库

    集中配置中心

    Spring Cloud Config 配置中心提供了一个中心化的外部配置,默认使用git存储配置信息,这样就可以对配置信息进行版本管理。
    在这里插入图片描述

    实践
    • 创建代码仓库configure-repo,用于集中存储配置文件
    • 创建项目config-server,用于接受各项目的连接,提供配置文件读取服务
    • 修改user-service服务,验证通过config-server读取集中配置库中的配置文件

    代码仓库:

    • 新建gitlab项目,http://gitlab.luffy.com/luffy-spring-cloud/configure-repo.git

    • 准备配置文件

      configs/common-dev.yml

      datasource:
        url: jdbc:mysql://mysql-dev:3306/
        driverClassName: com.mysql.jdbc.Driver
        username: xxx
        password: xxxxxx
      luffy: city
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      configs/user-service-dev.yml

      logging:
        level:
          org.springframework.cloud: debug
      env: dev
      
      • 1
      • 2
      • 3
      • 4

      configs/common-test.yml

      env: test
      
      • 1
    • 提交代码到master分支

    新建项目,config-server

    修改pom.xml(springboot和springcloud的版本)

    1. springboot 版本

      <version>2.3.6.RELEASE</version>
      
      • 1
    2. spring-cloud 版本

          <properties>
              <java.version>1.8</java.version>
              <spring-cloud.version>Hoxton.SR9</spring-cloud.version>
          </properties>
      
      • 1
      • 2
      • 3
      • 4
    3. 添加config-server 的依赖

              <dependency>
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-config-server</artifactId>
              </dependency>
      
      • 1
      • 2
      • 3
      • 4

    修改application.yml

    server:
      port: 8088
    
    spring:
      application:
        name: config-server
      profiles:
        active: git
      cloud:
        config:
          server:
            git:
              uri: http://gitlab.luffy.com/luffy-spring-cloud/configure-repo.git
              username: ${GIT_USER:root}
              password: ${GIT_PSW:1qaz2wsx}
              default-label: master
              search-paths: configs
            #native:
            #  searchLocations: classpath:/configs/{profile}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    修改启动类,添加注解

    package com.luffy.configserver;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.config.server.EnableConfigServer;
    
    @SpringBootApplication
    @EnableConfigServer
    public class ConfigServerApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ConfigServerApplication.class, args);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    启动config-server,访问:

    http://localhost:8088/common/dev
    http://localhost:8088/user-service/dev
    
    • 1
    • 2

    修改user-service服务,从config-server读取配置

    • 添加使用统一配置中心的依赖:

              <dependency>
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-starter-config</artifactId>
              </dependency>
      
      • 1
      • 2
      • 3
      • 4
    • 新建bootstrap.yml,不能放在application.yml中,因为bootstrap的加载早于应用程序bean启动的加载,因此,删掉application.yml,直接使用bootstrap.yml

      server:
        port: 7000
      
      
      eureka:
        client:
          serviceUrl:
            defaultZone: ${EUREKA_SERVER:http://admin:admin@localhost:8761/eureka/}
        instance:
          instance-id: ${eureka.instance.hostname}:${server.port}
          prefer-ip-address: true
          hostname: ${INSTANCE_HOSTNAME:user-service}
      spring:
        application:
          name: user-service
        cloud:
          config:
            uri: http://localhost:8088
            profile: dev  #当前读取dev环境的配置
            name: user-service, common  # 从user-service-dev.yml,common-dev.yml中读取
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
    • 新建ValueController.java

      package com.luffy.userservice.controller;
      
      import org.springframework.beans.factory.annotation.Value;
      import org.springframework.cloud.context.config.annotation.RefreshScope;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RestController;
      
      @RestController
      public class ValueController {
      
          @Value("${env}")
          private String env;
      
          @Value("${datasource.url}")
          private String datasource;
      
          @Value("${spring.application.name}")
          private String applicationName;
      
          @GetMapping("/value/env")
          public String getValueEnv(){
              return "current env is " + env;
          }
      
          @GetMapping("/value/application")
          public String getValueApplication(){
              return "current env is " + applicationName;
          }
      
          @GetMapping("/value/datasource")
          public Object getDatasource(){
              return datasource;
          }
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
    • 访问如下页面进行验证

      $ localhost:7000/value/env
      $ localhost:7000/value/application
      $ localhost:7000/value/datasource
      
      • 1
      • 2
      • 3
      高可用

    config-server多个实例,如何配置客户端?

    • config-server 作为服务提供者,注册到eureka服务注册中心
    • user-service配置从注册中心获取config-server的服务

    config-server注册到服务注册中心

    • pom.xml添加eureka依赖包

              <dependency>
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
              </dependency>
      
      • 1
      • 2
      • 3
      • 4
    • application.yml中连接服务注册中心

      eureka:
        client:
          serviceUrl:
            defaultZone: ${EUREKA_SERVER:http://admin:admin@localhost:8761/eureka/}
        instance:
          instance-id: ${eureka.instance.hostname}:${server.port}
          prefer-ip-address: true
          hostname: ${INSTANCE_HOSTNAME:config-server}
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    • 启动类添加注解

      @EnableDiscoveryClient
      
      • 1

    修改user-service,从注册中心发现服务

    • 修改bootstrap.yml

      server:
        port: 7000
      
      spring:
        cloud:
          config:
            profile: dev
            discovery:
              enabled: true
              service-id: config-server
            name: user-service, common
      
      eureka:
        client:
          serviceUrl:
            defaultZone: ${EUREKA_SERVER:http://admin:admin@peer1:8761/eureka/}
        instance:
          instance-id: ${eureka.instance.hostname}:${server.port}
          prefer-ip-address: true
          hostname: ${INSTANCE_HOSTNAME:user-service}
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
    客户端配置刷新

    配置中心的配置变动后,客户端如何获取最新的配置。

    • 修改ValueController.java,添加注解

      @RestController
      @RefreshScope
      public class ValueController 
      
      • 1
      • 2
      • 3
    • 添加actuator包

              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-actuator</artifactId>
              </dependency>
      
      • 1
      • 2
      • 3
      • 4
    • 开放显示所有管理接口

      management:
        endpoints:
          web:
            exposure:
              include: "*"
      
      • 1
      • 2
      • 3
      • 4
      • 5
    • 重启user-service

    • 修改configure-repo中的配置并提交,访问http://localhost:7000/value/env

    • 执行刷新

      $ curl -XPOST http://localhost:7000/actuator/refresh
      
      • 1
    • 再次访问http://localhost:7000/value/env

    调用链路追踪
    介绍

    服务追踪的追踪单元是从客户发起请求(request)抵达被追踪系统的边界开始,到被追踪系统向客户返回响应(response)为止的过程,称为一个 trace。每个 trace 中会调用若干个服务,为了记录调用了哪些服务,以及每次调用的消耗时间等信息,在每次调用服务时,埋入一个调用记录,称为一个 span。这样,若干个有序的 span 就组成了一个 trace。在系统向外界提供服务的过程中,会不断地有请求和响应发生,也就会不断生成 trace,把这些带有 span 的 trace 记录下来,就可以描绘出一幅系统的服务拓扑图。附带上 span 中的响应时间,以及请求成功与否等信息,就可以在发生问题的时候,找到异常的服务;根据历史数据,还可以从系统整体层面分析出哪里性能差,定位性能优化的目标。

    img

    Spring Cloud Sleuth 为服务之间调用提供链路追踪。通过 Sleuth 可以很清楚的了解到一个服务请求经过了哪些服务,每个服务处理花费了多长。从而让我们可以很方便的理清各微服务间的调用关系。此外 Sleuth 可以帮助我们:

    • 耗时分析: 通过 Sleuth 可以很方便的了解到每个采样请求的耗时,从而分析出哪些服务调用比较耗时;

    • 链路优化: 对于调用比较频繁的服务,可以针对这些服务实施一些优化措施。

    • 可视化错误: 对于程序未捕捉的异常,可以通过集成 Zipkin 服务界面上看到; Spring Cloud Sleuth 可以结合 Zipkin,将信息发送到 Zipkin,利用 Zipkin 的存储来存储信息,利用 Zipkin UI 来展示数据。

    https://zipkin.io/pages/quickstart

    启动zipkin
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: zipkin
      namespace: dev
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: zipkin
      template:
        metadata:
          labels:
            app: zipkin
        spec:
          containers:
            - name: zipkin
              image: openzipkin/zipkin:2.22
              imagePullPolicy: IfNotPresent
              ports:
                - containerPort: 9411
              resources:
                requests:
                  memory: 400Mi
                  cpu: 50m
                limits:
                  memory: 2Gi
                  cpu: 2000m
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: zipkin
      namespace: dev
    spec:
      ports:
        - port: 9411
          protocol: TCP
          targetPort: 9411
      selector:
        app: zipkin
      sessionAffinity: None
      type: ClusterIP
    status:
      loadBalancer: {}
    ---
    apiVersion: extensions/v1beta1
    kind: Ingress
    metadata:
      name: zipkin
      namespace: dev
    spec:
      rules:
        - host: zipkin.luffy.com
          http:
            paths:
              - backend:
                  serviceName: zipkin
                  servicePort: 9411
                path: /
    status:
      loadBalancer: {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    实践

    分别对bill-serviceuser-service进行改造:

    pom.xml中添加:

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-zipkin</artifactId>
            </dependency>
    
    • 1
    • 2
    • 3
    • 4

    application.yml

    spring:
      zipkin:
        base-url: http://zipkin.luffy.com  # zipkin服务器的地址
        sender:
          type: web  # 设置使用http的方式传输数据
      sleuth:
        sampler:
          probability: 1  # 设置抽样采集为100%,默认为0.1,即10%
    logging:
      level:
        org.springframework.cloud: debug
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    访问zuul网关的接口http://localhost:10000/apis/bill-service/bill/user/2

    2020-11-14 19:28:49.274 DEBUG [bill-service,949aa3570daa1031,43ea952f1e5e36eb,true] 36852 — [-user-service-6] c.s.i.w.c.f.TraceLoadBalancerFeignClient : Before send

    bill-service,949aa3570daa1031,43ea952f1e5e36eb,true
    
    • 1

    说明:

    • bill-service: 服务名称
    • 949aa3570daa1031: 是TranceId,一条链路中,只有一个TranceId
    • 43ea952f1e5e36eb:则是spanId,链路中的基本工作单元id
    • true:表示是否将数据输出到其他服务,true则会把信息输出到其他可视化的服务上观察
    SpringBoot Admin监控

    新建项目,springboot-admin

    pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.3.6.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.luffy</groupId>
        <artifactId>springboot-admin</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>springboot-admin</name>
        <description>Demo project for Spring Boot</description>
    
        <properties>
            <java.version>1.8</java.version>
            <spring-cloud.version>Hoxton.SR9</spring-cloud.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>de.codecentric</groupId>
                <artifactId>spring-boot-admin-starter-server</artifactId>
                <version>2.2.1</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
        </dependencies>
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>${spring-cloud.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    配置文件

    server:
      port: 8769
    
    spring:
      application:
        name: springboot-admin
    
    eureka:
      client:
        serviceUrl:
          defaultZone: ${EUREKA_SERVER:http://admin:admin@localhost:8761/eureka/}
      instance:
        instance-id: ${eureka.instance.hostname}:${server.port}
        prefer-ip-address: true
        hostname: ${INSTANCE_HOSTNAME:springboot-admin}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    启动类

    package com.luffy.springbootadmin;
    
    import de.codecentric.boot.admin.server.config.EnableAdminServer;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    
    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableAdminServer
    public class SpringbootAdminApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringbootAdminApplication.class, args);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    客户端,所有注册到eureka的服务,添加依赖即可

            <dependency>
                <groupId>de.codecentric</groupId>
                <artifactId>spring-boot-admin-starter-client</artifactId>
                <version>2.2.1</version>
            </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    小结
    1. 伴随着业务场景复杂度的提高,单体架构应用弊端显现,微服务的思想逐步盛行,微服务架构带来诸多便捷的同时,也带来了很多问题,最主要的是多个微服务的服务治理(服务发现、调用、负载均衡、跟踪)
    2. 为了解决服务治理问题,出现了微服务框架(Dubbo、Spring Cloud等)
    3. Spring Cloud是一个大的生态,基于Java语言封装了一系列的工具,方便业务直接使用来解决上述服务治理相关的问题
    4. Spring Cloud Netflix 体系下提供了eureka、ribbon、feign、hystrix、zuul等工具结合spring cloud sleuth合zipkin实现服务跟踪
    5. SpringBoot是微服务的开发框架,通过maven与Spring Cloud生态中的组件集成,极大方便了java应用程序的交付

    在这里插入图片描述

    https://blog.csdn.net/smallsunl/article/details/78778790

    问题:

    1. 无论是Dubbo还是SpringCloud,均属于Java语言体系下的产物,跨语言没法共用,同时,通过走了一遍内部集成的过程,可以清楚的发现,服务治理过程中,各模块的集成,均需要对原始业务逻辑形成侵入。
    2. 在kubernetes的生态下,已经与生俱来带了很多好用的功能(自动服务发现与负载均衡)
    3. 服务治理的根本其实是网络节点通信的治理,因此,以istio为代表的第二代服务治理平台开始逐步兴起

    ng Boot

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR9</spring-cloud.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-server</artifactId>
            <version>2.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    ```

    配置文件

    server:
      port: 8769
    
    spring:
      application:
        name: springboot-admin
    
    eureka:
      client:
        serviceUrl:
          defaultZone: ${EUREKA_SERVER:http://admin:admin@localhost:8761/eureka/}
      instance:
        instance-id: ${eureka.instance.hostname}:${server.port}
        prefer-ip-address: true
        hostname: ${INSTANCE_HOSTNAME:springboot-admin}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    启动类

    package com.luffy.springbootadmin;
    
    import de.codecentric.boot.admin.server.config.EnableAdminServer;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    
    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableAdminServer
    public class SpringbootAdminApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringbootAdminApplication.class, args);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    客户端,所有注册到eureka的服务,添加依赖即可

            <dependency>
                <groupId>de.codecentric</groupId>
                <artifactId>spring-boot-admin-starter-client</artifactId>
                <version>2.2.1</version>
            </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    小结
    1. 伴随着业务场景复杂度的提高,单体架构应用弊端显现,微服务的思想逐步盛行,微服务架构带来诸多便捷的同时,也带来了很多问题,最主要的是多个微服务的服务治理(服务发现、调用、负载均衡、跟踪)
    2. 为了解决服务治理问题,出现了微服务框架(Dubbo、Spring Cloud等)
    3. Spring Cloud是一个大的生态,基于Java语言封装了一系列的工具,方便业务直接使用来解决上述服务治理相关的问题
    4. Spring Cloud Netflix 体系下提供了eureka、ribbon、feign、hystrix、zuul等工具结合spring cloud sleuth合zipkin实现服务跟踪
    5. SpringBoot是微服务的开发框架,通过maven与Spring Cloud生态中的组件集成,极大方便了java应用程序的交付

    [外链图片转存中…(img-hhuLNCN9-1635082169319)]

    https://blog.csdn.net/smallsunl/article/details/78778790

    问题:

    1. 无论是Dubbo还是SpringCloud,均属于Java语言体系下的产物,跨语言没法共用,同时,通过走了一遍内部集成的过程,可以清楚的发现,服务治理过程中,各模块的集成,均需要对原始业务逻辑形成侵入。
    2. 在kubernetes的生态下,已经与生俱来带了很多好用的功能(自动服务发现与负载均衡)
    3. 服务治理的根本其实是网络节点通信的治理,因此,以istio为代表的第二代服务治理平台开始逐步兴起
    声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/708607
    推荐阅读
    相关标签
      

    闽ICP备14008679号