【Spring Cloud】【尚硅谷】学习笔记

Spring Cloud

Spring Cloud 介绍

Spring Cloud 技术栈

Spring Cloud是分布式微服务的一站式解决方案,是多种微服务架构落地技术的集合体,俗称微服务全家桶

技术栈课程京东、阿里的微服务架构
京东微服务架构阿里巴巴微服务架构京东物流微服务架构

SpringBoot 和 SpringCloud 版本选择

官网推荐版本组合:
SpringBoot2.2.2 版本和 SpringCloud H版本

Spring Cloud 组件的升级替换

注册中心 Eureka 未来会被Nacos替换
服务调用 Ribbon 未来会被LoadBalancer替换
服务调用 Feign 未来会被OpenFeign替换
服务降级 Hystrix 未来会被sentienl替换
服务网关 zuul 未来会被gateway替换
服务配置 config 未来会被Nacos替换
服务总线 bus 未来会被Nacos替换

在这里插入图片描述

跟着阳哥写代码

1.建module
2.改pom
3.写yml
4.主启动
5.业务类

创建父工程

创建父工程 pom.xml


<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.atguigu.springcloud</groupId>
  <artifactId>cloud2020</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.verison>1.2.17</log4j.verison>
    <lombok.version>1.16.18</lombok.version>
    <mysql.version>5.1.47</mysql.version>
    <durid.version>1.1.16</durid.version>
    <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
  </properties>

  <!--子模块继承之后,提供作用:锁定版本+子modlue不用写groupId和version-->
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>2.2.2.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>

      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Hoxton.SR1</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>

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


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

dependencyManagementdependencies
dependencyManagement:

  • 提供了一种管理依赖版本号的方式,通常使用在最顶层的父pom中,锁定版本+子modlue不用写groupId和version
  • 只是声明依赖,并不会实现引入jar的功能
  • 子项目pom中不声明version和scope,就会用父pom中声明的version和scope

dependencies:

  • 真正实现依赖引入的功能

支付模块构建(order)

微服务提供者 支付模块
创建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>cloud2020</artifactId>
        <groupId>com.atguigu.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>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>durid-spring-boot-starter</artifactId>
        </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>

application.yml


server:
  port: 8001

spring:
  application:
    name: cloud-payment-service
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.gjt.mm.mysql.Driver
    url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: root

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.atguigu.springcloud.entities

开发顺序:
vue - controller - service - dao - mysql


/**
 * 通用的,向前端传递信息类
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {
    private Integer code;
    private String message;
    private T data;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment {
    private Long id;
    private String serial;
}

dao层


@Mapper // 推荐写@Mapper注解,不要写@Repository注解,会有问题
public interface PaymentDao {
    // 写入数据库
    public int create(Payment payment);
    // 通过ID获取值
    public Payment getPaymentById(@Param("id")Long id);
}

<?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.atguigu.springcloud.dao.PaymentDao">

    <insert id="create" parameterType="Payment" useGeneratedKeys="true" keyProperty="id">
        insert into payment(serial) values(#{serial});
    </insert>

    <resultMap id="BaseResultMap" type="com.atguigu.springcloud.entities.Payment">
        <!--字段映射:数据库中的字段名 javaBean中的字段名 数据库中的类型-->
        <id column="id" property="id" jdbcType="BIGINT"></id>
        <id column="serial" property="serial" jdbcType="VARCHAR"></id>
    </resultMap>
    <select id="getPaymentById" parameterType="long" resultMap="BaseResultMap">
        select * from payment where id=#{id};
    </select>

</mapper>

PaymentService


public interface PaymentService {
    public int create(Payment payment);
    public Payment getPaymentById(@Param("id")Long id);
}

@Service
public class PaymentServiceImpl implements PaymentService{

    @Resource
    private PaymentDao paymentDao;

    @Override
    public int create(Payment payment) {
        return paymentDao.create(payment);
    }

    @Override
    public Payment getPaymentById(Long id) {
        return paymentDao.getPaymentById(id);
    }
}

controller


@RestController
@Slf4j
public class PaymentController {

    @Resource
    private PaymentService paymentService;

    @PostMapping(value="/payment/create")
    public CommonResult create(@RequestBody Payment payment){
        int i = paymentService.create(payment);
        log.info("插入结果:"+i);

        if(i > 0){
            return new CommonResult(200,"success",i);
        }else{
            return new CommonResult(444,"fail",null);
        }
    }

    @GetMapping(value="/payment/get/{id}")
    public CommonResult create(@PathVariable("id") Long id){
        Payment payment = paymentService.getPaymentById(id);
        log.info("插入结果:"+payment);

        if(payment != null){
            return new CommonResult(200,"success",payment);
        }else{
            return new CommonResult(444,"fail",null);
        }
    }
}

使用热部署Devtools
代码改动会自动重启


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

父工程pom文件添加maven插件


<build>
  <finalName>cloud2020</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>

微服务消费者 订单模块
创建cloud-provider-order80项目

修改pom文件引入依赖


<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>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

编写application.yml


# 80端口,默认端口,访问时可以省略不写
server:
  port: 80

需要的两个实体类


/**
 * 通用的,向前端传递信息类
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {
    private Integer code;
    private String message;
    private T data;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment {
    private Long id;
    private String serial;
}

注入RestTemplate到容器


// 相当于全局配置文件applicationContext.xml <bean id="" class=""/>
@Configuration
public class ApplicationContextConfig {
    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

controller调用restTemplate向支付模块发请求


@RestController
@Slf4j
public class OrderController {

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

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/payment/create")
    public CommonResult<Payment> create(Payment payment){
        // 写操作
        return restTemplate.postForEntity(PAYMENT_URL+"/payment/create",payment,CommonResult.class).getBody();
    }

    @GetMapping("/consumer/payment/get/{id}")
    public CommonResult<Payment> getPayment(@PathVariable("id") Long id){
        return restTemplate.getForEntity(PAYMENT_URL+"/payment/get/"+id,CommonResult.class).getBody();
    }

}

工程重构

将冗余的代码抽取出来,打成jar,需要的时候引入即可

创建cloud-api-commons项目

导入依赖到pom


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

拷贝上述两个微服务中公用的部分


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

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment {
    private Long id;
    private String serial;
}

使用mvn的插件,执行mvn clean、mvn install将依赖打包发布到仓库

上述项目就可以删除公用代码,然后再pom中引入依赖


<!-- 引入自己定义的api通用包-->
<dependency>
    <groupId>com.atguigu.springcloud</groupId>
    <artifactId>cloud-api-commons</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

RestTemplate

RestTemplate提供了多种便捷访问远程Http服务的方法
是一种简单便捷的访问restful服务模板类,时Spring提供的用于访问Rest服务的客户端模板工具集

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

使用:
使用restTemplate访问restful接口非常的简单粗暴无脑
(url, requestMap, ResponseBean.class)这三个参数分别代表REST请求地址,请求参数,HTTP响应转换被转换成的对象类型

Eureka 服务注册中心

Eureka 基础知识

什么是服务治理

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

什么是服务注册

Eureka 采取的是CS架构,Eureka Server作为服务注册功能的服务器,他就是注册中心;Eureka Client连接到注册中心,并维持心跳检测,通过心跳检测判断服务是否正常运行

Eureka服务模型

Eureka 的两个组件

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

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

单机 Eureka 构建步骤

编写 Eureka Server 服务注册中心

步骤:
建module
改pom
写yml
主启动
测试

创建cloud-eureka-server7001项目,引入以下依赖


<dependencies>
    <!-- eureka server -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        <version>2.0.2.RELEASE</version>
    </dependency>
    <!-- 引入自己创建的通用代码 -->
    <dependency>
        <groupId>com.atguigu.springcloud</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>1.0-SNAPSHOT</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>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

创建application.yml全局配置文件


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


创建主启动类


@SpringBootApplication
// 标注出身份,我是 eureka server 注册中心
@EnableEurekaServer
public class EurekaMain7001 {
    public static void main(String[] args) {
        SpringApplication.run(EurekaMain7001.class, args);
    }
}

测试访问 http://localhost:7001

cloud-eureka-server7001项目只是为了启动eureka server,所以不需要其他的业务逻辑的代码了

编写 Eureka client 客户端

将之前的cloud-provider-payment8001注册到注册中心,成为服务提供者provider
让之前的cloud-consumer-order80注册到注册中心,成为服务消费者consumer

修改cloud-provider-payment8001项目
在pom文件中追加eureka client客服端的依赖


<!-- eureka-client -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    <version>2.0.2.RELEASE</version>
</dependency>

application.yml追加eureka client客户端的设置


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

主启动类追加注解


@SpringBootApplication
@EnableEurekaClient //将当前服务注册到注册中心,设置为客户端,服务提供者
public class PaymentMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8001.class,args);
    }
}

修改cloud-consumer-order80项目,虽然80是服务的消费者,但是也可以注册到注册中心
在pom文件中追加eureka client客服端的依赖


<!-- eureka-client -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    <version>2.0.2.RELEASE</version>
</dependency>

application.yml追加eureka client客户端的设置


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

主启动类追加注解


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

集群 Eureka 构建步骤

Eureka 集群原理说明

集群出现的目的就是为了解决单点故障

服务注册:将服务信息注册到注册中心
服务发现:从注册中心中获取服务信息
实质:存key服务名,取value调用地址

微服务RPC远程服务调用最核心的是高可用
高可用的实现方式就是搭建集群,实现负载均衡+故障容错

集群实现方式:多台Eureka Server互相注册

Eureka Server 集群环境构建步骤

参考cloud-eureka-server7001微服务,新建一个cloud-eureka-server7002微服务

添加域名映射,修改host配置文件,追加内容


127.0.0.1	eureka7001.com
127.0.0.1	eureka7002.com

也就是在浏览器访问eureka7001.com或者eureka7002.com,对应的都是访问本地127.0.0.1

修改两个项目的application.yml文件,形成互相注册的集群环境
7001:


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

7002:


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

7001注册7002,7002注册7001,形成互相注册,形成集群,使Eureka高可用

将支付服务8001微服务发布到两台Eureka集群配置中

需要修改支付微服务的application.xml配置文件


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

将订单服务80微服务发布到两台Eureka集群配置中

需要修改支付微服务的application.xml配置文件


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

支付服务提供者8001集群环境构建

参考cloud-provider-payment8001项目,创建cloud-provider-payment8002项目,这样就实现了服务的集群环境

修改cloud-consumer-order80项目中的OrderController方法


@RestController
@Slf4j
public class OrderController {

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

    // 不在写死服务地址,而是写eureka server中的服务名称
    public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/payment/create")
    public CommonResult<Payment> create(Payment payment){
        // 写操作
        return restTemplate.postForEntity(PAYMENT_URL + "/payment/create", payment, CommonResult.class).getBody();
    }

    @GetMapping("/consumer/payment/get/{id}")
    public CommonResult<Payment> getPayment(@PathVariable("id") Long id){
        return restTemplate.getForEntity(PAYMENT_URL+"/payment/get/"+id,CommonResult.class).getBody();
    }

}

修改cloud-consumer-order80项目中的ApplicationContextConfig配置类,给resttemplate添加负载均衡功能的支持


@Configuration
public class ApplicationContextConfig {
    @Bean
    @LoadBalanced //给RestTemplate添加了负载均衡的能力,默认是轮询的方式
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

负载均衡

Ribbon搭配Eureka实现负载均衡,在RestTemplate上添加@LoadBalanced注解

actuator 微服务信息完善

依赖jar


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

主机名称:服务名称修改

以上述项目为例,在application.yml配置文件中追加配置

eureka:
	instance:
    # 修改名称,eureka server中value对应的名字
    instance-id: payment8001

访问信息有IP信息提示

以上述项目为例,在application.yml配置文件中追加配置

eureka:
	instance:
    # 修改名称,eureka server中value对应的名字
    instance-id: payment8001
    # 访问路径可以显示IP地址,鼠标悬停在服务名上,左下角显示服务ip地址
    prefer-ip-address: true

服务发现 Discovery

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

以cloud-provider-payment8001项目为例,修改controller


@RestController
@Slf4j
public class PaymentController {

    @Resource
    private PaymentService paymentService;
    @Resource
    private DiscoveryClient discoveryClient;

    @PostMapping(value="/payment/create")
    public CommonResult create(@RequestBody Payment payment){
        int i = paymentService.create(payment);
        log.info("插入结果:"+i);

        if(i > 0){
            return new CommonResult(200,"success",i);
        }else{
            return new CommonResult(444,"fail",null);
        }
    }

    @GetMapping(value="/payment/get/{id}")
    public CommonResult create(@PathVariable("id") Long id){
        Payment payment = paymentService.getPaymentById(id);
        log.info("插入结果:"+payment);

        if(payment != null){
            return new CommonResult(200,"success",payment);
        }else{
            return new CommonResult(444,"fail",null);
        }
    }

    @GetMapping(value = "/payment/discovery")
    public Object discovery(){
        // 获取eureka中注册的服务有哪些,相当于获取eureka上的服务的key
        List<String> services = discoveryClient.getServices();
        for (String service : services) {
            log.info("***element : "+service);
        }

        // 通过eureka中注册的服务名称,获取所有的服务实例的信息
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD_PAYMENT_SERVICE");
        for (ServiceInstance instance : instances) {
            log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
        }
        return this.discoveryClient;
    }

}

在主启动类上添加注解


@SpringBootApplication
@EnableEurekaClient //将当前服务注册到注册中心,设置为客户端,服务提供者
@EnableDiscoveryClient
public class PaymentMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8001.class,args);
    }
}

eureka 自我保护

保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务。

一句话总结:某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保护

属于CAP里面的AP分支

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

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

自我保护机制
默认情况下EurekaClient定时向EurekaServer端发送心跳包,如果Eureka在server端在一定时间内(默认90秒)没有接收到EurekaClient发送的心跳包,便会直接从服务注册列表中剔除该服务,但是在短时间(90秒中)内丢失了大量的服务实例心跳,这时候EurekaServer会开启自我保护机制,不会剔除该服务(该现象可能出现在如果网络不通,但是EurekaClient未出现宕机,此时如果换做别的注册中心,如果一定时间内没有收到心跳会将服务剔除,这样就出现了严重的失误,因为客户端还能正常发送心跳,只是网络延迟问题,儿子我保护机制时为了解决此问题而产生的)

在自我保护模式中,EurekaServer会保护服务注册列表中的信息,不在注销任何服务实例

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

综上,自我保护模式是一种应对网络异常的安全保护措施

关闭自我保护,修改application.yml配置文件


eureka:
	server:
		# 关闭自我保护机制,保证服务不可用时,被及时剔除
		enable-self-preservation: false
		# 默认时间
		eviction-interval-timer-in-ms: 2000


eureka:
	instance:
		# Eureka客户端向服务端发送心跳的时间间隔,单位是秒(默认30秒)
		lease-renewal-interval-in-seconds: 1
		# Eureka服务端在收到最后一次心跳后等待时间上限,单位是秒(默认90秒),超时剔除
		lease-expiration-duration-in-seconds: 2

Zookeeper

Spring Cloud 整合Zookeeper代替Eureka

注册中心 Zookeeper

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

服务提供者

pom文件引入zookeeper依赖


<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
    <!--排除启动器中配置好的版本-->
    <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>
</dependency>

编写application.yml,配置zookeeper


server:
  port: 8004

# 服务别名 注册zookeeper到注册中心名称
spring:
  application:
    name: cloud-provider-payment
  cloud:
    zookeeper:
      connect-string: 192.148.111.144:2181

创建主启动类


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

编写controller


@RestController
@Slf4j
public class PaymentController {
    @Value("${server.port}")
    private String serverPort;
    @GetMapping(value = "/patment/zk")
    public String paymentzk(){
        return "springcloud with zookpeer: " +
                serverPort + "\t" +
                UUID.randomUUID().toString();
    }
}

服务消费者

pom文件引入依赖


<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
    <!--排除启动器中配置好的版本-->
    <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>
</dependency>

创建启动类


@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 OrderController {

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

    @Resource
    private RestTemplate restTemplate;

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

}

Consul 服务注册与发现

Consul简介

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

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

它具有很多优点,包括:
基于raft协议,比较简洁;
支持健康检查,同时支持HTTP和DNS协议,支持跨数据中心的WAN集群,提供图形界面,跨平台,支持Linux、mac、windows

主要功能:
服务发现:提供HTTP和DNS两种方式发现
健康检测:支持多种方式,HTTP、TCP、Docker、shell脚本定制化
KV存储:key、value的存储方式
多数据中心:Consul支持多数据中心
可视化web界面

服务提供者

创建cloud-providerconsul-payment8006项目

pom文件引入consulu依赖


<dependencies>
   <dependency>
       <groupId>org.springframework.boot</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>
</dependencies>

创建application.yml配置consul


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

spring:
  application:
    name: consul-provider-payment

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

创建主启动类


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

编写controller


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

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

服务消费者

创建cloud-consumerconsul-order80项目

pom文件引入consul依赖


<dependencies>
    <dependency>
        <groupId>org.springframework.boot</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>
</dependencies>

编写application.yml全局配置文件


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

spring:
  application:
    name: consul-consumer-payment

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

创建主启动类


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

创建配置类


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

创建controller


@RestController
@Slf4j
public class OrderController {

    public static final String INVOKE_URL = "http://cloud-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;
    }
}

三个注册中心异同点

|组件名| CAP| 服务健康检查 | 对外暴露接口 | Spring Cloud 集成 |
|–|–|–|–|–|–|
| Eureka | java | AP | 可配置支持 | HTTP | 已集成 |
| Consul | go | CP | 支持 | HTTP/DNS | 已集成 |
| Zookeeper | java | CP | 支持 | 客户端 | 已集成 |

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

CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求
因此,根据CAP原理将NoSQL数据库分成了满足CA原则、满足CP原则和满足AP原则三大类:
CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大
CP - 满足一致性,分区容忍性的系统,通常性能不是特别高
AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些

AP(Eureka)
CP(Zookeeper、Consul)

AP架构
当网络分区出现后,为了保证可用性,系统B可以返回旧值,保证系统的可用性;违背了一致性C的要求,只满足可用性和分区容错,即AP

CP架构
当网络分区出现后,为了保证一致性,就必须拒绝错误的请求,否则无法保证一致性;违背了可用性A的要求,只满足一致性和分区容错,即CP

Ribbon 负载均衡服务调用

概述

Spring Cloud Ribbon 是基于Netflix Ribbon实现的一套客户端(服务消费者)负载均衡的工具

简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项,如连接超时、重试等

简单的说,就是在配置文件中列出LoadBalancer后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法

LB负载均衡(Load Balance)是什么
简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)
常见的负载均衡软件Nginx、LVS,硬件F5等

Ribbon本地负载均衡客户端 VS Nginx服务端负载均衡区别
Nginx是服务器负载均衡,客户端所有请求都会交给nginx,然后由nginx实现转发请求,即负载均衡是由服务端实现的
Ribbon本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术

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

进程内LB
将LB逻辑集成到消费方,消费方从服务注册中心获知由哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器
Ribbon属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取服务提供方的地址

总结一句话,Ribbon就是负载均衡+RestTemplate调用

Ribbon 负载均衡演示

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

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

RestTemplate常用方法
getForObject:返回对象为响应体中数据转化成的对象,基本上可以理解为json
getForEntity:返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应投、响应状态码、响应体等
postForObject
postForEntity
GET请求方法
POST请求方法

Ribbon 核心组件IRule

IRule接口根据特定算法中从服务列表中选取一个要访问的路径

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

如何替换

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

修改cloud-consumer-order80项目:

新建一个自定义的负载均衡配置类


@Configuration
public class MySelfRule {
    @Bean
    public IRule myRule(){
        // 定义为随机
        return new RandomRule();
    }
}

主启动类添加Ribbon注解,使自定义规则生效


@SpringBootApplication
@EnableEurekaClient
// name:要访问的微服务
// configuration:启用自己定义的配置
@RibbonClient(name="CLOUD_PAYMRNT_SERVICE",configuration= MySelfRule.class)
public class OrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain80.class,args);
    }
}

Ribbon 负载均衡算法

原理(轮询)

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

源码

查看轮询算法的源码 RoundRobinRule

手写

自定义一个负载的算法

基于cloud-consumer-order80项目实现

添加接口


public interface LoadBalancer {
    // 获取所有的服务实例
    public ServiceInstance instances(List<ServiceInstance> serviceInstances);
}

创建MyLB实现LoadBalancer接口


@Component
public class MyLB implements LoadBalancer {
    // 原子类
    private AtomicInteger atomicInteger = new AtomicInteger(0);

    // 获取当前是第几次访问
    public final int getAndIncrement(){
        int current;
        int next;

        do{
            current = this.atomicInteger.get();
            next = current>=Integer.MAX_VALUE ? 0 : current+1;
        }while(!this.atomicInteger.compareAndSet(current,next));

        return next;
    }


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

修改controller



@RestController
@Slf4j
public class OrderController {
    @Resource
    private RestTemplate restTemplate;
    @Resource
    private LoadBalancer loadBalancer;
    private DiscoveryClient discoveryClient;

    @GetMapping(value = "/consumer/payment/lb")
    public String getPaymentLB(){
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PATMENT-SERVICE");

        if(instances==null || instances.size()<=0){
            return null;
        }

        ServiceInstance serviceInstance = loadBalancer.instances(instances);
        URI uri = serviceInstance.getUri();

        return restTemplate.getForObject(uri+"/payment/lb",String.class);
    }
}

OpenFegin 服务接口调用

概述

Fegin是一个声明式WebService客户端,使用Fegin能让编写Web Service客户端更加简单
Fegin的使用方法是定义一个服务接口然后再上面添加注解,Fegin也支持可拔插式的编码器和解码器,Spring Cloud对Fegin进行了封装,使其支持了SpringMVC标注注解和HttpMessageConverters,Fegin可以与Eureka和Ribbon组合使用以支持负载均衡

Fegin能干什么?
Fegin旨在使编写Java Http客户端变得更加容易
前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模板化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每一个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Fegin在此基础上做了进一步封装,由它来帮助我们定义和实现依赖服务接口的定义。在Fegin的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上标注一个Fegin注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring Cloud Ribbon时,自动封装服务调用客户端的开发量。

Fegin集成了Ribbon
利用Ribbon维护了服务提供者的服务的列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过fegin只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用

Fegin 和 OpenFeign 的区别
OpenFeign 是Spring Cloud在Fegin的基础行支持了SpringMVC的注解,如@RequestMapping等等,OpenFegin的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务

OpenFegin 使用步骤

使用cloud-provider-payment8001项目,新建cloud-consumer-feign-order80项目

pom文件引入依赖


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

创建application.yml全局配置文件


server:
  port: 80

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

创建主启动类


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

业务逻辑接口+@FeignClient配置调用provider服务
新建PaymentFeignService接口并新增注解@FeignClient


@Component
@FeignClient(value="CLOUD-PAYMRNT-SERVICE")
public interface PaymentFeignService {
    // 对应想要调用的controller方法
    @GetMapping(value="/paymrnt/get/{id}")
    public CommonResult<Payment> getPaymentById(@Param("id") Long id);

}

控制层Controller


@RestController
@Slf4j
public class OrderFeignController {

    @Resource
    private PaymentFeignService paymentFeignService;

    @GetMapping(value="/consumer/paymrnt/get/{id}")
    public CommonResult<Payment> getPaymentById(@Param("id") Long id){
        return paymentFeignService.getPaymentById(id);
    }
}

OpenFegin 超时控制

默认Fegin客户端等待一秒钟,但是服务端处理需要超过一秒钟,导致feign客户端不想等待了,直接返回报错
为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制

yml文件中开启配置


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

OpenFegin 日志打印功能

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

日志级别:
NONE:默认的,不显示任何日志
BASIC:仅记录请求方法、URL、响应状态码及执行时间
HEADERS:在BASIC显示的信息的基础上,还有请求和响应的头信息
FULL:在HEADERS显示的信息的基础上,还有请求和响应的正文及元数据

添加配置类,配置日志级别


@Configuration
public class FeignConfig {
    @Bean
    public Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }
}

yml文件里需要开启日志的Fegin客户端


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

Hystrix 断路器

概述

分布式系统面临的问题

服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,BC又调用其他的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的雪崩效应

对于高流量的应用来说,单一的后端依赖可能会导致所有的服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。
所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩

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

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

Hystrix 主要功能
服务降级
服务熔断
接近实时的监控

Hystrix 重要概念

服务降级 fallback

导致服务降级的原因,比如程序运行异常、超时、服务熔断触发服务降级、线程池信号量打满也会导致服务降级

服务熔断 break

类比保险丝达到最大服务访问后,直接拒绝访问,熔断服务,然后调用服务降级的方法来返回友好提示
服务的降级 -> 进而熔断 -> 恢复调用链路

服务限流 flowlimit

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

Hystrix 案例

新建cloud-provider-hystrix-payment8001项目,提供支付服务

pom


<!-- hystrix -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

application.yml


server:
  port: 8001

spring:
  application:
    name: cloud-provider-hystrix-payment

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

主启动类


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

service


@Service
public class PaymrntService {
    public String paymentInfo_OK(Integer id){
        return "Thread Pool: "+Thread.currentThread().getName()
                + " paymentInfo_OK";
    }

    public String paymentInfo_TimeOut(Integer id){
        try {
            TimeUnit.SECONDS.sleep(3);
        }catch(Exception e){
            e.printStackTrace();
        }
        return "Thread Pool: "+Thread.currentThread().getName()
                + " paymentInfo_TimeOut";
    }
}

controller


@RestController
@Slf4j
public class PaymentController {

    @Resource
    private PaymrntService paymrntService;
    @GetMapping("/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id){
        String result = paymrntService.paymentInfo_OK(id);
        return result;
    }
    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(Integer id){
        String result = paymrntService.paymentInfo_TimeOut(id);
        return result;
    }
}

单独发送一个请求是没有问题,程序可以正常运行
使用JMeter做高并发压力测试,发送两万个请求到paymentInfo_TimeOut

当大批量访问paymentInfo_TimeOut,微服务将所有资源集中处理这些大批量的请求,paymentInfo_OK也会被拖慢

新建cloud-consumer-feign-hystrix-order80项目,提供调用支付服务

pom


<!-- hystrix -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

application.yml


server:
  port: 80

spring:
  application:
    name: cloud-consumer-feign-hystrix-order

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

主启动类


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

service


@Component
@FeignClient(value="CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService {
    @GetMapping("/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id);
    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(Integer id);
}

controller


@RestController
@Slf4j
public class OrderHystrixController {
    @Resource
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id){
        return paymentHystrixService.paymentInfo_OK(id);
    }
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(Integer id){
        return paymentHystrixService.paymentInfo_TimeOut(id);
    }
}

使用JMeter向8001发送20000条请求,这时8001同一层次的其他接口服务被困死,因为tomcat线程池里面的工作线程已经被挤占完毕,80此时调用8001,客户端访问响应缓慢,转圈圈

应对这种超时、卡死、宕机,服务提供者和消费者都需要有应对方式

服务降级

对服务提供者进行降级
设置自身调用超时时间的峰值,峰值内正常运行
超过了需要又兜底的方法处理,做服务降级fallback

服务提供者fallback

service,添加注解@HystrixCommand


@Service
public class PaymrntService {
    public String paymentInfo_OK(Integer id){
        return "Thread Pool: "+Thread.currentThread().getName()
                + " paymentInfo_OK";
    }

    // 设置一个兜底方法(服务降级)
    // name:表示哪个线程
    // value:表示超时时间设置
    @HystrixCommand(fallbackMethod="paymentInfo_TimeOutHandler",
            commandProperties={
                @HystrixProperties(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
            })
    public String paymentInfo_TimeOut(Integer id){
        try {
            TimeUnit.SECONDS.sleep(5);
        }catch(Exception e){
            e.printStackTrace();
        }
        return "Thread Pool: "+Thread.currentThread().getName()
                + " paymentInfo_TimeOut";
    }

    public String paymentInfo_TimeOutHandler(Integer id){
        return "服务器较忙,请稍后重试";
    }
}

主启动类添加注解@EnableCircuitBreaker


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

服务消费者fallback

controller,添加注解@HystrixCommand,实现服务降级功能


@RestController
@Slf4j
public class OrderHystrixController {
    @Resource
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id){
        return paymentHystrixService.paymentInfo_OK(id);
    }
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    @HystrixCommand(FallbackMenthod="paymentTimeOutFallbackMethod",commandProperties={
            @HystrixProperties(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")
    })
    public String paymentInfo_TimeOut(Integer id){
        return paymentHystrixService.paymentInfo_TimeOut(id);
    }
    // 添加一个兜底方法
    public String paymentTimeOutFallbackMethod(@PathVariable("id")Integer id){
        return "80 服务繁忙,稍后重试";
    }
}

启动类添加@EnableCircuitBreaker注解,启用服务降级


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

全局服务降级 DefaultProperties

每个业务方法对应一个兜底的方法,代码膨胀
统一和自定义分开


@RestController
@Slf4j
@DefaultProperties(defaultFallback="paymentTimeOutFallbackMethod")
public class OrderHystrixController {
    @Resource
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id){
        return paymentHystrixService.paymentInfo_OK(id);
    }
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    @HystrixCommand //没有特别指明,就用类上标注的统一的
    public String paymentInfo_TimeOut(Integer id){
        return paymentHystrixService.paymentInfo_TimeOut(id);
    }
    // 添加一个兜底方法
    public String paymentTimeOutFallbackMethod(@PathVariable("id")Integer id){
        return "80 服务繁忙,稍后重试";
    }
}

@DefaultProperties(defaultFallback=“”)
每个方法配置一个服务降级方法,技术上可以,实际上很傻
除了个别重要核心业务又专属,其他普通的可以通过@DefaultProperties(defaultFallback=“”)统一跳转到统一处理结果页面

通用的和独享的各自分开,避免了代码膨胀,合理减少了代码量

通配服务降级FeignFallback

修改cloud-consumer-feign-hystrix-order80

根据cloud-consumer-feign-hystrix-order80项目已经有的PaymentHystrixService接口,重新新建一个类(PaymentFallbackService)实现该接口,统一为接口里面的方法进行异常处理


@Component
public class PaymentFallbackService implements PaymentHystrixService{
    @Override
    public String paymentInfo_OK(Integer id) {
        return "PaymentFallbackService paymentInfo_OK";
    }

    @Override
    public String paymentInfo_TimeOut(Integer id) {
        return "PaymentFallbackService paymentInfo_TimeOut";
    }
}

@Component
@FeignClient(value="CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback=PaymentFallbackService.class)
public interface PaymentHystrixService {
    @GetMapping("/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id);
    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(Integer id);
}

yml文件修改


# 用于服务降级,在注解@FeignClient中添加fallbackFactory属性值
feign:
	hystrix:
		enable: true #在Feign中开启Hystrix

服务熔断

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

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

熔断机制概述
熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息
当检测到该节点微服务调用响应正常后,恢复调用链路

在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用次数到一定阈值,默认是5秒内20次调用失败,就会启动熔断机制,熔断机制的注解是@HystrixCommand

服务熔断实例

修改cloud-provider-hystrix-payment8001项目

修改PaymentService


@Service
public class PaymrntService {
    public String paymentInfo_OK(Integer id){
        return "Thread Pool: "+Thread.currentThread().getName()
                + " paymentInfo_OK";
    }

    // 设置一个兜底方法
    // 判断超时并且错误类型是timeoutInMilliseconds,会调用兜底的方法
    @HystrixCommand(fallbackMethod="paymentInfo_TimeOutHandler",
            commandProperties={
                @HystrixProperties(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
            })
    public String paymentInfo_TimeOut(Integer id){
        try {
            TimeUnit.SECONDS.sleep(3);
        }catch(Exception e){
            e.printStackTrace();
        }
        return "Thread Pool: "+Thread.currentThread().getName()
                + " paymentInfo_TimeOut";
    }

    public String paymentInfo_TimeOutHandler(Integer id){
        return "服务器较忙,请稍后重试";
    }

    // ===服务熔断
    @HystrixCommand(fallbackMethod="paymentCircuitBreaker_fallback", commandProperties={
            @HystrixProperty(name="circuitBreaker.enabled",value="true"),//是否开启断路器
            @HystrixProperty(name="circuitBreaker.requestVolumeThreshold",value="10"),//请求次数
            @HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds",value="10000"),//时间窗口期
            @HystrixProperty(name="circuitBreaker.errorThresholdPercentage",value="60"),//失败率达到多少,熔断
    })
    public String paymentCircuitBreaker(@PathVariable("id")Integer id){
        if(id<0){
            throw new RuntimeException("id 不能为负数");
        }
        String num = IdUtil.simpleUUID();
        return Thread.currentThread().getName()+" "+num;
    }
    public String paymentCircuitBreaker_fallback(@PathVariable("id")Integer id){
        return "error---"+id;
    }
}

修改PaymentController


@RestController
@Slf4j
public class PaymentController {

    @Resource
    private PaymrntService paymrntService;
    @GetMapping("/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id){
        String result = paymrntService.paymentInfo_OK(id);
        return result;
    }

    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(Integer id){
        String result = paymrntService.paymentInfo_TimeOut(id);
        return result;
    }


    // ===服务熔断
    @GetMapping("/payment/circuit/{id}")
    public String paymentCircuitBreaker(@PathVariable("id")Integer id){
        String result = paymrntService.paymentCircuitBreaker(id);
        return result;
    }
}

服务熔断总结

熔断类型:

熔断打开:请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设始终则进入熔断状态
熔断关闭:熔断关闭不会对服务进行熔断
熔断半开:部分请求根据调规则用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断

涉及到断路器的三个重要参数:快照时间窗、请求总数阈值、错误百分比阈值

快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗
请求总数阈值:在快照时间窗内,必须满足请求总数阈值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令在调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开
错误百分比阈值:当请求总数在快照时间窗内超过了阈值,比如发生了30次调用,如果30次调用中,有15次发生了异常,也就是超过了50%的错误比例,在默认设定50%阈值情况下,这时候就会将熔断器打开

熔断器开启或者关闭的条件
  • 当满足一定的阈值的时候(默认10秒内超过20个请求次数)
  • 当失败率达到一定的时候(默认10秒内超过50%的请求失败)
  • 到达以上阈值,熔断器将会开启
  • 当开启的时候,所有请求都不会进行转发
  • 一段时间之后(默认是5秒),这个时候熔断器是半开状态,会让其中一个请求进行转发。如果成功,熔断器会关闭,如果失败,继续开启,重复4和5
当熔断器打开之后

再有请求调用的时候,将不会调用逻辑,二是直接调用降级fallback,通过熔断器,实现了自动的发现错误并将降级逻辑切换为主逻辑,减少响应延迟效果

原来的侏罗纪要如何恢复呢?
对于这一个问题,hystrix也为我们实现了自动恢复功能
当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑,如果此次请求正常返回,那么熔断器将继续闭合,主逻辑恢复,如果这次请求依然有问题,熔断器继续进入打开状态,休眠时间窗重新计时

Hystrix 工作流程

查看官网架构图

服务监控hystrix Dashboard

概述

除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(hystrix Dashboard),hystrix会持续的记录所有通过hystrix发起的请求的执行信息,并以统计报表和图形展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了hystrix Dashboard的整合,对监控内容转化成可视化界面。

仪表盘9001

新建cloud-consumer-hystrix-dashboard9001项目

pom


<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>

application.yml


server:
  port: 9001

启动类


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

要监控的微服务的启动类要添加以下代码


/**
     * 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
     * ServletRegistrationBean因为springboot的默认路径不是“/hystrix.stream”
     * 只要在自己的项目里配置上下面的servlet就可以了
     * @return
     */
    public ServletRegistrationBean getServlet(){
        HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
        ServletRegistrationBean registrationBean = new ServletRegistrationBean();
        registrationBean.setLoadOnStartup(1);
        registrationBean.addUrlMappings("/hystrix.stream");
        registrationBean.setName("HystrixMetricsStreamServlet");
        return registrationBean;
    }

熔断演示

填写监控地址,例如:
http://localhost:8001/hystrix.stream

zuul 路由网关

Gateway 新一代网关

概述简介

Cloud全家桶中有个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关
但是在2.x版本中,zuul的升级一直在跳票,SpringCloud最后自己研发了一个网关替代Zuul,那就是SpringCloudGateway

一句话:gateway是原zuul 1.x版的替代

Gateway是在Spring生态系统之上构建的API网关服务,基于Spring5,SpringBoot2和Project Reactor等技术
Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等

SpringCloud Gateway是SpringCloud的一个全新项目,基于Spring5,SpringBoot2和Project Reactor等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的API路由管理方式

SpringCloud Gateway作为SpringCloud生态系统中的网关,目标是替代Zuul,在SpringCloud2以上的版本中,没有对新版的Zuul 2.0以上的最新高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty

Spring Cloud Gateway的目标提供统一的路由方式且基于Filter链的方式提供了网关基本的功能,例如:安全,监控,指标,限流

Spring Cloud Gateway的主要功能:反向代理、鉴权、流量控制、熔断、日志监控

为什么选择gateway?
一方面Zuul 1.0进入维护阶段,Gateway更值得信赖,而且很多功能用起来非常简单便捷
gateway是基于异步非阻塞模型上进行开发的,性能优秀

Spring Cloud Gateway具有如下特点:

  • 基于Spring5,SpringBoot2和Project Reactor等技术开发的网关
  • 动态路由:能够匹配任何请求属性
  • 可以对路由指定Predicate(断言)和Filter(过滤器)
  • 集成Hystrix的断路器功能
  • 集成Spring Cloud服务发现功能
  • 易于编写的Predicate(断言)和Filter(过滤器)
  • 请求限流功能
  • 支持路径重写

Spring Cloud Gateway与Zuul的区别:

  • Zuul 1.x 是一个基于阻塞IO的API Gateway
  • Zuul 1.x 基于Servlet2.5使用阻塞架构它不支持任何长连接(如WebSocket)Zuul的涉及模式和Nginx较像,每次IO操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx是C++实现,Zuul是Java实现,而JVM本身会有第一次加载较慢的情况,使得Zuul的性能相对较差
  • Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。Zuul 2.x的性能比Zuul 1.x有很大提升。在性能方面,根据官方提供的基准测试,SpringCloudGateway的RPS(每秒请求数)是Zuul的1.6倍
  • Spring Cloud Gateway建立在Spring5,SpringBoot2和Project Reactor之上,使用非阻塞API
  • Spring Cloud Gateway还支持WebSocket,并且与Spring紧密集成拥有更好的开发体验

传统的Web框架,比如说:Struts2,SpringMVC等都是九月Servlet API与Servlet容器基础之上运行的
但是在Servlet3.1之后有了异步非阻塞的支持。而WebFlux是一个典型非阻塞异步的框架,他的核心是基于Reactor的相关API实现的。相对于传统的web框架来书评,它可以运行在诸如Netty,Undertow及支持servlet3.1的容器上。非阻塞式+函数时编程(Spring5必须让你使用java8)

Spring webFlux 是Spring5引入的新的响应式框架,区别于SpringMVC,它不需要依赖ServletAPI,他是完全异步非阻塞的,并且基于Reactor来实现响应式流规范

三大核心概念

Route(路由)

路由是构建网关的基本模块,它有ID,目标URI,一些列的断言和过滤器组成,如果断言为true则匹配该路由

Predicate(断言)

参考的是java8的java.util.function.Predicate
开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由

Filter(过滤)

指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前后对请求进行修改

总体

web请求,通过一些匹配条件,定位到正真的服务节点。并在这个转发过程的前后,进行一些精细化控制
Predicate就是我们的匹配条件;而filter就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了

Gateway工作流程

客户端向Spring Cloud Gateway发出请求,然后在Gateway Handler Mapping中找到与请求相匹配的路由,将其发送到Gateway Web Handler

Handler再通过指定的过滤器链将请求发送到我们实际的服务执行业务逻辑,然后返回。
过滤器可以在发送代理请求前后执行业务逻辑

Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,凉凉监控 等有着非常重要的作用

核心逻辑:路由转发+执行过滤器链

入门配置

新建cloud-gateway-gateway9527项目

pom


<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

application.yml


server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      routes:
        - id: payment_routh #路由ID,没有固定规则但是要求唯一,建议配合服务名
          uri: http://localhost:8001 #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/get/** #断言,路径相匹配的进行路由

        - id: payment_routh2
            uri: http://localhost:8001
            predicates:
              - Path=/payment/lb/**

eureka:
  instance:
    hostname: cloud-gateway-service
  client:
  service-url:
    register-with-eureka: true
    fetch-registry: true
    defaultZone: http://eureka7001.com:7001/eureka

主启动类


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

测试连接 http://localhost:9527/payment/get/31

网关路由的两种配置方式:
1.在配置文件yml中配置
2.代码中注入RouteLocator的Bean


@Configuration
public class GateWayConfig {
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder){
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        // http://news.baidu.com/guonei
        // 访问localhost:9527/guinei  会跳转到 http://news.baidu.com/guonei
        routes.route("path_route_atguigu",
                r -> r.path("/guonei").uri("http://news.baidu.com/guonei")).build();

        return routes.build();
    }
}

通过微服务名实现动态路由

默认情况下Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能

基于以前的例子,启动7001,8001,8002

修改application.yml


server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          # 开启从注册中心动态创建落于的功能,利用微服务名进行路由
          enabled: true

      routes:
        - id: payment_routh #路由ID,没有固定规则但是要求唯一,建议配合服务名
          # uri: http://localhost:8001 #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/get/** #断言,路径相匹配的进行路由

        - id: payment_routh2
          uri: lb://cloud-payment-service
          predicates:
            - Path=/payment/lb/**

eureka:
  instance:
    hostname: cloud-gateway-service
  client:
  service-url:
    register-with-eureka: true
    fetch-registry: true
    defaultZone: http://eureka7001.com:7001/eureka

Predicate的使用

SpringCloudGateway创建Route对象时,使用RoutePredicateFactory创建Predicate对象,Predicate对象可以赋值给Route。SpringCloudGateway包含许多内置的Route Predicate Factories

种类:

  • After Route Predicate
  • Before Route Predicate
  • Between Route Predicate
  • Cookie Route Predicate
  • Header Route Predicate
  • Host Route Predicate
  • Method Route Predicate
  • Path Route Predicate
  • Query Route Predicate
  • 总结

# 表示什么时间之后,连接有效
predicates:
	- Path=/payment/get/**
	- After=2015-01-01。。。 #在xx时间之后,连接生效
	- Cookie=username,zzyy #获取cookie name和正则表达式,验证值和正则匹配
	- Header=X-Request-Id,\d+ #获取header name和正则表达式,验证值和正则匹配
	- Host=**.atguigu.com #判断连接中必须含有地址
	- Method=GET #判断必须是get请求

说白了,Predicate就是为了实现一组匹配规则,让请求过来找到响应的Route进行处理

Filter的使用

路由过滤器可用于修改进入HTTP请求和返回HTTP响应,路由过滤器只能指定路由进行使用

Spring Cloud Gateway内置了多种路由过滤器,他们都有GatewayFilter的工厂来来产生

生命周期:
业务逻辑之前(pre):
业务逻辑之后(host):

种类:
GatewayFilter:31种
GlobalFilter:10种

自定义过滤器

主要是需要实现两个接口GlobalFilter,Ordered


@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain){
        System.out.println("全局过滤器");
        String name = exchange.getrequest().getQueryParams().getFilter("uname");
        if(uname == null){
            System.out.println("用户名为null,非法");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

SpringCloud Config 分布式配置中心

概述

微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一套集中式的、动态的配置管理设置时必不可少的

SpringCloud提供了ConfigServer来解决这个问题,我们每一个微服务自己带着一个application.yml,上百个配置文件需要管理

是什么
SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置

SpringCloud Config分为服务端和客户端两部分

服务端也成为分布式配置中心,他是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密和解密信息等访问接口

客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容

作用

  • 集中管理配置文件
  • 不同环境不同配置,动态化的配置更新,分环境部署,比如:dev、test、prod、beta、release
  • 运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取配置自己的信息
  • 当配置发生变动时,服务不需要重启即可感知配置的变化并应用新的配置
  • 将配置信息以rest接口的形式暴露

与GitHub整合配置
由于SpringCloud Config默认使用git来存储配置文件(也有其他方式,比如支持SVN和本地文件),但最推荐的还是git,而且使用的时http\https访问的形式

Config 服务端配置于测试

创建cloud-config-center-3344项目

pom


<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
</dependency>

application.yml


server:
  port: 3344

spring:
  application:
    name: cloud-config-center #注册进Eureka服务器的微服务
  cloud:
    config:
      server:
        git:
          # Github上面的git仓库名字(地址)
          uri: git@github.com:zzyybs/springcloud-config.git
          #搜索目录(仓库)
          search-paths:
            - springcloud-config
      #读取分支
      label: master

eureka:
  client:
    serveice-url:
      defaultZone: http://localhost:7001/eureka/

启动类


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

修改host文件,增加映射


127.0.0.1	config-3344.com

测试:
http://config-3344.com:3344/master/config-dev.yml

label:分支
name:服务名
profiles:环境(dev、test、prod)

Config 客户端配置于测试

新建cloud-config-client-3355项目

pom


<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config</artifactId>
</dependency>

application.yml 是用户级的资源配置项
bootstrap.yml 是系统级的,优先级更加高

Spring Cloud会创建一个”Bootstrap Context“,作为Spring应用的‘Application Context’的父上下文。初始化的时候,‘Bootstrap Context’负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的‘Environment’

‘Bootstrap‘属性有高优先级,默认情况下,他们不会被本地配置覆盖。‘Bootstrap Context’和‘Application Context’有着不同的约定,所以新增了一个‘Bootstrap.yml’文件,保证‘Bootstrap Context’和‘Application Context’配置的分离

要将Client模块下的application.yml文件改名为bootstrap.yml,这是很关键的
因为bootstrap.yml是比Application.yml先加载的。bootstrap.yml优先级高于Application.yml

bootstrap.yml


server:
  port: 3355
spring:
  application:
    name: config-client
  cloud:
    # config客户端配置
    config:
      # 以下配置组合:http://localhost:3344/master/config-dev.yml
      label: master #分支名称
      name: config #配置文件名称
      profile: dev #读取后缀名称
      uri: http://localhost:3344

eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka

启动类


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

controller


@RestController
public class ConfigController {
    @Value("${config.info}")
    private String configInfo;
    @GetMapping("/configInfo")
    public String getConfigInfo(){
        return configInfo;
    }
}

Config 客户端之动态刷新

当git上修改了配置,客户端必须重启才能获取到变更,这是个噩梦

pom文件追加


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

修改yml


server:
  port: 3355
spring:
  application:
    name: config-client
  cloud:
    # config客户端配置
    config:
      # 以下配置组合:http://localhost:3344/master/config-dev.yml
      label: master #分支名称
      name: config #配置文件名称
      profile: dev #读取后缀名称
      uri: http://localhost:3344

eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka

#暴露监控端点
management:
  endpoints:
    web:
      exposure:
        include: "*"

controller添加刷新注解


@RestController
@RefreshScope
public class ConfigController {
    @Value("${config.info}")
    private String configInfo;
    @GetMapping("/configInfo")
    public String getConfigInfo(){
        return configInfo;
    }
}

必须发送post请求刷新3355


curl -X POST "http://localhost:3355/actuator/refresh"

如果有很多服务,执行命令也是很头疼的

SpringCloud Bus 消息总线

概述

分布式自动刷新配置功能

SpringCloud Bus 配合 SpringCloud Config使用可以实现配置的动态刷新

Bus支持两种消息代理:RabbitMQ 和 Kafka

SpringCloud Bus是用来将分布式系统的节点与轻量级消息系统链接起来的框架,它整合了Java的事件处理机制和消息中间件的功能,SpringCloud Bus目前支持RabbitMQ和Kafka

什么是总线
在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中所有微服务实例都链接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称它为消息总线。在总线上的各个实例,都可以方便地广播一些需要让其他链接在该主题上的实例都知道的消息。

基本原理
ConfigClient实例都监听MQ中同一个topic(默认的SpringCloiudBus)。当一个服务刷新数据的时候,他会把这个消息放入到topic中,这样其他监听统一topic的服务就能得到通知,然后去更新自身的配置

RabbitMQ环境配置

安装,启动

SpringCloud Bus 动态刷新全局广播

接着上面例子3355,创建cloud-config-client-3366项目(复制)

两种设计思想

  1. 利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置
  2. 利用消息总线触发一个服务端ConfigServer(3344)的/bus/refresh端点,而刷新所有客户端的配置

第二种更适合,因为

  • 打破了微服务的职责单一性,因为微服务本身是业务模块,它本不应该承担配置刷新的职责
  • 破坏了微服务个节点的对等性
  • 有一定的局限性。例如,微服务在迁移时,它的网络地址常常会发生变化,此时如果想要做到自动刷新,那就会增加更多的修改

给cloud-config-center-3344配置中心服务端添加消息总线支持
pom文件添加rabbitMQ的依赖


<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

application.yml添加rabbitMQ相关配置


server:
  port: 3344

spring:
  application:
    name: cloud-config-center #注册进Eureka服务器的微服务
  cloud:
    config:
      server:
        git:
          # Github上面的git仓库名字
          uri: git@github.com:zzyybs/springcloud-config.git
          #搜索目录
          search-paths:
            - springcloud-config
      #读取分支
      label: master

# rabbitmq相关配置
	rabbitmq:
	  host: localhost
	  port: 5672
	  username: guest
	  password: guest

eureka:
  client:
    serveice-url:
      defaultZone: http://localhost:7001/eureka/

# rabbitMQ相关配置,暴露bus刷新配置的端点
management:
  endpoints: # 暴露bus刷新配置的端点
    web:
      exposure:
        include: 'bus-refresh'

给cloud-config-client-3355客户端添加消息总线支持

pom文件添加rabbitMQ的依赖


<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

application.yml修改rabbitMQ的配置


server:
  port: 3355
spring:
  application:
    name: config-client
  cloud:
    # config客户端配置
    config:
      # 以下配置组合:http://localhost:3344/master/config-dev.yml
      label: master #分支名称
      name: config #配置文件名称
      profile: dev #读取后缀名称
      uri: http://localhost:3344

  # rabbitMQ相关配置15672是web管理界面的端口;5672是MQ访问的端口
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka

#暴露监控端点
management:
  endpoints:
    web:
      exposure:
        include: "*"

给cloud-config-client-3366客户端添加消息总线支持

pom文件添加rabbitMQ的依赖


<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

application.yml修改rabbitMQ的配置


server:
  port: 3366
spring:
  application:
    name: config-client
  cloud:
    # config客户端配置
    config:
      # 以下配置组合:http://localhost:3344/master/config-dev.yml
      label: master #分支名称
      name: config #配置文件名称
      profile: dev #读取后缀名称
      uri: http://localhost:3344

  # rabbitMQ相关配置15672是web管理界面的端口;5672是MQ访问的端口
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka

#暴露监控端点
management:
  endpoints:
    web:
      exposure:
        include: "*"

测试:

  1. 运维工程师
    修改GitHub上配置文件增加版本号
    发送post请求,一次发送,处处生效

curl -X POST "http://localhost:3344/actuator/bus-refresh"
  1. 配置中心

http://config-3344.com:3344/config-dev.yml
  1. 客户端

http://config-3344.com:3344/config-dev.yml

一次修改,一次请求,广播通知,处处生效

SpringCloud Bus 动态刷新全顶点通知

定点通知,不想全局通知,例如只想通知3355,不想通知3366

命令

# 格式
http://配置中心地址/actuator/bus-refresh/微服务名:port
# 通知3355
curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:3355"

SpringCloud Stream 消息驱动

消息驱动概述

屏蔽底层消息中间件的差异,降低切换成本,同意消息的编程模型

什么是SpringCloudStream
官方定义Spring Cloud Stream是一个构建消息驱动微服务的框架

应用程序通过inputs或者outputs来与Spring Cloud Stream中binder对象交互。
通过我们配置来binding(绑定),而Spring Cloud Stream的binder对象负责与消息中间件交互。
所以,我们只需要搞清楚如何与Spring Cloud Stream交互就可以方便使用消息驱动的方式。

通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动。
Spring Cloud Stream为一些供应商的消息中间件产品提供了个性化的自动化配置实现,引用了发布-订阅、消费组、分区的三个核心概念。

目前仅支持RabbitMQ和Kafka

Spring Cloud Stream是用于构建与共享消息传递系统连接的高度可伸缩的事件驱动微服务框架,该框架提供了一个灵活的编程模型,它建立在已经建立和熟悉的Spring熟语和最佳实践上,包括支持持久化的发布-订阅、消费组以及消息分区这三个核心概念

设计思想

标准MQ
生产者、消费者之间靠消息媒介传递信息内容(message)
消息必须走特定的通道(消息通道MessageChannel)
消息通道里的消息如何被消费呢,该负责收发处理(消息通道MessageChannel的子接口SubscribableChannel,由MessageHandler消息处理器订阅)

stream凭什么可以统一底层差异
在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接与消息中间件进行交互的时候,由于个消息中间价构建的初衷不同,他们的实现细节上会由较大的差异性,通过定义帮顶起作为中间层,完美的实现了应用程序与消息中间件细节之间的隔离。用过向应用程序暴露统一的Channel通道,使得应用程序不需要再考虑各种不同的消息中间件实现。

通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离

input 对应于消费者
output 对应于生产者

Binder
在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接于消息中间件进行信息交互的时候,由于个消息中间件构建的初衷不同,他们的实现细节上会有较大的差异性,通过定义绑定器作为中间层,完美的实现了应用程序于消息中间件细节之间的隔离。Stream对消息中间件的进一步封装,可以做到代码层面对中间件的无感知,甚至于动态的切换中间(rabbitmq切换为kafka),使得微服务开发的高度解耦,服务可以关注更多自己的业务流程

通过定义绑定器Binder作为中间层,实现了应用程序于消息中间件细节之间的隔离

Stream中的消息通信方式遵循了发布-订阅模式,主要使用topic主题进行广播,在RabbitMQ就是Exchange,在Kafka中就是topic

Spring Cloud Stream 标准流程套路
Binder:很方便的连接中间件,屏蔽差异
Channel:通道,是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过Channel对队列进行配置
source和Sink:简单的可理解为参照对象是Spring Cloud Stream自身,从Stream发布消息就是输出,接收消息就是输入

编码API和常用注解

组成说明
Middleware中间件,目前只支持RabbitMQ和Kafka
BinderBinder是应用于消息中间件之间的封装,目前实行了Kafka和RabbitMQ的Binder,通过Binder可以很方便的连接中间件,可以动态的改变消息类型(对应于Kafka的topic,RabbitMQ的exchange),这些都可以通过配置文件来实现
@Input注解标识输入通道,通过该输入通道接收到的消息进入应用程序
@Output注解标识输出通道,发布的消息将通过该通道离开应用程序
@StreamListener监听队列,用于消费者的队列的消息接收
@EnableBinding指通道channel和exchange绑定在一起

案例说明

配置好RabbitMQ环境后
新建cloud-stream-rabbitmq-provider8801模块,作为生产者进行发消息模块
新建cloud-stream-rabbitmq-consumer8802模块,作为消息接收模块
新建cloud-stream-rabbitmq-consumer8803模块,作为消息接收模块

消息驱动之生产者

新建cloud-stream-rabbitmq-provider8801模块,作为生产者进行发消息模块

pom文件引入rabbitMQ的依赖


<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>

application.yml


server:
  port: 8801

spring:
  application:
    name: cloud-stream-provider
  cloud:
    stream:
      binders: #在此处配置要绑定的rabbitmq的服务消息
        defaultRabbit: #表示定义的名称,用于binding整合
          type: rabbit #消息组件类型
          environment: #设置rabbitmq的相关的环境配置
            spring:
              rabbitmq:
                host: localhost
                port: 5672
                username: guest
                password: guest
        bindings: #服务的整合处理
          output: #这个名字是一个通道的名称
            destination: studyExchange #表示要使用的Exchange名称定义
            content-type: application/json #设置消息类型,本次为json,文本则设置text/plain
            binder: defaultRabbit #设置要绑定的消息服务的具体设置
eureka:
  client: #客户端进行eureka注册的配置
    service-uri:
      defaultZone: http://localhost:7001/eureka
  instance:
    lease-renewal-interval-in-seconds: 2 #设置心跳的时间间隔(默认是30秒)
    lease-expiration-duration-in-seconds: 5 #如果现在超过了5秒的间隔(默认是90秒)
    instance-id: send-8801.com #在信息列表时显示主机名称
    prefer-ip-address: true #访问的路径变为IP地址

启动类


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

发送消息的接口


public interface IMessageProvider {
    public String send();
}

发送消息接口实现类


//@Service//不再是springboot的service
@EnableBinding(source.class)//定义消息的推送管道
public class MessageProviderImpl implements IMessageProvider{
    @Resource
    private MessageChannel output;//消息发送管道
    @Override
    public String send() {
        String serial = UUID.randomUUID().toString();
        output.send(MessafeBuilder.withPayload(serial).build());
        System.out.println("serial:"+serial);
        return null;
    }
}

controller


public class SendMessageController {
    @Resource
    private IMessageProvider messageProvider;

    @GetMapping("/sendMessage")
    public String sendMessage(){
        return messageProvider.send();
    }
}

消息驱动之消费者

新建cloud-stream-rabbitmq-consumer8802模块,作为消息接收模块

pom文件引入rabbitMQ的依赖


<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>

application.yml


server:
  port: 8802

spring:
  application:
    name: cloud-stream-consumer
  cloud:
    stream:
      binders: #在此处配置要绑定的rabbitmq的服务消息
        defaultRabbit: #表示定义的名称,用于binding整合
          type: rabbit #消息组件类型
          environment: #设置rabbitmq的相关的环境配置
            spring:
              rabbitmq:
                host: localhost
                port: 5672
                username: guest
                password: guest
        bindings: #服务的整合处理
          input: #这个名字是一个通道的名称
            destination: studyExchange #表示要使用的Exchange名称定义
            content-type: application/json #设置消息类型,本次为json,文本则设置text/plain
            binder: defaultRabbit #设置要绑定的消息服务的具体设置
eureka:
  client: #客户端进行eureka注册的配置
    service-uri:
      defaultZone: http://localhost:7001/eureka
  instance:
    lease-renewal-interval-in-seconds: 2 #设置心跳的时间间隔(默认是30秒)
    lease-expiration-duration-in-seconds: 5 #如果现在超过了5秒的间隔(默认是90秒)
    instance-id: send-8802.com #在信息列表时显示主机名称
    prefer-ip-address: true #访问的路径变为IP地址

启动类


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

controller


@Component
@EnableBinding(Sink.class)
public class ReceiverMessageListenerController {
    @Value("${server.port}")
    private String serverport;

    @StreamListener(Sinl.INPUT)
    public void input(Message<String> message){
        System.out.println("消费者8802:"+message.getPayload());
    }
}

分组消费与持久化(高级)

分组消费

依照8802,复制一个8803消息消费

现在是8801发送一条消息,8802和8803都可以收到
这时我们就可以使用Stream中的消息分组来解决

注意在Stream中处于同一个group中的多个消费者是竞争关系,就能够保证消息只会被其中一个应用消费一次。不同组是可以全面消费的(重复消费)
同一个组内会发生竞争关系,只有其中一个可以消费

故障现象:重复消费
导致原因:默认分组group是不同的,组流水号不一样,被认为不同组,可以消费

自定义配置分组
自定义配置分为同一个组,解决重复消费问题

原理:
微服务应用放置于同一个group中,就能保证消息只会被其中一个应用消费一次
不同的组是可以消费的,同一个组内会发生竞争关系,只有其中一个可以消费

修改application.yml,让8802和8803分成两个组


spring:
  application:
    name: cloud-stream-consumer
  cloud:
    stream:
      binders: #在此处配置要绑定的rabbitmq的服务消息
        defaultRabbit: #表示定义的名称,用于binding整合
          type: rabbit #消息组件类型
          environment: #设置rabbitmq的相关的环境配置
            spring:
              rabbitmq:
                host: localhost
                port: 5672
                username: guest
                password: guest
        bindings: #服务的整合处理
          input: #这个名字是一个通道的名称
            destination: studyExchange #表示要使用的Exchange名称定义
            content-type: application/json #设置消息类型,本次为json,文本则设置text/plain
            binder: defaultRabbit #设置要绑定的消息服务的具体设置
            group: atguiguA

持久化

分组属性还具有持久化的作用

没有自定义分组属性的消息消费方服务重启,导致消息丢失;
而自定义分组属性的消息消费方服务重启,会主动消费重启期间的消息

SpringCloud Sleuth 分布式请求链路跟踪

概述

大型分布式项目,微服务的调用会形成很长的链路
SpringCloud Sleuth帮我们管理监控这样的链路,提供了一套完整的服务跟踪的解决方案,在分布式系统中提供追踪解决方案并且兼容支持了zipkin

搭建链路监控步骤

zipkin

安装配置zipkin环境

一条链路通过TraceId唯一标识,Span标识发起请求信息,各span通过parentId关联起来

Trace:类似于树结构的Span集合,标识一条调用链路,存在唯一标识
span:表示调用链路来源,通俗的理解span就是一次请求信息

服务提供者

修改之前的cloud-provider-payment8001项目


<!-- 包含了sleuth+zipkin -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>

application.yml


server:
  port: 8001

spring:
  application:
    name: cloud-payment-service
  zipkin:
    base-url: http://localhost:9411
  sleuth:
    sampler:
      # 采样率介于0到1之间,1则表示全部采集
      probability: 1
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: root

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.atguigu.springcloud.entities


# eureka client 客户端配置
eureka:
  client:
    # 表示是否将自己注册到eureka server,默认为true
    register-with-eureka: true
    # 是否从eureka server抓取已有的注册信息,默认是true
    # 单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡的配置
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
  instance:
    # 主机名称修改
    instance-id: payment8001

controller


@GetMapping("/payment/zipkin")
public String paymentZipkin(){
    return "paymentZipkin";
}

服务消费者

修改之前的cloud-consumer-order80项目


<!-- 包含了sleuth+zipkin -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>

application.yml


spring:
  application:
    name: cloud-payment-service
  zipkin:
    base-url: http://localhost:9411
  sleuth:
    sampler:
      # 采样率介于0到1之间,1则表示全部采集
      probability: 1

controller


@GetMapping("/consumer/payment.zipkin")
public String paymentZipkin(){
    String result = restTemplate.getForObject("http://localhost:8001/payment/zipkin/",String.class);
    return result;
}

依次启动eureka7001、8001、80

查看http://localhost:9411
会展示出上面链路的详细信息

总结一句话:
sleuth 用来收集链路信息
zipkin 用来展示sleuth收集的链路信息

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值