SpringCloud学习笔记

本笔记是根据尚硅谷周阳老师的springcloud视频教程整理

B站视频教程:尚硅谷SpringCloud框架开发教程

springcloud大纲

学习大纲如下图

image-20220319111107904

版本选择

springboot:推荐2.x版本,不要去使用1.x版本了

springboot和springcloud版本选择的对应关系如下图

image-20220319112819884

springboot和springcloud版本的对应关系

本课程使用的各技术版本

技术版本
cloudHoxton.SR1
boot2.2.RELEASE
cloudalibaba 2.1.0.RELEASE
javajava8
Maven3.5以上
mysql5.7以上

cloud各种组件的停更/替换

以前

image-20220319113815361

现在

image-20220319114738105

cloud的相关文档

H版英文文档

springcloud中文文档

springboot英文文档

微服务架构编码 构建

约定>配置>编码

IDEA新建project工作空间

  • New Project

image-20220319153224412

  • 聚合总父工程名字

image-20220319153444695

  • maven用自己本地的

image-20220319153603583

  • 项目编码

image-20220319154052554

  • 注解生效激活

image-20220319154300117

  • java版本选8

image-20220319154402918

  • 父pom文件:dependencyManagement主要用来锁定版本号的,锁定之后,子模块就不需要写版本号了。
<?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.cloud</groupId>
    <artifactId>cloud2020</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>cloud-provider-payment8001</module>
        <module>cloud-api-commons</module>
        <module>cloud-consumer-order80</module>
    </modules>

    <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.18.0</lombok.version>
        <mysql.version>5.1.47</mysql.version>
        <druid.version>1.1.16</druid.version>
        <!--<mybatis.spring.boot.version>1.3.2</mybatis.spring.boot.version> -->
        <!--我这里用的mybatis-plus -->
        <mybatis.plus.boot.version>3.5.1</mybatis.plus.boot.version>
    </properties>

    <!-- 子模块继承之后,提供作用:锁定版本+子模块不用写groupId和version  -->
    <dependencyManagement>
        <dependencies>
            <!--  springboot 2.2.2    -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.2.2.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--  spring cloud Hoxton.SR1   -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--  spring cloud alibaba 2.1.0.RELEASE    -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.1.0.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>${druid.version}</version>
            </dependency>
            <!--<dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>${mybatis.spring.boot.version}</version>
            </dependency>-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis.plus.boot.version}</version>
            </dependency>
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>${log4j.version}</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>${junit.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>
  • maven中跳过单元测试

image-20220319155316100

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

热部署的配置

配置热部署之后,springboot项目可以自动的重启。配置步骤如下:

第一步:添加依赖到模块中

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

第二步:添加依赖到父项目中

<build>
    <fileName>你自己的工程名字<fileName>
    <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配置 Enabling automatic build

image-20220324141952206

第四步:idea配置Update the value of

  • 在idea中按下: crtl+shift+alt+/

  • 选择Registry

image-20220324142204566

  • 将下图的两个配置勾上

image-20220324142406888

Rest微服务工程搭建

cloud-api-commons

此模块为公共模块

  • 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">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.atguigu.cloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-api-commons</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <!--            <optional>true</optional>-->
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.1.0</version>
        </dependency>
    </dependencies>
</project>
  • 结构如图

image-20220322111050141

  • CommonResult类:公共的json返回结果类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {
    private Integer code;
    private String message;
    private T data;
    public CommonResult(Integer code, String message) {
        this(code, message, null);
    }
}
  • payment类:支付类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;

/**
 * (Payment)实体类
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment implements Serializable {
    private Long id;
    private String serial;
}
  • Order类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order implements Serializable {
    private Long id;
    private Long userId;
    private Long productId;
    private Integer count;
    private BigDecimal money;
    private Integer status; //订单状态:0:创建中,1:已创建
}

cloud-provider-payment8001

微服务提供者-支付模块

image-20220319161847316

完整项目结构如下

image-20220322112053163

创建完成后回到父工程查看pom文件变化

image-20220319163010993

  • 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.cloud</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.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
        </dependency>
        <!--引入cloud-api-commons公共依赖模块-->
        <dependency>
            <groupId>com.atguigu.cloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>
        <!--热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

    </dependencies>

</project>
  • cloud-provider-payment8001 的application.yml文件
server:
  port: 8001

spring:
  application:
    name: cloud-payment-service
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/db2020_cloud
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.atguigu.springcloud.entities
  • cloud-provider-payment8001 的启动类:PaymentMain8001
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.atguigu.springcloud.dao")
public class PaymentMain8001 {

    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8001.class,args);
    }
}
  • cloud-provider-payment8001 的PaymentDao类
import com.atguigu.springcloud.entities.Payment;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

public interface PaymentDao extends BaseMapper<Payment> {
}
  • cloud-provider-payment8001 的PaymentServiceImpl
import com.atguigu.springcloud.service.PaymentService;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.dao.PaymentDao;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;

/**
 * (Payment)表服务实现类
 */
@Service
public class PaymentServiceImpl implements PaymentService {
    @Resource
    private PaymentDao paymentDao;

    /**
     * 通过ID查询单条数据
     *
     * @param id 主键
     * @return 实例对象
     */
    @Override
    public Payment queryById(Long id) {
        return this.paymentDao.selectById(id);
    }

    /**
     * 新增数据
     *
     * @param payment 实例对象
     * @return 实例对象
     */
    @Override
    public Payment insert(Payment payment) {
        this.paymentDao.insert(payment);
        return payment;
    }

    /**
     * 修改数据
     *
     * @param payment 实例对象
     * @return 实例对象
     */
    @Override
    public Payment update(Payment payment) {
        this.paymentDao.updateById(payment);
        return payment;
    }

    /**
     * 通过主键删除数据
     *
     * @param id 主键
     * @return 是否成功
     */
    @Override
    public boolean deleteById(Long id) {
        return this.paymentDao.deleteById(id) > 0;
    }
}
  • cloud-provider-payment8001 的PaymentController
import com.atguigu.springcloud.service.PaymentService;
import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;


/**
 * (Payment)表控制层
 */
@RestController
@RequestMapping("payment")
public class PaymentController {
    /**
     * 服务对象
     */
    @Resource
    private PaymentService paymentService;

    /**
     * 通过主键查询单条数据
     *
     * @param id 主键
     * @return 单条数据
     */
    @GetMapping("get/{id}")
    public CommonResult<Payment> selectOne(@PathVariable("id") Long id) {
        Payment payment = this.paymentService.queryById(id);
        return new CommonResult<Payment>(200,"select success 8001 hlf2!",payment);
    }

    @PostMapping("create")
    //注意:这里千万别忘了加@RequestBody,否则RestTemplate调用之后参数处理不了
    public CommonResult create(@RequestBody Payment payment) {
        Payment insert = this.paymentService.insert(payment);
        System.out.println(insert);
        System.out.println("1234567890");
        return new CommonResult(200,"insert success" ,insert);
    }
}

cloud-consumer-order80

订单模块,此模块会通过RestTemplate调用payment(支付模块)。这个模块的结构和支付模块的结构类似

image-20220322113335114

  • 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">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.atguigu.cloud</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-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
        </dependency>
        <!--引入公共模块-->
        <dependency>
            <groupId>com.atguigu.cloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>
        <!--热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

    </dependencies>

</project>
  • ApplicationContextConfig:RestTemplate的配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ApplicationContextConfig {

    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}
  • OrderController
import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;

@RestController
public class OrderController {

    @Resource
    private RestTemplate restTemplate;

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

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

    @GetMapping("/consumer/payment/get/{id}")
    public CommonResult<Payment> selectOne(@PathVariable("id") Long id) {
        return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
    }
}
  • 主启动类:OrderMain80
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

总结

这一部分周阳老师暂时还没有用到微服务springcloud的组件,只是方便大家熟悉代码,为后面的学习打基础用的。

Eruka

Eruka是一个注册中心组件

Eruka的基础知识

image-20220322114806433

image-20220322114833299

image-20220322114904872

image-20220322114935615

构建单机Eureka

cloud-eureka-server7001

这个7001模块为注册中心,属于eruka的服务端模块

  • cloud-eureka-server7001的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">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.atguigu.cloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>cloud-eureka-server7001</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</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>com.atguigu.cloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>
  • cloud-eureka-server7001的application.yml
server:
  port: 7001

spring:
  application:
    name: cloud-eureka-server7001

eureka:
  instance:
    hostname: localhost
  client:
    fetch-registry: false  #不注册自己
    register-with-eureka: false # 不检索自己
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  server:
    #关闭自我保护机制,保证不可用服务立即被踢出 ,默认是true开启的
    enable-self-preservation: true
    eviction-interval-timer-in-ms: 2000
  • EurekaMain7001.java 注意要加上**@EnableEurekaServer** 这个注解
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

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

cloud-provider-payment8001

这个模块是eruka的客户端模块

  • cloud-provider-payment8001的pom.xml,需要加上eruka的客户端依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  • cloud-provider-payment8001的application.yml,需要加上eruka的客户端配置
eureka:
  client:
    #表示将自己注册到eureka
    register-with-eureka: true
    # 是否从EurekaServer抓取已有的注册信息,单节点无所谓,集群必须设置为true
    fetch-registry: true
    # eureka服务端的地址
    service-url:
      defaultZone: http://localhost:7001/eureka
  • cloud-provider-payment8001的主启动类加上**@EnableEurekaClient**注解

cloud-consumer-order80

这个模块同样是eureka的客户端模块

  • cloud-consumer-order80的pom.xml增加eureka的依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  • cloud-consumer-order80的application.yml增加eureka的配置
eureka:
  client:
    #表示将自己注册到eureka
    register-with-eureka: true
    # 是否从EurekaServer抓取已有的注册信息,单节点无所谓,集群必须设置为true
    fetch-registry: true
    # eureka服务端的地址
    service-url:
      defaultZone: http://localhost:7001/eureka
  • cloud-consumer-order80的主启动类加上**@EnableEurekaClient**注解

先启动eureka的服务端,再启动两个客户端,访问7001服务端可以看到注册进去的两个服务的名称。

image-20220324152456869

构建Eureka服务端集群

eureka集群原理如下

image-20220324153046747

cloud-eureka-server7002

这个模块也是eureka服务端,类比cloud-eureka-server7001新建cloud-eureka-server7002。但是需要做一些修改。

  • 修改映射配置
1.找到C:\Windows\System32\drivers\etc路径下的hosts文件,末尾添加如下:

127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com

2.cmd命令行刷新hosts文件

ipconfig /flushdns
  • 修改7001的application.yml文件

eureka服务端的集群配置特点:相互注册相互守望

server:
  port: 7001
spring:
  application:
    name: cloud-eureka-service #修改1:7001和7002这里改成一致
eureka:
  instance:
    # eureka服务端的实例名称
    hostname: eureka7001.com #修改2
  client:
    # false表示不向注册中心注册自己
    register-with-eureka: false
    # false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要检索服务
    fetch-registry: false
    service-url:
      # 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址
      defaultZone: http://eureka7002.com:7002/eureka/ #修改3 相互注册,相互守望
  • 修改7002的application.yml文件
server:
  port: 7002
spring:
  application:
    name: cloud-eureka-service
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两个eureka服务后发现:相互注册、相互守望

image-20220324161530766

cloud-provider-payment8001

将这个服务注册到Eureka的集群,修改8001的application.yml文件

eureka:
  client:
    #表示将自己注册到eureka
    register-with-eureka: true
    # 是否从EurekaServer抓取已有的注册信息,单节点无所谓,集群必须设置为true
    fetch-registry: true
    # eureka服务端的地址
    service-url:
      #defaultZone: http://localhost:7001/eureka # 单机版
      #主要就是修改下面这行
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka 

然后测试,就会发现8001同时注册到7001和7002两个eureka的服务端了。

cloud-consumer-order80

将这个服务注册到Eureka的集群,修改80的application.yml文件,和上面的cloud-provider-payment8001做同样的修改

#主要就是修改下面这行
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka

然后测试,就会发现7001和7002这两个eureka的服务端中同时注册了80和8001两个服务

构建Eureka客户端集群

cloud-provider-payment8002

参考cloud-provider-payment8001构建cloud-provider-payment8002

修改8001和8002的controller

目的是测试订单服务调用的到底是哪个端口的支付服务image-20220324165830811

image-20220324165846068

启动测试发现同一个服务名下注册了两个端口的支付服务

image-20220324170940264

测试http://localhost/consumer/payment/get/31发现调用的永远是8001的那个微服务,显然没有实现负载均衡。

负载均衡

实现订单微服务均衡的调用支付的微服务。也就是8001和8002轮流调用

  • 订单controller中的地址不能写死

image-20220324172027959

  • ApplicationContextConfig中开启RestTemplate的负载均衡,**@LoadBalanced**注解

image-20220324172101334

测试结果:8001/8002端口交替出现

actuator微服务信息完善

非必须,只不过完善后看起来会更加的清晰。在eureka中做如下修改即可

instance:
    instance-id: payment8001 #修改名称
    prefer-ip-address: true  #显示ip

主机名称:服务名称修改

当前问题

image-20220324172830327

修改application.yml即可

image-20220324172856819

修改之后的效果

image-20220324173012503

访问信息有IP信息提示

当前问题:没有IP提示

修改application.yml即可

image-20220324173146576

修改后的效果

image-20220324173215157

服务发现Discovery

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

cloud-provider-payment8001

  • 启动类加上注解:**@EnableDiscoveryClient** (不加这个注解发现不了!!!)
  • 修改PaymentController,加入下面的代码
@Resource
private DiscoveryClient discoveryClient;//服务发现,获取服务信息

@GetMapping("discovery")
public Object discovery() {
    //获取所有微服务
    List<String> services = discoveryClient.getServices();
    services.forEach(service->{
        System.out.println("----service:"+service);
    });
    //获取一个微服务下的全部实例
    List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
    for (ServiceInstance instance : instances) {
        System.out.println(instance.getServiceId()+"\t" + instance.getHost()+"\t"+ instance.getPort()+"\t"+instance.getUri());;
    }

    return this.discoveryClient;
}

image-20220324211316405

eureka自我保护

  • 故障现象

image-20220324203712327

  • 导致原因:某时刻 一个微服务不可用了,Eureka不会立刻清理,依旧会对该服务的信息进行保存。

属于CAP里面的AP分支。

禁用自我保护

在eurekaServer和eurekaClient分别进行配置

注册中心eurekaServer端7001

出产默认,自我保护机制是开启的 ,可以在配置文件中关闭:

eureka.server.enable-self-preservation: false

关闭效果

image-20220324204326254

生产者客户端eurekaClient端8001
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 30
lease-expiration-duration-in-seconds: 90

image-20220324205202954

Zookeeper服务注册与发现

略(后面再补)

Consul服务注册与发现

官网:https://www.consul.io/intro/index.html

consul简介

是什么

image-20220325102353499

能干嘛

image-20220325102501076

consul的安装

下载:https://www.consul.io/downloads.html

中文文档:https://www.springcloud.cc/spring-cloud-consul.html

ps:下载很慢,可以安装迅雷之后用迅雷加速下载

  • 下载完毕之后的压缩包中有一个consul.exe文件

  • consul.exe文件的路径配置到环境变量的path中即可,例如下面

D:\springcloud_soft\consul_1.11.4

image-20220325103604551

  • 配好之后cmd窗口中输入下面的指令,看到consul的版本号,表示安装成功
consul --version

consul的启动和访问

  • 在cmd窗口中运行启动命令 (使用开发模式启动)
consul agent -dev

服务提供者

新建cloud-providerconsul-payment8006工程

  • cloud-providerconsul-payment8006的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">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.atguigu.cloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-providerconsul-payment8006</artifactId>


    <dependencies>
        <!--SpringCloud consul-server-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
        <!--引入公共依赖-->
        <dependency>
            <groupId>com.atguigu.cloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
            <scope>compile</scope>
        </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>
  • application.yml
server:
  # consul服务端口
  port: 8006
spring:
  application:
    name: cloud-provider-payment
  cloud:
    consul:
      # consul注册中心地址
      host: localhost
      port: 8500
      discovery:
        hostname: 127.0.0.1
        service-name: ${spring.application.name}
        prefer-ip-address: true
  • 主启动类PaymentMain8006,主要添加@EnableDiscoveryClient注解
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain8006 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8006.class, args);
    }
}
  • controller
@RestController
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();
    }
}

服务消费者

新建cloud-consumerconsul-order80模块

  • cloud-consumerconsul-order80的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">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.atguigu.cloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumerconsul-order80</artifactId>

    <dependencies>
        <!--SpringCloud consul-server-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
        <!--引入公共依赖-->
        <dependency>
            <groupId>com.atguigu.cloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
            <scope>compile</scope>
        </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>
  • cloud-consumerconsul-order80的application.yml
server:
  port: 80
spring:
  application:
    name: cloud-consumer-order
  cloud:
    consul:
      # consul注册中心地址
      host: localhost
      port: 8500
      discovery:
        hostname: 127.0.0.1
        service-name: ${spring.application.name}
        prefer-ip-address: true
  • ApplicationContextConfig,主要是为了RestTemplate的远程调用
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ApplicationContextConfig {

    @Bean
    @LoadBalanced //开启RestTemplate的负载均衡
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}
  • OrderController
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;

@RestController
public class OrderController {

    @Resource
    private RestTemplate restTemplate;

    //注意下面这个地址的对应
    public static final String PAYMENT_URL="http://cloud-provider-payment";

    @GetMapping("/consumer/payment/consul")
    public String consul() {
        return restTemplate.getForObject(PAYMENT_URL+"/payment/consul",String.class);
    }
}
  • 主启动类添加@EnableDiscoveryClient注解

测试

  • 测试consul注册中心中是否将服务提供者和消费者注册进去image-20220325160430199
  • 测试服务消费者能否成功调用服务提供者

测试url:http://localhost/consumer/payment/consul

结果成功调用

image-20220325160529522

三个注册中心异同点

image-20220325160638135

CAP

  • CAP概念
C: Consistency(强一致性)

A: Availability(可用性)

P: Parttition tolerance(分区容错性)
  • 分区容错性要保证,所以要么是CP,要么是AP

  • CAP理论关注粒度是否是数据,而不是整体系统设计的策略

  • 经典CAP图

image-20220325160945521

image-20220325160653008

AP(eureka)

image-20220325161050875

CP(Zookeeper/Consul)

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

image-20220325161332377

Ribbon负载均衡服务调用

和feign的功能类似主要是用来负载均衡远程调用,Ribbon目前也进入维护模式

image-20220403093022665

概述

image-20220403092611726

官网资料: https://github.com/Netflix/ribbon/wiki/Getting-Started

一句话:负载均衡+RestTemplate调用(前面我们讲解过了80通过轮询负载访问8001/8002)

Ribbon负载均衡演示

架构说明

image-20220403093511666

总结

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

2.之前没有引入Ribbon依赖也可以使用是因为,eureka的client中默认引入了Ribbon的依赖

image-20220403093931357

3.二说RestTemplate的使用

RestTemplate官网

  • getForObject方法/getForEntity方法

image-20220403094225269

  • postForObject/postForEntity

image-20220403094408540

RestTemplate的:
    xxxForObject()方法,返回的是响应体中的数据
    xxxForEntity()方法.返回的是entity对象,这个对象不仅仅包含响应体数据,还包含响应体信息(状态码等)
    
    xxx 可以是get也可以使post

Ribbon核心组件IRule

IRule接口,Riboon使用该接口,根据特定算法从所有服务中,选择一个服务。Rule接口有7个实现类,每个实现类代表一个负载均衡算法

image-20220403095315885

将轮询调用改为随机调用

此处要注意一个细节:新建的rule配置类不能放在主启动类所在的包及子包下

image-20220403095905874

修改cloud-consumer-order80,具体步骤如下:

第一步:新建package以及新建MySelfRule规则类

image-20220403100515702

注意:新建的rule配置类不能放在主启动类所在的包及子包下

MySelfRule内容如下

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MySelfRule {

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

第二步:主启动类添加**@RibbonClient**注解

@SpringBootApplication
@MapperScan("com.atguigu.springcloud.dao")
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class)
//表示,访问CLOUD_pAYMENT_SERVICE的服务时,使用我们自定义的负载均衡算法
public class OrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain80.class,args);
    }
}

第三步:测试 http://localhost/consumer/payment/get/31

Ribbon负载均衡算法

OpenFeign服务接口调用

Feign是一个声明式的web服务客户端,让编写web服务客户端变得非常容易,只需创建一个接口并在接口上添加注解即可(一个接口 + 一个注解)

GitHub

概述

image-20220403102948332

image-20220403103319851

Feign和OpenFeign两者区别

image-20220403103435984

OpenFeign使用步骤

1.主启动加@EnableFeignClients 注解

2.新建接口+接口上加@FeignClient注解

具体案例如下:

  • 新建cloud-consumer-feign-order80模块

  • cloud-consumer-feign-order80的pom.xml

<!--openfeign-->
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-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>
        <dependency>
            <groupId>com.atguigu.cloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
  • cloud-consumer-feign-order80的application.yml
server:
  port: 80

spring:
  application:
    name: cloud-order-service

eureka:
  client:
    register-with-eureka: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka
  • cloud-consumer-feign-order80的主启动
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients  //表示开启Feign
public class OrderFeignMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderFeignMain80.class,args);
    }
}
  • cloud-consumer-feign-order80的PaymentFeignService
import com.atguigu.springcloud.entities.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE") //表示Feign调用哪个微服务
public interface PaymentFeignService {

    @GetMapping(value = "/payment/get/{id}")
    public CommonResult getPaymentById(@PathVariable("id") Long id);
}
  • cloud-consumer-feign-order80的OrderFeignController
@RestController
public class OrderFeignController {

    @Resource
    private PaymentFeignService paymentFeignService;

    @GetMapping(value = "/consumer/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
        return paymentFeignService.getPaymentById(id);
    }
}
  • 测试
先启动eureka7001
再启动2个微服务8001/8002
启动OpenFeign启动
测试:http://localhost/consumer/payment/get/31

测试发现:自带负载均衡(轮询)
  • 小总结

image-20220403110312477

OpenFeign超时控制

OpenFeign默认等待一秒钟,超过后报错.

OpenFeign默认支持Ribbon

因为OpenFeign的底层是ribbon进行负载均衡,所以它的超时时间是由ribbon控制。

YML文件里开启OpenFeign客户端的超时控制

ribbon:
  ReadTimeout:  5000
  ConnectTimeout: 5000

案例如下:超时设置,故意设置超时演示出错情况

  • 服务提供方8001故意写暂停程序
@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout(){
    try { TimeUnit.SECONDS.sleep(3); }catch (Exception e) {e.printStackTrace();}
    return serverPort;
}
  • 服务消费方80添加超时方法PaymentFeignService
@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout();
  • 服务消费方80添加超时方法OrderFeignController
@GetMapping(value = "/consumer/payment/feign/timeout")
public String paymentFeignTimeout(){
   return paymentFeignService.paymentFeignTimeout();
}

image-20220403114909935

解决超时错误: YML文件里需要开启OpenFeign客户端超时控制

ribbon:
  ReadTimeout:  5000
  ConnectTimeout: 5000

OpenFeign日志打印功能

image-20220403115138866

Feign日志级别有如下

image-20220403115159325

配置步骤如下:

第一步:配置日志bean

package com.atguigu.springcloud.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FeignConfig {

    @Bean
    Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }
}

第二步:YML文件里需要开启日志的Feign客户端

logging:
  level:
    com.atguigu.springcloud.service.PaymentFeignService: debug

第三步:控制台就可以看到日志了

Hystrix断路器

概述

image-20220403163238944

image-20220403163258545

image-20220403163320203

image-20220403163344596

image-20220403163550382

Hystrix能干嘛

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

Hystrix官方文档

Hystrix官宣,停更进维

Hystrix重要概念

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

哪些情况会触发降级?

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

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

就是保险丝:      服务的降级->进而熔断->恢复调用链路

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

降级和熔断的区别与联系:

服务降级有很多种降级方式!如开关降级、限流降级、熔断降级!
服务熔断属于降级方式的一种!

hystrix案例基础

构建基础模块

  • 新建cloud-provider-hystrix-payment8001模块

  • cloud-provider-hystrix-payment8001的pom.xml

<dependencies>
        <!--新增hystrix-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <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>
  • cloud-provider-hystrix-payment8001的application.yml
server:
  port: 8001

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

spring:
  application:
    name: cloud-provider-hystrix-payment
  • cloud-provider-hystrix-payment8001的service
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;

@Service
public class PaymentService {

    //成功
    public String paymentInfo_OK(Integer id){
        return "线程池:"+Thread.currentThread().getName()+"   paymentInfo_OK,id:  "+id+"\t"+"哈哈哈"  ;
    }

    //失败
    public String paymentInfo_TimeOut(Integer id){
        int timeNumber = 3;
        try { TimeUnit.SECONDS.sleep(timeNumber); }catch (Exception e) {e.printStackTrace();}
        return "线程池:"+Thread.currentThread().getName()+"   paymentInfo_TimeOut,id:  "+id+"\t"+"呜呜呜"+" 耗时(秒)"+timeNumber;
    }

}
  • cloud-provider-hystrix-payment8001的controller
import com.atguigu.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;

@RestController
@Slf4j
public class PaymentController {

    @Resource
    private PaymentService paymentService;

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

    //ok
    @GetMapping("/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id){
        String result = paymentService.paymentInfo_OK(id);
        log.info("*******result:"+result);
        return result;
    }
    //3秒
    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        String result = paymentService.paymentInfo_TimeOut(id);
        log.info("*******result:"+result);
        return result;
    }
}

测试基础模块

以上述为根基模块,从正确->错误->降级熔断->恢复

高并发测试

下载压力测试工具JMeter

下载慢的话,可以用迅雷

JMeter解压后运行bat文件即可

image-20220403172357178

运行之后默认是英文的,可以设置为中文

参考: 设置JMeter界面为中文

第一步:右键测试,添加,线程,线程组

image-20220403173607152

第二步:填写好线程组相关信息,这里就2000吧怕电脑带不动,视频里是20000

image-20220403173647183

第三步:创建http请求

image-20220403173747234

image-20220403173814986

然后启动就可以进行压力测试了

此时使用压测工具,并发20000个请求,请求会延迟的那个方法,
压测中,发现,另外一个方法并没有被压测,但是我们访问它时,却需要等待
这就是因为被压测的方法它占用了服务器大部分资源,导致其他请求也变慢了

tomcat的默认的工作线程数被打满了,没有多余的线程来分解压力和处理。

Jmeter压测结论: 上面还是服务提供者8001自己测试,假如此时外部的消费者80也来访问,那消费者只能干等,最终导致消费端80不满意,服务端8001直接被拖死

看热闹不嫌弃事大,80新建加入

  • 新建cloud-consumer-feign-hystrix-order80模块
  • cloud-consumer-feign-hystrix-order80的pom.xml
 <dependencies>
        <!--新增hystrix-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>com.atguigu.cloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </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>
  • cloud-consumer-feign-hystrix-order80的application.yml
cloud-consumer-feign-hystrix-order80的application.ymlserver:
  port: 80

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

spring:
  application:
    name: cloud-provider-hystrix-order
  • cloud-consumer-feign-hystrix-order80的主启动
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients
public class OrderHystrixMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderHystrixMain80.class,args);
    }
}
  • cloud-consumer-feign-hystrix-order80的service
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@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(@PathVariable("id") Integer id);
}
  • cloud-consumer-feign-hystrix-order80的controller
import com.atguigu.springcloud.service.PaymentHystrixService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@Slf4j
public class OrderHystrixController {

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

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id){
        String result = paymentHystrixService.paymentInfo_OK(id);
        log.info("*******result:"+result);
        return result;
    }
    //feigh默认是1s得做出响应,这个远程调用肯定会报超时错误
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        String result = paymentHystrixService.paymentInfo_TimeOut(id);
        log.info("*******result:"+result);
        return result;
    }

}

正常测试:http://localhost/consumer/payment/hystrix/ok/31

高并发测试

2W个线程压8001
消费端80微服务再去访问正常的OK微服务8001地址

http://localhost/consumer/payment/hystrix/timeout/31
要么转圈圈等待
要么消费端报超时错误

故障现象和导致原因

现象:80此时调用8001,客户端访问响应缓慢,转圈圈

原因:8001同一层次的其他接口服务被困死,因为tomcat线程里面的工作线程已经被挤占完毕

上诉结论

正因为有上述故障或不佳表现,才有我们的降级/容错/限流等技术诞生

如何解决?解决的要求

image-20220403181146372

服务降级

<1> cloud-provider-hystrix-payment8001 的降级配置(生产者降级)

先从自身找问题设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处理,作服务降级fallback。

  • 主启动类激活:添加新注解**@EnableCircuitBreaker**
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker  //激活hystrix
public class PaymentHystrixMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8001.class,args);
    }
}
  • 业务类配置:一旦调用服务方法失败并抛出了错误信息后,会自动调用**@HystrixCommand**标注好的fallbackMethod调用类中的指定方法

注意:配置过的热部署方式对java代码的改动明显,但对@HystrixCommand内属性的修改建议重启微服务

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;

@Service
public class PaymentService {

    //成功
    public String paymentInfo_OK(Integer id){
        return "线程池:"+Thread.currentThread().getName()+"   paymentInfo_OK,id:  "+id+"\t"+"哈哈哈"  ;
    }

    //失败
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")  //3秒钟以内就是正常的业务逻辑
    })
    public String paymentInfo_TimeOut(Integer id){
        //int age = 10/0;
        try { TimeUnit.SECONDS.sleep(id); }catch (InterruptedException e) {e.printStackTrace();}
        return "线程池:"+Thread.currentThread().getName()+"   paymentInfo_TimeOut,id:  "+id+"\t"+"呜呜呜"+" 耗时(秒)";
    }

    //兜底方法
    public String paymentInfo_TimeOutHandler(Integer id){
        return "线程池:"+Thread.currentThread().getName()+"   系统繁忙或报错, 请稍候再试  ,id:  "+id+"\t"+"哭了哇呜";
    }

}

测试(注意:这里目前只是测试payment这个服务)

我这里是否超时,可以通过传参来控制更加方便!

http://localhost:8001/payment/hystrix/timeout/2   //不报错 3秒以内
http://localhost:8001/payment/hystrix/timeout/5   //报错   超过3秒

<2> cloud-consumer-feign-hystrix-order80的降级配置(消费端降级)

一般服务降级,都是放在客户端/消费端(order模块)

  • cloud-consumer-feign-hystrix-order80的application.yml

老师是配置下面的,但是有问题,会一直走fallback

feign:
  hystrix:
    enabled: true #如果处理自身的容错就开启。开启方式与生产端不一样。

还要增加ribbon的配置

feign:
  hystrix:
    enabled: true #如果处理自身的容错就开启。开启方式与生产端不一样。

## 巨坑只配上面的会一直走fallback
ribbon:
  ReadTimeout:  6000
  ConnectTimeout: 6000

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000
  • cloud-consumer-feign-hystrix-order80的主启动类添加**@EnableHystrix**注解
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients
@EnableHystrix //消费端开启Hystrix
public class OrderHystrixMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderHystrixMain80.class,args);
    }
}
  • cloud-consumer-feign-hystrix-order80的controller
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
    @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")  //1.5秒钟以内就是正常的业务逻辑
    })
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        String result = paymentHystrixService.paymentInfo_TimeOut(id);
        return result;
    }

    //兜底方法
    public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
        return "我是消费者80,对付支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,(┬_┬)";
    }

测试:

http://localhost/consumer/payment/hystrix/timeout/1  正常,不走fallback
http://localhost/consumer/payment/hystrix/timeout/5  超时,走fallback

代码膨胀问题解决

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

解决方案: @DefaultProperties(defaultFallback=””) 注解

image-20220403230331413

image-20220403230236695

修改cloud-consumer-feign-hystrix-order80的controller

package com.atguigu.springcloud.controller;

import com.atguigu.springcloud.service.PaymentHystrixService;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;

@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class OrderHystrixController {

    @Resource
    private PaymentHystrixService paymentHystrixService;

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

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id){
        String result = paymentHystrixService.paymentInfo_OK(id);
        log.info("*******result:"+result);
        return result;
    }

    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    /*@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")  //1.5秒钟以内就是正常的业务逻辑
    })*/
    @HystrixCommand //这种没有指明的就会用全局fallback
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        String result = paymentHystrixService.paymentInfo_TimeOut(id);
        return result;
    }

    //兜底方法
    public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
        return "我是消费者80,对付支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,(┬_┬)";
    }

    /**
     * 全局fallback
     *
     * @return
     */
    public String payment_Global_FallbackMethod() {
        return "Global异常处理信息,请稍后重试.o(╥﹏╥)o";
    }

}

测试

http://localhost/consumer/payment/hystrix/timeout/1   正常
http://localhost/consumer/payment/hystrix/timeout/3   超时,走全局fallback

代码耦合度的问题

服务降级,客户端去调用服务端,碰上服务端宕机或关闭

image-20220404160740103

本次案例服务降级处理是在客户端80实现完成,与服务端8001没有关系 只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦。

也就是说只修改cloud-consumer-feign-hystrix-order80模块即可

  • PaymentHystrixService接口是远程调用pay模块的,我们这里创建一个类实现PaymentHystrixService接口,在实现类中统一处理异常
package com.atguigu.springcloud.service;

import org.springframework.stereotype.Service;

@Service
public class PaymentFallbackService implements  PaymentHystrixService{
    //这个类来实现fallback
    @Override
    public String paymentInfo_OK(Integer id) {
        return "实现类PaymentFallbackService  paymentInfo_OK";
    }

    @Override
    public String paymentInfo_TimeOut(Integer id) {
        return "实现类PaymentFallbackService  paymentInfo_TimeOut";
    }
}
  • 让PayService的实现类生效
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class) //就是在这里fallback
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(@PathVariable("id") Integer id);
}

测试

http://localhost/consumer/payment/hystrix/timeout/3 走PaymentFallbackService里的fallback
http://localhost/consumer/payment/hystrix/timeout/1 正常返回

这样虽然解决了代码耦合度问题,但是又出现了过多重复代码的问题,每个方法都有一个降级方法

服务熔断

概念

  • 断路器:一句话就是家里的保险丝
  • 熔断

image-20220404163210814

修改cloud-provider-hystrix-payment8001

  • cloud-provider-hystrix-payment8001的PaymentService新建下面方法
//---服务的熔断
    @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"),//失败率达到多少后跳闸

    }
    )
    //这里属性整体意思是:10秒之内(窗口,会移动),如果并发==超过==10个,或者10个并发中,失败了6个,就开启熔断器
    public String paymentCircuitBreaker(Integer id) {
        if (id<0) {
            throw new RuntimeException("******id不能为负数");
        }
        //IdUtil是Hutool包下的类,这个Hutool就是整合了常用方法
        String simpleUUID = IdUtil.simpleUUID();
        return Thread.currentThread().getName()+"\t" + "成功调用,流水号是:" + simpleUUID;
    }

    public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
        return "id不能为负数,请稍后再试............"+id;
    }
  • cloud-provider-hystrix-payment8001的PaymentController新建方法
 //测试熔断
    @GetMapping("/payment/circuit/{id}")
    public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
        String result = paymentService.paymentCircuitBreaker(id);
        log.info("*******result:"+result);
        return result;
    }

测试

http://localhost:8001/payment/circuit/31   正确
http://localhost:8001/payment/circuit/-31  错误

多次访问,并且错误率超过60%,此时服务熔断,此时即使访问正确的也会报错.
但是,当过了几秒后,又恢复了.
因为在10秒窗口期内,它自己会尝试接收部分请求,发现服务可以正常调用,慢慢的当错误率低于60%,取消熔断.

》Hystrix所有可配置的属性

**全部在这个方法中记录,以成员变量的形式记录,**以后需要什么属性,查看这个类即可

image-20220404165558262

原理/总结

  • 大神结论

image-20220404165630986

  • 熔断类型
熔断打开: 请求不再调用当前服务,内部设置一般为MTTR(平均故障处理时间),当打开长达导所设时钟则进入半熔断状态

熔断关闭: 熔断关闭后不会对服务进行熔断

熔断半开: 部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断
  • 熔断流程(其实就是大神结论 的图)
断路器的打开和关闭,是按照一下5步决定的
      1,并发此时是否达到我们指定的阈值
      2,错误百分比,比如我们配置了60%,那么如果并发请求中,10次有6次是失败的,就开启断路器
      3,上面的条件符合,断路器改变状态为open(开启)
      4,这个服务的断路器开启,所有请求无法访问
      5,在我们的时间窗口期,期间,尝试让一些请求通过(半开状态),如果请求还是失败,证明断路器还是开启状态,服务没有恢复,如果请求成功了,证明服务已经恢复,断路器状态变为close关闭状态
  • 断路器开启或者关闭的条件
1.当满足一定的阈值的时候(默认10秒钟超过20个请求次数)
2.当失败率达到一定的时候(默认10秒内超过50%的请求次数)
3.到达以上阈值,断路器将会开启
4.当开启的时候,所有请求都不会进行转发
5.一段时间之后(默认5秒),这个时候断路器是半开状态,会让其他一个请求进行转发. 如果成功,断路器会关闭,若失败,继续开启.重复4和5
  • 断路器打开之后

image-20220404170333355

服务限流

后面高级篇讲解alibaba的Sentinel说明

hystrix工作流程

1请求进来,首先查询缓存,如果缓存有,直接返回
      如果缓存没有,--->2
2,查看断路器是否开启,如果开启的,Hystrix直接将请求转发到降级返回,然后返回
      如果断路器是关闭的,
                判断线程池等资源是否已经满了,如果已经满了
                      也会走降级方法
              如果资源没有满,判断我们使用的什么类型的Hystrix,决定调用构造方法还是run方法
        然后处理请求
        然后Hystrix将本次请求的结果信息汇报给断路器,因为断路器此时可能是开启的
                      (因为断路器开启也是可以接收请求的)
                断路器收到信息,判断是否符合开启或关闭断路器的条件,
                如果本次请求处理失败,又会进入降级方法
        如果处理成功,判断处理是否超时,如果超时了,也进入降级方法
        最后,没有超时,则本次请求处理成功,将结果返回给controller

image-20220404170605986

服务监控hystrixDashboard

概述

image-20220404170708635

第一步:新建cloud-consumer-hystrix-dashboard9001 监控服务

  • cloud-consumer-hystrix-dashboard9001的pom.xml
<dependencies>
        <!--hystrix dashboard-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        </dependency>
        <!--监控-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-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>
  • cloud-consumer-hystrix-dashboard9001的application.yml
server:
  port: 9001
  • cloud-consumer-hystrix-dashboard9001的主启动
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;

@SpringBootApplication
@EnableHystrixDashboard   //加上这个注解
public class HystrixDashboardMain9001 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixDashboardMain9001.class);
    }
}
  • 所有Provider微服务提供类(8001/8002/8003)都需要监控依赖部署
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

第二步:修改cloud-provider-hystrix-payment8001的主启动

import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker  //激活hystrix
public class PaymentHystrixMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8001.class,args);
    }

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

监控测试

1.启动eureka
2.启动cloud-provider-hystrix-payment8001
3.启动cloud-consumer-hystrix-dashboard9001

访问地址:http://localhost:9001/hystrix
填写地址:http://localhost:8001/hystrix.stream

在web界面,指定9001要监控8001

image-20220404173133297

就可以看到监控的信息了

image-20220404173209440

监控说明:7色+1圈+1线

整图说明

image-20220404173414768

image-20220404173349194

image-20220404173440126

Gateway新一代网关

Gateway官网

概述

image-20220405102713103

image-20220405103022721

gateway之所以性能好,因为底层使用WebFlux,而webFlux底层使用netty通信(NIO)

image-20220405102822498

GateWay的特性

image-20220405103236241

GateWay与zuul的区别

image-20220405103409891

Zuul1.x模型

image-20220405103533745

image-20220405103553800

GateWay三大核心概念

  • Route(路由) :路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如断言为true则匹配该路由(就是根据某些规则,将请求发送到指定服务上

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

  • Filter(过滤): 指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改.

Gateway工作流程

image-20220405104258807

image-20220405104309788

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

入门配置

需求:目前不想暴露8001端口,希望在8001外面套一层9527,通过访问9527来间接的访问8001.

新建Module cloud-gateway-gateway9527

  • cloud-gateway-gateway9527的pom.xml
<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--引入公共依赖-->
        <dependency>
            <groupId>com.atguigu.cloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
    </dependencies>
  • cloud-gateway-gateway9527的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
  • cloud-gateway-gateway9527的主启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

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

测试:启动7001、8001、9527

访问http://localhost:9527/payment/get/31和访问http://localhost:8001/payment/get/31效果一样

于是就可以验证网关的效果了

访问说明

image-20220405110939286

Gateway网关路由有两种配置方式

第一种:在配置文件yml中配置(上面的操作便是)

第二种:代码中注入RouteLocator的Bean(不推荐)

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class GateWayConfig {

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        routes.route("path_rote_atguigu", r -> r.path("/guonei").uri("http://news.baidu.com/guonei")).build();
        return routes.build();
    }
}

image-20220405111446368

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

上面的配置虽然实现了网关,但是是在配置文件中写死了要路由的地址

现在需要修改,不指定地址,而是根据微服务名字进行路由,我们可以在注册中心获取某组微服务的地址

修改网关yml的两个地方即可

image-20220405112636796

完整网关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 #需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。
          predicates:
            - Path=/payment/get/**   #断言,路径相匹配的进行路由

        - id: payment_routh2
          #uri: http://localhost:8001
          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

测试

启动7001 、8001、8002、9527

测试地址:http://localhost:9527/payment/get/31 发现8001和8002来回调用实现了负载和动态路由

Predicate的使用

image-20220405115859709

之前在配置文件中配置了断言

image-20220405115941620

这个断言表示,如果外部访问路径是指定路径,就路由到指定微服务上

启动9527可以看到,这里有一个Path,这个是断言的一种,断言的类型

image-20220405120021877

  • After: 可以指定,只有在指定时间后,才可以路由到指定微服务
before:  与after类似,他说在指定时间之前的才可以访问
between: 需要指定两个时间,在他们之间的时间才可以访问(两个时间用逗号隔开)

下面配置一个After作为例子

9527的yml新加After的配置

image-20220405121529030

那么这个时间怎么得出来呢,可以通过下面的代码

public class TestDate {
    public static void main(String[] args) {
        ZonedDateTime zonedDateTime = ZonedDateTime.now();
        System.out.println(zonedDateTime);
    }
}

这里表示,只有在2022年的4月5的12点5分5秒之后访问才可以路由,在此之前的访问,都会报404

image-20220405121444653

  • Cookie: 只有包含某些指定Cookie(key,value),的请求才可以路由

image-20220405122108341

  • Header: 只有包含指定请求头的请求,才可以路由

image-20220405122216820

测试

image-20220405122236209

  • Host:只有指定主机的才可以访问,

    比如我们当前的网站的域名是www.aa.com

    那么这里就可以设置,只有用户是www.aa.com的请求,才进行路由

    在application.yml配置

    image-20220405122447170

  • Method: 只有指定请求才可以路由,比如get请求…

image-20220405122603016

  • Path: 只有访问指定路径,才进行路由。比如访问,/abc才路由

image-20220405122659793

  • Query: 必须带有请求参数才可以访问

image-20220405122753973

Filter的使用

image-20220405113408653

Spring Cloud Gateway的Filter

  • 生命周期(类比servlet的Filter)

image-20220405113529335

  • 种类

image-20220405113611266

GateWayFilter,单一的过滤器。与断言类似,比如闲置,请求头,只有特定的请求头才放行,反之就过滤

image-20220405114055228

  • 自定义全局GlobalFilter

image-20220405114732641

在cloud-gateway-gateway9527模块中新建filter包,并且新建MyLogGateWayFilter类

package com.atguigu.springcloud.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Date;

@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter,Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        log.info("*********come in MyLogGateWayFilter: "+new Date());
        //获取到请求参数的uname
        String uname = exchange.getRequest().getQueryParams().getFirst("uname");
        //如果uname为空就直接过滤,不走路由
        if(StringUtils.isEmpty(uname)){
            log.info("*****用户名为Null 非法用户,(┬_┬)");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);//给人家一个回应
            return exchange.getResponse().setComplete();
        }
        //uname不为空就走下一个路由
        return chain.filter(exchange);
    }

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

测试

启动 7001 、8001、9527

http://localhost:9527/payment/get/31?uname=1    带uname参数  访问正常
http://localhost:9527/payment/get/31            不带uname参数 访问不了

Config分布式配置中心

概述

微服务面临的问题

可以看到,每个微服务都需要一个配置文件,并且,如果有几个微服务都需要连接数据库
那么就需要配4次数据库相关配置,并且当数据库发生改动,那么需要同时修改4个微服务的配置文件才可以

所以有了springconfig配置中心

image-20220405163736356

image-20220405163800691

作用

image-20220405163917302

创建配置中心服务端

第一步:新建gitee仓库,提交相关文件

image-20220405170143355

config-dev.yml内容(随便写点都行,主要用来区分) prod和test的yml和下面的类似

config:
  info: I am test,version=1

第二步:新建cloud-config-3344模块

  • cloud-config-3344 的pom.xml
<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>com.atguigu.cloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </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>
  • cloud-config-3344 的application.yml
server:
  port: 3344
spring:
  application:
    name: cloud-config-center
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/heliufang/springcloud-config.git # git地址
          search-paths:
            - springcloud-config #git路径
      label: master #git分支
eureka:
  client:
    service-url:
      defaultZone:  http://localhost:7001/eureka
  • cloud-config-3344 的主启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

@SpringBootApplication
@EnableConfigServer //开启配置中心服务
public class ConfigCenterMain3344 {
    public static void main(String[] args) {
        SpringApplication.run(ConfigCenterMain3344 .class,args);
    }
}
  • 配置windows的hosts
127.0.0.1 config-3344.com

image-20220405172153643

  • 测试
启动7001
启动3344
访问:http://config-3344.com:3344/master/config-test.yml

可以获取到git上的配置信息表示配置成功!

读取配置文件的规则

第一种(推荐这种)

image-20220405172901762

第二种

image-20220405172954026

这里默认会读取master分支,因为我们配置文件中配置了

image-20220405173027659

第三种:注意这个方式读取到的配置是json格式

image-20220405173118387

重要配置细节总结

image-20220405172656195

创建配置中心客户端

新建cloud-config-client-3355模块

  • cloud-config-client-3355的pom.xml
<dependencies>
        <!-- 注意下面这个依赖,否则启动不了 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>com.atguigu.cloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </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>
  • cloud-config-client-3355的 bootstrap.yml
server:
  port: 3355

spring:
  application:
    name: config-client
  cloud:
    config:
      label: master # 分支名称
      name: config # 配置文件名称
      profile: dev # 读取后缀名称    三个综合就会读取master分支上的config-dev.yml配置文件
      uri: http://localhost:3344 # 配置中心地址 地址要写对否则启动不了

eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka

image-20220406140617135

  • cloud-config-client-3355的 主启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class ConfigClientMain3355 {
    public static void main(String[] args) {
        SpringApplication.run( ConfigClientMain3355.class,args);
    }
}
  • cloud-config-client-3355的 controller
@RestController
public class ConfigClientController {

    @Value("${config.info}")
    private String configInfo;

    @GetMapping("/configInfo")
    public String getConfigInfo(){
        return configInfo;
    }
}
  • 测试
启动:7001、3344、3355
直接通过配置中心访问git上面的信息:  http://config-3344.com:3344/master/config-test.yml
3355来通过3344来访问git上面的信息: http://localhost:3355/configInfo

测试结论:成功实现了客户端3355访问SpringCloud Config3344通过git获取配置信息

Config客户端之动态刷新

Linux运维修改git上的配置文件内容做调整

刷新3344,发现ConfigServer配置中心立刻响应

刷新3355,发现ConfigServer客户端没有任何响应

3355没有变化除非自己重启或者重新加载

难道每次运维修改配置文件,客户端都需要重启??噩梦

  • cloud-config-client-3355的yml增加配置
# 配置动态刷新
management:
  endpoints:
    web:
      exposure:
        include: "*"
  • cloud-config-client-3355的pom.xml中要加入下面依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  • cloud-config-client-3355的ConfigClientController加上@RefreshScope注解
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RefreshScope //开启刷新
public class ConfigClientController {

    @Value("${config.info}")
    private String configInfo;

    @GetMapping("/configInfo")
    public String getConfigInfo(){
        return configInfo;
    }
}

测试

启动7001、3344、3355

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

http://localhost:3355/configInfo  git上提交修改后还是没有刷新??

需要发送一个post请求: curl -X POST "http://localhost:3355/actuator/refresh"

此时再测试  http://localhost:3355/configInfo 发现刷新成功了

具体流程就是:

​ 我们启动好服务后

​ 运维人员,修改了配置文件,然后发送一个post请求通知3355

​ 3355就可以获取最新配置文件

问题:

​ 如果有多个客户端怎么办(3355,3356,3357…..)

​ 虽然可以使用shell脚本,循环刷新

​ 但是,可不可以使用广播,一次通知??

​ 这些springconfig做不到,需要使用springcloud Bus消息总线

Bus 消息总线

概述

image-20220406162546409

image-20220406162610509

注意:上面的两图片,就代表两种广播方式(推荐第二种)

​ 图1: 它是Bus直接通知给其中一个客户端,由这个客户端开始蔓延,传播给其他所有客户端

​ 图2: 它是通知给配置中心的服务端,有服务端广播给所有客户端

image-20220406162646126

RabbitMQ环境配置

参考:RabbitMQ安装

访问:http://localhost:15672/

账号密码都是guest

动态刷新之全局广播

必须先具备良好的RabbitMQ环境

演示广播效果,增加复杂度,再以3355为模板再制作一个3366

<1>给cloud-config-center-3344配置中心服务端添加消息总线支持

  • cloud-config-center-3344的pom增加下面 依赖 amqp
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
  • cloud-config-center-3344的yml增加下面的配置
rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

management:
  endpoints:
    web:
      exposure:
        include: 'bus-refresh'

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

  • cloud-config-client-3355客户端的pom 增加amqp
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
  • cloud-config-client-3355客户端的yml 添加rabbitmq的配置
rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

<3>给cloud-config-client-3366客户端添加消息总线支持 参考上面的3355

测试

启动7001,3344,3355,3366

此时修改git上的配置文件

此时只需要刷新3344,即可让3355,3366动态获取最新的配置文件

curl -X POST "http://localhost:3344/actuator/bus-refresh"


http://localhost:3355/configInfo
http://localhost:3366/configInfo

其原理就是:所有客户端都监听了一个rabbitMq的topic,我们将信息放入这个topic,所有客户端都可以送到,从而实时更新

动态刷新之定点通知

  1. 就是只通知部分服务,比如只通知3355,不通知3366

  2. 公式:http://localhost:配置中心的端口号/actuator/bus-refresh/{destination}

  3. /bus/refresh请求不再发送到具体的服务实例上,而是发给config server并通过destination参数类指定需要更新配置的服务或实例

image-20220406172021082

curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:3355"

Stream消息驱动

现在一个很项目可能分为三部分:
前端—>后端—->大数据
而后端开发使用消息中间件,可能会使用RabbitMq
而大数据开发,一般都是使用Kafka,
那么一个项目中有多个消息中间件,对于程序员,因为人员都不友好

而Spring Cloud Stream就类似jpa,屏蔽底层消息中间件的差异,程序员主要操作Spring Cloud Stream即可

​ 不需要管底层是kafka还是rabbitMq

Sleuth分布式请求链路追踪

概述

image-20220406173528868

Spring Cloud Sleuth提供了一套完整的服务跟踪的解决方案,在分布式系统中提供追踪解决方案并且兼容支持了zipkin

image-20220406180455437

可以看到,类似链表的形式

搭建链路监控

安装zipkin

下载: https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/

运行jar包

​ java -jar xxxx.jar

image-20220406180253904

然后就可以访问web界面, 默认zipkin监听的端口是9411

​ localhost:9411/zipkin/

image-20220406180341322

使用sleuth

不需要额外创建项目,使用之前的8001和order的80即可

<1>修改cloud-provider-payment8001模块

  • cloud-provider-payment8001模块pom
<!--包含了sleuth+zipkin-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>
  • cloud-provider-payment8001模块yml 添加下面的配置
zipkin:
    base-url: http://localhost:9411 #指定zipkin地址
  sleuth:
    sampler:
      probability: 1 # 采样率介于0-1之间,1表示全部采集

image-20220406182402336

<2>修改cloud-consumer-order80模块

和上面的<1>一样

<3>测试

启动7001.8001,80,9411

先访问:http://localhost/consumer/payment/get/31 调用一次

然后在zipkin中就可以看到相关信息了

image-20220406182618713

Spring Cloud Alibaba入门简介

之所以有Spring Cloud Alibaba,是因为Spring Cloud Netflix项目进入维护模式

也就是,就不是不更新了,不会开发新组件了

所以,某些组件都有代替版了,比如Ribbon由Loadbalancer代替,等等

image-20220406205040717

Spring Cloud Alibaba官网

Spring Cloud Alibaba官网中文

Sentinel:把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

Nacos:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。

RocketMQ:一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。

Dubbo:Apache Dubbo™ 是一款高性能 Java RPC 框架。

Seata:阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。

Alibaba Cloud ACM:一款在分布式架构环境中对应用配置进行集中管理和推送的应用配置中心产品。

Alibaba Cloud OSS: 阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。

Alibaba Cloud SchedulerX: 阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。

Alibaba Cloud SMS: 覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。

Nacos服务注册和配置中心

简介

<1>前四个字母分别为Naming和Configuration的前两个字母,最后的s为Service

<2>一个更易于构建云原生应用的动态服务发现,配置管理和服务管理中心

<3>Nacos就是注册中心+配置中心的组合, Nacos = Eureka+Config+Bus

<4>替代Eureka做服务注册中心,替代Config做服务配置中心

下载nacos

https://nacos.io/zh-cn/index.html

安装运行Nacos

本地Java8+Maven环境已经OK

先从官网下载Nacos:https://github.com/alibaba/nacos/releases/tag/1.1.4

解压安装包,直接运行bin目录下的startup.cmd

命令运行成功后直接访问http://localhost:8848/nacos (默认账号密码都是nacos)

image-20220406211600106

Nacos作为注册中心

现在不需要额外的服务注册模块了,Nacos单独启动了

<1>新建cloudalibaba-provider-payment9001模块 (服务提供者)

  • 父工程的pom引入下面的依赖
<!--  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>
  • cloudalibaba-provider-payment9001模块的pom
<dependencies>
         <!--服务的注册与发现-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-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><dependency>
        <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>
    </dependencies>
  • cloudalibaba-provider-payment9001模块的application.yml
server:
  port: 9001

spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #配置Nacos地址

management:
  endpoints:
    web:
      exposure:
        include: '*'
  • cloudalibaba-provider-payment9001模块的主启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient
@SpringBootApplication
public class PaymentMain9001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain9001.class,args);
    }
}
  • cloudalibaba-provider-payment9001模块的controller
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;


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

    @GetMapping(value = "/payment/nacos/{id}")
    public String getPayment(@PathVariable("id") Integer id)
    {
        return "nacos registry, serverPort: "+ serverPort+"\t id"+id;
    }
}

测试

启动9001,然后查看Nacos的web界面,可以看到9001已经注册成功

image-20220406214745738为了下面演示nacos的负载均衡,参考上面的9001模块,新建cloudalibaba-provider-payment9002模块

<2>新建cloudalibaba-consumer-nacos-order83模块

  • cloudalibaba-consumer-nacos-order83模块的 pom.xml
<dependencies>
    <!--SpringCloud ailibaba nacos -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
        <dependency>
        <groupId>com.atguigu.springcloud</groupId>
        <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>
  • cloudalibaba-consumer-nacos-order83模块的 application.yml
server:
  port: 83

spring:
  application:
    name: nacos-order-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

service-url:
  nacos-user-service: http://nacos-payment-provider # 远程调用地址
  • cloudalibaba-consumer-nacos-order83模块的 主启动
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient
@SpringBootApplication
public class OrderNacosMain83
{
    public static void main(String[] args)
    {
        SpringApplication.run(OrderNacosMain83.class,args);
    }
}
  • cloudalibaba-consumer-nacos-order83模块的 ApplicationContextConfig
package com.atguigu.springcloud.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;


@Configuration
public class ApplicationContextConfig
{
    @Bean
    @LoadBalanced //负载均衡
    public RestTemplate getRestTemplate()
    {
        return new RestTemplate();
    }
}
  • cloudalibaba-consumer-nacos-order83模块的 OrderNacosController
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;

@RestController
@Slf4j
public class OrderNacosController
{
    @Resource
    private RestTemplate restTemplate;//远程调用

    @Value("${service-url.nacos-user-service}")
    private String serverURL;

    @GetMapping(value = "/consumer/payment/nacos/{id}")
    public String paymentInfo(@PathVariable("id") Long id)
    {
        return restTemplate.getForObject(serverURL+"/payment/nacos/"+id,String.class);
    }

}

测试

启动83,访问9001,9002

访问:http://localhost:83/consumer/payment/nacos/13

可以看到,实现了轮询负载均衡

<3>Nacos与其他服务注册的对比

image-20220406221523144

Nacos支持AP和CP模式的切换

image-20220406221624134

上面这个curl命令,就是切换模式

Nacos作为服务配置中心

基础配置

新建cloudalibaba-config-nacos-client3377模块

  • cloudalibaba-config-nacos-client3377模块的pom
<dependencies>
    <!--nacos-config-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
    <!--nacos-discovery-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!--web + actuator-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!--一般基础配置-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
  • cloudalibaba-config-nacos-client3377模块的application.yml
spring:
  profiles:
    active: dev
  • cloudalibaba-config-nacos-client3377模块的bootstrap.yml
server:
  port: 3377

spring:
  application:
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #服务注册中心地址
      config:
        server-addr: localhost:8848 #配置中心地址
        file-extension: yaml #指定yaml格式的配置
  • cloudalibaba-config-nacos-client3377模块的主启动类
@EnableDiscoveryClient
@SpringBootApplication
public class NacosConfigClientMain3377 {
    public static void main(String[] args) {
        SpringApplication.run(NacosConfigClientMain3377.class, args);
    }
}
  • cloudalibaba-config-nacos-client3377模块的controller
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RefreshScope //开启配置自动刷新
public class ConfigClientController
{
    @Value("${config.info}")
    private String configInfo;

    @GetMapping("/config/info")
    public String getConfigInfo() {
        return configInfo;
    }
}
  • 在Nacos中添加配置信息

配置规则,就是我们在客户端如何指定读取配置文件,配置文件的命名的规则

默认的命名方式:

image-20220407105231059

image-20220407105243474

image-20220407105557290

在nacos的web界面上创建配置文件

image-20220407105727354

注意: DataId就是配置文件名字,名字一定要按照上面的规则命名,否则客户端会读取不到配置文件

测试: 重启3377客户端

测试一: 访问:http://localhost:3377/config/info 可以成功读取到nacos上面的配置信息

测试二: 然后修改nacos上面的配置文件,发现客户端是可以立即更新的,这是因为Nacos支持Bus总线,会自动发送命令更新所有客户端

分类配置

image-20220407110953525

Nacos的图形化管理界面

配置管理

image-20220407111135409

命名空间

image-20220407111208957

Namespace+Group+Data ID三者关系?为什么这么设计?

NameSpace默认有一个: public名称空间

这三个类似java的: 包名 + 类名 + 方法名

image-20220407111346267

<1>DataID方案

image-20220407210059609

image-20220407210242676

image-20220407210154361

通过配置文件,实现多环境的读取

image-20220407210312764

测试http://localhost:3377/config/info 看能否成功获取到配置信息

此时,改为dev,就会读取dev的配置文件,改为test,就会读取test的配置文件

<2>Group方案

  • 通过Group实现环境区分,新建Group

image-20220407211618099

  • 在nacos图形界面控制台上面新建配置文件DataID

image-20220407211717910

  • 3377模块的bootstrap+application

在config下增加一条group的配置即可。可配置为DEV_GROUP或TEST_GROUP

image-20220407211849932

测试http://localhost:3377/config/info 看能否成功获取到配置信息

<3>Namespace方案

  • 新建dev/test的Namespace

image-20220407213513504

  • 回到服务管理-服务列表查看

image-20220407213536425

  • 按照域名配置填写

image-20220407213604591

  • bootstrap.yml和application.yml 注意 application.yml填写好dev

image-20220407213837628

测试http://localhost:3377/config/info 看能否成功获取到配置信息

Nacos集群和持久化配置

切换mysql数据库

Nacos默认自带的是嵌入式数据库derby

derby到我们自己的mysql切换配置步骤

第一步:mysql中新建nacos-config数据库,nacos-server-1.1.4\nacos\conf目录下找到sql脚本并执行

第二步:修改Nacos安装目录下的安排application.properties,添加

##################################################
spring.datasource.platform=mysql
 
db.num=1
db.url.0=jdbc:mysql://localhost:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=root

此时可以重启nacos,那么就会改为使用我们自己的mysql

测试:在nacos上面新建一个yaml配置 发现我们自己mysql的config_info表中多了一条数据

Linux版Nacos+MySQL生产环境配置

Sentinel

一句话解释,之前我们讲解过的Hystrix。官网

概述

image-20220407220639494

image-20220407220520775

sentinel安装

1>下载sentinel的jar包 下载网址

2>运行sentinel

​ 由于是一个jar包,所以可以直接java -jar运行

​ 注意,默认sentinel占用8080端口

3>访问sentinel localhost:8080 登录的账号密码均为sentinel

初始化演示工程

新建cloudalibaba-sentinel-service8401模块

  • cloudalibaba-sentinel-service8401模块的pom
<dependencies>
    <!--引入公共依赖-->
    <dependency>
        <groupId>com.atguigu.cloud</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>${project.version}</version>
        <scope>compile</scope>
    </dependency>
    <!--引入nacos依赖-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!--引入nacos-sentinel依赖-->
    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-datasource-nacos</artifactId>
    </dependency>
     <!--引入sentinel依赖-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</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>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>4.6.3</version>
    </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>
  • cloudalibaba-sentinel-service8401模块的application.yml
server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080
        port: 8719  #默认8719,假如被占用了会自动从8719开始依次+1扫描。直至找到未被占用的端口

management:
  endpoints:
    web:
      exposure:
        include: '*'
  • cloudalibaba-sentinel-service8401模块的主启动
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient
@SpringBootApplication
public class MainApp8401 {
    public static void main(String[] args) {
        SpringApplication.run(MainApp8401.class, args);
    }
}
  • cloudalibaba-sentinel-service8401模块的controller
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class FlowLimitController {
    @GetMapping("/testA")
    public String testA() {
        return "------testA";
    }
    @GetMapping("/testB")
    public String testB() {
        return "------testB";
    }
}

测试

启动nacos、sentinel、以及8401

访问:http://localhost:8080/ 发现什么都没有,原因是sentinel是懒加载,需要调用一次才可以

于是访问:http://localhost:8401/testA http://localhost:8401/testB

此时再查看sentinel就有信息了,说明sentinel8080正在监控微服务8401

流控规则

image-20220408113311475

image-20220408113536235

<1>直接快速失败-QPS

image-20220408172915366

测试:1s发一个请求没事,快速的刷就被限流了

测试地址:http://localhost:8401/testA

image-20220408173132950

<2>直接-线程数

image-20220408173442690

image-20220408173558591

修改8401的controller

@GetMapping("/testA")
public String testA() {
    try { TimeUnit.SECONDS.sleep(2); }catch (Exception e) {e.printStackTrace();}
    return "------testA";
}

测试:浏览器和postman各个发一个 http://localhost:8401/testA 请求,就会出现限流了

比如a请求过来,处理很慢,在一直处理,此时b请求又过来了
        此时因为a占用一个线程,此时要处理b请求就只有额外开启一个线程
        那么就会报错

<3>关联-快速失败

应用场景: 比如支付接口达到阈值,就要限流下订单的接口,防止一直有订单

image-20220408174255511

当testB达到阈值,qps大于1,就让testA之后的请求直接失败

可以使用postman压测

image-20220408174520683

<4>链路

多个请求调用了同一个微服务

<5>预热Warm up

image-20220408180006425

image-20220408180021903

image-20220408180042637

image-20220408180104783

应用场景

image-20220408180157218

测试: 前5s阈值是3,所以浏览器快速的刷 http://localhost:8401/testB 被限流了,经过5s后阈值慢慢的加到10了就不报限流错误,除非阈值超过10.

<6>排队等待

image-20220408181813746

image-20220408181844849

image-20220408181921992

降级规则

就是熔断降级

image-20220408183707198

image-20220408183740371

<1>RT配置(秒级)

image-20220408184419499

第一步:8401的controller新增一个请求方法用于测试

@GetMapping("/testD")
public String testD() {
    try { TimeUnit.SECONDS.sleep(1); }catch (Exception e) {e.printStackTrace();}
    return "------testD";
}

第二步:配置的PT,默认是秒级的平均响应时间

image-20220408185256797

默认计算平均时间是: 1秒类进入5个请求,并且响应的平均值超过阈值(这里的200ms),就报错

1秒5请求是Sentinel默认设置的

image-20220408185344799

image-20220408185401867

默认熔断后.就直接抛出异常

<2>异常比例(秒级)

image-20220408205303408

image-20220408205310655

修改8401的controller方法

@GetMapping("/testD")
public String testD() {
    //try { TimeUnit.SECONDS.sleep(1); }catch (Exception e) {e.printStackTrace();}
    int i = 1/0;//异常比例为100%
    return "------testD";
}

配置sentinel

image-20220408205555725

配置jMeter

image-20220408211201573

测试

没触发熔断,正常抛出异常(先不开jmeter就是这样)

image-20220408211259778

触发熔断(开启jmeter)

image-20220408211340920

测试结论

image-20220408211505660

<3>异常数

image-20220408211534177

image-20220408211545405

8401的controller添加一个方法

@GetMapping("/testE")
public String testE(){
    int age = 10/0;
    return "------testE 测试异常数";
}

配置sentinel

image-20220408212008073

热点key限流

image-20220408220159366

image-20220408220233895

比如:

​ localhost:8080/aa?name=aa

​ localhost:8080/aa?name=bb

​ 加入两个请求中,带有参数aa的请求访问频次非常高,我们就现在name==aa的请求,但是bb的不限制

如何自定义降级方法,而不是默认的抛出异常?

image-20220408220334186

image-20220408220403106

 @GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey")
//@SentinelResource(value = "testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
                         @RequestParam(value = "p2",required = false) String p2) {
    //int age = 10/0;//sentinel只对sentinel热点规则处理,不会对这个处理
    return "------testHotKey";
}

//兜底方法
public String deal_testHotKey (String p1, String p2, BlockException exception){
    return "------deal_testHotKey,o(╥﹏╥)o";
}

定义热点规则

image-20220408220538753

测试1.此时我们访问/testHotkey并且带上p1参数,如果qps大于1,就会触发我们定义的降级方法

测试2.此时我们访问/testHotkey并且不带上p1参数,就没事

只有带了p1,才可能会触发热点限流

image-20220408220834527

设置热点规则中的其他选项

image-20220408221314106

添加配置

image-20220408221419827

image-20220408221955917

注意: 参数类型只支持,8种基本类型+String类

注意:

如果我们程序出现异常,是不会走blockHander的降级方法的,因为这个方法只配置了热点规则,没有配置限流规则

我们这里配置的降级方法是sentinel针对热点规则配置的

只有触发热点规则才会降级

image-20220408222133092

系统规则(不推荐,太暴力了)

系统自适应限流:
从整体维度对应用入口进行限流

对整体限流,比如设置qps到达100,这里限流会限制整个系统(不友好,影响整体性能)

image-20220408222634882

配置全局QPS

image-20220408222722996

测试: 阈值超过一每个请求都被限流了

image-20220408222938838

SentinelResource注解

<1>按资源名称限流+后续处理

以前流控异常会抛出下面的异常,现在是想改成自定义的

image-20220408231120098

  • 8401新增一个controller
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.atguigu.springcloud.entities.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
public class RateLimitController {
    @GetMapping("/byResource")
    @SentinelResource(value = "byResource",blockHandler = "handleException")
    public CommonResult byResource() {
        return new CommonResult(200,"按资源名称限流测试OK",new Payment(2020L,"serial001"));
    }
    //自定义流控异常处理
    public CommonResult handleException(BlockException exception) {
        return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服务不可用");
    }
}
  • sentinel中添加流控配置

image-20220408231306581

  • 测试: 可以看到已经进入自定义的降级方法了

image-20220408231410515

此时关闭8401服务,这些定义的规则是临时的,关闭服务,规则就没有了

<2>按照Url地址限流+后续处理

  • 8401的RateLimitController新增方法
@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl")
public CommonResult byUrl(){
    return new CommonResult(200,"按url限流测试OK",new Payment(2020L,"serial002"));
}
  • sentinel配置: 前面配置的是@SentinelResource的value值,而这里配置的是url,同样有效。

image-20220408233237294

可以看到,上面两个配置的降级方法,又出现Hystrix遇到的问题了

​ 降级方法与业务方法耦合

​ 每个业务方法都需要对应一个降级方法

<3>客户自定义限流处理逻辑

  • 8401的新增CustomerBlockHandler方法
package com.atguigu.springcloud.myhandler;

import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.atguigu.springcloud.entities.*;

public class CustomerBlockHandler {

    public static CommonResult handleException(BlockException exception) {
        return new CommonResult(2020, "自定义限流处理信息....CustomerBlockHandler");
    }
}
  • 8401的新增RateLimitController新建下面的方法
@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
                  blockHandlerClass = CustomerBlockHandler.class,
                  blockHandler = "handleException")
public CommonResult customerBlockHandler(){
    return new CommonResult(200,"按客戶自定义",new Payment(2020L,"serial003"));
}
  • sentinel配置

image-20220409000306772

测试: http://localhost:8401/rateLimit/customerBlockHandler

image-20220409000344210

配置对应的图示

image-20220409000425790

<4>SentinelResource注解的其他属性

image-20220409000554700

image-20220409000626440

服务熔断功能

sentinel整合ribbon+openFeign+fallback

Ribbon系列

<1>新建cloudalibaba-provider-payment9003模块

  • cloudalibaba-provider-payment9003的pom
<dependencies>
    <!--SpringCloud ailibaba nacos -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
        <groupId>com.atguigu.cloud</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>${project.version}</version>
        <scope>compile</scope>
    </dependency>
    <!-- SpringBoot整合Web组件 -->
    <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>
    <!--日常通用jar包配置-->
    <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>
  • cloudalibaba-provider-payment9003的application.yml
server:
  port: 9003

spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #配置Nacos地址

management:
  endpoints:
    web:
      exposure:
        include: '*'
  • cloudalibaba-provider-payment9003的controller
import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;

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

    public static HashMap<Long, Payment> hashMap = new HashMap<>();
    static{
        hashMap.put(1L,new Payment(1L,"28a8c1e3bc2742d8848569891fb42181"));
        hashMap.put(2L,new Payment(2L,"bba8c1e3bc2742d8848569891ac32182"));
        hashMap.put(3L,new Payment(3L,"6ua8c1e3bc2742d8848569891xt92183"));
    }

    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id){
        Payment payment = hashMap.get(id);
        CommonResult<Payment> result = new CommonResult(200,"from mysql,serverPort:  "+serverPort,payment);
        return result;
    }

}
  • cloudalibaba-provider-payment9003的主启动
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

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

同理,参考cloudalibaba-provider-payment9003,新建cloudalibaba-provider-payment9004

<2>新建cloudalibaba-consumer-nacos-order84模块

  • cloudalibaba-consumer-nacos-order84模块的pom
<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <dependency>
            <groupId>com.atguigu.cloud</groupId>
            <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>
  • cloudalibaba-consumer-nacos-order84模块的application.yml
server:
  port: 84

spring:
  application:
    name: nacos-order-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080
        port: 8719

service-url:
  nacos-user-service: http://nacos-payment-provider
  • cloudalibaba-consumer-nacos-order84模块的config
package com.atguigu.springcloud.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ApplicationContextConfig {
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}
  • cloudalibaba-consumer-nacos-order84模块的controller
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;

@RestController
@Slf4j
public class CircleBreakerController {

    public static final String SERVICE_URL = "http://nacos-payment-provider";

    @Resource
    private RestTemplate restTemplate;

    @RequestMapping("/consumer/fallback/{id}")
    //@SentinelResource(value = "fallback") //没有配置-抛出错误给用户,不友好
    //@SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback只负责业务异常
    //@SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只负责sentinel控制台配置违规
    @SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler",
            exceptionsToIgnore = {IllegalArgumentException.class})
    public CommonResult<Payment> fallback(@PathVariable Long id) {
        CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id, CommonResult.class,id);
        if (id == 4) {
            throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
        }else if (result.getData() == null) {
            throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
        }
        return result;
    }

    //fallback 处理业务异常
    public CommonResult handlerFallback(@PathVariable  Long id,Throwable e) {
        Payment payment = new Payment(id,"null");
        return new CommonResult<>(444,"兜底异常handlerFallback,exception内容  "+e.getMessage(),payment);
    }

    //blockHandler 处理sentinel控制台配置违规
    public CommonResult blockHandler(@PathVariable  Long id,BlockException blockException) {
        Payment payment = new Payment(id,"null");
        return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException  "+blockException.getMessage(),payment);
    }

}
  • 测试:启动9003,9004,84

@SentinelResource的下面五种情况进行测试(修改controller中的fallback方法上面的注解注释即可)

image-20220409110632070

测试结论:

1>fallback管运行异常

2>blockHandler管sentinel配置违规异常

3>两个都配置blockhandler优先生效

4>exceptionsToIgnore指定一个异常类,表示如果当前方法抛出的是指定的异常,不降级,直接对用户抛出异常

image-20220409111028119

Feign系列

主要是修改cloudalibaba-consumer-nacos-order84模块

  • cloudalibaba-consumer-nacos-order84的pom.xml
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  • cloudalibaba-consumer-nacos-order84的application.yml 开启feign
#对Feign的支持
feign:
  sentinel:
    enabled: true
  • cloudalibaba-consumer-nacos-order84新建PaymentService接口
import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService {
    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}
  • cloudalibaba-consumer-nacos-order84新建PaymentService接口实现类
import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import org.springframework.stereotype.Component;

@Component //注意要加上这个注解
public class PaymentFallbackService implements PaymentService {
    @Override
    public CommonResult<Payment> paymentSQL(Long id){
        return new CommonResult<>(44444,"服务降级返回,---PaymentFallbackService",new Payment(id,"errorSerial"));
    }
}
  • cloudalibaba-consumer-nacos-order84的controller添加下面方法
// OpenFeign
@Resource
private PaymentService paymentService;

@GetMapping(value = "/consumer/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
    return paymentService.paymentSQL(id);
}

测试: 如果关闭9003.看看84会不会降级,可以看到,正常降级了

image-20220409112707584

熔断框架比较

image-20220409112740511

持久化规则

问题:一旦我们重启应用,Sentinel规则将消失,生产环境需要将配置规则进行持久化

解决:将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上Sentinel上的流控规则持续有效

这里以之前的cloudalibaba-sentinel-service8401模块为案例进行修改:

  • cloudalibaba-sentinel-service8401的pom增加下面依赖
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
  • cloudalibaba-sentinel-service8401的application.yml
datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: cloudalibaba-sentinel-service
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

image-20220409121742167

实际上就是指定,我们的规则要保证在哪个名称空间的哪个分组下

这里没有指定namespace, 但是是可以指定的

注意,这里的dataid要与8401的服务名一致

  • 在nacos中创建一个配置文件,dataId就是上面配置文件中指定的

image-20220409115909698

注意:下面这个resource后的url一定要填写正确,否则无效

[
    {
         "resource": "/rateLimit/byUrl",
         "limitApp": "default",
         "grade":   1,
         "count":   1,
         "strategy": 0,
         "controlBehavior": 0,
         "clusterMode": false    
    }
]

image-20220409115945717

测试

启动8401,访问localhost:8401/rateLimit/byUrl

在sentinel中可以看到,直接读取到了规则

此时重启8401,如果sentinel又可以正常读取到规则,那么证明持久化成功

本文为 程序员青阳 原创文章,遵循 CC BY-NC-SA 4.0 版权协议,转载请附上原文链接及本声明。

原文链接:https://heliufang.github.io/posts/580061a7/index.html