SpringCloud学习笔记

SpringCloud学习笔记

文章目录


一、什么是微服务

  • 微服务是一种架构风格
  • 一个应用拆分为一组小型服务
  • 每个服务运行在自己的进程内,也就是可独立部署和升级
  • 服务之间使用轻量级HTTP交互
  • 服务围绕业务功能拆分
  • 可以由全自动部署机制独立部署
  • 去中心化,服务自治。服务可以使用不同的语言、不同的存储技术

二、分布式微服务架构

在这里插入图片描述
主要技术维度:

  • 服务注册与发现
  • 服务调用
  • 服务降级
  • 负载均衡
  • 服务熔断
  • 服务消息队列
  • 服务网关
  • 配置中心管理
  • 自动化构建部署
  • 服务监控
  • 全链路追踪
  • 服务定时任务
  • 调度操作

SpringCloud是当下最流行的分布式微服务架构,是多种微服务架构落地技术的集合体,俗称微服务全家桶。部分cloud组件情况如下(×表示停更不停用)
在这里插入图片描述

三、微服务架构构建

1. 约定>配置>编码

2. IDEA新建project工作空间

2.1. 微服务cloud整体聚合父工程Project

  • 新建Maven项目
  1. New Project - maven工程 - create from archetype: maven-archetype-site
  2. 聚合总父工程名字
  3. Maven选版本
  4. 工程名字
  5. 字符编码 - Settings - File encoding
  6. 注解生效激活 - Settings - Annotation Processors
  7. Java编译版本选8
  8. File Type过滤 - Settings - File Type

2.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">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.zpl.springcloud</groupId>
  <artifactId>cloud2021</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>8.0.22</mysql.version>
    <druid.version>1.1.16</druid.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>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.0.5</version>
      </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>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>

2.3. Maven中的细节

Maven中的DependencyManagement和Dependencies

  • Maven 使用dependencyManagement 元素来提供了一种管理依赖版本号的方式。
  • 通常会在一个组织或者项目的最顶层的父POM 中看到dependencyManagement 元素。 使用pom.xml 中的dependencyManagement 元素能让所有在子项目中引用一个依赖而不用显式的列出版本号
  • Maven 会沿着父子层次向上走,直到找到一个拥有dependencyManagement 元素的项目,然后它就会使用这个dependencyManagement 元素中指定的版本号。 这样做的好处就是:如果有多个子项目都引用同一样依赖,则可以避免在每个使用的子项目里都声明一个版本号,这样当想升级或切换到另一个版本时,只需要在顶层父容器里更新,而不需要一个一个子项目的修改 ;
  • 另外如果某个子项目需要另外的一个版本,只需要声明version就可。
  • dependencyManagement里只是声明依赖,并不实现引入,因此子项目需要显示的声明需要用的依赖。 * 如果不在子项目中声明依赖,是不会从父项目中继承下来的;只有在子项目中写了该依赖项,并且没有指定具体版本, 才会从父项目中继承该项,并且version和scope都读取自父pom; * 如果子项目中指定了版本号,那么会使用子项目中指定的jar版本。

父工程创建完成执行mvn : install将父工程发布到仓库方便子工程继承。

3. Rest微服务工程构建

3.1. 构建cloud-provider-payment8001微服务提供者支付Module模块

新建Module,选择新建Maven项目,新建完成,父工程POM文件出现modules标签以及具体的模块名称

更改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>cloud2021</artifactId>
        <groupId>org.zpl.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <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>com.baomidou</groupId>
            <artifactId>mybatis-plus-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>

写yml

spring:
  application:
    name: cloud-payment-service
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource  #当前数据源操作类型
    driver-class-name: com.mysql.cj.jdbc.Driver   #MySQL驱动包
    url: jdbc:mysql://localhost:3306/springcloud?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimeZone=GMT%2B8
    username: root
    password: zpl809020

server:
  port: 8001

编写启动类(添加扫描mapper的注解)

@MapperScan("com.zpl.springcloud.mapper")
@SpringBootApplication
public class PaymentMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8001.class,args);
    }
}

编写业务类

  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
  1. 新建实体类Payment和统一结果处理类Result(JSON封装类)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment {
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    private String serial;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {
    private Integer code;
    private String message;
    private T data;

    public Result(Integer code, String message)
    {
        this(code,message,null);
    }

}
  1. 新建Mapper接口PaymentMapper
@Repository
public interface PaymentMapper extends BaseMapper<Payment> {
}
  1. 新建Service接口PaymentService以及该接口的实现类PaymentServiceImpl
public interface PaymentService extends IService<Payment> {
}
@Service
public class PaymentServiceImpl extends ServiceImpl<PaymentMapper, Payment> implements PaymentService {
}
  1. 新建控制类PaymentController
@RestController
@Slf4j
@RequestMapping("/payment")
public class PaymentController {

    @Autowired
    private PaymentService paymentService;

    @Value("${server.port}")
    private String serverPort;

    @PostMapping("/create")
    public Result create(@RequestBody Payment payment)
    {
        paymentService.save(payment);
        return new Result(200,"插入成功",serverPort);
    }

    @GetMapping("/list")
    public Result list()
    {
        QueryWrapper<Payment> wrapper = new QueryWrapper();
        List<Payment> list = paymentService.list(wrapper);
        return new Result(200,"查找成功",list + serverPort);
    }

}

  1. 测试通过,模块完成

3.2. 开启热部署

开发时开启,项目上线后关闭。
添加依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

聚合父类总工程的pom.xml中添加插件

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

开启自动编译
IDEA设置中搜索compiler,勾选下面四项
在这里插入图片描述
打开编译器自动编译
IDEA全局搜索Registry,打开,并勾选下面两项
在这里插入图片描述
在这里插入图片描述
重启IDEA,完成

3.3. 构建cloud-consumer-order80微服务消费者订单Module模块

新建模块cloud-consumer-order80

更改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>cloud2021</artifactId>
        <groupId>org.zpl.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumer-order80</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.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>

写yml

server:
  port: 80

编写启动类

@SpringBootApplication
public class OrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain80.class,args);
    }
}

编写业务类

  1. 新建实体类Payment和统一结果处理类Result(JSON封装类)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment {
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    private String serial;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {
    private Integer code;
    private String message;
    private T data;

    public Result(Integer code, String message)
    {
        this(code,message,null);
    }

}
  1. 首说RestTemplate
  • 概念:RestTemplate提供了多种便捷访问远程Http服务的方法, 是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集
  • 官网地址 使用使用restTemplate访问restful接口非常的简单粗暴无脑。(url, requestMap, ResponseBean.class)这三个参数分别代表 REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。
  1. config目录下新建配置类ApplicationConfig
@Configuration
public class ApplicationConfig {

    @Bean
    public RestTemplate getRestTemplate()
    {
        return new RestTemplate();
    }
}
  1. 新建控制类OrderController
@RestController
@Slf4j
@RequestMapping("/consumer")
public class OrderController {

    public static final String PAYMENT_URL = "http://localhost:8001";

    @Resource
    private RestTemplate restTemplate;


    @GetMapping("/payment/create")
    public Result<Payment> create(Payment payment)
    {
        return restTemplate.postForObject(PAYMENT_URL + "/payment/create",payment,Result.class);
    }

    @GetMapping("/payment/list")
    public Result<Payment> list()
    {
        return restTemplate.getForObject(PAYMENT_URL + "/payment/list",Result.class);
    }

}

  1. 运行生产者payment8001服务和消费者order80服务,通过Controller中设置的路由访问,成功通过。实现了消费者调用生产者的服务。

3.4. 工程重构

在上面的生产者payment8001服务和消费者order80服务模块中含有相同实体类等重复部分。为此,新建一个模块,专门存放整个工程的公共api

新建模块cloud-api-commons

更改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>cloud2021</artifactId>
        <groupId>org.zpl.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-api-commons</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>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.1.0</version>
        </dependency>
    </dependencies>
</project>

编写实体类和JSON封装类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment {
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    private String serial;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {
    private Integer code;
    private String message;
    private T data;

    public Result(Integer code, String message)
    {
        this(code,message,null);
    }

}

Maven 命令先clean后install

改造Order80和Payment8001模块

  • 删除各自的实体类和JSON封装类
  • 在各自POM文件引入自定义api通用包
        <dependency>
            <groupId>org.zpl.springcloud</groupId> <!--引入自定义的api通用包,可以使用Payment支付entity -->
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>

四、Eureka服务注册与发现

1. 基础知识

服务治理

  • Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务治理
  • 在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务于服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。

服务注册

  • Eureka采用了CS的设计架构,Eureka Server 作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用 Eureka的客户端连接到 Eureka Server并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。
  • 在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息 比如 服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者|服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址))下左图是Eureka系统架构,右图是Dubbo的架构,请对比
    在这里插入图片描述

Eureka两个组件

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

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

2. 单机Eureka构建步骤

2.1. 构建Eureka Server

新建Module:cloud-eureka-server7001

更改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>cloud2021</artifactId>
        <groupId>org.zpl.springcloud</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>
        <dependency>
            <groupId>org.zpl.springcloud</groupId>   <!--引入自定义的api通用包,可以使用Payment支付entity -->
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </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-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>

写yml

server:
  port: 7001

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

编写启动类

@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001 {
    public static void main(String[] args) {
        SpringApplication.run(EurekaMain7001.class,args);
    }
}

测试
http://localhost:7001/,成功进入eureka首页

2.2. 支付微服务8001入驻EurekaServer7001

更改已有模块cloud-provider-payment8001下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>cloud2021</artifactId>
        <groupId>org.zpl.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-provider-payment8001</artifactId>

    <dependencies>
        <!--  eureka client -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.zpl.springcloud</groupId>   <!--引入自定义的api通用包,可以使用Payment支付entity -->
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </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>com.baomidou</groupId>
            <artifactId>mybatis-plus-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>

写yml

spring:
  application:
    name: cloud-payment-service
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource  #当前数据源操作类型
    driver-class-name: com.mysql.cj.jdbc.Driver   #MySQL驱动包
    url: jdbc:mysql://localhost:3306/springcloud?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimeZone=GMT%2B8
    username: root
    password: zpl809020

server:
  port: 8001

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

编写启动类(开启eureka客户端,@EnableEurekaClient)

@MapperScan("com.zpl.springcloud.mapper")
@SpringBootApplication
@EnableEurekaClient
public class PaymentMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8001.class,args);
    }
}

测试
先启动EurekaServer7001,再启动该模块,浏览器访问http://localhost:7001/,可以看到Payment8001已作为Eureka client入驻Eureka Server7001,测试http请求,成功运行。

2.2. 订单微服务80入驻EurekaServer7001

更改已有模块cloud-consumer-order80下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>cloud2021</artifactId>
        <groupId>org.zpl.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumer-order80</artifactId>

    <dependencies>
        <!--  eureka client -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.zpl.springcloud</groupId> <!--引入自定义的api通用包,可以使用Payment支付entity -->
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </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-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>

写yml

spring:
  application:
    name: cloud-order-service

server:
  port: 80

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

编写启动类(开启eureka客户端,@EnableEurekaClient)

@SpringBootApplication
@EnableEurekaClient
public class OrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain80.class,args);
    }
}

测试
先启动EurekaServer7001,再启动Payment8001模块,最后启动该模块。浏览器访问http://localhost:7001/,可以看到Payment8001和Order80已作为Eureka client入驻Eureka Server7001。测试两者的http请求,均正常运行

3. 集群Eureka构建

3.1. Eureka集群原理

在这里插入图片描述
问题:微服务RPC远程服务调用最核心的是什么
高可用,试想你的注册中心只有一个, 它出故障了那就麻烦了,会导致整个为服务环境不可用。所以一个合理的解决办法:搭建Eureka注册中心集群 ,实现负载均衡+故障容错

3.2. EurekaServer集群构建过程

参考cloud-eureka-server7001,新建cloud-eureka-server7002模块。注意以下变化

修改配置映射hosts文件

  • Windows:找到C:\Windows\System32\drivers\etc路径下的hosts文件,hosts文件中添加127.0.0.1 eureka7001.com和127.0.0.1 eureka7002.com
  • Linux:命令行使用vim编辑器进入/etc/hosts文件,同样添加127.0.0.1 eureka7001.com和127.0.0.1 eureka7002.com

写yml(集群模式)

  • 更改EurekaServer7001的yml文件
server:
  port: 7001

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

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

3.3. 将支付服务8001微服务发布到上面2台Eureka集群配置中

更改payment8001的yml如下:

spring:
  application:
    name: cloud-payment-service
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource  #当前数据源操作类型
    driver-class-name: com.mysql.cj.jdbc.Driver   #MySQL驱动包
    url: jdbc:mysql://localhost:3306/springcloud?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimeZone=GMT%2B8
    username: root
    password: zpl809020

server:
  port: 8001

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

3.4. 将订单服务80微服务发布到上面2台Eureka集群配置中

更改order80的yml如下:

spring:
  application:
    name: cloud-order-service

server:
  port: 80

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

3.5. 测试

  • 先要启动EurekaServer,7001/7002服务
  • 再要启动服务提供者provider,8001
  • 再要启动消费者,80
    结果,浏览器访问http://eureka7001.com:7001/ 和http://eureka7002.com:7002/能看到支付微服务8001和订单微服务80成功入驻7001和7002上。且7001和7002互为备份,测试http请求也均OK。

3.6. 构建支付服务提供者集群环境(在8001基础上新建一个8002支付微服务)

新建cloud-provider-payment8002,具体参考8001,有以下注意点

写yml

spring:
  application:
    name: cloud-payment-service
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource  #当前数据源操作类型
    driver-class-name: com.mysql.cj.jdbc.Driver   #MySQL驱动包
    url: jdbc:mysql://localhost:3306/springcloud?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimeZone=GMT%2B8
    username: root
    password: zpl809020

server:
  port: 8002

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

新建主启动类

@MapperScan("com.zpl.springcloud.mapper")
@SpringBootApplication
@EnableEurekaClient
public class PaymentMain8002 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8002.class,args);
    }
}

mapper、service和controller从8001粘贴

3.7. 负载均衡

现在,我们有了EurekaServer集群,有7001和7002两个注册中心。我们还有两个支付微服务(服务提供者)8001和8002,外加一个订单微服务80(服务消费者)。这时订单微服务80可以调用两个支付微服务8001和8002中的任意一个。这就需要用到负载均衡技术,根据需要调用其中一个合适的支付微服务。

开启注解
进入order80微服务下配置文件config中的配置类ApplicationConfig,开启@LoadBalanced注解

@Configuration
public class ApplicationConfig {

    @Bean
    @LoadBalanced   //restTemplate开启负载均衡
    public RestTemplate getRestTemplate()
    {
        return new RestTemplate();
    }
}

更改Controller中写死的调用支付微服务的地址

将原本写死的8001地址:public static final String PAYMENT_URL = "http://localhost:8001"更改为下面服务名(两个支付微服务均在yml中命名为CLOUD-PAYMENT-SERVICE),由于开启负载均衡功能,会根据默认策略(轮询)从应用名为CLOUD-PAYMENT-SERVICE的两个微服务中交替调用。

public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";

测试

  • 先要启动EurekaServer,7001/7002服务
  • 再要启动服务提供者provider,8001/8002服务
  • 最后启动服务消费者consumer,80服务
  • 浏览器访问http://localhost/consumer/payment/list,发现端口号8001和8002交替出现,即成功实现负载均衡。
  • Ribbon和Eureka整合后Consumer可以直接调用服务而不用再关心地址和端口号,且该服务还有负载功能了。

4. actuator微服务信息完善

4.1. 主机名称:服务名称修改

修改支付微服务8001和支付微服务8002的yml
(修改服务名instance-id)

#payment8001配置文件修改部分
eureka:
  client:
    #表示是否将自己注册进EurekaServer默认为true。
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka , http://eureka7002.com:7002/eureka  #集群模式
  instance:
    instance-id: payment8001
#payment8002配置文件修改部分
eureka:
  client:
    #表示是否将自己注册进EurekaServer默认为true。
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka , http://eureka7002.com:7002/eureka  #集群模式
  instance:
    instance-id: payment8002

完成修改,访问eureka7001或eureka7002服务注册中心,能看到应用CLOUD-PAYMENT-SERVICE下的两个微服务分别为payment8001和payment8002。

4.2. 访问信息有IP信息提示

默认鼠标放在微服务payment8001和payment8002上,无IP信息提示,类似修改两个微服务yml文件即可
写yml

#以8001服务为例,开启ip信息提示。prefer-ip-address: true,8002亦是如此
instance:
    instance-id: payment8001
    prefer-ip-address: true

再次访问eureka注册中心,鼠标放置payment8001或payment8002上,出现ip信息提示

5. 服务发现Discovery

对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息,下面以payment8001微服务为例说明。
修改cloud-provider-payment8001的Controller

@RestController
@Slf4j
@RequestMapping("/payment")
public class PaymentController {

    @Autowired
    private PaymentService paymentService;

    @Resource
    private DiscoveryClient discoveryClient;


    @Value("${server.port}")
    private String serverPort;

    @PostMapping("/create")
    public Result create(@RequestBody Payment payment)
    {
        paymentService.save(payment);
        return new Result(200,"插入成功",serverPort);
    }

    @GetMapping("/list")
    public Result list()
    {
        QueryWrapper<Payment> wrapper = new QueryWrapper();
        List<Payment> list = paymentService.list(wrapper);
        return new Result(200,"查找成功",list + serverPort);
    }

    @GetMapping("/discovery")
    public Object discovery()
    {
       List<String> list = discoveryClient.getServices();  //获取注册进入eureka的所有服务
       for (String string : list)
       {
           log.info(string);
       }

       List<ServiceInstance> instance = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");//获取所有支付微服务信息
       for (ServiceInstance serviceInstance : instance)
       {
           log.info(serviceInstance.getServiceId() + "\t" + serviceInstance.getHost() + "\t" +
                   serviceInstance.getPort() + "\t" + serviceInstance.getUri());
       }

       return this.discoveryClient;

    }

}

为payment8001主启动类添加注解@EnableDiscoveryClient

测试
启动EurekaServer,在启动8001微服务,浏览器访问http://localhost:8001/payment/discovery,即可获得payment8001微服务的详细信息。

6. Eureka自我保护

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

6.2. 故障原因

为什么会产生Eureka自我保护机制?
为了防止EurekaClient可以正常运行,但是 与 EurekaServer网络不通情况下,EurekaServer不会立刻将EurekaClient服务剔除。

什么是自我保护模式?
默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生(延时、卡顿、拥挤)时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题——当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。 在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲解:好死不如赖活着 。

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

一句话:某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存。这属于CAP原则里面的AP分支。

但是,Eureka停止更新了该怎么办,下面我们看一下另一个服务注册中心Zookeeper。

五、Zookeeper服务注册与发现

1. 注册中心Zookeeper

  • zookeeper是一个分布式协调工具,可以实现注册中心功能
  • 关闭Linux服务器防火墙后启动zookeeper服务器
  • zookeeper服务器取代Eureka服务器,zk作为服务注册中心

2. 服务提供者

新建cloud-provider-payment8004

写POM(注意项目中zookeeper版本与Linux服务器中zookeeper版本一致,不一致则排除掉当前版本,并导入Linux服务器中对应的zookeeper版本)

<?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>cloud2021</artifactId>
        <groupId>org.zpl.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-provider-payment8004</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
            <!--先排除自带的zookeeper3.5.3-->
            <exclusions>
                <exclusion>
                    <groupId>org.apache.zookeeper</groupId>
                    <artifactId>zookeeper</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.9</version>
            <!--   排除多余slf4j         -->
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.zpl.springcloud</groupId>   <!--引入自定义的api通用包,可以使用Payment支付entity -->
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </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-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>

YML

server:
  port: 8004

spring:
  application:
    name: cloud-provider-payment
  cloud:
    zookeeper:
      connect-string: 192.168.1.102:2181 //注册到Linux中的zookeeper

主启动类

@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain8004 {

    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8004.class,args);
    }
}

Controller

@RestController
@RequestMapping("/payment")
public class PaymentController {

    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/zk")
    public String paymentZk()
    {
        return "springcloud with zookeeper: " + serverPort + "\t" + UUID.randomUUID().toString();
    }

}

测试

  • 浏览器测试:http://localhost:8004/payment/zk
  • zookeeper客户端测试:
    在这里插入图片描述
    在这里插入图片描述
    获得对应json串后使用在线工具格式化进行查看。

服务节点是临时节点还是持久节点
zookeeper的服务节点是临时节点,符合CAP原则中的CP原则。

3. 服务消费者

新建cloud-consumerzk-order80

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>cloud2021</artifactId>
        <groupId>org.zpl.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumerzk-order80</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
            <!--先排除自带的zookeeper3.5.3-->
            <exclusions>
                <exclusion>
                    <groupId>org.apache.zookeeper</groupId>
                    <artifactId>zookeeper</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.9</version>
            <!--   排除多余slf4j         -->
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.zpl.springcloud</groupId>   <!--引入自定义的api通用包,可以使用Payment支付entity -->
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </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-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>

YML

server:
  port: 80

spring:
  application:
    name: cloud-consumer-order
  cloud:
    zookeeper:
      connect-string: 192.168.1.102:2181

主启动类

@SpringBootApplication
@EnableDiscoveryClient
public class OrderZKMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderZKMain80.class,args);
    }
}

配置类

@Configuration
public class ApplicationContextConfig {

    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate()
    {
        return new RestTemplate();
    }
}

Controller

@RestController
@Slf4j
public class OrderZkController {

    public static final String INVOKE_URL = "http://cloud-provider-payment";

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/payment/zk")
    public String payment()
    {
        String result = restTemplate.getForObject(INVOKE_URL + "/payment/zk",String.class);
        return result;
    }

}

测试

  • 浏览器测试:http://localhost/consumer/payment/zk
  • zookeeper客服端测试(支付微服务和订单微服务均已注册到zookeeper中):
    在这里插入图片描述

六、Consul服务注册与发现

1. Consul简介

定义:
Consul 是一套开源的分布式服务发现和配置管理系统,由 HashiCorp 公司用 Go 语言开发。 提供了微服务系统中的服务治理、配置中心、控制总线等功能。这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建全方位的服务网格,总之Consul提供了一种完整的服务网格解决方案。 它具有很多优点。包括: 基于 raft 协议,比较简洁; 支持健康检查, 同时支持 HTTP 和 DNS 协议 支持跨数据中心的 WAN 集群 提供图形界面 跨平台,支持 Linux、Mac、Windows。

用途:

  • 服务发现-提供HTTP和DNS两种发现方式。
  • 健康监测-支持多种方式,HTTP、TCP、Docker、Shell脚本定制化监控。
  • KV存储-Key、Value的存储方式。
  • 多数据中心-Consul支持多数据中心
  • 可视化Web界面

2. 安装并运行Consul

官网安装说明

下载完成后只有一个consul.exe文件,硬盘路径下双击运行,查看版本号信息。

使用开发模式启动

  • consul agent -dev
  • 通过以下地址可以访问Consul的首页:http://localhost:8500

3. 服务提供者

新建cloud-providerconsul-payment8006

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>cloud2021</artifactId>
        <groupId>org.zpl.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-providerconsul-payment8006</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</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-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>

YML

###consul服务端口号
server:
  port: 8006

spring:
  application:
    name: consul-provider-payment
####consul注册中心地址
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        #hostname: 127.0.0.1
        service-name: ${spring.application.name}

主启动类

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

Controller

@RestController
@Slf4j
public class PaymentController
{
    @Value("${server.port}")
    private String serverPort;

    @RequestMapping(value = "/payment/consul")
    public String paymentConsul()
    {
        return "springcloud with consul: "+serverPort+"\t   "+ UUID.randomUUID().toString();
    }
}

测试
浏览器输入:http://localhost:8006/payment/consul

4. 服务消费者

新建cloud-consumerconsul-order80

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>cloud2021</artifactId>
        <groupId>org.zpl.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumerconsul-order80</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</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-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>

YML

###consul服务端口号
server:
  port: 80

spring:
  application:
    name: cloud-consumer-order
####consul注册中心地址
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        #hostname: 127.0.0.1
        service-name: ${spring.application.name}

主启动类

@SpringBootApplication
@EnableDiscoveryClient
public class OrderConsulMain80
{
    public static void main(String[] args) {
            SpringApplication.run(OrderConsulMain80.class, args);
    }
}

配置类

@Configuration
public class ApplicationContextConfig
{
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate()
    {
        return new RestTemplate();
    }
}

Controller

@RestController
@Slf4j
public class OrderConsulController
{
    public static final String INVOKE_URL = "http://consul-provider-payment";

    @Resource
    private RestTemplate restTemplate;

    @GetMapping(value = "/consumer/payment/consul")
    public String paymentInfo()
    {
        String result = restTemplate.getForObject(INVOKE_URL+"/payment/consul",String.class);
        return result;
    }
}
 

测试

  • 启动consul,cloud-providerconsul-payment8006,cloud-consumerconsul-order80
  • 浏览器访问:http://localhost:8500/ 主页会显示出consul,cloud-providerconsul-payment8006,cloud-consumerconsul-order80三服务
  • 浏览器输入:http://localhost:8006/payment/consul

5. 三个注册中心的异同点

CAP原则

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

组件开发语言强调的CAP原则对外暴露接口Spring Cloud集成
EurekaJavaAP可配支持HTTP
ConsulGoCP支持HTTP/DNS
ZookeeperJavaCP支持客户端已集成

在这里插入图片描述
最多只能同时较好的满足两个。

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

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

  • CA - 单点集群,满足—致性,可用性的系统,通常在可扩展性上不太强大。
  • CP - 满足一致性,分区容忍必的系统,通常性能不是特别高。
  • AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。

AP架构(Eureka)

当网络分区出现后,为了保证可用性,系统B可以返回旧值,保证系统的可用性。

结论:违背了一致性C的要求,只满足可用性和分区容错,即AP
在这里插入图片描述
CP架构(ZooKeeper/Consul)

当网络分区出现后,为了保证一致性,就必须拒接请求,否则无法保证一致性。

结论:违背了可用性A的要求,只满足一致性和分区容错,即CP。
在这里插入图片描述

七、Ribbon负载均衡服务调用

1. 概述

1.1. 是什么

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

1.2. 能做什么

LB(负载均衡)

LB负载均衡(Load Balance)简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)。常见的负载均衡有软件Nginx,LVS,硬件 F5等。 Ribbon本地负载均衡客户端 VS Nginx服务端负载均衡区别 Nginx是服务器负载均衡,客户端所有请求都会交给nginx,然后由nginx实现转发请求。即负载均衡是由服务端实现的。 Ribbon本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。

负载均衡分类

集中式LB:集中式LB即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx), 由该设施负责把访问请求通过某种策略转发至服务的提供方;
进程内LB: 进程内LB 将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。 Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。

就像前面80调用微服务8001和8002,就使用到了ribbon中的负载均衡(负载均衡+RestTemplate调用)

2. Ribbon负载均衡演示

2.1. 架构说明

Ribbon在工作时分成两步第一步先选择 EurekaServer ,它优先选择在同一个区域内负载较少的server.第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。

Ribbon在工作时分成两步:

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

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

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

POM

<dependency>
    <groupld>org.springframework.cloud</groupld>
    <artifactld>spring-cloud-starter-netflix-ribbon</artifactid>
</dependency>

先前项目没有手动引入ribbon,但却能使用ribbon,这是因为spring-cloud-starter-netflix-eureka-client自带了spring-cloud-starter-ribbon引用。

2.2. RestTemplate的使用

官网

getForObject方法/getForEntity方法:
返回对象为响应体中数据转化成的对象,基本上可以理解为Json 返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等

@GetMapping("/payment/EntityList")
    public Result<Payment> EntityList()
    {
        ResponseEntity<Result> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/list",Result.class);
        if (entity.getStatusCode().is2xxSuccessful())
        {
            return entity.getBody();
        } else {
            return new Result<>(400,"操作失败");
        }
    }

postForObject/postForEntity类似

@GetMapping("/payment/EntityCreate")
    public Result<Payment> EntityCreate(Payment payment)
    {
       ResponseEntity<Result> entity = restTemplate.postForEntity(PAYMENT_URL + "/payment/create",payment,Result.class);
       if (entity.getStatusCode().is2xxSuccessful())
       {
           return entity.getBody();
       }else {
           return new Result<>(400,"操作失败");
       }
    }

3. Ribbon核心组件IRule

3.1 IRule:根据特定算法中从服务列表中选取一个要访问的服务

  • com.netflix.loadbalancer.RoundRobinRule-轮询
  • com.netflix.loadbalancer.RandomRule-随机
  • com.netflix.loadbalancer.RetryRule-先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务
  • WeightedResponseTimeRule-对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
  • BestAvailableRule-会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
  • AvailabilityFilteringRule-先过滤掉故障实例,再选择并发较小的实例
  • ZoneAvoidanceRule-默认规则,复合判断server所在区域的性能和server的可用性选择服务器

3.2. 如何替换

  • 修改cloud-consumer-order80
  • 注意配置细节:
    官方文档明确给出了警告:这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。
  • 新建包myrule,使得其不和主启动类在同一目录下,例如:
    在这里插入图片描述
  • 在上面新建的包下新建MySelfRule规则类
@Configuration
public class MySelfRule {
    @Bean
    public IRule myRule()
    {
        return new RandomRule();//定义为随机
    }
}

  • 主启动类添加@RibbonClient
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class)
public class OrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain80.class,args);
    }
}
  • 测试:http://localhost/consumer/payment/list,看到微服务端口号随机出现,即每次随机调用对应端口号的微服务。

4. Ribbon负载均衡算法

4.1 原理

负载均衡算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标 ,每次服务重启动后rest接口计数从1开始。

: List [0] instances = 127.0.0.1:8002   List [1] instances = 127.0.0.1:8001
8001+ 8002 组合成为集群,它们共计2台机器,集群总数为2,

按照轮询算法原理: 当总请求数为1时: 1 % 2 =1 对应下标位置为1 ,则获得服务地址为127.0.0.1:8001当总请求数位2时: 2 % 2 =0 对应下标位置为0 ,则获得服务地址为127.0.0.1:8002当总请求数位3时: 3 % 2 =1 对应下标位置为1 ,则获得服务地址为127.0.0.1:8001当总请求数位4时: 4 % 2 =0 对应下标位置为0 ,则获得服务地址为127.0.0.1:8002如此类推…

4.2 RoundRobinRule源码

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 
     */

    //注意该方法
    public Server choose(Object key);
    
    public void setLoadBalancer(ILoadBalancer lb);
    
    public ILoadBalancer getLoadBalancer();    
}


package com.netflix.loadbalancer;

import com.netflix.client.config.IClientConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * The most well known and basic load balancing strategy, i.e. Round Robin Rule.
 *
 * @author stonse
 * @author Nikos Michalakis <nikos@netflix.com>
 *
 */
public class RoundRobinRule extends AbstractLoadBalancerRule {

    private AtomicInteger nextServerCyclicCounter;
    private static final boolean AVAILABLE_ONLY_SERVERS = true;
    private static final boolean ALL_SERVERS = false;

    private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);

    public RoundRobinRule() {
        nextServerCyclicCounter = new AtomicInteger(0);
    }

    public RoundRobinRule(ILoadBalancer lb) {
        this();
        setLoadBalancer(lb);
    }

    //注意该方法
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        }

        Server server = null;
        int count = 0;
        while (server == null && count++ < 10) {
            List<Server> reachableServers = lb.getReachableServers();
            List<Server> allServers = lb.getAllServers();
            int upCount = reachableServers.size();
            int serverCount = allServers.size();

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

            int nextServerIndex = incrementAndGetModulo(serverCount);
            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;
    }

    /**
     * Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
     *
     * @param modulo The modulo to bound the value of the counter.
     * @return The next value.
     */
    private int incrementAndGetModulo(int modulo) {
        for (;;) {
            int current = nextServerCyclicCounter.get();
            int next = (current + 1) % modulo;//求余法
            if (nextServerCyclicCounter.compareAndSet(current, next))
                return next;
        }
    }

    @Override
    public Server choose(Object key) {
        return choose(getLoadBalancer(), key);
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}


4.3 手写一个本地负载均衡器(使用轮询算法 )

  • 7001/7002集群启动
  • 8001/8002微服务改造
@RestController
@Slf4j
public class PaymentController{

    ...
	@GetMapping(value = "/payment/lb")
    public String getPaymentLB() {
        return serverPort;//返回服务接口
    } 
    ...
}
  • 80订单微服务改造
    ApplicationConfig去掉注解@LoadBalanced
@Configuration
public class ApplicationConfig {

    @Bean
    //@LoadBalanced   //restTemplate开启负载均衡
    public RestTemplate getRestTemplate()
    {
        return new RestTemplate();
    }
}

新建lb包,该包下新建LoadBalancer接口和该接口实现类Impl

public interface LoadBalancer {
    //接口方法:从多个相同功能的微服务中挑选一个微服务(负载均衡)
    ServiceInstance instance(List<ServiceInstance> serviceInstances);
}

@Component
public class Impl implements LoadBalancer {
    private AtomicInteger atomicInteger = new AtomicInteger(0);

    public final int Nums()
    {
        int current;
        int next;
        do {
            current = this.atomicInteger.get();
            next = current >= 2147483647 ? 0 : current + 1;
        } while (!this.atomicInteger.compareAndSet(current,next));//自旋锁
        return next;
    }


    @Override
    public ServiceInstance instance(List<ServiceInstance> serviceInstances) {
        int index = Nums() % serviceInstances.size();
        return serviceInstances.get(index);
    }
}

修改controller(新增部分)

@RestController
@Slf4j
@RequestMapping("/consumer")
public class OrderController {

    public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";

    @Resource
    private RestTemplate restTemplate;

    @Resource
    private LoadBalancer loadBalancer;

    @Resource
    private DiscoveryClient discoveryClient;
    
    ...
    
    @GetMapping(value = "/payment/lb")
    public String getPaymentLB()
    {
        List<ServiceInstance> instance = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        if (instance == null || instance.size() <= 0)
        {
            return null;
        }
        //使用自定义轮询算法实现负载均衡
        ServiceInstance serviceInstance = loadBalancer.instance(instance);
        return restTemplate.getForObject(serviceInstance.getUri()+"/payment/lb",String.class);

    }
}

测试:http://localhost/consumer/payment/lb,端口号交替出现,成功。

八、OpenFeign服务接口调用

1. 概述

1.1. 是什么

Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。它的使用方法是定义一个服务接口然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
Feign是一个声明式的Web服务客户端,让编写Web服务客户端变得非常容易,只需创建一个接口并在接口上添加注解即可
GitHub

1.2. 能做什么

Feign旨在使编写Java Http客户端变得更容易。前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。Feign集成了Ribbon利用Ribbon维护了Payment的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。

1.3 Feign和OpenFeign两者区别

Feign是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端。Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务OpenFeign是Spring Cloud 在Feign的基础上支持了SpringMVC的注解,如@RequestMapping等等。OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

        <!--  openfeign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--  feign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
        </dependency>

2. OpenFeign使用步骤

2.1. 接口+注解

微服务调用接口+@FeignClient

Feign在消费端使用
在这里插入图片描述

2.2. 示例

新建cloud-consumer-feign-order80

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>cloud2021</artifactId>
        <groupId>org.zpl.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumer-feign-order80</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--  eureka client -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.zpl.springcloud</groupId> <!--引入自定义的api通用包,可以使用Payment支付entity -->
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </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-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>

YML

server:
  port: 80

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

主启动

@SpringBootApplication
@EnableFeignClients
public class OrderOpenFeignMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderOpenFeignMain80.class,args);
    }
}

业务类

  • 业务逻辑接口+@FeignClient配置调用provider服务
  • 新建PaymentFeignService接口并新增注解@FeignClient
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {

    @GetMapping("/payment/list")
    public Result list();
}
  • Controller
@RestController
@Slf4j
@RequestMapping("/consumer")
public class OpenFeignController {

    @Resource
    private PaymentFeignService paymentFeignService;

    @GetMapping("/payment/list")
    public Result list()
    {
        return paymentFeignService.list();
    }
}

测试

  • 先启动2个eureka集群7001/7002
  • 再启动2个微服务8001/8002
  • 启动OpenFeignOrder80微服务
  • 浏览器访问:http://localhost/consumer/payment/list,成功访问,且输出端口号轮询变换。这是因为Feign自带负载均衡配置项。

3. OpenFeign超时控制

3.1. 超时设置,故意设置超时演示出错情况

服务提供方8001故意写暂停程序

@RestController
@Slf4j
@RequestMapping("/payment")
public class PaymentController {

    @Autowired
    private PaymentService paymentService;

    @Resource
    private DiscoveryClient discoveryClient;

    @Value("${server.port}")
    private String serverPort;

    ...
    
    @GetMapping("/timeout")
    public String timeout()
    {
        try {
            TimeUnit.SECONDS.sleep(3);//故意停止3秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return serverPort;
    }
}

服务消费方80添加超时方法PaymentFeignService

@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {

    @GetMapping("/payment/list")
    public Result list();

    //新增超时方法,调用8001微服务
    @GetMapping("/payment/timeout")
    public String timeout();
}

服务消费方80添加超时方法OrderFeignController

@RestController
@Slf4j
@RequestMapping("/consumer")
public class OpenFeignController {

    @Resource
    private PaymentFeignService paymentFeignService;

    @GetMapping("/payment/list")
    public Result list()
    {
        return paymentFeignService.list();
    }

    //新增超时
    @GetMapping("/payment/timeout")
    public String timeout()
    {
        return paymentFeignService.timeout();
    }

}

测试

  • 浏览器输入:http://localhost/consumer/payment/timeout
  • 将会跳出错误Spring Boot默认错误页面,主要异常:feign.RetryableException:Read timed out executing GET http://CLOUD-PAYMENT-SERVCE/payment/feign/timeout。

原因
OpenFeign调用服务接口默认等待1秒钟,超过后报错

超时控制
默认Feign客户端只等待一秒钟,但是服务端处理需要超过1秒钟,导致Feign客户端不想等待了,直接返回报错。为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制。yml文件中开启配置。其中OpenFeign默认支持Ribbon

YML中开启超时控制

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

4. OpenFeign日志打印功能

4.1. 概述

Feign 提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解 Feign 中 Http 请求的细节。说白了就是对Feign接口的调用情况进行监控和输出

4.2. 日志级别

  • NONE:默认的,不显示任何日志;
  • BASIC:仅记录请求方法、URL、响应状态码及执行时间;
  • HEADERS:除了 BASIC 中定义的信息之外,还有请求和响应的头信息;
  • FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。

4.3. 配置日志bean

@Configuration
public class FeignConfig {

    //设置为FULL
    @Bean
    Logger.Level feignLoggerLevel()
    {
        return Logger.Level.FULL;
    }
}

4.4. YML文件里需要开启日志的Feign客户端

logging:
  level:
    com.zpl.springcloud.service.PaymentFeignService: debug   # feign日志以什么级别监控哪个接口

4.5. 后台查看

运行项目,后台可以查看到更多日志信息。

九、Hystrix断路器

1. 概述

分布式系统面临的问题

分布式系统面临的问题复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。

服务雪崩

多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”. 对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。

所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。

1.1. 是什么

Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。

“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。

1.2. 能做什么

服务降级、服务熔断、服务限流、接近实时的监控 。

2. Hystrix重要概念

2.1. 服务降级

概念

服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallback

哪些情况会出发降级

  • 程序运行异常
  • 超时
  • 服务熔断触发服务降级
  • 线程池/信号量打满也会导致服务降级

2.2. 服务熔断

概念

类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示。

服务的降级 -> 进而熔断 -> 恢复调用链路

2.3. 服务限流

秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行

总结

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值