SpringCloud初级

![image-20220326101610252](SpringCloud

文章目录


一、什么是微服务架构

由马丁弗勒提出的一种架构模式

微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相协作(通常是基于Http协议的Restful API)。每个服务都围绕着具体业务进行构建,并且能够被独立的部署到生产环境、类生产环境等,另外,应当尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建

image-20220326101610252

image-20220326101610252

image-20220326101610252

1.1 SpringCloud官网

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

Spring Cloud 为开发者提供了工具来快速构建分布式系统中的一些常见模式(例如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话,集群状态)。分布式系统的协调导致了样板模式,使用 Spring Cloud 开发人员可以快速建立实现这些模式的服务和应用程序。它们在任何分布式环境中都能很好地工作,包括开发人员自己的笔记本电脑、裸机数据中心以及 Cloud Foundry 等托管平台。

特征

Spring Cloud 专注于为典型用例提供良好的开箱即用体验,并提供可扩展机制以覆盖其他用例。

  • 分布式/版本化配置
  • 服务注册和发现
  • 路由
  • 服务到服务调用
  • 负载均衡
  • 断路器
  • 全局锁
  • 领导选举和集群状态
  • 分布式消息传递

image-20220326101610252

image-20220326101610252

image-20220326101610252

1.2 国内网站服务架构

image-20220326101610252

image-20220326101610252

image-20220326101610252

1.3 SpringCloud主流技术栈

image-20220326101610252

二、版本选型

  • Cloud H版
  • Boot 2.2.x

2.1 官网中Cloud与boot版本的对应关系

image-20220410140403758

image-20220326101610252

image-20220410141616974

2.2 本次学习工具的版本号

技术版本号
SpringCloudHoxton.SR1
SpringBoot2.2.2 RELEASE
SpringCloud alibaba2.1.0 RELEASE
Javajava8
Maven3.5及以上
MySQL5.7及以上

三、Cloud组件停更/升级/替换

3.1 以前

image-20220326101610252

3.2 现在

image-20220326101610252

  • SpringCloud
    • 官网:https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle
    • 中文文档:https://www.bookstack.cn/read/spring-cloud-docs/docs-index.md
  • SpringBood
    • 官网:https://docs.spring.io/spring-boot/docs/2.2.2.RELEASE/reference/htmlsingle/

四、微服务架构编码构建

4.1 开发规定

约定 > 配置 > 编码

4.2 IDEA新建project工作空间

4.2.1 聚合父工程Project

步骤:

1.New Project
2.聚合总父工程名字
3.Maven选版本
4.工程名字
5.字符编码
6.注解生效激活
7.java编译版本选8
8.File Type过滤
  1. New Project

    image-20220326101610252

  2. 聚合总父工程名字

  3. Maven选版本

  4. 工程名字

  5. 字符编码

    image-20220326101610252

  6. 注解生效激活

    image-20220326101610252

  7. java编译版本选8

    image-20220326101610252

  8. File Type过滤

    image-20220326101610252

4.2.2 父工程POM

  1. 将打包方式设为packing

    image-20220326101610252

  2. 删掉父工程idea,src文件夹,只留下pom文件,干净整洁

    image-20220326101610252

  3. 统一管理jar包版本

POM文件

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.lebrwcd</groupId>
  <artifactId>SpringCloud</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>pom</packaging>

  <!-- 统一管理jar包版本 -->
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <junit.version>4.12</junit.version>
    <log4j.version>1.2.17</log4j.version>
    <lombok.version>1.16.18</lombok.version>
    <mysql.version>5.1.47</mysql.version>
    <druid.version>1.1.16</druid.version>
    <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
  </properties>


  <!-- 子模块继承之后,提供作用:锁定版本+子modlue不用写groupId和version  -->
  <dependencyManagement>
    <dependencies>
      <!--spring boot 2.2.2-->
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>2.2.2.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <!--spring cloud Hoxton.SR1-->
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Hoxton.SR1</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <!--spring cloud alibaba 2.1.0.RELEASE-->
      <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-alibaba-dependencies</artifactId>
        <version>2.1.0.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>${mysql.version}</version>
      </dependency>
      <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>${druid.version}</version>
      </dependency>
      <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>${mybatis.spring.boot.version}</version>
      </dependency>
      <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>${junit.version}</version>
      </dependency>
      <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>${log4j.version}</version>
      </dependency>
      <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>
        <optional>true</optional>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <fork>true</fork>
          <addResources>true</addResources>
        </configuration>
      </plugin>
    </plugins>
  </build>

</project>

注意:父工程pom文件中${*.version}全部爆红是不用管它的,也不用换版本。原因是maven本地仓库里没有这个版本的。在子pom中实际引入依赖后,M会自动下载

4.2.3 DependencyManagement 与 Dependencies

4.2.3.1 DependencyManagement

Maven 使用dependencyManagement 元素来提供了一种管理依赖版本号的方式

通常会在一个组织或者项目的最顶层的父POM 中看到dependencyManagement 元素。

使用pom.xml 中的dependencyManagement 元素能让所有子项目引用一个依赖而不用显式的列出版本号。

Maven 会沿着父子层次向上走,直到找到一个拥有dependencyManagement 元素的项目,然后它就会使用这个 dependencyManagement 

元素中指定的版本号。

image-20220326101610252

image-20220326101610252

这样做的好处就是:如果有多个子项目都引用同一样依赖,则可以避免在每个使用的子项目里都声明一个版本号,这样当想升级或切换到另一个版本时,只需要在顶层父容器里更新,而不需要一个一个子项目的修改 ;另外如果某个子项目需要另外的一个版本,只需要独立声明version即可。
  • dependencyManagement里只是声明依赖,并不实现引入依赖,因此子项目需要显示的声明需要用的依赖。

  • 如果不在子项目中声明依赖,是不会从父项目中继承下来的;只有在子项目中写了该依赖项,并且没有指定具体版本,

才会从父项目中继承该项,并且version和scope都读取自父pom;

  • 如果子项目中指定了版本号,那么会使用子项目中指定的jar版本。
4.2.3.2 Maven中跳过单元测试

image-20220326101610252

4.2.4 mvn:install将父pom存入仓库以便子pom继承

image-20220326101610252

4.3 Rest微服务工程构建

image-20220326101610252

4.3.1 构建步骤

4.3.1.1 cloud-provider-payment8001

微服务提供者支付Module模块

image-20220326101610252

image-20220326101610252

步骤:

1. 建module
2. 改pom
3. 写yml
4. 主启动
5. 业务类
	5.1 建表sql
	5.2 entities
	5.3 dao/mapper
	5.4 service
	5.5 controller
6. 测试
  1. 建module

    image-20220326101610252

    查看父工程pom文件变化:

    <groupId>com.lebrwcd</groupId>
    <artifactId>SpringCloud</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    
    <modules>
      <module>cloud-provider-payment8001</module>
    </modules>
    
  2. 改pom

    <?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        
        <!--父工程-->
        <parent>
            <artifactId>SpringCloud</artifactId>
            <groupId>com.lebrwcd</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    	
        <!--gav坐标不用写g,v,用父类的-->
        <artifactId>cloud-provider-payment8001</artifactId>
    
        <!--父工程只是声明依赖,子工程实际引入依赖,版本号不用写,父工程统一管理-->
        <dependencies>
            <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.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>1.1.10</version>
            </dependency>
            <!--mysql-connector-java-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
            <!--jdbc-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
    </project>
    
  3. 写yml

    server:
      port: 8001
    
    spring:
      application:
        name: cloud-provider-payment8001
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource  # 当前数据源操作类型
        driver-class-name: org.gjt.mm.mysql.Driver  # mysql驱动包 com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/springcloud_db?useUnicode=true&characterEncoding=utf-8&useSSL=false
        username: root
        password: wcd0209
    
    
    mybatis:
      mapper-locations: classpath:mapper/*.xml
      type-aliases-package: com.lebrwcd.springcloud.entities    # 所有Entity别名类所在包
    
  4. 主启动

    @SpringBootApplication
    public class paymentMain {
        public static void main(String[] args) {
            SpringApplication.run(paymentMain.class,args);
        }
    }
    
  5. 业务类

    1. 建表SQL

      CREATE TABLE `payment` (
      
                                 `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
      
                                 `serial` varchar(200) DEFAULT '',
      
                                 PRIMARY KEY (`id`)
      
      ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
      
    2. entities

      • 主实体Payment

        @Data
        @AllArgsConstructor
        @NoArgsConstructor
        public class Payment implements Serializable {
        
            private Long id;
            private String serial;
        
        }
        
      • Json封装体CommonResult

        @Data
        @AllArgsConstructor
        @NoArgsConstructor
        public class CommonResult<T> {
        
            private Integer code;
            private String message;
            private T      data;
        
            public CommonResult(Integer code,String message){
                this(code,message,null);
            }
        
        }
        
    3. dao

      • dao接口

        @Mapper		//mapper注解
        public interface PaymentDao {
        
            public int create(Payment payment);
        
            public Payment getPaymentById(@Param("id") Long id);
        
        }
        
      • dao接口映射文件mapper.xml src/main/resources/mapper/PaymentMapper.xml

        <?xml version="1.0" encoding="UTF-8" ?>
        <!DOCTYPE mapper
                PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
        <mapper namespace="com.lebrwcd.springcloud.dao.PaymentDao">
        
            <!--映射的实体map-->
            <resultMap id="PaymentMap" type="com.lebrwcd.springcloud.entities.Payment">
                <id column="id" property="id" jdbcType="BIGINT"/>
                <id column="serial" property="serial" jdbcType="VARCHAR"/>
            </resultMap>
            
            <insert id="create" parameterType="Payment" useGeneratedKeys="true" keyProperty="id">
                insert into payment(serial) values (#{serial})
            </insert>
         
            <select id="getPaymentById" resultMap="PaymentMap">
                select *
                from payment
                where id = #{id}
          </select>
        </mapper>
        
    4. service

      • service接口

        public interface PaymentService {
        
            int create(Payment payment);
        
            Payment getPaymentById(@Param("id") Long id);
        
        }
        
      • service实现类

        @Service	//service注解
        public class PaymentServiceimpl implements PaymentService {
        
            @Autowired
            private PaymentDao paymentDao;
        
            @Override
            public int create(Payment payment) {
                return paymentDao.create(payment);
            }
        
            @Override
            public Payment getPaymentById(Long id) {
                return paymentDao.getPaymentById(id);
            }
        }
        
    5. controller

      • Restful风格
      @RestController
      @Slf4j
      public class PaymentController {
      
          @Autowired
          private PaymentService paymentService;
      
          @PostMapping(value = "/payment/create")
          public CommonResult create(@RequestBody Payment payment){
      
              int result = paymentService.create(payment);
              log.info("插入数据返回结果:" + result);
      
              if(result > 0){
                  return new CommonResult(200,"插入成功",payment);
              }else{
                  return new CommonResult(404,"插入失败",null);
              }
          }
      
          @GetMapping(value = "/payment/get/{id}")
          public CommonResult get(@PathVariable("id") Long id){
      
              Payment payment = paymentService.getPaymentById(id);
              log.info("插入数据:{}",id);
      
              if(payment != null){
                  return new CommonResult(200,"查询成功",payment);
              }else{
                  return new CommonResult(404,"查询失败,所查id为:"+id,null);
              }
          }
      
      }
      
  6. 测试

    • http://localhost:8001/payment/get/1

    image-20220326101610252

​ post请求的去postman测试

image-20220326101610252

小总结

1. 建module
2. 改pom
3. 写yml
4. 主启动
5. 业务类
	5.1 建表sql
	5.2 entities
	5.3 dao/mapper
	5.4 service
	5.5 controller
6. 测试
	get-->浏览器地址栏
	其余请求-->postman
4.3.1.2 热部署
  1. Adding devtools to your project

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    
  2. Addinf plugin to your pom.xml

    下段配置我们粘贴进聚合父类总工程的pom.xml里

     <build>
        <finalName>cloud-provider-payment8001</finalName>
        <plugins>
          <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
              <fork>true</fork>
              <addResources>true</addResources>
            </configuration>
          </plugin>
        </plugins>
      </build>
    
    
  3. Enabling automatic build

    image-20220326101610252

  4. Update the value of

    image-20220326101610252

  5. 重启IDEA

4.3.1.3 cloud-consuemer-order80

微服务消费者订单Module模块

image-20220326101610252

同样的步骤:

  1. 建module

  2. 改pom

  3. 写yaml

    1. 这里我用的是81端口,80端口被占用了

      server:
      	port: 81
      
  4. 主启动

    @SpringBootApplication
    public class OrderMain81(){
        public static void main(String[] args){
            SpringApplication.run(OrderMain81.class,args);
        }
    }
    
  5. 业务类

    • entities:Payment、CommonResult

    • RestTemplate

      • RestTemplate是执行HTTP请求的同步阻塞式的客户端,它在HTTP客户端库(例如JDK HttpURLConnection,Apache HttpComponents,okHttp等)基础封装了更加简单易用的模板方法API。也就是说RestTemplate是一个封装,底层的实现还是java应用开发中常用的一些HTTP客户端。但是相对于直接使用底层的HTTP客户端库,它的操作更加方便、快捷,能很大程度上提升我们的开发效率。

        RestTemplate作为spring-web项目的一部分,在Spring 3.0版本开始被引入。RestTemplate类通过为HTTP方法(例如GET,POST,PUT,DELETE等)提供重载的方法,提供了一种非常方便的方法访问基于HTTP的Web服务。如果你的Web服务API基于标准的RESTful风格设计,使用效果将更加的完美。

      • Spring RestTemplate 是 Spring 提供的用于访问 Rest 服务的客户端,RestTemplate 提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率,是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集

      • 如何使用?

        ​ 使用restTemplate访问restful接口非常的简单粗暴无脑。

        ​ (url, requestMap, ResponseBean.class)这三个参数分别代表

        ​ REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。

        • 客户端向服务端提交的是post请求

        image-20220326101610252

        • 客户端向服务端提交的是get请求

          image-20220326101610252

    • config配置类

      • ApplicationContextConfig

        @Configuration
        public class ApplicationContextConfig {
        	
            //往容器中注入RestTemplate组件
            @Bean
            public RestTemplate getRestTemplate(){
                return new RestTemplate();
            }
        }
        
    • controller

      客户端只需要写controller,因为客户端(消费者)不可能能连接到后台服务的dao,service

      @RestController
      public class OrderController {
      
          public static final String SERV_URL = "http://localhost:8001" ;
      
          @Resource
          private RestTemplate restTemplate;
      
          @GetMapping("/consumer/payment/create")
          public CommonResult<Payment> create(Payment payment){
      
              //第一个参数是Rest请求的url,第二个参数是请求参数,第三个参数是响应类型
              return restTemplate.postForObject(SERV_URL+"/payment/create",payment,CommonResult.class);
          }
      
          @GetMapping("/consumer/payment/get/{id}")
          public CommonResult<Payment> getPayment(@PathVariable("id") Long id){
              //第一个参数是Rest请求的url,第二个参数是响应类型,第三个参数是url路径变量
              return restTemplate.getForObject(SERV_URL+"/payment/get/"+id,CommonResult.class,id);
          }
      }
      
  6. 测试

    image-20220326101610252

image-20220326101610252

null的原因是服务端的controller中的create方法中,请求参数没有用@RequestBody接收,加上@RequestBody就好了

image-20220326101610252

IDEA2021版的RunDashBoard就是Service

image-20220326101610252

4.3.1.4 工程重构

image-20220326101610252

  • 我们发现这两个服务之间有重复的内容,我们需要把重复的提取出来,打成jar包供两者使用

重构步骤:

  1. 新建module:cloud-api-common

  2. 写pom

    <?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>SpringCloud</artifactId>
            <groupId>com.lebrwcd</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>cloud-api-common</artifactId>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <!--Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。-->
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>5.1.0</version>
            </dependency>
        </dependencies>
    
    </project>
    
  3. 提取公用的entities

    1. 在cloud-api-common中新建entities

      image-20220326101610252

    2. 安装到本地maven仓库中,maven中,依此点击clean、install

      image-20220326101610252

    3. 删掉两个服务中entities包,在它们的Pom文件中加入:

      <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
          <groupId>com.lebrwcd</groupId>
          <artifactId>cloud-api-common</artifactId>
          <version>1.0-SNAPSHOT</version>
      </dependency>
      
  4. 重构完成

4.3.2 目前工程样图

image-20220326101610252

我们发现,自己这样一个提供者,一个消费者也可以运行,但是如果以后消费者多了,服务者是否有空闲能够对消费者进行服务?比如医院,假设一开始是一个病人一个医生,那可以很好的进行服务,但是如果病人多了,医生忙不过来,是否需要加上一种门诊,也就是挂号服务,还有多少流量,今天医生还能接多少病人啊等等情况,所以就出现了服务注册中心,服务提供者往服务注册中心注册自己的服务,下一节开始讲解服务注册中心

五、Eureka服务注册与发现

<image-20220326101610252

5.1 基础知识

5.1.1 什么是服务治理

Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务治理

在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务与服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。

5.1.2 什么是服务注册与发现

Eureka采用了CS的设计架构,Eureka Server 作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用 Eureka的客户端连接到 Eureka Server并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。

在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息 比如 服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者|服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用远程RPC调用框架,核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何RPC远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址))

image-20220326101610252

5.1.3 Eureka两组件

Eureka包含两个组件:Eureka Server和Eureka Client

5.1.3.1 Eureka Server

Eureka Server提供服务注册服务

@EnableEurekaServer

各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。

5.1.3.2 Eureka Client

@EnableEurekaClient

EurekaClient通过注册中心进行访问

是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)

5.2 单点Eureka

单点Eureka指的是只有一台服务注册中心

image-20220326101610252

5.2.1 Eureka-Server服务端 7001

服务注册中心,类似于物业公司

步骤:

  1. 建module : cloud-eureka-server7001

  2. 改pom

    <?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>SpringCloud</artifactId>
            <groupId>com.lebrwcd</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>cloud-eureka-server7001</artifactId>
    
        <dependencies>
            
            <!--eureka-server-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
            </dependency>
            
            <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
            <dependency>
                <groupId>com.lebrwcd</groupId>
                <artifactId>cloud-api-common</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
            <!--boot web actuator-->
            <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-devtools</artifactId>
                <scope>runtime</scope>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
            </dependency>
        </dependencies>
    
    </project>
    
  3. 写yaml

    server:
      port: 7001
    
    
    eureka:
      instance:
        hostname: localhost #eureka服务端的实例名称
      client:
        register-with-eureka: false  #false表示不向注册中心注册自己。
        fetch-registry: false  #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
        service-url:  #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
          defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
    
    1. 主启动 @EnableEurekaServer
    @SpringBootApplication
    @EnableEurekaServer   //声明自己是Eureka服务注册中心
    public class EurekaMain7001 {
    
        public static void main(String[] args) {
            SpringApplication.run(EurekaMain7001.class,args);
        }
    }
    
  4. 测试 地址栏上输入 :http://localhost:7001

    image-20220326101610252

5.2.2 Eureka-Client客户端 8001

服务提供者,需要先入驻物业公司(注册),才能对外提供服务

步骤:

  1. 改module,改之前的cloud-provicder-payment8001

  2. 改pom

    在原先的pom文件基础上加上:

    
            <!--eureka-client-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
    
  3. 写yaml

    server:
      port: 8001
    
    spring:
      application:
        name: cloud-provider-payment8001   #微服务名称
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource  # 当前数据源操作类型
        driver-class-name: org.gjt.mm.mysql.Driver  # mysql驱动包 com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/springcloud_db?useUnicode=true&characterEncoding=utf-8&useSSL=false
        username: root
        password: wcd0209
    
    #eureka配置
    eureka:
      client:
        #表示是否将自己注册进EurekaServer默认为true。
        register-with-eureka: true
        #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
        fetch-registry: true
        service-url:
          defaultZone: http://localhost:7001/eureka
    
    
    mybatis:
      mapper-locations: classpath:mapper/*.xml
      type-aliases-package: com.lebrwcd.springcloud.entities    # 所有Entity别名类所在包
    
  4. 主启动

    在主启动类上添加注解:EnableEurekaClient,表示自己将向注册中心Server注册,对外提供服务

  5. 测试:

    1. 需要先启动EurekaServer7001

    2. 访问:http://localhost:7001/

    3. spring:
      application:
      name: cloud-provider-payment8001 #微服务名称 这里的名称就是Eureka中注册进去的服务名称

      image-20220326101610252

    4. 自我保护机制

      image-20220326101610252

5.2.3 Eureka-Client 81

服务消费者,可以选择在注册中心注册,也可选择不注册,服务消费者与服务注册中心进行单点连接,获得服务注册中心中的服务,然后通过远程过程调用的方式获得服务提供者中对应的服务

步骤:

  1. 改module cloud-consumer-order81

  2. 改pom,在原先pom的基础上加上:

    
            <!--eureka-client-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
    
  3. 写yaml

    server:
      port: 81
    
    spring:
      application:
        name: cloud-order-service  #微服务名称
    
    eureka:
      client:
        fetch-registry: true  #抓取已有的注册信息
        #不注册到eureka服务注册中心中
        register-with-eureka: false
        service-url:
          defaultZone: http://localhost:7001/eureka
    
  4. 主启动,在主启动类上加入@EnableEurekaClient

  5. 测试

    1. 不注册到注册中心的情况

      image-20220326101610252

    2. 抓取注册中心中的服务,cloud-provider-payment8001,通过RPC远程过程调用

image-20220326101610252

5.3 Eureka集群

5.3.1 集群原理说明

image-20220326101610252

Eureka集群的原理:互相注册,相互守望

image-20220326101610252

5.3.2 Eureka集群环境构建步骤

步骤:

  1. 参考7001新建module7002

  2. 改7002的pom

  3. 修改host映射配置

    1. 找到C:\windows32\drivers\etc\中的host文件
    2. 修改映射配置进host文件
      • 127.0.0.1 eureka7001.com
      • 127.0.0.1 eureka7002.com
  4. 写yaml

    以前的单机配置:

    server:
      port: 7001
    
    eureka:
      instance:
        hostname: localhost #eureka服务端的实例名称
      client:
        #false表示不向注册中心注册自己。
        register-with-eureka: false
        #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
        fetch-registry: false
        service-url:
        #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
          defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
    

    现在的集群配置:

    • 7001:

      server:
        port: 7001
      
      
      eureka:
        instance:
          hostname: eureka7001.com #eureka服务端的实例名称
        client:
          register-with-eureka: false  #false表示不向注册中心注册自己。
          fetch-registry: false  #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
          service-url:  #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
            defaultZone: http://eureka7002.com:7002/eureka/   #互相注册,相互守望
      
    • 7002:

      server:
        port: 7002
      
      
      eureka:
        instance:
          hostname: eureka7002.com  #eureka服务端的实例名称
        client:
          register-with-eureka: false #服务注册中心不向自己注册
          fetch-registry: false   #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
          service-url:
            defaultZone: http://eureka7001.com:7001/eureka/
      

    注意:出现如下错误是因为yaml文件格式错误,tab,空格问题

    image-20220326101610252

  5. 主启动,在7002上面加上@EnableEurekaServer

  6. 测试:

    • 地址栏:http://eureka7001.com:7001

      image-20220326101610252

    • 地址栏:http://eureka7002.com:7002

      image-20220326101610252

    “互相注册,相互守望”

image-20220326101610252

5.3.3 将支付服务8001与订单服务81发布到上面两台Eureka集群

  1. 修改两个模块中的Yaml
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka  #集群版配置
  1. 测试:

    1. 先启动服务注册中心,EurekaServer,7001/7002
    2. 再启动服务提供者provider,8001
    3. 再启动服务消费者consumer,81
  2. image-20220326101610252

image-20220326101610252

5.3.4 服务提供者集群配置

image-20220326101610252

步骤:

  1. 新建module:cloud-provider-payment8002

  2. 拷贝8001的内容,除了主启动

  3. 修改yaml,复杂8001的,修改端口号为8002即可

    server:
      port: 8002
    
    spring:
      application:
        name: cloud-payment-Service   #微服务名称
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource  # 当前数据源操作类型
        driver-class-name: org.gjt.mm.mysql.Driver  # mysql驱动包 com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/springcloud_db?useUnicode=true&characterEncoding=utf-8&useSSL=false
        username: root
        password: wcd0209
    
    eureka:
      client:
        #表示是否将自己注册进EurekaServer默认为true。
        register-with-eureka: true
        #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
        fetch-registry: true
        service-url:
          #defaultZone: http://localhost:7001/eureka    #单机版
          defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka  #集群版
    
    
    mybatis:
      mapper-locations: classpath:mapper/*.xml
      type-aliases-package: com.lebrwcd.springcloud.entities    # 所有Entity别名类所在包
    
  4. 修改controller,为了后面负载均衡方便查看效果,8001/8002都改

    1. public class PaymentController
      {
          @Value("${server.port}")
          private String serverPort;
      
          @Resource
          private PaymentService paymentService;
      
          @PostMapping(value = "/payment/create")
          public CommonResult create(@RequestBody Payment payment)
          {
              int result = paymentService.create(payment);
              log.info("*****插入操作返回结果:" + result);
      
              if(result > 0)
              {
                  return new CommonResult(200,"插入成功,返回结果"+result+"\t 服务端口:"+serverPort,payment);
              }else{
                  return new CommonResult(444,"插入失败",null);
              }
      
  5. 测试

    1. 先开启服务注册中心,7001/7002

    2. 再开启服务提供者:8001/8002

    3. 观看服务注册中心

      image-20220326101610252

服务提供者自测:

image-20220326101610252

image-20220326101610252

5.3.5 负载均衡

继续上面的步骤,再开启服务消费者:81

image-20220326101610252

image-20220326101610252

我们刷新,发现端口号一直是8001,我们不是弄了两个服务提供者吗,怎么一直是8001,问题是我们服务消费者order中把服务提供者中的地址给写死了,如果在单机环境下可以这么做,但是在服务提供者集群环境下,不能这么做

//public static final String SERV_URL = "http://localhost:8001" ; 服务提供者集群环境下地址不能写死
//通过在eureka上注册过的微服务名称调用
public static final String SERV_URL = "http://CLOUD-PAYMENT-SERVICE" ; 

image-20220412175440991

好,我们再来测试一下:http://localhost:81/consumer/payment/get/5

image-20220326101610252

  • 这个需要使用@LoadBalanced赋予RestTemplate负载均衡的能力,默认采用轮询的方式

image-20220326101610252

结果:

image-20220326101610252

刷新一下:

image-20220326101610252

类似于Nginx中的负载均衡默认轮询方式,即请求过来了,会平均分配给Nginx子进程

5.3.6 小总结

5.3.6.1 单机Eureka

一台服务注册中心,一个服务提供者,一个服务消费者

image-20220326101610252

5.3.6.2 服务注册中心集群Eureka

多台服务注册中心,一台服务提供者,一个服务消费者

image-20220326101610252

5.3.6.3 Eureka常规部署

多台服务注册中心,多台服务提供者,一个服务消费者

image-20220326101610252

  • 服务注册中心之间互相注册,相互守望
  • 服务提供者如果微服务名一样,则处于一个微服务下,以端口号区分
  • 服务消费者与服务提供者通信时RestTemplate记得开启负载均衡

Eureka yaml配置

#服务注册中心:7001为例

#以eureka开始
eureka:
  #实例
  instance:
    hostname: eureka7001.com #eureka服务端的实例名称
  #往服务注册中心注册的对象  
  client:
    register-with-eureka: false  #false表示不向注册中心注册自己。一般是服务注册中心这么配置
    fetch-registry: false  #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    service-url:  #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
      defaultZone: http://eureka7002.com:7002/eureka/   #互相注册,相互守望
#服务提供者:

eureka:
  client:
    #表示是否将自己注册进EurekaServer默认为true。
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetch-registry: true
    service-url:
      #defaultZone: http://localhost:7001/eureka    #单机版
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka  #集群版
#服务消费者:

eureka:
  client:
    #服务消费者一定要拉取到服务注册中心中的注册信息,即有哪些服务
    fetch-registry: true
    #是否注册到服务注册中心,可false,可true
    register-with-eureka: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka

5.4 actuator微服务信息完善

5.4.1 主机名称修改

image-20220326101610252

5.4.1.1 修改服务提供者的yaml文件
eureka:
  client:
    #表示是否将自己注册进EurekaServer默认为true。
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetch-registry: true
    service-url:
      #defaultZone: http://localhost:7001/eureka    #单机版
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka  #集群版
  instance:
    instance-id: payment8001        
    prefer-ip-address: true
5.4.1.2 效果

image-20220413144740193

5.4.2 ip地址提示

image-20220326101610252

5.4.2.1 修改yaml
eureka:
  client:
    #表示是否将自己注册进EurekaServer默认为true。
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetch-registry: true
    service-url:
      #defaultZone: http://localhost:7001/eureka    #单机版
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka  #集群版
  instance:
    instance-id: payment8001      #实例id,展示在web页面  
    prefer-ip-address: true       #显示ip地址
5.4.2.2 效果

image-20220326101610252

5.5 服务发现Discovery

对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息

image-20220326101610252

5.5.1 对8001进行自测

  1. 修改controller

    @Resource
    private DiscoveryClient discoveryClient;
    
    
        @GetMapping("/payment/discovery")
        public Object discovert(){
    
            //获得该服务注册中心上有哪些微服务
            List<String> services = discoveryClient.getServices();
            for (String service : services) {
                log.info("*****service:" + service);
            }
    
            //通过微服务名称获得其对应的信息
            List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
            for (ServiceInstance instance : instances) {
                log.info(instance.getServiceId() + "**" + instance.getHost()+"  "+instance.getPort()+"  "+ instance.getUri());
            }
    
            return discoveryClient;
    
        }
    
    
  2. 修改主程序:加上注解@EnableDiscoveryClient

    @SpringBootApplication
    @EnableEurekaClient
    @EnableDiscoveryClient       
    public class PaymentMain8001 {
        public static void main(String[] args) {
            SpringApplication.run(PaymentMain8001.class,args);
        }
    }
    
  3. 测试:

image-20220326101610252

image-20220326101610252

5.6 Eureka自我保护

5.6.1 概述

保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式,

Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务。

如果在Eureka Server的首页看到以下这段提示,则说明Eureka进入了保护模式:

EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. 

RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE 
紧急!Eureka可能错误地声称实例已经启动,而实际上它们并没有。更新小于阈值,因此为了安全实例不会过期

image-20220326101610252

5.6.2 为什么会产生Eureka自我保护机制?

为了防止EurekaClient可以正常运行,但是 与 EurekaServer网络不通情况下,EurekaServer不会立刻将EurekaClient服务剔除

相当于你欠物业中心2000快,但是物业中心不会这么快把你从注册表中删除,而是会保留你的信息,直至你交钱了

5.6.2.1 什么是自我保护模式?

属于CAP分支里面的AP

默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生(延时、卡顿、拥挤)时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题——当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。

image-20220326101610252

`在自我保护模式中Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。

它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲解:好死不如赖活着

综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留)也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。

5.6.2.2 测试

采用单机的eureka测试,即EurekaServer为7001,EurekaClient为8001

image-20220326101610252

如果我们开启自我保护模式的话,假设我们8001出故障了,我们看看7001会不会马上把8001删除,如果开启保护模式的话是不会删除的

image-20220326101610252

我们发现:7001还有保存8001

image-20220413160433827

5.6.3 关闭自我保护机制

默认情况下自我保护机制是开启的,如何关闭自我保护机制呢?

image-20220326101610252

步骤:

  1. 去7001修改yaml

    eureka:
      instance:
        hostname: eureka7001.com #eureka服务端的实例名称
      client:
        register-with-eureka: false  #false表示不向注册中心注册自己。
        fetch-registry: false  #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
        service-url:  #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
          defaultZone: http://eureka7002.com:7002/eureka/
      server:
        #关闭自我保护机制,保证不可用服务被及时踢除
        enable-self-preservation: false
        eviction-interval-timer-in-ms: 2000
    

效果:

image-20220326101610252

  1. 修改8001的yaml

    eureka:
      client:
        #表示是否将自己注册进EurekaServer默认为true。
        register-with-eureka: true
        #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
        fetch-registry: true
        service-url:
          defaultZone: http://localhost:7001/eureka    #单机版
          #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka  #集群版
      instance:
        instance-id: payment8001
        prefer-ip-address: true
        #心跳检测与续约时间
        #开发时设置小些,保证服务关闭后注册中心能即使剔除服务
        #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
        lease-renewal-interval-in-seconds: 1
        #Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
        lease-expiration-duration-in-seconds: 2
    
  2. 测试:先启动7001,再启动8001,关闭8001

image-20220326101610252

关闭8001:假设8001出故障

超过2s后没有心跳直接将该服务剔除

image-20220326101610252

Eureka停更了怎么办??

image-20220326101610252

六、Zookeeper服务注册与发现

image-20220326101610252

6.1 Zookeeper启动(linux)

zookeeper是一个分布式协调工具,可以实现注册中心功能

关闭linux服务器防火墙后启动zookeeper服务器,zookeeper服务器替代Eureka服务器,zk作为服务注册中心

image-20220413171448228

  • ​ 启动Zk服务端(服务注册中心—》物业公司)

    ./zkServer.sh start

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1OylY3wK-1651920441550)(https://gitee.com/lebrwcd/typora/raw/master/img/image-20220413171531000.png)]

image-20220326101610252

  • 启动zk客户端,默认端口号2181

    /zkCli.sh

    image-20220326101610252

    一些基础环境要搭建好

    • centos中zk的服务要开启,关闭防火墙不然windows端无法访问
    • 启动zk客户端,要操控zk服务注册中心
    • windows端cmd命令ping一下centos的ip

6.2 zk服务提供者

步骤:

  1. 创建module:cloud-provider-payment8004

  2. 写pom

    <?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>SpringCloud</artifactId>
            <groupId>com.lebrwcd</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>cloud-provider-payment8004</artifactId>
    
        <dependencies>
        <!-- SpringBoot整合Web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
            <groupId>com.lebrwcd</groupId>
            <artifactId>cloud-api-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- SpringBoot整合zookeeper客户端 -->
            <!--我这里服务器端的zk版本是3.7.0的,比较稳定,所以没有视频中出现的错误-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    </project>
    
  3. 写yaml

    server:
      port: 8004
    
    spring:
      application:
        name: cloud-payment-Service   #微服务名称
      cloud:  #springcloud 整合 zookeeper
        zookeeper:  
          connect-string: 192.168.110.131:2181   #zk服务注册中心
        
    
  4. 主启动

    
    @SpringBootApplication
    @EnableDiscoveryClient  //该注解用于向使用consul或者zookeeper作为注册中心时注册服务
    public class PaymentMain8004 {
    
        public static void main(String[] args) {
            SpringApplication.run(PaymentMain8004.class,args);
        }
    
    }
    
  5. 业务类

    controller层

    @RestController
    @Slf4j
    public class PaymentController {
    
        @Value("${server.port}")
        private String serverPort;
    
        @GetMapping("/payment/zk")
        public Object zk(){
            return "springcloud with zookeeper: "+serverPort+"\t"+ UUID.randomUUID().toString();
    
        }
    }
    
  6. 启动8004,测试

    image-20220326101610252

  7. 到centos的zk服务注册中心查看

    image-20220326101610252

6.2.1 zookeeper中的服务节点是临时节点

在Eureka中,Eureka自动开启保护机制,Eureka监听一个服务,当该服务没有心跳时,Eureka不会删除,而是保存着

与Eureka不同的是,Zk就可能比较强硬了,你没心跳了,就给我爬,直接把你给剔除,当你下一次恢复心跳了,对于zk来说,你已经不是上一次那个你了,你的流水号会改变

测试:当关闭8004,一段时间后查看zk状态

image-20220326101610252

重启8004

image-20220326101610252

6.3 zk服务消费者

image-20220326101610252

步骤:

  1. 建module:cloud-consumerzk-order81

  2. 写pom

  3. 写yaml

  4. 主程序

  5. 业务类

    @RestController
    @Slf4j
    public class OrderController {
    
        private static final String SERV_URL = "http://cloud-payment-Service";
    
        @Autowired
        private RestTemplate restTemplate;
    
        @GetMapping("/consumer/payment/zk")
        public String invoke(){
            return restTemplate.getForObject(SERV_URL+"/payment/zk",String.class);
        }
    }
    
  6. 测试:http://localhost:81/consumer/payment/zk

    image-20220326101610252

image-20220326101610252

七、Consul服务注册与发现

7.1 Consul简介

官网:https://www.consul.io/

Consul 是一套开源的分布式服务发现配置管理系统,由 HashiCorp 公司用 Go 语言开发

提供了微服务系统中的服务治理、配置中心、控制总线等功能。这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建全方位的服务网格,总之Consul提供了一种完整的服务网格解决方案。

7.2 功能

  1. 服务发现:提供HTTP和DNS两种发现方式
  2. 健康监测:支持多种方式,HTTP、TCP、Docker、Shell脚本
  3. kv存储:Key、Value的存储方式
  4. 多数据中心:Consul支持多数据中心
  5. 可视化web界面

7.3 安装与允许

官网下载:https://www.consul.io/downloads

下载完成后解压,只有一个consul.exe文件

image-20220326101610252

在该路径下进入cmd,开发者模式

查看版本号:consule --version

image-20220326101610252

启动consul:consul agent -dev

<image-20220326101610252

访问web界面:`http://localhost:8500

image-20220326101610252

7.4 服务提供者

image-20220326101610252

步骤:

  1. 建module:cloud-provider-payment8006

  2. 改pom

    <!--SpringCloud consul-server -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
    
  3. 写pom

    server:
      port: 8006
    
    spring:
      application:
        name: cloud-provider-service
      cloud:
        consul:
          host: localhost
          port: 8500
          discovery:
            service-name: ${spring.application.name}
    
  4. 主启动

    @SpringBootApplication
    @EnableDiscoveryClient
    public class PaymentMain8006 {
    
        public static void main(String[] args) {
            SpringApplication.run(PaymentMain8006.class,args);
        }
    }
    
  5. 业务类

    @RestController
    @Slf4j
    public class PaymentController {
    
        @Value("${server.port")
        private String serverPort;
    
    
        @GetMapping("/payment/consul")
        public String payment(){
            return "springcloud with consul: "+serverPort+"\t\t"+ UUID.randomUUID().toString();
        }
    
    }
    
  6. 测试:先启动8006,将服务注册到consul

    image-20220326101610252

  7. http://localhost:8006/payment/consul

    image-20220326101610252>

7.5 服务消费者

步骤和zookeeper的基本一致

就是pom改一下依赖,yaml改一下端口号,controller中的SERV_URL改一下,其余一样

测试:

先启动8006,再启动81

image-20220326101610252

image-20220326101610252

consul和zk一样,当服务提供者出现故障的时候,会剔除掉该服务

image-20220326101610252

7.6 总结:三者异同

image-20220326101610252

7.6.1 CAP
  • C:Consistency(强一致性)
  • A:Availability(可用性)
  • P:Partition tolerance(分区容错性)
  • CAP理论关注粒度是数据,而不是整体系统设计的策略

最多只能同时较好的满足两个。

CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,

因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类:

CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。

CP - 满足一致性,分区容忍必的系统,通常性能不是特别高。

AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。

image-20220326101610252

CAP理论的一个例子。开学了,你和同桌互对寒假作业答案,对到一半同桌去了厕所,恰好老师进来要收作业,

你决定:

AP【交就交吧,有错也无所谓了,交作业要紧】;

CP【等同桌回来,把答案对完再交,满分要紧】

八、Ribbon负载均衡服务调用

8.1 概述

Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端 负载均衡的工具。

简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。

官网:https://github.com/Netflix/ribbon/wiki

Ribbon目前也进入维护模式,未来的替换方案是:SpringCloud LoadBalance

但目前主流还是Spring Cloud Ribbon

image-20220326101610252

image-20220326101610252

8.2 LB(Load Balance)

LB负载均衡(Load Balance)是什么

简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)。 Highly availability

常见的负载均衡有软件Nginx,LVS,硬件 F5等。

Ribbon本地负载均衡客户端 VS Nginx服务端负载均衡区别

  • Nginx服务器负载均衡,客户端所有请求都会交给nginx,然后由nginx实现转发请求。即负载均衡是由服务端实现的。

  • Ribbon本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。

8.2.1 集中式LB

在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx), 由该设施负责把访问请求通过某种策略转发

至服务的提供方;

医院的大门,根据医院排班进入哪个科室

8.2.2 进程内LB

将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。

Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。

医院科室,科室内有多个医生,询问有哪个医生是空闲的,选择合适的医生进行就诊

一句话:负载均衡 + RestTemplate调用

8.3 Ribbon负载均衡架构演示

image-20220326101610252

Ribbon是站在服务消费者端的

Ribbon在工作时分成两步:

  1. 第一步先选择 EurekaServer ,它优先选择在同一个区域内负载较少的server.

  2. 第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址(服务提供者)。

其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。

总结:Ribbon就是一个软负载均衡的客户端组件,它可以和其他所需请求的客户端结合使用,和Eureka结合只是其中一个实例

为什么我们在前面Eureka的例子中能实现负载均衡呢?

  • ribbon依赖引入

    <!--在引入EurekaClient的同时也引入了-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </dependency>
    
  • RestTemplate:@LoadBalance

image-20220326101610252

image-20220326101610252

原因是:我们在引入Eureka客户端的依赖时,它集成了Ribbon依赖

image-20220326101610252

8.3.1 二说RestTemplate

官网:https://docs.spring.io/spring-framework/docs/5.2.2.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html

常用方法:

image-20220326101610252

<T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables);

<T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables);

<T> T postForObject(URI url, @Nullable Object request, Class<T> responseType);

<T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables);

<T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables);

<T> ResponseEntity<T> postForEntity(URI url, @Nullable Object request, Class<T> responseType);
8.3.1.1 getForObject & getForEntity
  • getForObject返回的是json格式的字符串
  • getForEntity返回的内容比较详细,返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等
@GetMapping("/consumer/payment/get/{id}")
public CommonResult getPayment(@PathVariable("id") Long id){

    log.info("查询Id:" + id);
    return restTemplate.getForObject(SERV_URL+"/payment/get/"+id,CommonResult.class,id);
    //{"code":200,"message":"查询成功,port:8002","data":{"id":5,"serial":"zpp001"}}
}

@GetMapping("/consumer/paymentEntity/get/{id}")
public CommonResult<Payment> getPayment2(@PathVariable("id") Long id){

    ResponseEntity<CommonResult> entity = restTemplate.getForEntity(SERV_URL + "/payment/get/" + id, CommonResult.class);

    if(entity.getStatusCode().is2xxSuccessful()){
        return entity.getBody();
        //{"code":200,"message":"查询成功,port:8002","data":{"id":5,"serial":"zpp001"}}
    }else{
        return new CommonResult<>(444,"查询失败");
    }

}
8.3.1.2 postForObject & postForEntity
  • postForObject返回的是json格式的字符串

  • postForEntity返回的内容比较详细,返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等

    image-20220326101610252

8.4 Ribbon核心组件IRule

public interface IRule {
    /*
     * choose one alive server from lb.allServers or  通过特点算法中从服务列表中选取一个要访问的服务
     * lb.upServers according to key
     * 
     * @return choosen Server object. NULL is returned if none 
     *  server is available 
     */
    Server choose(Object var1);

    void setLoadBalancer(ILoadBalancer var1);

    ILoadBalancer getLoadBalancer();
}

IRule下的7中实现规则

image-20220326101610252

image-20220326101610252

image-20220326101610252

默认采用轮询规则

8.4.1 如何替换规则

步骤:

  1. 自定义配置类

    官方文档明确给出了警告:
    
    这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下(即主启动类所在包及其子包)
    
    否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。
    

    image-20220326101610252

    image-20220326101610252

image-20220326101610252

  1. 在主启动类上加@RibbonClient

    在启动该微服务的时候就能去加载我们的自定义Ribbon配置类,从而使配置生效

    @SpringBootApplication
    @EnableEurekaClient
    @RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration=MySelfRule.class)
    

    @RibbonClient属性

    • name/value:唯一标识一组客户端资源(服务提供者名称),包括负载平衡器。
    • configuration:自定义规则的类
  2. 测试:http://localhost:81/consumer/payment/get/1

    出现端口号随机的方式

8.5 Ribbon负载均衡算法

8.5.1 原理

算法:第n次请求数 % 集群数量 = 实际调用服务器位置下标

image-20220326101610252

8.5.2 RoundRibonRule源码

public Server choose(ILoadBalancer lb, Object key) {
    //如果没有设置负载均衡
    if (lb == null) {
        log.warn("no load balancer");
        return null;
    }
	
    //起始默认服务数为0
    Server server = null;
    int count = 0;
    while (server == null && count++ < 10) {
        //从负载均衡器中获取可达的服务,即活着的服务
        List<Server> reachableServers = lb.getReachableServers();
        //从负载均衡器获取所有服务
        List<Server> allServers = lb.getAllServers();
        //活着的服务的数量,Eureka-web界面中的Up(2),即有两台,8001,8002
        int upCount = reachableServers.size();
        //集群数量 8001,8002 两台
        int serverCount = allServers.size();

        if ((upCount == 0) || (serverCount == 0)) {
            log.warn("No up servers available from load balancer: " + lb);
            return null;
        }

        //通过increateAndGetModulo
        int nextServerIndex = incrementAndGetModulo(serverCount);
        -----------------------------------------------------
            								//传入集群数量 2
              private int incrementAndGetModulo(int modulo) {
        			for (;;) { //不是死循环,而是自旋锁
                        	//默认值是0
            				int current = nextServerCyclicCounter.get();
                        	//0+1 % 2 = 1
            				int next = (current + 1) % modulo;
                        								//CAS,JUC中的知识,即比较并替换
            				if (nextServerCyclicCounter.compareAndSet(current, next))
               						return next;
        					}
   				}
        --------------------------------------------------    
        //通过求的服务器下标得到对对应的服务器    
        server = allServers.get(nextServerIndex);

        if (server == null) {
            /* Transient. */
            Thread.yield();
            continue;
        }

        if (server.isAlive() && (server.isReadyToServe())) {
            return (server);
        }

        // Next.
        server = null;
    }

    if (count >= 10) {
        log.warn("No available alive servers after 10 tries from load balancer: "
                + lb);
    }
    return server;
}

8.5.3 手写

代码核心:负载均衡算法,第几次访问 % 服务实例数量 = index

  1. 获取服务实例的数量
  2. 获取第几次访问

步骤:

  1. 编写自己的LoadBalance接口

    负责获得服务实例,后续调用实例的方法

    public interface LoadBalance {
    
        ServiceInstance instances(List<ServiceInstance> serviceInstances);
    
    }
    
  2. 编写LoadBalance接口的实现类MyLB

    @Component   //让Spring扫描到进入容器
    public class MyLB implements LoadBalance{
    
        //原子值,初始化为0
        private AtomicInteger atomicInteger = new AtomicInteger(0);
    
        //获得第几次访问的值
        public int RequestNum(){
            int current;
            int next;
    
            do{
                current = this.atomicInteger.get();
                next = current >= 2147483647 ? 0 : current + 1;
            }while(!this.atomicInteger.compareAndSet(current,next));  //执行一次自旋锁,原子数 + 1
            System.out.println("*******第几次访问:"+next);
            return next;  //返回第几次访问的次数
    
        }
    
        @Override
        public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
    
            //轮询负载均衡算法,获得实际访问服务器的下标
            int index = RequestNum() % serviceInstances.size();
    
            ServiceInstance serviceInstance = serviceInstances.get(index);
    		
            //返回实际服务器实例
            return serviceInstance;
        }
    }
    

测试:

  1. 注释掉config.ApplicationContextConfig中RestTemplate的 @LoadBalance注解

  2. 启动7001/7002

  3. 修改8001/8002 controller

    @GetMapping(value = "/payment/lb")
    public String getPaymentLB()
    {
        return serverPort;
    }
    
  4. 修改order81controller

    //注意导包正确  org.springframework.cloud.client.discovery.DiscoveryClient;
    @Resource 
    private DiscoveryClient discoveryClient;
    
    //注入自己的
    @Resource
    private LoadBalance loadBalance;
    
    
    @GetMapping("/consumer/payment/lb")
    public String lb(){
    
        //通过服务发现得到服务名为CLOUD-PAYMENT-SERVICE的所有实例
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
    
        if(instances == null || instances.size() <= 0){
            return null;
        }
    
        ServiceInstance instances1 = loadBalance.instances(instances);
        URI uri = instances1.getUri();
    
        return restTemplate.getForObject(uri+"/payment/lb",String.class);
    
    }
    
  5. http://localhost:81/consumer/payment/lb

    image-20220326101610252

image-20220326101610252

image-20220326101610252

九、OpenFeign服务接口调用

image-20220326101610252

9.1 概述

9.1.1 是什么

官网解释:

https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle/#spring-cloud-openfeign

image-20220326101610252

9.1.2 能干嘛

image-20220326101610252

9.1.3 Feign和OpenFeign的区别

image-20220326101610252

9.2 使用步骤

image-20220326101610252

image-20220326101610252

  1. 建module:cloud-consuemr-openfeign-order81

  2. 改pom

    <!--openfeign-->
    <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
  3. 写yML

    server:
        port: 81
    
    
    eureka:
      client:
        register-with-eureka: false
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
    
  4. 主启动

    @SpringBootApplication
    @EnableFeignClients  //在微服务启动的时候,激活OpenFeign
    public class OpenFeignOrderMain01 {
        public static void main(String[] args) {
            SpringApplication.run(OpenFeignOrderMain01.class,args);
        }
    }
    
  5. 业务类

    1. 微服务接口

      @Component    //spring容器可扫描到以注入
      @FeignClient(value = "CLOUD-PAYMENT-SERVICE")   //使用OpenFeign,对应的微服务名称为:xxx
      public interface PaymentFeignService {
      
          //微服务接口
          @GetMapping(value = "/payment/get/{id}")
          public CommonResult get(@PathVariable("id") Long id);
      
      }
      
    2. controller

      @RestController
      @Slf4j
      public class FeignController {
      
          @Autowired
          private PaymentFeignService paymentFeignService;
      
          @GetMapping("/consumer/payment/get/{id}")
          public CommonResult get(@PathVariable("id") long id){
      
              //不再通过RestTemplate,调用微服务接口
              return paymentFeignService.get(id);
          }
      }
      

      image-20220326101610252

  6. 测试

    image-20220326101610252

    image-20220326101610252

  7. 总结:

    image-20220326101610252

  • 服务提供者的controller编写好一个方法
  • (服务提供者对外暴露服务接口)在服务消费者端的service层编写微服务接口(@FeignClient),微服务名称为服务提供者的spring.application.name,前提是在服务者消费者端的主启动类需要添加OpenFeign激活注解@EnableFiegnClient
  • 服务消费者的controller端调用微服务接口

9.3 OpenFeign超时控制

image-20220326101610252

默认OpenFeign客户端只等待一秒钟,但是服务端处理需要超过1秒钟,导致Feign客户端不想等待了,直接返回报错。

为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制。

OpenFeign默认支持Ribbon

image-20220326101610252


server:
  port: 80

eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

### OpenFeign超时控制的yaml配置

#设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
  ReadTimeout: 5000   #5s
#指的是建立连接后从服务器读取到可用资源所用的时间
  ConnectTimeout: 5000

9.4 OpenFeign日志

Feign 提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解 Feign 中 Http 请求的细节。

说白了就是对Feign接口的调用情况进行监控和输出

9.4.1 日志级别

NONE:默认的,不显示任何日志;

BASIC:仅记录请求方法、URL、响应状态码及执行时间;

HEADERS:除了 BASIC 中定义的信息之外,还有请求和响应的头信息;

FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。

9.4.2 配置

  1. Config类配置

    @Configuration
    public class FeignConfig {
    
        @Bean
        Logger.Level feignloggerlever(){
            return Logger.Level.FULL;
        }
    
    }
    
  2. yaml文件配置

    server:
        port: 81
    
    
    eureka:
      client:
        register-with-eureka: false
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
    
    logging:
      level:
        # feign日志以什么级别监控哪个接口
        com.lebrwcd.springcloud.service.PaymentFeignService: debug
    

image-20220326101610252

on.run(OpenFeignOrderMain01.class,args);
}
}


5. 业务类

1. 微服务接口

   ```java
   @Component    //spring容器可扫描到以注入
   @FeignClient(value = "CLOUD-PAYMENT-SERVICE")   //使用OpenFeign,对应的微服务名称为:xxx
   public interface PaymentFeignService {
   
       //微服务接口
       @GetMapping(value = "/payment/get/{id}")
       public CommonResult get(@PathVariable("id") Long id);
   
   }
   ```

2. controller

   ```java
   @RestController
   @Slf4j
   public class FeignController {
   
       @Autowired
       private PaymentFeignService paymentFeignService;
   
       @GetMapping("/consumer/payment/get/{id}")
       public CommonResult get(@PathVariable("id") long id){
   
           //不再通过RestTemplate,调用微服务接口
           return paymentFeignService.get(id);
       }
   }
   ```

   [外链图片转存中...(img-10J0m4Ax-1651920441582)]

6. 测试

[外链图片转存中...(img-JIvOjSPs-1651920441583)]

[外链图片转存中...(img-X2HyVaFe-1651920441584)]

7. 总结:

[外链图片转存中...(img-IGvOlRCl-1651920441584)]

- 服务提供者的controller编写好一个方法
- (服务提供者对外暴露服务接口)在服务消费者端的service层编写微服务接口(`@FeignClient`),微服务名称为服务提供者的spring.application.name,前提是在服务者消费者端的主启动类需要添加OpenFeign激活注解`@EnableFiegnClient`
- 服务消费者的controller端调用微服务接口



9.3 OpenFeign超时控制
---

[外链图片转存中...(img-pyYz5jUJ-1651920441585)]

**默认OpenFeign客户端只等待一秒钟**,但是服务端处理需要超过1秒钟,导致Feign客户端不想等待了,直接返回报错。

为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制。

**OpenFeign默认支持Ribbon**

[外链图片转存中...(img-AYGOR8nd-1651920441585)]

```yaml

server:
port: 80

eureka:
client:
 register-with-eureka: false
 service-url:
   defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

### OpenFeign超时控制的yaml配置

#设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000   #5s
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000

9.4 OpenFeign日志

Feign 提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解 Feign 中 Http 请求的细节。

说白了就是对Feign接口的调用情况进行监控和输出

9.4.1 日志级别

NONE:默认的,不显示任何日志;

BASIC:仅记录请求方法、URL、响应状态码及执行时间;

HEADERS:除了 BASIC 中定义的信息之外,还有请求和响应的头信息;

FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。

9.4.2 配置

  1. Config类配置

    @Configuration
    public class FeignConfig {
    
        @Bean
        Logger.Level feignloggerlever(){
            return Logger.Level.FULL;
        }
    
    }
    
  2. yaml文件配置

    server:
        port: 81
    
    
    eureka:
      client:
        register-with-eureka: false
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
    
    logging:
      level:
        # feign日志以什么级别监控哪个接口
        com.lebrwcd.springcloud.service.PaymentFeignService: debug
    

[外链图片转存中…(img-Uz3PvMuc-1651920441585)]

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值