【SpringCloud】SpringCloud基础

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9ZAJaujE-1648282992252)(https://gitee.com/qingxi5/typora/raw/master/note/20210617153357.png)]

微服务架构编码构建

父工程

1)新建

image-20210618192944353

2)字符编码

image-20210618193406646

3)注解生效激活

image-20210618193453283

4)java编译版本选8

image-20210618193524184

5)File Type过滤

image-20210618193548851

6)DependencyManagement 和 Dependencies

Maven使用dependencyManagement元素来提供了一种管理依赖版本号的方式。
通常会在一个组织或者项目的最顶层的父POM中看到dependencyManagement元素。使用pom.xml中的dependencyManagement元素能让所有在子项目中引用一个依赖而不用显式的列出版本号。Maven会沿着父子层次向上走,直到找到一个拥有dependencyManagement元素的项目,然后它就会使用这个dependencyManagement元素中指定的版本号。

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

dependencyManagement里只是声明依赖,并不实现引入,因此子项目需要显示的声明需要用的依赖。
如果不在子项目中声明依赖,是不会从父项目中继承下来的;只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父项目中继承该项,并且version和scope都读取自父pom;如果子项目中指定了版本号,那么会使用子项目中指定的jar版本。

Rest微服务工程构建

image-20210618202555447

1)cloud-provider-payment8001微服务提供者支付Module模块

建cloud-provider-payment8001
改POM

<parent>
    <artifactId>mscloud03</artifactId>
    <groupId>com.atguigu.springcloud</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

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

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.10</version>
    </dependency>
    <!--mysql-connector-java-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!--jdbc-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
写YML
 
server:
  port: 8001

spring:
  application:
    name: cloud-payment-service
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource            # 当前数据源操作类型
    driver-class-name: org.gjt.mm.mysql.Driver              # mysql驱动包 com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 123456

mybatis:
  mapperLocations: classpath:mapper/*.xml
  type-aliases-package: com.atguigu.springcloud.entities    # 所有Entity别名类所在包
主启动
业务类

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

2、entities
2.1 主实体Payment
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment implements Serializable
{
    private Long id;
    private String serial;
}

2.2 Json封装体CommonResult
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T>
{
    private Integer code;
    private String  message;
    private T data;

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

3、dao
3.1 接口PaymentDao
@Mapper  //import org.apache.ibatis.annotations.Mapper;
public interface PaymentDao
{
    public int create(Payment payment);

    public Payment getPaymentById(@Param("id") Long id);
}

3.2 mybaits的映射文件PaymentMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="com.atguigu.springcloud.dao.PaymentDao">
    <resultMap id="BaseResultMap" type="com.atguigu.springcloud.entities.Payment">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="serial" property="serial" jdbcType="VARCHAR"/>
    </resultMap>

    <insert id="create" parameterType="Payment" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO payment(SERIAL) VALUES(#{serial});
    </insert>

    <select id="getPaymentById" parameterType="Long" resultMap="BaseResultMap" >
        SELECT * FROM payment WHERE id=#{id};
    </select>
</mapper>

4、service
4.1 接口PaymentService
public interface PaymentService
{
    public int create(Payment payment);

    public Payment getPaymentById(@Param("id") Long id);
}

4.2 实现类
@Service
public class PaymentServiceImpl implements PaymentService
{
    @Resource
    private PaymentDao paymentDao;

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

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

5、controller
@RestController
@Slf4j
public class PaymentController
{
    @Resource
    private PaymentService paymentService;

    @PostMapping(value = "/payment/create")
    public CommonResult create(@RequestBody Payment payment)
    {
        int result = paymentService.create(payment);
        log.info("*****插入操作返回结果:" + result);

        if(result > 0)
        {
            return new CommonResult(200,"插入数据库成功",result);
        }else{
            return new CommonResult(444,"插入数据库失败",null);
        }
    }

    @GetMapping(value = "/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id)
    {
        Payment payment = paymentService.getPaymentById(id);
        log.info("*****查询结果:{}",payment);
        if (payment != null) {
            return new CommonResult(200,"查询成功",payment);
        }else{
            return new CommonResult(444,"没有对应记录,查询ID: "+id,null);
        }
    }
}
测试

通过修改idea的workspace.xml的方式来快速打开Run Dashboard窗口

开启Run DashBoard
自己路径:D:\devSoft\JetBrains\IdeaProjects\自己project名\.idea
<option name="configurationTypes">
  <set>
      <option value="SpringBootApplicationConfigurationType" />
  </set>
</option>

2)cloud-consumer-order80微服务消费者订单Module模块

建cloud-consumer-order80
改POM
写YML

server:
  port: 80
主启动
业务类

1、entities
1.1 主实体Payment
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment implements Serializable
{
    private Long id;
    private String serial;
}

1.2 Json封装体CommonResult
public class CommonResult<T>
{
    private Integer code;
    private String message;
    private T data;

    public CommonResult()
    {
    }

    public CommonResult(Integer code, String message, T data)
    {
        this.code = code;
        this.message = message;
        this.data = data;
    }
    public CommonResult( Integer code,String message) {
        this( code, message,null);
    }

    public CommonResult(T data) {
        this(200, "操作成功", data);
    }

    //setter--getter
    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }
}
2、RestTemplate
2.1 概念
RestTemplate提供了多种便捷访问远程Http服务的方法,是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集

2.2 使用
使用restTemplate访问restful接口非常的简单。
(url, requestMap, ResponseBean.class)这三个参数分别代表REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。
GET请求方法
<T> T getForObject(String url, Class<T> responseType, Object... uriVariables);
<T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables);
<T> T getForObject(URI url, Class<T> responseType);
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables);
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables);
<T> ResponseEntity<T> getForEntity(URI var1, Class<T> responseType);
POST请求方法:
<T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables);
<T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables);
<T> T postForObject(URI url, @Nullable Object request, Class<T> responseType);
<T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables);
<T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables);
<T> ResponseEntity<T> postForEntity(URI url, @Nullable Object request, Class<T> responseType);
image-20210622145638948
2.3 getForObject方法和getForEntity方法
2.3.1 返回对象为响应体中数据转化成的对象,基本上可以理解为Json
image-20210622145735553
2.3.2 返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等

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

2.4 postForObject/postForEntity

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

3、config配置类
3.1 ApplicationContextConfig
@Configuration
public class ApplicationContextConfig
{
    @Bean
    public RestTemplate restTemplate()
    {
        return new RestTemplate();
    }
}

4、controller
@RestController
public class OrderController
{
    public static final String PaymentSrv_URL = "http://localhost:8001";

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/consumer/payment/create") //客户端用浏览器是get请求,但是底层实质发送post调用服务端8001
    public CommonResult create(Payment payment)
    {
        return restTemplate.postForObject(PaymentSrv_URL + "/payment/create",payment,CommonResult.class);
    }

    @GetMapping("/consumer/payment/get/{id}")
    public CommonResult getPayment(@PathVariable Long id)
    {
        return restTemplate.getForObject(PaymentSrv_URL + "/payment/get/"+id, CommonResult.class, id);
    }
}

3)热部署Devtools

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

2、
下段配置粘贴进聚合父类总工程的pom.xml里
<build>
    <finalName>你自己的工程名字</finalName>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <fork>true</fork>
                <addResources>true</addResources>
            </configuration>
        </plugin>
    </plugins>
</build>
3、Enabling automatic build
1647925838338 1647925872567
4、Update the value of
1647925838338 image-20210622104348014
5、重启

4)工程重构

系统中有重复部分,重构
image-20210622104543334
新建cloud-api-commons、POM、entities
订单80和支付8001分别改造
1、删除各自的原先有过的entities文件夹
2、各自粘贴POM内容
<dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
    <groupId>com.atguigu.springcloud</groupId>
    <artifactId>cloud-api-commons</artifactId>
    <version>${project.version}</version>
</dependency>
image-20210622105007903

Eureka服务注册与发现

基础知识

服务治理  
Spring Cloud封装了Netflix公司开发的Eureka模块来实现服务治理
在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务于服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。
服务注册与发现
Eureka采用了CS的设计架构,Eureka Server作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用Eureka的客户端连接到Eureka Server并维持心跳连接。这样系统的维护人员就可以通过Eureka Server来监控系统中各个微服务是否正常运行。
在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息。比如,服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者|服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址))
下左图是Eureka系统架构,右图是Dubbo的架构,请对比
image-20210622110636448
Eureka包含两个组件:Eureka Server和Eureka Client
Eureka Server提供服务注册服务
各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。 
EurekaClient通过注册中心进行访问
是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)

单机Eureka构建

1)IDEA生成eurekaServer端服务注册中心,类似于物业公司

1、建Module
cloud-eureka-server7001

2、改POM
<!--eureka-server-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

3、写YML
server:
  port: 7001

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

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

5、测试
http://localhost:7001/
见下图
image-20210622111411825

2)EurekaClient端cloud-provider-payment8001
将注册进EurekaServer成为服务提供者provider,类似学校对外提供授课服务

1、建Module
cloud-provider-payment8001

2、改POM
<!--eureka-client-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

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

5、测试
先要启动EurekaServer、http://localhost:7001/、微服务注册名配置说明见下图
image-20210622113327783

3)EurekaClient端cloud-consumer-order80
将注册进EurekaServer成为服务消费者consumer,类似来上课消费的各位同学

1、建Module
cloud-consumer-order80

2、改POM
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

3、写YML
server:
  port: 80

spring:
    application:
        name: cloud-order-service

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

5、测试
先要启动EurekaServer7001服务、再要启动服务提供者provider8001服务

集群Eureka构建步骤

1)Eureka集群原理说明

image-20210622114002369
微服务RPC远程服务调用最核心的是什么
高可用,试想你的注册中心只有一个only one, 它出故障了会导致整个服务环境不可用。
解决办法:搭建Eureka注册中心集群 ,实现负载均衡+故障容错
image-20210622114102004
互相注册、相互守望

2)EurekaServer集群环境构建步骤

1、建Module
新建cloud-eureka-server7002

2、改POM

修改映射配置
3.1 找到C:\Windows\System32\drivers\etc路径下的hosts文件
3.2 修改映射配置添加进hosts文件
    127.0.0.1  eureka7001.com
    127.0.0.1  eureka7002.com

3、写YML(7002类似)
server:
  port: 7001

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

4、主启动

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

server:
  port: 8001

spring:
  application:
    name: cloud-payment-service
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource            # 当前数据源操作类型
    driver-class-name: org.gjt.mm.mysql.Driver              # mysql驱动包
    url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 123456

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

mybatis:
  mapperLocations: classpath:mapper/*.xml
  type-aliases-package: com.atguigu.springcloud.entities    # 所有Entity别名类所在包

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

server:
  port: 80

spring:
    application:
        name: cloud-order-service

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

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

1、建Module
新建cloud-provider-payment8002

2、改POM

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

5、业务类

6、修改8001/8002的Controller(8002类似)
@RestController
@Slf4j
public class PaymentController
{
    @Value("${server.port}")	//*
    private String serverPort;	//*

    @Resource
    private PaymentService paymentService;

    @PostMapping(value = "/payment/create")
    public CommonResult create(@RequestBody Payment payment)
    {
        int result = paymentService.create(payment);
        log.info("*****插入操作返回结果:" + result);

        if(result > 0)
        {
            return new CommonResult(200,"插入成功,返回结果"+result+"\t 服务端口:"+serverPort,payment);
        }else{
            return new CommonResult(444,"插入失败",null);
        }
    }

    @GetMapping(value = "/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id)
    {
        Payment payment = paymentService.getPaymentById(id);
        log.info("*****查询结果:{}",payment);
        if (payment != null) {
            return new CommonResult(200,"查询成功"+"\t 服务端口:"+serverPort,payment);
        }else{
            return new CommonResult(444,"没有对应记录,查询ID: "+id,null);
        }
    }
}

6)负载均衡

订单服务访问地址不能写死
 //原地址是写死的
//public static final String PAYMENT_SRV = "http://localhost:8001";
// 通过在eureka上注册过的微服务名称调用,但并不确定要具体调用那台微服务
public static final String PAYMENT_SRV = "http://CLOUD-PAYMENT-SERVICE";
使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
ApplicationContextBean
@Configuration
public class ApplicationContextBean
{
    @Bean
    @LoadBalanced //使用@LoadBalanced注解赋予RestTemplate负载均衡的能力,这样才能直接通过微服务名称直接调用微服务。
    public RestTemplate getRestTemplate()
    {
        return new RestTemplate();
    }
}

actuator微服务信息完善

1)主机名称:服务名称修改

修改前
image-20210622115935168
修改cloud-provider-payment8001
eureka:
  client:
    #表示是否将自己注册进EurekaServer默认为true。
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetchRegistry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka  # 集群版
      #defaultZone: http://localhost:7001/eureka  # 单机版
  instance:
    instance-id: payment8001
修改后
image-20210622120115071

2)访问信息有IP信息提示

修改cloud-provider-payment8001
eureka:
  client:
    #表示是否将自己注册进EurekaServer默认为true。
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetchRegistry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka  # 集群版
      #defaultZone: http://localhost:7001/eureka  # 单机版
  instance:
    instance-id: payment8001
    prefer-ip-address: true     #访问路径可以显示IP地址
image-20210622132257042

服务发现Discovery

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

即消费者想要通过注册中心看看提供者的信息,类似于关于我们

2)修改cloud-provider-payment8001的Controller

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

    @Resource
    private PaymentService paymentService;

    @Resource
    private DiscoveryClient discoveryClient;

    @PostMapping(value = "/payment/create")
    public CommonResult create(@RequestBody Payment payment)
    {
        int result = paymentService.create(payment);
        log.info("*****插入操作返回结果:" + result);

        if(result > 0)
        {
            return new CommonResult(200,"插入成功,返回结果"+result+"\t 服务端口:"+serverPort,payment);
        }else{
            return new CommonResult(444,"插入失败",null);
        }
    }

    @GetMapping(value = "/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id)
    {
        Payment payment = paymentService.getPaymentById(id);
        log.info("*****查询结果:{}",payment);
        if (payment != null) {
            return new CommonResult(200,"查询成功"+"\t 服务端口:"+serverPort,payment);
        }else{
            return new CommonResult(444,"没有对应记录,查询ID: "+id,null);
        }
    }

    @GetMapping(value = "/payment/discovery")
    public Object discovery()
    {
        List<String> services = discoveryClient.getServices();
        for (String element : services) {
            System.out.println(element);
        }

        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        for (ServiceInstance element : instances) {
            System.out.println(element.getServiceId() + "\t" + element.getHost() + "\t" + element.getPort() + "\t"
                    + element.getUri());
        }
        return this.discoveryClient;
    }
}

3)8001主启动类

@EnableDiscoveryClient

Eureka自我保护

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

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

3、什么是自我保护模式?
默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生(延时、卡顿、拥挤)时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题——当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。
在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。
它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲解:好死不如赖活着
综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留)也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。
image-20210622132900882
4、怎么禁止自我保护
4.1 注册中心eureakeServer端7001
使用eureka.server.enable-self-preservation = false 可以禁用自我保护模式

4.2 生产者客户端eureakeClient端8001
eureka:
  client: #服务提供者provider注册进eureka服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      # cluster version
      #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
      # singleton version
      defaultZone: http://eureka7001.com:7001/eureka
#心跳检测与续约时间
#开发时设置小些,保证服务关闭后注册中心能即使剔除服务
  instance:
  #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
    lease-renewal-interval-in-seconds: 1
  #Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
    lease-expiration-duration-in-seconds: 2

Zookeeper服务注册与发现

注册中心Zookeeper

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

服务提供者

1、建Module
新建cloud-provider-payment8004

2、改POM
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>

3、写YML
#8004表示注册到zookeeper服务器的支付服务提供者端口号
server:
  port: 8004
#服务别名----注册zookeeper到注册中心名称
spring:
  application:
    name: cloud-provider-payment
  cloud:
    zookeeper:
      connect-string: 192.168.111.144:2181

4、主启动
@EnableDiscoveryClient //该注解用于向使用consul或者zookeeper作为注册中心时注册服务

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

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

6、启动8004注册进zookeeper
6.1 解决zookeeper版本jar包冲突问题
6.2 排出zk冲突后的新POM
最终符合CentOS上的版本,因为zookeeper可能其他程序也在用。
<!-- SpringBoot整合zookeeper客户端 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
    <!--先排除自带的zookeeper3.5.3-->
    <exclusions>
        <exclusion>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!--添加zookeeper3.4.9版本-->
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.9</version>
</dependency>
zookeeper是临时节点

3.3 服务消费者

1、建Module
新建cloud-consumerzk-order80

2、改POM

3、写YML
server:
  port: 80

spring:
  application:
    name: cloud-consumer-order
  cloud:
  #注册到zookeeper地址
    zookeeper:
      connect-string: 192.168.111.144:2181
      
4、主启动

5、业务类
5.1 配置Bean
@Configuration
public class ApplicationContextBean
{
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate()
    {
        return new RestTemplate();
    }
}
5.2 Controller
@RestController
public class OrderZKController
{
    public static final String INVOKE_URL = "http://cloud-provider-payment";

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping(value = "/consumer/payment/zk")
    public String paymentInfo()
    {
        String result = restTemplate.getForObject(INVOKE_URL+"/payment/zk", String.class);
        System.out.println("消费者调用支付服务(zookeeper)--->result:" + result);
        return result;
    }
}

5、测试
image-20210622140251458

Consul服务注册与发现

Consul简介

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

服务提供者

1、建Module
新建Module支付服务provider8006

2、改POM
<!--SpringCloud consul-server -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-discovery</artifactId>
</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>

3、写YML
###consul服务端口号
server:
  port: 8006

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

5、业务类
@RestController
public class PaymentController
{
    @Value("${server.port}")
    private String serverPort;

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

5、测试
image-20210622143002741

服务消费者

1、建Module
新建Module消费服务order80

2、改POM
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-discovery</artifactId>
</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>

3、写YML
###consul服务端口号
server:
  port: 80

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

5、配置bean
@Configuration
public class ApplicationContextBean
{
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate()
    {
        return new RestTemplate();
    }
}

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

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping(value = "/consumer/payment/consul")
    public String paymentInfo()
    {
        String result = restTemplate.getForObject(INVOKE_URL+"/payment/consul", String.class);
        System.out.println("消费者调用支付服务(consule)--->result:" + result);
        return result;
    }
}

6、测试
image-20210622143450581

三个注册中心异同点

1647926565705

1)CAP

C:Consistency(强一致性)
A:Availability(可用性)
P:Partition tolerance(分区容错性)
CAP理论关注粒度是数据,而不是整体系统设计的策略
最多只能同时较好的满足两个。
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,
因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类:
CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
CP - 满足一致性,分区容忍必的系统,通常性能不是特别高。
AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。
image-20210622143603662

2)AP(Eureka)

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

3)CP(Zookeeper/Consul)

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

Ribbon负载均衡服务调用

概述

是什么
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。
简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。
能干吗
负载均衡+RestTemplate调用
集中式LB
即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx), 由该设施负责把访问请求通过某种策略转发至服务的提供方;

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

Ribbon负载均衡演示

1)架构说明

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

总结:Ribbon其实就是一个软负载均衡的客户端组件,可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。
image-20210622144946632
spring-cloud-starter-netflix-eureka-client自带了spring-cloud-starter-ribbon引用,没有引入spring-cloud-starter-ribbon也可以使用ribbon。

Ribbon核心组件IRule

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

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

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

2)如何替换

1、修改cloud-consumer-order80

2、配置细节
这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。
3、新建package 
image-20210622150848541
4、包下新建MySelfRule规则类
@Configuration
public class MySelfRule
{
    @Bean
    public IRule myRule()
    {
        return new RandomRule();//定义为随机
    }
}
5、主启动类添加@RibbonClient

Ribbon负载均衡算法

1)原理

 负载均衡算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标  ,每次服务重启动后rest接口计数从1开始。
 
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
 
如:   List [0] instances = 127.0.0.1:8002
   List [1] instances = 127.0.0.1:8001
 
8001+ 8002 组合成为集群,它们共计2台机器,集群总数为2, 按照轮询算法原理:
 
当总请求数为1时: 1 % 2 =1 对应下标位置为1 ,则获得服务地址为127.0.0.1:8001
当总请求数位2时: 2 % 2 =0 对应下标位置为0 ,则获得服务地址为127.0.0.1:8002
当总请求数位3时: 3 % 2 =1 对应下标位置为1 ,则获得服务地址为127.0.0.1:8001
当总请求数位4时: 4 % 2 =0 对应下标位置为0 ,则获得服务地址为127.0.0.1:8002
如此类推......

2)自定义负载均衡器

1、7001/7002集群启动
2、8001/8002微服务改造
@GetMapping(value = "/payment/lb")
public String getPaymentLB()
{
    return serverPort;
}
3、80订单微服务改造
3.1 ApplicationContextBean去掉注解@LoadBalanced
@Configuration
public class ApplicationContextBean
{
    @Bean
    //@LoadBalanced
    public RestTemplate getRestTemplate()
    {
        return new RestTemplate();
    }
}

3.2 LoadBalancer接口
public interface LoadBalancer
{
    ServiceInstance instances(List<ServiceInstance> serviceInstances);
}

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

    public final int getAndIncrement()
    {
        int current;
        int next;
        do
        {
            current = this.atomicInteger.get();
            next = current >= 2147483647 ? 0 : current + 1;
        } while(!this.atomicInteger.compareAndSet(current, next));
        System.out.println("*****next: "+next);
        return next;
    }

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

3.4 OrderController
@RestController
public class OrderController
{
    //public static final String PAYMENT_SRV = "http://localhost:8001";
    public static final String PAYMENT_SRV = "http://CLOUD-PAYMENT-SERVICE";

    @Resource
    private RestTemplate restTemplate;
    //可以获取注册中心上的服务列表
    @Resource
    private DiscoveryClient discoveryClient;
    @Resource
    private LoadBalancer loadBalancer;

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

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

    @GetMapping("/consumer/payment/getForEntity/{id}")
    public CommonResult<Payment> getPayment2(@PathVariable("id") Long id)
    {
        ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_SRV+"/payment/get/"+id, CommonResult.class);
        if(entity.getStatusCode().is2xxSuccessful()){
            return entity.getBody();
        }else {
            return new CommonResult(444, "操作失败");
        }
    }
 
    @Resource
    private LoadBalancer loadBalancer;

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

        if(instances == null || instances.size()<=0) {
            return null;
        }
        ServiceInstance serviceInstance = loadBalancer.instances(instances);
        URI uri = serviceInstance.getUri();

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

3.5 测试
http://localhost/consumer/payment/lb

OpenFeign服务接口调用

概述

1)OpenFeign是什么

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

2)能干嘛

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

3)Feign和OpenFeign两者区别

Feign是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端
Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务

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

OpenFeign使用步骤

1)接口+注解:微服务调用接口+@FeignClient

2)新建cloud-consumer-feign-order80,Feign在消费端使用

3)POM

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

4)YML

server:
  port: 80

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

5)主启动

@EnableFeignClients

6)业务类

1、业务逻辑接口+@FeignClient配置调用provider服务
2、新建PaymentFeignService接口并新增注解@FeignClient
3、控制层Controller
@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);
    }
}

7)测试

先启动2个eureka集群7001/7002
再启动2个微服务8001/8002
启动OpenFeign启动
http://localhost/consumer/payment/get/31
Feign自带负载均衡配置项

8)总结

image-20210622160008191

OpenFeign超时控制

1)超时设置,故意设置超时演示出错情况

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

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

    @Resource
    private PaymentService paymentService;

    @Resource
    private DiscoveryClient discoveryClient;

    @PostMapping(value = "/payment/create")
    public CommonResult create(@RequestBody Payment payment)
    {
        int result = paymentService.create(payment);
        log.info("*****插入操作返回结果:" + result);

        if(result > 0)
        {
            return new CommonResult(200,"插入成功,返回结果"+result+"\t 服务端口:"+serverPort,payment);
        }else{
            return new CommonResult(444,"插入失败",null);
        }
    }

    @GetMapping(value = "/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id)
    {
        Payment payment = paymentService.getPaymentById(id);
        log.info("*****查询结果:{}",payment);
        if (payment != null) {
            return new CommonResult(200,"查询成功"+"\t 服务端口:"+serverPort,payment);
        }else{
            return new CommonResult(444,"没有对应记录,查询ID: "+id,null);
        }
    }

    @GetMapping(value = "/payment/discovery")
    public Object discovery()
    {
        List<String> services = discoveryClient.getServices();
        for (String element : services) {
            System.out.println(element);
        }

        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        for (ServiceInstance element : instances) {
            System.out.println(element.getServiceId() + "\t" + element.getHost() + "\t" + element.getPort() + "\t"
                    + element.getUri());
        }
        return this.discoveryClient;
    }

    @GetMapping(value = "/payment/lb")
    public String getPaymentLB()
    {
        System.out.println("*****lb from port: "+serverPort);
        return serverPort;
    }

    @GetMapping(value = "/payment/feign/timeout")
    public String paymentFeignTimeOut()
    {
        System.out.println("*****paymentFeignTimeOut from port: "+serverPort);
        //暂停几秒钟线程
        try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
        return serverPort;
    }
}
服务消费方80添加超时方法PaymentFeignService

@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService
{
    @GetMapping(value = "/payment/get/{id}")
    CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);

    @GetMapping(value = "/payment/feign/timeout")
    String paymentFeignTimeOut();
}
服务消费方80添加超时方法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);
    }

    @GetMapping(value = "/consumer/payment/feign/timeout")
    public String paymentFeignTimeOut()
    {
        return paymentFeignService.paymentFeignTimeOut();
    }
}
测试

http://localhost/consumer/payment/feign/timeout
image-20210622160453785

2)OpenFeign默认等待1秒钟,超过后报错

3)是什么

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

OpenFeign默认支持Ribbon

4)YML文件里需要开启OpenFeign客户端超时控制

server:
  port: 80

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

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

OpenFeign日志打印功能

1)是什么

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

2)日志级别

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

3)配置日志bean

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

4)YML文件里需要开启日志的Feign客户端

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

5)后台日志查看

Hystrix断路器

概述

1)分布式系统面临的问题

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

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

2)是什么

Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。

3)能干嘛

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

重要概念

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

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

2、服务熔断
类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示
就是保险丝:服务的降级->进而熔断->恢复调用链路

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

案例

1)构建

1、建Module
新建cloud-provider-hystrix-payment8001

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

3、写YML
server:
  port: 8001

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

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
      defaultZone: http://eureka7001.com:7001/eureka
      
4、主启动

5、业务类
5.1 service
@Service
public class PaymentService
{
    /**
     * 正常访问,一切OK
     * @param id
     * @return
     */
    public String paymentInfo_OK(Integer id)
    {
        return "线程池:"+Thread.currentThread().getName()+"paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O";
    }

    /**
     * 超时访问,演示降级
     * @param id
     * @return
     */
    public String paymentInfo_TimeOut(Integer id)
    {
        try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
        return "线程池:"+Thread.currentThread().getName()+"paymentInfo_TimeOut,id: "+id+"\t"+"O(∩_∩)O,耗费3秒";
    }
}

5.2 controller
@RestController
@Slf4j
public class PaymentController
{
    @Autowired
    private PaymentService paymentService;

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


    @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;
    }

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

6、测试
启动eureka7001
启动cloud-provider-hystrix-payment8001
访问success的方法---http://localhost:8001/payment/hystrix/ok/31
每次调用耗费5秒钟---http://localhost:8001/payment/hystrix/timeout/31
上述module均OK
以上述为根基平台,从正确->错误->降级熔断->恢复

2)高并发测试

1、Jmeter压测测试
1)开启Jmeter,来20000个并发压死8001,20000个请求都去访问paymentInfo_TimeOut服务
2)再来一个访问,http://localhost:8001/payment/hystrix/ok/31
3)两个都在自己转圈圈,tomcat的默认的工作线程数被打满 了,没有多余的线程来分解压力和处理。
image-20210623093537330
2、Jmeter压测结论
上面还是服务提供者8001自己测试,假如此时外部的消费者80也来访问,那消费者只能干等,最终导致消费端80不满意,服务端8001直接被拖死
3、80新建加入
1)建Module
cloud-consumer-feign-hystrix-order80

2)改POM

3)写YML
server:
  port: 80

eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/
      
4)主启动
@EnableFeignClients

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

    @GetMapping("/payment/hystrix/timeout/{id}")
    String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}

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

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

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

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

7)高并发测试
消费者80,要么转圈圈等待,要么消费端报超时错误

3)故障现象和导致原因

8001同一层次的其它接口服务被困死,因为tomcat线程池里面的工作线程已经被挤占完毕
80此时调用8001,客户端访问响应缓慢,转圈圈

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

如何解决?解决的要求
超时导致服务器变慢(转圈)---超时不再等待
出错(宕机或程序运行出错)---出错要有兜底

解决
对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级
对方服务(8001)down机了,调用者(80)不能一直卡死等待,必须有服务降级
对方服务(8001)OK,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者),自己处理降级

服务降级

1)降级配置

@HystrixCommand

2)8001先从自身找问题

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

3)8001fallback

1、业务类启用
@Service
public class PaymentService
{
    /**
     * 正常访问,一切OK
     * @param id
     * @return
     */
    public String paymentInfo_OK(Integer id)
    {
        return "线程池:"+Thread.currentThread().getName()+"paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O";
    }

    /**
     * 超时访问,演示降级
     * @param id
     * @return
     */
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
    })
    public String paymentInfo_TimeOut(Integer id)
    {
        int second = 5;
        try { TimeUnit.SECONDS.sleep(second); } catch (InterruptedException e) { e.printStackTrace(); }
        return "线程池:"+Thread.currentThread().getName()+"paymentInfo_TimeOut,id: "+id+"\t"+"O(∩_∩)O,耗费秒: "+second;
    }
    public String paymentInfo_TimeOutHandler(Integer id){
        return "/(ㄒoㄒ)/调用支付接口超时或异常:\t"+ "\t当前线程池名字" + Thread.currentThread().getName();
    }
}
2、一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法。
3、添加新注解@EnableCircuitBreaker
1647926954605
上图故意制造两个异常:
1)int age = 10/0; 计算异常
2)我们能接受3秒钟,它运行5秒钟,超时异常。
当前服务不可用了,做服务降级,兜底的方案都是paymentInfo_TimeOutHandler

4)80fallback

1、80订单微服务,也可以更好的保护自己,自己也依样画葫芦进行客户端降级保护
2、我们自己配置过的热部署方式对java代码的改动明显,但对@HystrixCommand内属性的修改建议重启微服务

3、yml
server:
  port: 80

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

feign:
  hystrix:
    enabled: true
    
4、主启动
@EnableHystrix

5、业务类
@RestController
@Slf4j
public class PaymentHystirxController
{
    @Resource
    private PaymentHystrixService paymentHystrixService;

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

    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")
    })
    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秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
    }
}

5)目前问题

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

6)解决问题

1、每个方法配置一个???膨胀
1.1 feign接口系列

1.2 @DefaultProperties(defaultFallback = "")
@DefaultProperties(defaultFallback = ""),见下图。
1)每个方法配置一个服务降级方法,技术上可以,实际上不行
2)N 除了个别重要核心业务有专属,其它普通的可以通过@DefaultProperties(defaultFallback = "")  统一跳转到统一处理结果页面
通用的和独享的各自分开,避免了代码膨胀,合理减少了代码量。

1.3 controller配置
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class PaymentHystirxController
{
    @Resource
    private PaymentHystrixService paymentHystrixService;

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

    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    @HystrixCommand //加了@DefaultProperties属性注解,并且没有写具体方法名字,就用统一全局的
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id)
    {
        String result = paymentHystrixService.paymentInfo_TimeOut(id);
        return result;
    }
    public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id)
    {
        return "paymentTimeOutFallbackMethod,对方系统繁忙,请10秒钟后再次尝试/(ㄒoㄒ)/";
    }

    public String payment_Global_FallbackMethod()
    {
        return "Global异常处理信息,请稍后再试,/(ㄒoㄒ)/~~";
    }
}
image-20210623100114254
2、和业务逻辑混一起???混乱
2.1 本次案例服务降级处理是在客户端80实现完成的,与服务端8001没有关系,只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦。

2.2 未来我们要面对的异常
运行、超时、宕机
2.3 业务类PaymentController
混合在一块 ,每个业务方法都要提供一个。
image-20210623100802163
2.4 修改cloud-consumer-feign-hystrix-order80
根据cloud-consumer-feign-hystrix-order80已经有的PaymentHystrixService接口,重新新建一个类(PaymentFallbackService)实现该接口,统一为接口里面的方法进行异常处理。

2.5 PaymentFallbackService类实现PaymentFeignClientService接口
@Component //必须加 //必须加 //必须加
public class PaymentFallbackService implements PaymentFeignClientService
{
    @Override
    public String getPaymentInfo(Integer id)
    {
        return "服务调用失败,提示来自:cloud-consumer-feign-order80";
    }
}

2.6 yml
# 用于服务降级 在注解@FeignClient中添加fallbackFactory属性值
feign:
  hystrix:
   enabled: true #在Feign中开启Hystrix
   
2.7 PaymentFeignClientService接口
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
public interface PaymentFeignClientService
{
    @GetMapping("/payment/hystrix/{id}")
    public String getPaymentInfo(@PathVariable("id") Integer id);
}

2.8 测试
单个eureka先启动7001
PaymentHystrixMain8001启动
正常访问测试---http://localhost/consumer/payment/hystrix/ok/31
故意关闭微服务8001
客户端自己调用提示---此时服务端provider已经down了,但是我们做了服务降级处理, 让客户端在服务端不可用时也会获得提示信息而不会挂起耗死服务器

服务熔断

1)断路器—保险丝

2)熔断是什么

熔断机制概述
熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。
 
在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand。

3)案例

1、修改cloud-provider-hystrix-payment8001
2、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"),
})
public String paymentCircuitBreaker(@PathVariable("id") Integer id)
{
    if(id < 0)
    {
        throw new RuntimeException("******id 不能负数");
    }
    String serialNumber = IdUtil.simpleUUID();

    return Thread.currentThread().getName()+"\t"+"调用成功,流水号: " + serialNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id)
{
    return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~   id: " +id;
}

3、PaymentController
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id)
{
    String result = paymentService.paymentCircuitBreaker(id);
    log.info("****result: "+result);
    return result;
}

4、测试
自测cloud-provider-hystrix-payment8001
正确---http://localhost:8001/payment/circuit/31
错误---http://localhost:8001/payment/circuit/-31
一次正确一次错误trytry
重点测试---多次错误,然后慢慢正确,发现刚开始不满足条件,就算是正确的访问地址也不能进行

4)总结

1、熔断类型
熔断打开---请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设时钟则进入半熔断状态
熔断关闭---熔断关闭不会对服务进行熔断
熔断半开---部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断
2、断路器
2.1 断路器在什么情况下开始起作用
涉及到断路器的三个重要参数:快照时间窗、请求总数阀值、错误百分比阀值。
1)快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
2)请求总数阀值:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开。
3)错误百分比阀值:当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开。

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

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

2.3 断路器打开之后
1)再有请求调用的时候,将不会调用主逻辑,而是直接调用降级fallback。通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。
2)原来的主逻辑要如何恢复呢?
对于这一问题,hystrix也为我们实现了自动恢复功能。
当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。
2.4 All配置
//========================All
@HystrixCommand(fallbackMethod = "str_fallbackMethod",
        groupKey = "strGroupCommand",
        commandKey = "strCommand",
        threadPoolKey = "strThreadPool",

        commandProperties = {
                // 设置隔离策略,THREAD 表示线程池 SEMAPHORE:信号池隔离
                @HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
                // 当隔离策略选择信号池隔离的时候,用来设置信号池的大小(最大并发数)
                @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"),
                // 配置命令执行的超时时间
                @HystrixProperty(name = "execution.isolation.thread.timeoutinMilliseconds", value = "10"),
                // 是否启用超时时间
                @HystrixProperty(name = "execution.timeout.enabled", value = "true"),
                // 执行超时的时候是否中断
                @HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "true"),
                // 执行被取消的时候是否中断
                @HystrixProperty(name = "execution.isolation.thread.interruptOnCancel", value = "true"),
                // 允许回调方法执行的最大并发数
                @HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests", value = "10"),
                // 服务降级是否启用,是否执行回调函数
                @HystrixProperty(name = "fallback.enabled", value = "true"),
                // 是否启用断路器
                @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
                // 该属性用来设置在滚动时间窗中,断路器熔断的最小请求数。例如,默认该值为 20 的时候,
                // 如果滚动时间窗(默认10秒)内仅收到了19个请求, 即使这19个请求都失败了,断路器也不会打开。
                @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),
                // 该属性用来设置在滚动时间窗中,表示在滚动时间窗中,在请求数量超过
                // circuitBreaker.requestVolumeThreshold 的情况下,如果错误请求数的百分比超过50,
                // 就把断路器设置为 "打开" 状态,否则就设置为 "关闭" 状态。
                @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
                // 该属性用来设置当断路器打开之后的休眠时间窗。 休眠时间窗结束之后,
                // 会将断路器置为 "半开" 状态,尝试熔断的请求命令,如果依然失败就将断路器继续设置为 "打开" 状态,
                // 如果成功就设置为 "关闭" 状态。
                @HystrixProperty(name = "circuitBreaker.sleepWindowinMilliseconds", value = "5000"),
                // 断路器强制打开
                @HystrixProperty(name = "circuitBreaker.forceOpen", value = "false"),
                // 断路器强制关闭
                @HystrixProperty(name = "circuitBreaker.forceClosed", value = "false"),
                // 滚动时间窗设置,该时间用于断路器判断健康度时需要收集信息的持续时间
                @HystrixProperty(name = "metrics.rollingStats.timeinMilliseconds", value = "10000"),
                // 该属性用来设置滚动时间窗统计指标信息时划分"桶"的数量,断路器在收集指标信息的时候会根据
                // 设置的时间窗长度拆分成多个 "桶" 来累计各度量值,每个"桶"记录了一段时间内的采集指标。
                // 比如 10 秒内拆分成 10 个"桶"收集这样,所以 timeinMilliseconds 必须能被 numBuckets 整除。否则会抛异常
                @HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "10"),
                // 该属性用来设置对命令执行的延迟是否使用百分位数来跟踪和计算。如果设置为 false, 那么所有的概要统计都将返回 -1。
                @HystrixProperty(name = "metrics.rollingPercentile.enabled", value = "false"),
                // 该属性用来设置百分位统计的滚动窗口的持续时间,单位为毫秒。
                @HystrixProperty(name = "metrics.rollingPercentile.timeInMilliseconds", value = "60000"),
                // 该属性用来设置百分位统计滚动窗口中使用 “ 桶 ”的数量。
                @HystrixProperty(name = "metrics.rollingPercentile.numBuckets", value = "60000"),
                // 该属性用来设置在执行过程中每个 “桶” 中保留的最大执行次数。如果在滚动时间窗内发生超过该设定值的执行次数,
                // 就从最初的位置开始重写。例如,将该值设置为100, 滚动窗口为10秒,若在10秒内一个 “桶 ”中发生了500次执行,
                // 那么该 “桶” 中只保留 最后的100次执行的统计。另外,增加该值的大小将会增加内存量的消耗,并增加排序百分位数所需的计算时间。
                @HystrixProperty(name = "metrics.rollingPercentile.bucketSize", value = "100"),
                // 该属性用来设置采集影响断路器状态的健康快照(请求的成功、 错误百分比)的间隔等待时间。
                @HystrixProperty(name = "metrics.healthSnapshot.intervalinMilliseconds", value = "500"),
                // 是否开启请求缓存
                @HystrixProperty(name = "requestCache.enabled", value = "true"),
                // HystrixCommand的执行和事件是否打印日志到 HystrixRequestLog 中
                @HystrixProperty(name = "requestLog.enabled", value = "true"),
        },
        threadPoolProperties = {
                // 该参数用来设置执行命令线程池的核心线程数,该值也就是命令执行的最大并发量
                @HystrixProperty(name = "coreSize", value = "10"),
                // 该参数用来设置线程池的最大队列大小。当设置为 -1 时,线程池将使用 SynchronousQueue 实现的队列,
                // 否则将使用 LinkedBlockingQueue 实现的队列。
                @HystrixProperty(name = "maxQueueSize", value = "-1"),
                // 该参数用来为队列设置拒绝阈值。 通过该参数, 即使队列没有达到最大值也能拒绝请求。
                // 该参数主要是对 LinkedBlockingQueue 队列的补充,因为 LinkedBlockingQueue
                // 队列不能动态修改它的对象大小,而通过该属性就可以调整拒绝请求的队列大小了。
                @HystrixProperty(name = "queueSizeRejectionThreshold", value = "5"),
        }
)
public String strConsumer() {
    return "hello 2020";
}
public String str_fallbackMethod()
{
    return "*****fall back str_fallbackMethod";
}

hystrix工作流程

1)图例

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

2)步骤

1、创建 HystrixCommand(用在依赖的服务返回单个操作结果的时候) 或 HystrixObserableCommand(用在依赖的服务返回多个操作结果的时候) 对象。
2、命令执行。其中 HystrixComand 实现了下面前两种执行方式;而 HystrixObservableCommand 实现了后两种执行方式:execute():同步执行,从依赖的服务返回一个单一的结果对象, 或是在发生错误的时候抛出异常。queue():异步执行, 直接返回 一个Future对象, 其中包含了服务执行结束时要返回的单一结果对象。observe():返回 Observable 对象,它代表了操作的多个结果,它是一个 Hot Obserable(不论 "事件源" 是否有 "订阅者",都会在创建后对事件进行发布,所以对于 Hot Observable 的每一个 "订阅者" 都有可能是从 "事件源" 的中途开始的,并可能只是看到了整个操作的局部过程)。toObservable(): 同样会返回 Observable 对象,也代表了操作的多个结果,但它返回的是一个Cold Observable(没有 "订阅者" 的时候并不会发布事件,而是进行等待,直到有 "订阅者" 之后才发布事件,所以对于 Cold Observable 的订阅者,它可以保证从一开始看到整个操作的全部过程)。
3、若当前命令的请求缓存功能是被启用的, 并且该命令缓存命中, 那么缓存的结果会立即以 Observable 对象的形式 返回。
4、检查断路器是否为打开状态。如果断路器是打开的,那么Hystrix不会执行命令,而是转接到 fallback 处理逻辑(第 8 步);如果断路器是关闭的,检查是否有可用资源来执行命令(第 5 步)。
5、线程池/请求队列/信号量是否占满。如果命令依赖服务的专有线程池和请求队列,或者信号量(不使用线程池的时候)已经被占满, 那么 Hystrix 也不会执行命令, 而是转接到 fallback 处理逻辑(第8步)。
6、Hystrix 会根据我们编写的方法来决定采取什么样的方式去请求依赖服务。HystrixCommand.run() :返回一个单一的结果,或者抛出异常。HystrixObservableCommand.construct(): 返回一个Observable 对象来发射多个结果,或通过 onError 发送错误通知。
7、Hystrix会将 "成功"、"失败"、"拒绝"、"超时" 等信息报告给断路器, 而断路器会维护一组计数器来统计这些数据。断路器会使用这些统计数据来决定是否要将断路器打开,来对某个依赖服务的请求进行 "熔断/短路"。
8、当命令执行失败的时候, Hystrix 会进入 fallback 尝试回退处理, 我们通常也称该操作为 "服务降级"。而能够引起服务降级处理的情况有下面几种:第4步: 当前命令处于"熔断/短路"状态,断路器是打开的时候。第5步: 当前命令的线程池、 请求队列或 者信号量被占满的时候。第6步:HystrixObservableCommand.construct() 或 HystrixCommand.run() 抛出异常的时候。
9、当Hystrix命令执行成功之后, 它会将处理结果直接返回或是以Observable 的形式返回。

tips:如果我们没有为命令实现降级逻辑或者在降级处理逻辑中抛出了异常, Hystrix 依然会返回一个 Observable 对象, 但是它不会发射任何结果数据, 而是通过 onError 方法通知命令立即中断请求,并通过onError()方法将引起命令失败的异常发送给调用者。

服务监控hystrixDashboard

1)概述

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

2)仪表盘9001

1、新建cloud-consumer-hystrix-dashboard9001
2、改POM
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>

3、写YML
server:
  port: 9001
  
4、HystrixDashboardMain9001+新注解@EnableHystrixDashboard
5、所有Provider微服务提供类(8001/8002/8003)都需要监控依赖配置
<!-- actuator监控信息完善 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

6、启动cloud-consumer-hystrix-dashboard9001该微服务后续将监控微服务8001

3)断路器演示

1、修改cloud-provider-hystrix-payment8001
1.1 注意:新版本Hystrix需要在主启动类MainAppHystrix8001中指定监控路径
/**
 *此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
 *ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
 *只要在自己的项目里配置上下面的servlet就可以了
 */
@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;
}
2、监控测试
2.1 启动1个eureka或者3个eureka集群均可
2.2 观察监控窗口
1)9001监控8001---http://localhost:8001/hystrix.stream
image-20210623105133006
Delay:该参数用来控制服务器上轮询监控信息的延迟时间,默认为2000毫秒,可以通过配置该属性来降低客户端的网络和CPU消耗。
Title:该参数对应了头部标题Hystrix Stream之后的内容,默认会使用具体监控实例的URL,可以通过配置该信息来展示更合适的标题。 
2)测试地址
http://localhost:8001/payment/circuit/31
http://localhost:8001/payment/circuit/-31
上述测试通过---ok
先访问正确地址,再访问错误地址,再正确地址,会发现图示断路器都是慢慢放开的。
监控结果,成功
image-20210623105345354
监控结果,失败
image-20210623105411364
3)如何看?
7色
1圈(实心圆:共有两种含义。它通过颜色的变化代表了实例的健康程度,它的健康度从绿色<黄色<橙色<红色递减。
该实心圆除了颜色的变化之外,它的大小也会根据实例的请求流量发生变化,流量越大该实心圆就越大。所以通过该实心圆的展示,就可以在大量的实例中快速的发现故障实例和高压力实例。)
1线(曲线:用来记录2分钟内流量的相对变化,可以通过它来观察到流量的上升和下降趋势。)
image-20210623105708887 image-20210623105733859

Gateway

概述简介

1)是什么

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

SpringCloud Gateway使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架。

2)能干嘛

反向代理、鉴权、流量控制、熔断、日志监控
image-20210623110440933

三大核心概念

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

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

Filter(过滤)
  指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
image-20210623110814867
web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。
predicate就是我们的匹配条件;而filter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了

Gateway工作流程

image-20210623110925943
客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。
 
Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。
 
Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。

入门配置

1)建Module

cloud-gateway-gateway9527

2)改POM

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

3)写YML

server:
  port: 9527

spring:
  application:
    name: cloud-gateway

eureka:
  instance:
    hostname: cloud-gateway-service
  client: #服务提供者provider注册进eureka服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

4)主启动

@EnableEurekaClient

5)9527网关如何做路由映射

cloud-provider-payment8001看看controller的访问地址---get、lb
我们目前不想暴露8001端口,希望在8001外面套一层9527

6)YML新增网关配置

server:
  port: 9527

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

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

eureka:
  instance:
    hostname: cloud-gateway-service
  client: #服务提供者provider注册进eureka服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

7)测试

启动7001
启动8001---cloud-provider-payment8001
启动9527网关
访问说明
-添加网关前---http://localhost:8001/payment/get/31
-添加网关后---http://localhost:9527/payment/get/31

8)YML配置说明(两种)

1、在配置文件yml中配置(见前面)
2、代码中注入RouteLocator的Bean
2.1 通过9527网关访问到外网的百度新闻网址
2.2 业务实现
@Configuration
public class GateWayConfig
{
    /**
     * 配置了一个id为route-name的路由规则,
     * 当访问地址 http://localhost:9527/guonei时会自动转发到地址:http://news.baidu.com/guonei
     * @param builder
     * @return
     */
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder)
    {
        RouteLocatorBuilder.Builder routes = builder.routes();

        routes.route("path_route_atguigu", r -> r.path("/guonei").uri("http://news.baidu.com/guonei")).build();

        return routes.build();
    }
    @Bean
    public RouteLocator customRouteLocator2(RouteLocatorBuilder builder)
    {
        RouteLocatorBuilder.Builder routes = builder.routes();
        routes.route("path_route_atguigu2", r -> r.path("/guoji").uri("http://news.baidu.com/guoji")).build();
        return routes.build();
    }
}

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

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

1)启动

一个eureka7001 + 两个服务提供者8001/8002

2)POM

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

3)yml

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          # uri: http://localhost:8001          #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/get/**         # 断言,路径相匹配的进行路由

        - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          # uri: http://localhost:8001          #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/lb/**         # 断言,路径相匹配的进行路由
需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。
lb://serviceName是spring cloud gateway在微服务中自动为我们创建的负载均衡uri

4)测试

Predicate的使用

Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础架构的一部分。
Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个Route Predicate工厂可以进行组合
 
Spring Cloud Gateway 创建 Route 对象时,使用 RoutePredicateFactory创建Predicate对象,Predicate对象可以赋值给Route。Spring Cloud Gateway包含许多内置的Route Predicate Factories。
 
所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑and。

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

Filter的使用

1)是什么

路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。
Spring Cloud Gateway 内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生
生命周期,Only Two---pre、post
种类,Only Two---GatewayFilter 、GlobalFilter

2)常用的GatewayFilter

AddRequestParameter

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能
          lower-case-service-id: true #使用小写服务名,默认是大写
      routes:
        - id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: lb://cloud-provider-payment #匹配后的目标服务地址,供服务的路由地址
          #uri: http://localhost:8001 #匹配后提供服务的路由地址
          filters:
            - AddRequestParameter=X-Request-Id,1024 #过滤器工厂会在匹配的请求头加上一对请求头,名称为X-Request-Id值为1024
          predicates:
            - Path=/paymentInfo/**        # 断言,路径相匹配的进行路由
            - Method=GET,POST

3)自定义过滤器

1、两个主要接口介绍
implements GlobalFilter,Ordered

2、能干嘛
全局日志记录
统一网关鉴权

3、案例代码
@Component //必须加,必须加,必须加
public class MyLogGateWayFilter implements GlobalFilter,Ordered
{
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
    {
        System.out.println("time:"+new Date()+"\t 执行了自定义的全局过滤器: "+"MyLogGateWayFilter"+"hello");

        String uname = exchange.getRequest().getQueryParams().getFirst("uname");
        if (uname == null) {
            System.out.println("****用户名为null,无法登录");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

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

Config分布式配置中心

概述

1)分布式系统面临的配置问题

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

2)是什么

image-20210623113623697
是什么
SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置。
 
怎么玩
SpringCloud Config分为服务端和客户端两部分。
服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口
客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容。

3)能干嘛

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

4)与GitHub整合配置

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

Config服务端配置与测试

1)建Module

新建Module模块cloud-config-center-3344,它即为Cloud的配置中心模块cloudConfig Center

2)pom

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

3)yml

server:
  port: 3344

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

#服务注册到eureka地址
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka

4)主启动

@EnableConfigServer
ConfigCenterMain3344

5)windows下修改hosts文件,增加映射

127.0.0.1  config-3344.com

6)测试通过Config微服务是否可以从GitHub上获取配置内容

启动微服务3344
http://config-3344.com:3344/master/config-dev.yml

7)配置读取规则

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

/{label}/{application}-{profile}.yml
  master分支
    http://config-3344.com:3344/master/config-dev.yml
    http://config-3344.com:3344/master/config-test.yml
    http://config-3344.com:3344/master/config-prod.yml
  dev分支
    http://config-3344.com:3344/dev/config-dev.yml
    http://config-3344.com:3344/dev/config-test.yml
    http://config-3344.com:3344/dev/config-prod.yml
/{label}-{name}-{profiles}.yml
 
label:分支(branch)
name :服务名
profiles:环境(dev/test/prod)

Config客户端配置与测试

1)建Module

新建cloud-config-client-3355

2)pom

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

3)bootstrap.yml

applicaiton.yml是用户级的资源配置项
bootstrap.yml是系统级的,优先级更加高
 
Spring Cloud会创建一个“Bootstrap Context”,作为Spring应用的`Application Context`的父上下文。初始化的时候,`Bootstrap Context`负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的`Environment`。
 
Bootstrap属性有高优先级,默认情况下,它们不会被本地配置覆盖。 `Bootstrap context`和`Application Context有着不同的约定,所以新增了一个bootstrap.yml文件,保证`Bootstrap Context`和Application Context`配置的分离。
 
要将Client模块下的application.yml文件改为bootstrap.yml,这是很关键的,
因为bootstrap.yml是比application.yml先加载的。bootstrap.yml优先级高于application.yml
server:
  port: 3355

spring:
  application:
    name: config-client
  cloud:
    #Config客户端配置
    config:
      label: master #分支名称
      name: config #配置文件名称
      profile: dev #读取后缀名称   上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
      uri: http://localhost:3344 #配置中心地址k

#服务注册到eureka地址
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka

4)修改config-dev.yml配置并提交到GitHub中,比如加个变量age或者版本号version

5)主启动

6)业务类

@RestController
public class ConfigClientController
{
    @Value("${config.info}")
    private String configInfo;

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

7)测试

启动Config配置中心3344微服务并自测
  http://config-3344.com:3344/master/config-prod.yml
  http://config-3344.com:3344/master/config-dev.yml
启动3355作为Client准备访问
  http://localhost:3355/configInfo

8)存在问题

Linux运维修改GitHub上的配置文件内容做调整
刷新3344,发现ConfigServer配置中心立刻响应
刷新3355,发现ConfigClient客户端没有任何响应
3355没有变化除非自己重启或者重新加载
难到每次运维修改配置文件,客户端都需要重启??噩梦

Config客户端之动态刷新

1)避免每次更新配置都要重启客户端微服务3355

2)动态刷新

1、修改3355模块
2、POM引入actuator监控
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

3、修改YML,暴露监控端口
# 暴露监控端点
management:
  endpoints:
    web:
      exposure:
        include: "*"
        
4、@RefreshScope业务类Controller修改
5、需要运维人员发送Post请求刷新3355
curl -X POST "http://localhost:3355/actuator/refresh"

3)存在问题

假如有多个微服务客户端3355/3366/3377。。。。。。
每个微服务都要执行一次post请求,手动刷新?
可否广播,一次通知,处处生效?
我们想大范围的自动刷新,求方法

Bus消息总线

概述

1)是什么

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

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

2)能干嘛

Spring Cloud Bus能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改、事件推送等,也可以当作微服务间的通信通道。
1647927274566

3)总线

什么是总线
在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中所有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称它为消息总线。在总线上的各个实例,都可以方便地广播一些需要让其他连接在该主题上的实例都知道的消息。
 
基本原理
ConfigClient实例都监听MQ中同一个topic(默认是springCloudBus)。当一个服务刷新数据的时候,它会把这个信息放入到Topic中,这样其它监听同一Topic的服务就能得到通知,然后去更新自身的配置。

动态刷新全局广播

1)必须先具备良好的RabbitMQ环境先

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

1、建Module
cloud-config-client-3366
2、改POM
3、写YML
4、主启动
5、controller

3)设计思想

1、利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置
image-20210623132116699
2、利用消息总线触发一个服务端ConfigServer的/bus/refresh端点,而刷新所有客户端的配置
image-20210623132230994
图二的架构显然更加适合,图一不适合的原因如下
打破了微服务的职责单一性,因为微服务本身是业务模块,它本不应该承担配置刷新的职责。
破坏了微服务各节点的对等性。
有一定的局限性。例如,微服务在迁移时,它的网络地址常常会发生变化,此时如果想要做到自动刷新,那就会增加更多的修改

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

1、pom
<!--添加消息总线RabbitMQ支持-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

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

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

1、pom
2、yml
#rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

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

1、pom
2、yml

7)测试

运维工程师
  修改Github上配置文件增加版本号
  发送POST请求
    curl -X POST "http://localhost:3344/actuator/bus-refresh"
    一次发送,处处生效
    
配置中心
  http://config-3344.com:3344/config-dev.yml
  
客户端
  http://localhost:3355/configInfo
  http://localhost:3366/configInfo
  获取配置信息,发现都已经刷新了

动态刷新定点通知

不想全部通知,只想定点通知
  只通知3355
  不通知3366
  
简单一句话
  指定具体某一个实例生效而不是全部 
  公式:http://localhost:配置中心的端口号/actuator/bus-refresh/{destination}
  /bus/refresh请求不再发送到具体的服务实例上,而是发给config server并 通过destination参数类指定需要更新配置的服务或实例
  
案例
  我们这里以刷新运行在3355端口上的config-client为例
    只通知3355
    不通知3366
  curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:3355"
image-20210623133123220

Stream消息驱动

消息驱动概述

1)是什么

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

什么是SpringCloudStream
官方定义 Spring Cloud Stream 是一个构建消息驱动微服务的框架。
 
应用程序通过 inputs 或者 outputs 来与 Spring Cloud Stream中binder对象交互。
通过我们配置来binding(绑定) ,而 Spring Cloud Stream 的 binder对象负责与消息中间件交互。
所以,我们只需要搞清楚如何与 Spring Cloud Stream 交互就可以方便使用消息驱动的方式。
 
通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动。
Spring Cloud Stream 为一些供应商的消息中间件产品提供了个性化的自动化配置实现,引用了发布-订阅、消费组、分区的三个核心概念。
 
目前仅支持RabbitMQ、Kafka。

2)设计思想

1、标准MQ
生产者/消费者之间靠消息媒介传递信息内容--- Message
消息必须走特定的通道---消息通道MessageChannel
消息通道里的消息如何被消费呢,谁负责收发处理---消息通道MessageChannel的子接口SubscribableChannel,由MessageHandler消息处理器所订阅
image-20210623133804036
2、为什么用Cloud Stream
比方说我们用到了RabbitMQ和Kafka,由于这两个消息中间件的架构上的不同,像RabbitMQ有exchange,kafka有Topic和Partitions分区,这些中间件的差异性导致我们实际项目开发给我们造成了一定的困扰,我们如果用了两个消息队列的其中一种,后面的业务需求,我想往另外一种消息队列进行迁移,这时候无疑就是一个灾难性的,一大堆东西都要重新推倒重新做,因为它跟我们的系统耦合了,这时候springcloud Stream给我们提供了一种解耦合的方式。
stream凭什么可以统一底层差异?
在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接与消息中间件进行信息交互的时候,
由于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性,通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离。
通过向应用程序暴露统一的Channel通道,使得应用程序不需要再考虑各种不同的消息中间件实现。
通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。
Binder
在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接与消息中间件进行信息交互的时候,由于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性,通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离。Stream对消息中间件的进一步封装,可以做到代码层面对中间件的无感知,甚至于动态的切换中间件(rabbitmq切换为kafka),使得微服务开发的高度解耦,服务可以关注更多自己的业务流程
通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。
Binder可以生成Binding,Binding用来绑定消息容器的生产者和消费者,它有两种类型,INPUT和OUTPUT,INPUT对应于消费者,OUTPUT对应于生产者。
image-20210623134356685
3、Stream中的消息通信方式遵循了发布-订阅模式
Topic主题进行广播
在RabbitMQ就是Exchange
在Kakfa中就是Topic

3)标准流程套路

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

4)编码API和常用注解

image-20210623135030187

案例说明

工程中新建三个子模块
  cloud-stream-rabbitmq-provider8801, 作为生产者进行发消息模块
  cloud-stream-rabbitmq-consumer8802,作为消息接收模块
  cloud-stream-rabbitmq-consumer8803  作为消息接收模块

消息驱动之生产者

1、建Module
cloud-stream-rabbitmq-provider8801

2、改POM
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>

3、写YML
spring:
  application:
    name: cloud-stream-provider
  cloud:
      stream:
        binders: # 在此处配置要绑定的rabbitmq的服务信息;
          defaultRabbit: # 表示定义的名称,用于于binding整合
            type: rabbit # 消息组件类型
            environment: # 设置rabbitmq的相关的环境配置
              spring:
                rabbitmq:
                  host: localhost
                  port: 5672
                  username: guest
                  password: guest
        bindings: # 服务的整合处理
          output: # 这个名字是一个通道的名称
            destination: studyExchange # 表示要使用的Exchange名称定义
            content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
            binder: defaultRabbit # 设置要绑定的消息服务的具体设置
            
4、主启动
5、业务类
5.1 发送消息接口
public interface IMessageProvider
{
    public String send() ;
}

5.2 发送消息接口实现类
@EnableBinding(Source.class) // 可以理解为是一个消息的发送管道的定义
public class MessageProviderImpl implements IMessageProvider
{
    @Resource
    private MessageChannel output; // 消息的发送管道

    @Override
    public String send()
    {
        String serial = UUID.randomUUID().toString();
        this.output.send(MessageBuilder.withPayload(serial).build()); // 创建并发送消息
        System.out.println("***serial: "+serial);

        return serial;
    }
}

5.3 Controller
@EnableBinding(Source.class) // 可以理解为是一个消息的发送管道的定义
public class MessageProviderImpl implements IMessageProvider
{
    @Resource
    private MessageChannel output; // 消息的发送管道

    @Override
    public String send()
    {
        String serial = UUID.randomUUID().toString();
        this.output.send(MessageBuilder.withPayload(serial).build()); // 创建并发送消息
        System.out.println("***serial: "+serial);

        return serial;
    }
}

6、测试
启动7001eureka
启动rabbitmq
  rabbitmq-plugins enable rabbitmq_management
  http://localhost:15672/
启动8801
访问
  http://localhost:8801/sendMessage
image-20210623135741907

消息驱动之消费者

1、建Module
cloud-stream-rabbitmq-consumer8802

2、改POM
3、写YML
spring:
  application:
    name: cloud-stream-consumer
  cloud:
      stream:
        binders: # 在此处配置要绑定的rabbitmq的服务信息;
          defaultRabbit: # 表示定义的名称,用于于binding整合
            type: rabbit # 消息组件类型
            environment: # 设置rabbitmq的相关的环境配置
              spring:
                rabbitmq:
                  host: localhost
                  port: 5672
                  username: guest
                  password: guest
        bindings: # 服务的整合处理
          input: # 这个名字是一个通道的名称
            destination: studyExchange # 表示要使用的Exchange名称定义
            content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
            binder: defaultRabbit # 设置要绑定的消息服务的具体设置
            
4、主启动
5、业务类
@Component
@EnableBinding(Sink.class)
public class ReceiveMessageListener
{
    @Value("${server.port}")
    private String serverPort;

    @StreamListener(Sink.INPUT)
    public void input(Message<String> message)
    {
        System.out.println("消费者1号,------->接收到的消息:" + message.getPayload()+"\t port: "+serverPort);
    }
}

分组消费与持久化

1)依照8802,clone出来一份运行8803

2)启动

RabbitMQ
7001
  服务注册
8801
  消息生产
8802
  消息消费
8803
  消息消费

3)运行后有两个问题

有重复消费问题
消息持久化问题

4)消费

目前是8802/8803同时都收到了,存在重复消费问题

如何解决:分组和持久化属性group
生产实际案例
比如在如下场景中,订单系统我们做集群部署,都会从RabbitMQ中获取订单信息,那如果一个订单同时被两个服务获取到,那么就会造成数据错误,我们得避免这种情况。
这时我们就可以使用Stream中的消息分组来解决,注意在Stream中处于同一个group中的多个消费者是竞争关系,就能够保证消息只会被其中一个应用消费一次。
不同组是可以全面消费的(重复消费),同一组内会发生竞争关系,只有其中一个可以消费。
image-20210623140447671

5)分组

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

2、8802/8803都变成不同组,group两个不同
2.1 group: atguiguA、atguiguB
2.2 8802修改YML---group: atguiguA
2.3 8803修改YML---group: atguiguB
2.4 我们自己配置
分布式微服务应用为了实现高可用和负载均衡,实际上都会部署多个实例,本例启动了两个消费微服务(8802/8803)
多数情况,生产者发送消息给某个具体微服务时只希望被消费一次,按照上面我们启动两个应用的例子,虽然它们同属一个应用,但是这个消息出现了被重复消费两次的情况。为了解决这个问题,在Spring Cloud Stream中提供了消费组的概念。
image-20210623140845389
还是重复消费
3、8802/8803实现了轮询分组,每次只有一个消费者
8801模块的发的消息只能被8802或8803其中一个接收到,这样避免了重复消费。

4、8802/8803都变成相同组,group两个相同
group: atguiguA
8802修改YML
8803修改YML
结论
  同一个组的多个微服务实例,每次只会有一个拿到

6)持久化

通过上述,解决了重复消费问题,再看看持久化
停止8802/8803并去除掉8802的分组group: atguiguA
  8803的分组group: atguiguA没有去掉
8801先发送4条消息到rabbitmq
先启动8802,无分组属性配置,后台没有打出来消息
image-20210623141215310
再启动8803,有分组属性配置,后台打出来了MQ上的消息
image-20210623141237799

Sleuth

概述

问题
在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的的服务节点调用来协同产生最后的请求结果,每一个前段请求都会形成一条复杂的分布式服务调用链路,链路中的任何一环出现高延时或错误都会引起整个请求最后的失败。

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

搭建链路监控步骤

1)zipkin

1、完整的调用链路 
表示一请求链路,一条链路通过Trace Id唯一标识,Span标识发起的请求信息,各span通过parent id 关联起来

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

2、一条链路通过Trace Id唯一标识,Span标识发起的请求信息,各span通过parent id 关联起来

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

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

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

2)服务提供者

1、建Module
cloud-provider-payment8001

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

3、写YML
spring:
  application:
    name: cloud-payment-service
  zipkin:
    base-url: http://localhost:9411
  sleuth:
    sampler:
      #采样率值介于 0 到 1 之间,1 则表示全部采集
     probability: 1
     
4、业务类
@GetMapping("/payment/zipkin")
public String paymentZipkin()
{
    return "hi ,i'am paymentzipkin server fall back,welcome to atguigu,O(∩_∩)O哈哈~";
}

3)服务消费者(调用方)

1、建Module
cloud-consumer-order80

2、改POM
3、写YML
spring:
    application:
        name: cloud-order-service
    zipkin:
      base-url: http://localhost:9411
    sleuth:
      sampler:
        probability: 1

4、业务类
// ====================> zipkin+sleuth
@GetMapping("/consumer/payment/zipkin")
public String paymentZipkin()
{
    String result = restTemplate.getForObject("http://localhost:8001"+"/payment/zipkin/", String.class);
    return result;
}

4)依次启动eureka7001/8001/80

5)打开浏览器访问:http://localhost:9411

会出现以下界面

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

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

查看依赖关系

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

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

Alibaba入门

概述

1)作用

服务限流降级:默认支持 Servlet、Feign、RestTemplate、Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。
服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。
分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。
消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。
阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8NeN1Q5C-1648282992282)(https://gitee.com/qingxi5/typora/raw/master/note/20210623145946.png)]

Nacos服务注册和配置中心

Nacos简介

是什么
  一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
  Nacos: Dynamic Naming and Configuration Service
  Nacos就是注册中心 + 配置中心的组合
    等价于---Nacos = Eureka+Config +Bus
能干嘛
  替代Eureka做服务注册中心
  替代Config做服务配置中心

1)各种注册中心比较

image-20210623150329348

Nacos作为服务注册中心

1)基于Nacos的服务提供者

1、建Module
cloudalibaba-provider-payment9001

2、改POM
2.1 父pom
<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>

2.2 本模块POM
<!--SpringCloud ailibaba nacos -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

3、写YML
server:
  port: 9001

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

management:
  endpoints:
    web:
      exposure:
        include: '*'
 
4、主启动
@EnableDiscoveryClient

5、业务类
@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;
    }
}

6、测试

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

为了下节演示nacos的负载均衡,参照9001新建9002

2)基于Nacos的服务消费者

1、建Module
cloudalibaba-consumer-nacos-order83

2、改POM
<!--SpringCloud ailibaba nacos -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

3、写YML
server:
  port: 83

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

#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
  nacos-user-service: http://nacos-payment-provider 
  
4、主启动
5、业务类
5.1 ApplicationContextBean
@Configuration
public class ApplicationContextBean
{
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate()
    {
        return new RestTemplate();
    }
}

5.2 OrderNacosController
@RestController
public class OrderNacosController
{
    @Resource
    private RestTemplate restTemplate;

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

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

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

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

6、测试
83访问9001/9002,轮询负载OK

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

3)服务注册中心对比

image-20210623150736802
Nacos 支持AP和CP模式的切换
 
C是所有节点在同一时间看到的数据是一致的;而A的定义是所有的请求都会收到响应。
 
何时选择使用何种模式?
一般来说,如果不需要存储服务级别的信息且服务实例是通过nacos-client注册,并能够保持心跳上报,那么就可以选择AP模式。当前主流的服务如 Spring cloud 和 Dubbo 服务,都适用于AP模式,AP模式为了服务的可能性而减弱了一致性,因此AP模式下只支持注册临时实例。
 
如果需要在服务级别编辑或者存储配置信息,那么 CP 是必须,K8S服务和DNS服务则适用于CP模式。
CP模式下则支持注册持久化实例,此时则是以 Raft 协议为集群运行模式,该模式下注册实例之前必须先注册服务,如果服务不存在,则会返回错误。

curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'

Nacos作为服务配置中心

1)Nacos作为配置中心-基础配置

1、建Module
cloudalibaba-config-nacos-client3377

2、改POM
<!--nacos-config-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

3、写YML
Nacos同springcloud-config一样,在项目初始化时,要保证先从配置中心进行配置拉取,拉取配置之后,才能保证项目的正常启动。 springboot中配置文件的加载是存在优先级顺序的,bootstrap优先级高于application
3.1 bootstrap
spring:
  application:
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
      config:
        server-addr: localhost:8848 #Nacos作为配置中心地址
        file-extension: yaml #指定yaml格式的配置
        
3.2 application
spring:
  profiles:
    active: dev # 表示开发环境
    
4、主启动
5、业务类
@RestController
@RefreshScope //在控制器类加入@RefreshScope注解使当前类下的配置支持Nacos的动态刷新功能。
public class ConfigClientController
{
    @Value("${config.info}")
    private String configInfo;

    @GetMapping("/config/info")
    public String getConfigInfo() {
        return configInfo;
    }
}
6、在Nacos中添加配置信息
6.1 理论
Nacos中的dataid的组成格式及与SpringBoot配置文件中的匹配规则,见下图。

最后公式:
${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

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

6.2 实操
1)配置新增

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

2)Nacos界面配置对应---设置DataID
公式:
${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

prefix 默认为 spring.application.name 的值
spring.profile.active 即为当前环境对应的 profile,可以通过配置项 spring.profile.active 来配置。
file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置
image-20210623152701433
小总结
image-20210623152734377
3)历史配置
Nacos会记录配置文件的历史版本默认保留30天,此外还有一键回滚功能,回滚操作将会触发配置更新

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

7、测试
启动前需要在nacos客户端-配置管理-配置管理栏目下有对应的yaml配置文件
运行cloud-config-nacos-client3377的主启动类
调用接口查看配置信息---http://localhost:3377/config/info
8、自带动态刷新
修改下Nacos中的yaml配置文件,再次调用查看配置的接口,就会发现配置已经刷新

2)Nacos作为配置中心-分类配置

1、问题---多环境多项目管理
问题1:
实际开发中,通常一个系统会准备
dev开发环境
test测试环境
prod生产环境。
如何保证指定环境启动时服务能正确读取到Nacos上相应环境的配置文件呢?

问题2:
一个大型分布式微服务系统会有很多微服务子项目,
每个微服务项目又都会有相应的开发环境、测试环境、预发环境、正式环境......
那怎么对这些微服务配置进行管理呢?
2、Nacos的图形化管理界面
2.1 配置管理

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

2.2 命名空间
image-20210623153457532
3、Namespace+Group+Data ID三者关系
3.1 是什么
   类似Java里面的package名和类名
   最外层的namespace是可以用于区分部署环境的,Group和DataID逻辑上区分两个目标对象。
3.2 三者情况
默认情况:
Namespace=public,Group=DEFAULT_GROUP, 默认Cluster是DEFAULT
 
Nacos默认的命名空间是public,Namespace主要用来实现隔离。
比方说我们现在有三个环境:开发、测试、生产环境,我们就可以创建三个Namespace,不同的Namespace之间是隔离的。
 
Group默认是DEFAULT_GROUP,Group可以把不同的微服务划分到同一个分组里面去
 
Service就是微服务;一个Service可以包含多个Cluster(集群),Nacos默认Cluster是DEFAULT,Cluster是对指定微服务的一个虚拟划分。
比方说为了容灾,将Service微服务分别部署在了杭州机房和广州机房,这时就可以给杭州机房的Service微服务起一个集群名称(HZ),给广州机房的Service微服务起一个集群名称(GZ),还可以尽量让同一个机房的微服务互相调用,以提升性能。
 
最后是Instance,就是微服务的实例。
image-20210623153728636
4、Case---三种方案加载配置
4.1 DataID方案
1)指定spring.profile.active和配置文件的DataID来使不同环境下读取不同的配置
2)默认空间+默认分组+新建dev和test两个DataID
新建dev配置DataID
image-20210623160523814
新建test配置DataID
image-20210623160551171
3)通过spring.profile.active属性就能进行多环境下配置文件的读取
image-20210623160708191
4.2 Group方案
1)通过Group实现环境区分
image-20210623160810542
2)在nacos图形界面控制台上面新建配置文件DataID
image-20210623160927996
3)bootstrap+application
在config下增加一条group的配置即可。可配置为DEV_GROUP或TEST_GROUP
image-20210623161000445
4.3 Namespace方案
1)新建dev/test的Namespace
image-20210623161110713
2)回到服务管理-服务列表查看
image-20210623161218807
3)按照域名配置填写
image-20210623161237437
4)YML
bootstrap
 
# nacos注册中心
server:
  port: 3377

spring:
  application:
    name: nacos-order
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
      config:
        server-addr: localhost:8848 #Nacos作为配置中心地址
        file-extension: yaml #这里我们获取的yaml格式的配置
        namespace: 5da1dccc-ee26-49e0-b8e5-7d9559b95ab0
        #group: DEV_GROUP
        group: TEST_GROUP
application

# Nacos注册配置,application.yml
spring:
  profiles:
    #active: test
    active: dev
    #active: info

Nacos集群和持久化配置

1)架构图

image-20210623161556052

2)说明

默认Nacos使用嵌入式数据库实现数据的存储。所以,如果启动多个默认配置下的Nacos节点,数据存储是存在一致性问题的。
为了解决这个问题,Nacos采用了集中式存储的方式来支持集群化部署,目前只支持MySQL的存储。
image-20210623161748808 image-20210623161812560

3)Nacos持久化配置解释

1、Nacos默认自带的是嵌入式数据库derby
2、derby到mysql切换配置步骤
2.1 nacos-server-1.1.4\nacos\conf目录下找到sql脚本
2.2 nacos-server-1.1.4\nacos\conf目录下找到application.properties

4)Linux版Nacos+MySQL生产环境配置

1、预计需要,1个Nginx+3个nacos注册中心+1个mysql
2、集群配置步骤(重点)
2.1 Linux服务器上mysql数据库配置
2.2 application.properties配置
2.3 Linux服务器上nacos的集群配置cluster.conf
1)梳理出3台nacos集器的不同服务端口号
2)复制出cluster.conf
3)内容。是Linux命令hostname -i能够识别的IP
image-20210623162841673
2.4 编辑Nacos的启动脚本startup.sh,使它能够接受不同的启动端口
1)/mynacos/nacos/bin 目录下有startup.sh
平时单机版的启动,都是./startup.sh即可。但是集群启动,我们希望可以类似其它软件的shell命令,传递不同的端口号启动不同的nacos实例。
2)修改startup.sh内容
命令:./startup.sh -p 3333表示启动端口号为3333的nacos服务器实例,和上一步cluster.conf配置一致。
2.5 Nginx的配置,由它作为负载均衡器
upstream cluster{
    server 127.0.0.1:3333;
    server 127.0.0.1:4444;
    server 127.0.0.1:5555;
}

server {
    listen       1111;
    server_name  localhost;
    #charset koi8-r;
    #access_log  logs/host.access.log  main;
    location / {
        #root   html;
        #index  index.html index.htm;
        proxy_pass http://cluster;
    }
}
2.6 截止到此处,1个Nginx+3个nacos注册中心+1个mysql
1)测试通过nginx访问nacos
2)新建一个配置测试
3)linux服务器的mysql插入一条记录
3、测试
微服务cloudalibaba-provider-payment9002启动注册进nacos集群
4、总结
image-20210623163852804

Sentinel实现熔断与限流

Sentinel

1)是什么

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

2)能干啥

image-20210623164225445

安装Sentinel控制台

1)sentinel组件由2部分构成

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2Wv7djuY-1648282992294)(https://gitee.com/qingxi5/typora/raw/master/note/20210623164351.png)]

2)运行前提

java8环境OK
8080端口不能被占用

初始化演示工程

1)启动Nacos8848成功

2)Module

1、建Module
cloudalibaba-sentinel-service8401

2、改POM
<!--SpringCloud ailibaba nacos -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!--SpringCloud ailibaba sentinel -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

3、写YML
server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        #Nacos服务注册中心地址
        server-addr: localhost:8848
    sentinel:
      transport:
        #配置Sentinel dashboard地址
        dashboard: localhost:8080
        #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
        port: 8719

management:
  endpoints:
    web:
      exposure:
        include: '*'

4、主启动
5、业务类
@RestController
public class FlowLimitController
{
    @GetMapping("/testA")
    public String testA()
    {
        return "------testA";
    }

    @GetMapping("/testB")
    public String testB()
    {
        return "------testB";
    }
}

3)启动Sentinel8080、启动微服务8401

4)启动8401微服务后查看sentienl控制台,需要执行一次访问即可

image-20210623164903730

流控规则

1)基本介绍

image-20210623165301790 image-20210623165326469

2)流控模式

1、直接(默认)
直接->快速失败
表示1秒钟内查询1次就是OK,若超过次数1,就直接-快速失败,报默认错误
image-20210623165604596
2、关联
当关联的资源达到阈值时,就限流自己。当与A关联的资源B达到阀值后,就限流A自己。

设置效果
当关联资源/testB的qps阀值超过1时,就限流/testA的Rest访问地址,当关联资源到阈值后限制配置好的资源名
image-20210623165734168
3、链路
多个请求调用了同一个微服务

3)流控效果

1、直接->快速失败(默认的流控处理)
直接失败,抛出异常
2、预热
2.1 说明
公式:阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值
2.2 默认coldFactor为3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。
2.3 WarmUp配置
默认 coldFactor 为 3,即请求QPS从(threshold / 3) 开始,经多少预热时长才逐渐升至设定的 QPS 阈值。
案例,阀值为10+预热时长设置5秒。系统初始化的阀值为10 / 3 约等于3,即阀值刚开始为3;然后过了5秒后阀值才慢慢升高恢复到10
image-20210623170331241
3、排队等待
匀速排队,让请求以均匀的速度通过,阀值类型必须设成QPS,否则无效。
设置含义:/testA每秒1次请求,超过的话就排队等待,等待的超时时间为20000毫秒。
image-20210623170500985 image-20210623170613739

降级规则

1)基本介绍

image-20210623170907453
RT(平均响应时间,秒级)
    平均响应时间   超出阈值  且   在时间窗口内通过的请求>=5,两个条件同时满足后触发降级
    窗口期过后关闭断路器
    RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)

异常比列(秒级)
	QPS >= 5 且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级

异常数(分钟级)
	异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。
 
当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认是抛 DegradeException)。
Sentinel的断路器是没有半开状态的
半开的状态系统自动去检测是否请求有异常,没有异常就关闭断路器恢复使用,有异常则继续打开断路器不可用。具体可以参考Hystrix

2)降级策略实战

1、RT
image-20210623171204687 image-20210623171219370
1)代码
@GetMapping("/testD")
public String testD()
{
    //暂停几秒钟线程
    try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
    log.info("testD 测试RT");
    return "------testD";
}
2)配置
image-20210623171434784
3)jmeter压测
image-20210623171508302
4)结论
按照上述配置,永远一秒钟打进来10个线程(大于5个了)调用testD,我们希望200毫秒处理完本次任务,
如果超过200毫秒还没处理完,在未来1秒钟的时间窗口内,断路器打开(保险丝跳闸)微服务不可用,保险丝跳闸断电了,后续我停止jmeter,没有这么大的访问量了,断路器关闭(保险丝恢复),微服务恢复OK
2、异常比例
image-20210623171636084 image-20210623171646495
1)代码
@GetMapping("/testD")
public String testD()
{
    log.info("testD 测试RT");
    int age = 10/0;
    return "------testD";
}
2)配置
image-20210623171812900
3)jmeter压测
image-20210623171829551
4)结论
按照上述配置,单独访问一次,必然来一次报错一次(int age  = 10/0),调一次错一次;开启jmeter后,直接高并发发送请求,多次调用达到我们的配置条件了。
断路器开启(保险丝跳闸),微服务不可用了,不再报错error而是服务降级了。
3、异常数
时间窗口一定要大于等于60秒。
异常数是按照分钟统计的
image-20210623171924546 image-20210623171948663
1)代码
@GetMapping("/testE")
public String testE()
{
    log.info("testE 测试异常比例");
    int age = 10/0;
    return "------testE 测试异常比例";
}
2)配置
http://localhost:8401/testE,第一次访问绝对报错,因为除数不能为零,我们看到error窗口,但是达到5次报错后,进入熔断后降级。
image-20210623172146821
3)jmeter压测
image-20210623172217137

热点key限流

1)基本介绍

何为热点
热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的TopN数据,并对其访问进行限流或者其它操作

2)@SentinelResource

兜底方法
分为系统默认和客户自定义两种

3)代码

@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "dealHandler_testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1, 
                         @RequestParam(value = "p2",required = false) String p2){
    return "------testHotKey";
}
public String dealHandler_testHotKey(String p1,String p2,BlockException exception)
{
    return "-----dealHandler_testHotKey";
}
 
sentinel系统默认的提示:Blocked by Sentinel (flow limiting)

4)配置

1、@SentinelResource(value = "testHotKey")
异常打到了前台用户界面看到,不友好
2、 @SentinelResource(value = "testHotKey",blockHandler = "dealHandler_testHotKey")
方法testHotKey里面第一个参数只要QPS超过每秒1次,马上降级处理
用了我们自己定义的

5)测试

error
  http://localhost:8401/testHotKey?p1=abc
error
  http://localhost:8401/testHotKey?p1=abc&p2=33
right
  http://localhost:8401/testHotKey?p2=abc

6)参数例外项

上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流

特例情况
  普通---超过1秒钟一个后,达到阈值1后马上被限流
  我们期望p1参数当它是某个特殊值时,它的限流值和平时不一样
  特例---假如当p1的值等于5时,它的阈值可以达到200
配置
image-20210623184815659
测试
  http://localhost:8401/testHotKey?p1=5
  http://localhost:8401/testHotKey?p1=3
  当p1等于5的时候,阈值变为200
  当p1不等于5的时候,阈值就是平常的1
前提条件
  热点参数的注意点,参数必须是基本类型或者String

系统规则

image-20210623184929306

@SentinelResource

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

1、启动Nacos成功
  http://localhost:8848/nacos/#/login
2、启动Sentinel成功
  java -jar sentinel-dashboard-1.7.0.jar
3、Module
3.1 cloudalibaba-sentinel-service8401
3.2 pom
<dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
    <groupId>com.atguigu.springcloud</groupId>
    <artifactId>cloud-api-commons</artifactId>
    <version>${project.version}</version>
</dependency>

3.3 yml 
server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
    sentinel:
      transport:
        dashboard: localhost:8080 #配置Sentinel dashboard地址
        port: 8719

management:
  endpoints:
    web:
      exposure:
        include: '*'
 
3.4 业务类RateLimitController
@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 服务不可用");
    }
}

3.5 主启动

4、配置流控规则
4.1 配置步骤
image-20210623185411807
4.2 图形配置和代码关系
表示1秒钟内查询次数大于1,就跑到我们自定义的处流,限流
image-20210623185447952
5、测试
1秒钟点击1下,OK
超过上述,疯狂点击,返回了自己定义的限流处理信息,限流发生

6、额外问题
此时关闭问服务8401看看
Sentinel控制台,流控规则消失了?

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

1、通过访问的URL来限流,会返回Sentinel自带默认的限流处理信息
2、业务类RateLimitController
@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 服务不可用");
    }

    @GetMapping("/rateLimit/byUrl")
    @SentinelResource(value = "byUrl")
    public CommonResult byUrl()
    {
        return new CommonResult(200,"按url限流测试OK",new Payment(2020L,"serial002"));
    }
}
3、访问一次
4、Sentinel控制台配置
image-20210623185744087
5、测试
疯狂点击http://localhost:8401/rateLimit/byUrl
会返回Sentinel自带的限流处理结果

3)上面兜底方案面临的问题

1、系统默认的,没有体现我们自己的业务要求。
2、依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观。
3、每个业务方法都添加一个兜底的,那代码膨胀加剧。
4、全局统一的处理方法没有体现。

4)客户自定义限流处理逻辑

1、创建CustomerBlockHandler类用于自定义限流处理逻辑
2、自定义限流处理类
public class CustomerBlockHandler
{
    public static CommonResult handleException(BlockException exception){
        return new CommonResult(2020,"自定义的限流处理信息......CustomerBlockHandler");
    }
}

3、RateLimitController
/**
 * 自定义通用的限流处理逻辑,
 blockHandlerClass = CustomerBlockHandler.class
 blockHandler = handleException2
 上述配置:找CustomerBlockHandler类里的handleException2方法进行兜底处理
 */
/**
 * 自定义通用的限流处理逻辑
 */
@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
        blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handleException2")
public CommonResult customerBlockHandler()
{
    return new CommonResult(200,"按客户自定义限流处理逻辑");
}

4、启动微服务后先调用一次
5、Sentinel控制台配置
image-20210623190107987

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

image-20210623190142927

5)更多注解属性说明

Sentinel主要有三个核心Api
SphU定义资源
Tracer定义统计
ContextUtil定义了上下文

服务熔断功能

1)sentinel整合ribbon+openFeign+fallback

2)Ribbon系列

1、启动nacos和sentinel
2、提供者9003/9004
2.1 新建cloudalibaba-provider-payment9003/9004两个一样的做法
2.2 POM
2.3 yml 
server:
  port: 9003

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

management:
  endpoints:
    web:
      exposure:
        include: '*'

2.4 主启动
2.5 业务类
@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;
    }
}

2.6 测试
3、消费者84
3.1 新建cloudalibaba-consumer-nacos-order84
3.2 pom
<!--SpringCloud ailibaba nacos -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--SpringCloud ailibaba sentinel -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

3.3 yml
server:
  port: 84

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

#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
  nacos-user-service: http://nacos-payment-provider
  
3.4 主启动
3.5 业务类
1)ApplicationContextConfig
@Configuration
public class ApplicationContextConfig
{
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate()
    {
        return new RestTemplate();
    }
}

2)CircleBreakerController 
目的
  fallback管运行异常
  blockHandler管配置违规

测试地址
  http://localhost:84/consumer/fallback/1
  
没有任何配置,给客户error页面,不友好
@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") 
     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,本例sentinel无配置
@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",fallback = "handlerFallback") //fallback负责业务异常
    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;
    }
    public CommonResult handlerFallback(@PathVariable  Long id,Throwable e) {
        Payment payment = new Payment(id,"null");
        return new CommonResult<>(444,"兜底异常handlerFallback,exception内容  "+e.getMessage(),payment);
    }
}
image-20210623191114794
只配置blockHandler,本例sentinel需配置
异常超过2次后,断路器打开,断电跳闸,系统被保护

@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",blockHandler = "blockHandler") //blockHandler负责在sentinel里面配置的降级限流
    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 ("非法参数异常....");
        }else if (result.getData() == null) {
            throw new NullPointerException ("NullPointerException,该ID没有对应记录");
        }
        return result;
    }
    public CommonResult handlerFallback(@PathVariable  Long id,Throwable e) {
        Payment payment = new Payment(id,"null");
        return new CommonResult<>(444,"fallback,无此流水,exception  "+e.getMessage(),payment);
    }
    public CommonResult blockHandler(@PathVariable  Long id,BlockException blockException) {
        Payment payment = new Payment(id,"null");
        return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException  "+blockException.getMessage(),payment);
    }
}
image-20210623191219317 image-20210623191316050
fallback和blockHandler都配置,本例sentinel需配置
若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。

@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",fallback = "handlerFallback",blockHandler = "blockHandler")
    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 ("非法参数异常....");
        }else if (result.getData() == null) {
            throw new NullPointerException ("NullPointerException,该ID没有对应记录");
        }
        return result;
    }
    public CommonResult handlerFallback(@PathVariable  Long id,Throwable e) {
        Payment payment = new Payment(id,"null");
        return new CommonResult<>(444,"fallback,无此流水,exception  "+e.getMessage(),payment);
    }
    public CommonResult blockHandler(@PathVariable  Long id,BlockException blockException) {
        Payment payment = new Payment(id,"null");
        return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException  "+blockException.getMessage(),payment);
    }
}
image-20210623191422386
忽略属性,本例sentinel无配置
程序异常打到前台了,对用户不友好

@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", 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 ("非法参数异常....");
        }else if (result.getData() == null) {
            throw new NullPointerException ("NullPointerException,该ID没有对应记录");
        }
        return result;
    }
    public CommonResult handlerFallback(@PathVariable  Long id,Throwable e) {
        Payment payment = new Payment(id,"null");
        return new CommonResult<>(444,"fallback,无此流水,exception  "+e.getMessage(),payment);
    }
    public CommonResult blockHandler(@PathVariable  Long id,BlockException blockException) {
        Payment payment = new Payment(id,"null");
        return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException  "+blockException.getMessage(),payment);
    }
}
image-20210623191555846

3)Feign系列

1、修改84模块
84消费者调用提供者9003
Feign组件一般是消费侧

2、改POM
<!--SpringCloud openfeign -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

3、写YML
激活Sentinel对Feign的支持
# 激活Sentinel对Feign的支持
feign:
  sentinel:
    enabled: true  
    
4、主启动
@EnableFeignClients

5、业务类
5.1 带@FeignClient注解的业务接口
@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)//调用中关闭9003服务提供者
public interface PaymentService
{
    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}

5.2 fallback = PaymentFallbackService.class
@Component
public class PaymentFallbackService implements PaymentService
{
    @Override
    public CommonResult<Payment> paymentSQL(Long id)
    {
        return new CommonResult<>(444,"服务降级返回,没有该流水信息",new Payment(id, "errorSerial......"));
    }
}

5.3 Controller
//==================OpenFeign
@Resource
private PaymentService paymentService;

@GetMapping(value = "/consumer/openfeign/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id)
{
    if(id == 4)
    {
        throw new RuntimeException("没有该id");
    }
    return paymentService.paymentSQL(id);
}

6、测试
http://localhost:84/consumer/paymentSQL/1
测试84调用9003,此时故意关闭9003微服务提供者,看84消费侧自动降级,不会被耗死

4)熔断框架比较

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

规则持久化

1)是什么

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

2)怎么玩

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

3)步骤

1、建Module
修改cloudalibaba-sentinel-service8401

2、改POM
<!--SpringCloud ailibaba sentinel-datasource-nacos -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

3、写YML
spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
    sentinel:
      transport:
        dashboard: localhost:8080 #配置Sentinel dashboard地址
        port: 8719
      datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: cloudalibaba-sentinel-service
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow
            
4、添加Nacos业务规则配置
image-20210623192341778

内容解析

resource:资源名称;
limitApp:来源应用;
grade:阈值类型,0表示线程数,1表示QPS;
count:单机阈值;
strategy:流控模式,0表示直接,1表示关联,2表示链路;
controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;
clusterMode:是否集群。
5、启动8401后刷新sentinel发现业务规则有了
image-20210623192517092
6、快速访问测试接口
http://localhost:8401/rateLimit/byUrl
7、停止8401再看sentinel
image-20210623192609029
8、重新启动8401再看sentinel
乍一看还是没有,稍等一会儿
多次调用---http://localhost:8401/rateLimit/byUrl
重新配置出现了,持久化验证通过

Seata处理分布式事务

分布式事务问题

1)分布式前

单机单库没这个问题
从1:1  ->  1:N  ->  N:N

2)分布式之后

单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,
业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。
image-20210623192948950

3)一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题

Seata简介

1)是什么

Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。

2)能干嘛

一个典型的分布式事务过程
1、分布式事务处理过程的一ID+三组件模型
1.1 Transaction ID XID
全局唯一的事务ID
1.2 3组件概念
Transaction Coordinator (TC)
事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;
Transaction Manager (TM)
控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;
Resource Manager (RM)
控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚
2、处理过程
TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID;
XID 在微服务调用链路的上下文中传播;
RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖;
TM 向 TC 发起针对 XID 的全局提交或回滚决议;
TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。
image-20210623193335957

3)怎么玩

本地@Transactional
全局@GlobalTransactional
SEATA 的分布式交易解决方案
image-20210623193659014

订单/库存/账户业务数据库准备

1、以下演示都需要先启动Nacos后启动Seata,保证两个都OK,Seata没启动报错no available server to connect
2、分布式事务业务说明
这里我们会创建三个服务,一个订单服务,一个库存服务,一个账户服务。
当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品的库存,再通过远程调用账户服务来扣减用户账户里面的余额,最后在订单服务中修改订单状态为已完成。
该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。
下订单--->扣库存--->减账户(余额)

3、创建业务数据库
seata_order:存储订单的数据库;
seata_storage:存储库存的数据库;
seata_account:存储账户信息的数据库。

4、按照上述3库分别建对应业务表

5、按照上述3库分别建对应的回滚日志表
image-20210623194018337

订单/库存/账户业务微服务准备

1)业务需求

下订单->减库存->扣余额->改(订单)状态

2)新建订单Order-Module

1、seata-order-service2001
2、POM
3、YML
4、file.conf
5、registry.conf
6、domain
7、Dao接口及实现
8、Service接口及实现
9、Controller
10、Config配置
11、主启动

3)新建库存Storage-Module

1、seata-storage-service2002
2、POM
3、YML
4、file.conf
5、registry.conf
6、domain
7、Dao接口及实现
8、Service接口及实现
9、Controller
10、Config配置
11、主启动

4)新建账户Account-Module

1、seata-account-service2003
2、POM
3、YML
4、file.conf
5、registry.conf
6、domain
7、Dao接口及实现
8、Service接口及实现
9、Controller
10、Config配置
11、主启动

5)Test

6)补充

1、再看TC/TM/RM三大组件
分布式事务的执行流程
  TM 开启分布式事务(TM 向 TC 注册全局事务记录);
  按业务场景,编排数据库、服务等事务内资源(RM 向 TC 汇报资源准备状态 );
  TM 结束分布式事务,事务一阶段结束(TM 通知 TC 提交/回滚分布式事务);
  TC 汇总事务信息,决定分布式事务是提交还是回滚;
  TC 通知所有 RM 提交/回滚 资源,事务二阶段结束。
  
2、AT模式如何做到对业务的无侵入
2.1 是什么
image-20210623194750174
2.2 一阶段加载
在一阶段,Seata 会拦截“业务 SQL”,
1)解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,
2)执行“业务 SQL”更新业务数据,在业务数据更新之后,
3)其保存成“after image”,最后生成行锁。
以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。
image-20210623194837198
2.3 二阶段提交
二阶段如是顺利提交的话,
因为“业务 SQL”在一阶段已经提交至数据库,所以Seata框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。
image-20210623194907583
2.4 二阶段回滚
二阶段回滚:
二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。
回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”,
如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。
image-20210623194943003
3、补充

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_青昔_

您的鼓励是我最大的动力 '◡'

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值