分布式微服务Spring Cloud

Spring Cloud

Spring Cloud是分布式微服务架构的一站式解决方案,是多种微服务架构落地技术的集合体.
除Cloud外,国内较火的是Dubbo.
Dubbo(读音[ˈdʌbəʊ])是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring框架无缝集成。Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

中文社区:https://www.springcloud.cc/

本文代码地址:https://github.com/Tyleryq/Spring-Cloud-Test

微服务概念

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

版本选择

官方强烈推荐升级使用2.x版本.Cloud命名使用伦敦地铁站名.
Spring Cloud与Spring Boot版本应该对应,从官网查看使用的Cloud版本应用哪个Boot版本,否则版本不对应将报错.
在这里插入图片描述
在这里插入图片描述

创建工程

创建父工程

用maven建一个web-app:
在这里插入图片描述
父工程不需要src文件夹,可以删除

编辑父项目的pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.test</groupId>
  <artifactId>cloudTest</artifactId>
  <packaging>pom</packaging>  <!--使用多模块,作为父项目-->
  <version>1.0-SNAPSHOT</version>
  <modules>
      <module>Payment8001</module>
  </modules>
  <name>cloudTest Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <dependencies>


  </dependencies>
  <build>
    <finalName>cloudTest</finalName>
  </build>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <junit.version>4.12</junit.version>
    <log4j.version>1.2.17</log4j.version>
    <lombok.version>1.16.18</lombok.version>
    <mysql.version>8.0.13</mysql.version>
    <druid.version>1.1.6</druid.version>
    <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
  </properties>

  <!-- 子模块继承之后,提供作用:锁定版本+ Fmodlue不用写groupId version -->
  <!--使用pom.xml中的dependencyManagement元素能让所有在子项目中引用一个依赖而不用显式的列出版本号。
Maven会沿着父子层次向上走,直到找到一个拥有dependencyManagement元素的项目,然后它就会使用这个
dependencyManagement元素中指定的版本号。
如果子项目中指定了版本号,那么会使用子项目中指定的jar版本。

-->
  <dependencyManagement>
    <dependencies>
      <!--spring boot 2.2.2-->
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>2.2.2.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <!--spring cloud Hoxton. SR1-->
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Hoxton.SR1</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <!--spring cloud alibaba 2.1.0.RELEASE-->
      <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-alibaba-dependencies</artifactId>
        <version>2.1.0.RELEASE</version>
        <type>pom</type>
        <scope> import</scope>
      </dependency>
      <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>${mysql.version}</version>
      </dependency>
      <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>${druid.version}</version>
      </dependency>

      <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>${mybatis.spring.boot.version}</version>
      </dependency>


    </dependencies>
  </dependencyManagement>
</project>

创建子模块

右键父工程,new->Module,直接下一步:
在这里插入图片描述
填写ArtifactId,一般为服务名加端口号:
在这里插入图片描述
finish:
在这里插入图片描述
子模块创建完成:
在这里插入图片描述
子模块创建完成后,父模块pom.xml自动设置好了他们间的关系:
在这里插入图片描述

子模块pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloudTest</artifactId>
        <groupId>com.test</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>Payment8001</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>

        <!--mysql-connector-. java-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--jdbc-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>


</project>

父工程已经指定各依赖的版本,不需再写版本号.

yml配置文件

使用Spring Boot做开发,需要写application配置文件和启动类
在这里插入图片描述
application.yml:

server :
  port: 8001

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

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

在这里插入图片描述

@SpringBootApplication
public class PaymentMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8001.class,args);
    }
}
创建数据库和表

在这里插入图片描述
在这里插入图片描述

编写实体类和DAO层

在这里插入图片描述
在这里插入图片描述

Payment实体类:

@Data
@AllArgsConstructor
@NoArgsConstructor          //使用lombok自动生成构造函数和get,set
public class Payment implements Serializable {
    private long id;
    private String serial;
}

写一个通用结果集来向前端返回数据

/**
 * 通用返回结果集
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {
    private Integer code;
    private String messeage;
    private T data;

    public CommonResult(int code,String messeage){
        this.code=code;
        this.messeage=messeage;
    }
}

dao:

@Mapper
public interface PaymentDao {
    public int intsert(Payment payment);    //返回插入后的id,用于判断是否插入成功
    public Payment getPaymentById(@Param("id") long id);
}

yml中配置的mybatis实体类包没用,需要配置Spring Boot vsf,这里就先写类全名吧.
在这里插入图片描述

<?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.test.springcloud.dao.PaymentDao">
    <insert id="intsert" parameterType="com.test.springcloud.entities.Payment" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO payment(serial) VALUES(#{serial})
    </insert>
    <select id="getPaymentById" resultType="com.test.springcloud.entities.Payment" parameterType="long">
        select * from payment where id = #{id}
    </select>
</mapper>
编写业务层和控制层

在这里插入图片描述

@Service
public class PaymentServiceImpl implements PaymentService {
    @Autowired
    private PaymentDao paymentDao;
    @Override
    public int add(Payment payment) {
        return paymentDao.intsert(payment);
    }

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

在这里插入图片描述

@RestController
@Slf4j
public class PaymentController {
    @Autowired
    private PaymentService paymentService;
    @PostMapping(value = "/payment/add")
    public CommonResult add(Payment payment){
        int result = paymentService.add(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 getById(@PathVariable("id") long id){
        Payment result = paymentService.getPaymentById(id);
        log.info("查询结果:"+result);
        if(result!=null){
            return new CommonResult(200,"查询成功",result);
        } else {
            return new CommonResult(444,"没有查到,ID:"+id,null);
        }
    }
}

热部署Devtools

项目启动后再修改代码,可以不需再重启项目就能让代码生效,会自动帮你重启.
pom.xml需引入的依赖:

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

IDEA设置自动编译:
在这里插入图片描述

按快捷键ctrl+shift+alt+/,选择registry,下面两项打√:
在这里插入图片描述
在这里插入图片描述
重启IDEA.

启动项目
添加代码:
在这里插入图片描述
代码生效:
在这里插入图片描述

服务间的调用

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

配置一个RestTemplate对象:
在这里插入图片描述

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

使用restTemplate.postForObject()和getForObject()分别发post,get请求,调用payment的微服务.

@RestController
public class PaymentController {
    private final String url="http://localhost:8001";
    @Autowired       //从容器获取对象
    private RestTemplate restTemplate;
    @RequestMapping("/customer/payment/add")
    public CommonResult addPayment(Payment payment){
        return restTemplate.postForObject(url+"/payment/add",payment,CommonResult.class);
    }

    @RequestMapping("/customer/payment/get/{id}")
    public CommonResult getPayment(@PathVariable("id") long id){
        return restTemplate.getForObject(url+"/payment/get/"+id,CommonResult.class);
    }
}

调用成功:
在这里插入图片描述

提取微服务间的重复使用的类

将子模块间重复出现的类放到commons-api模块下.
新建模块commons-api:
在这里插入图片描述
放入重复使用的类:
在这里插入图片描述

其他模块删除重复的类:
在这里插入图片描述
pom.xml中引入commons-api模块:

        <dependency>
            <groupId>com.test</groupId>
            <artifactId>commons-api</artifactId>
            <version>${project.version}</version>
        </dependency>

除了commons-api中定义的类会被引入,其pom中依赖也会被引入.

服务注册与发现

Spring Cloud默认服务与注册组件为Eureka,但是其已经停更了.目前比较流行的服务注册组件还有Zookeeper,Consul,Nacos等可以用来替换.

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

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

Eureka包含两个组件: Eureka Server和Eureka Client
Eureka Server提供服务注册服务
各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将 会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。
EurekaClient通过注册中心进行访问
是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、 使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这 个服务节点移除(默认90秒)

Eureka

创建Eureka服务端

新建子模块Eureka-server7001:
在这里插入图片描述
pom.xml添加eureka-server依赖:

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

这里不需要使用数据库,就不要加入mybatis,mysql-connector,druid等依赖了,否则启动时报错未配置数据源.

application.yml:

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

启动类:
在这里插入图片描述

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

加上@EnableEurekaServer将其设置为EurekaServer.
启动后访问:

在这里插入图片描述

注册服务

服务模块pom.xml引入Eureka client依赖:

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

application.yml配置Eureka:

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

使用@EnableEurekaClient注解:

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

启动后注册成功:
在这里插入图片描述

服务名称为yml在配置的Spring application name.

构建Eureka集群Server

上面只有一个Eureka Server,为了高可用性需做多个相同的Eureka Server,他们间,相互注册,相互守望.
为了模拟多台电脑,在C:\Windows\System32\drivers\etc中修改hosts:
在这里插入图片描述
用来的Eureka server修改yml的hostname和defaultZone:

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

新建一个相同的Eureka server模块:
在这里插入图片描述
加入pom.xml依赖.
写application.yml配置文件:

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

defaultZone如果有多个用","隔开

新建启动类并启动.
相互守望成功:
在这里插入图片描述
在这里插入图片描述

将微服务加入Eureka server集群

修改cloud-payment-service模块的yml,eureka service-url
defaultZone配置集群地址,用","隔开.

server :
  port: 8001

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

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

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/

启动后两个Eureka server都能看到注册的微服务:
在这里插入图片描述
在这里插入图片描述

微服务集群

做多个相同的微服务部署在不同服务器,Eureka server做负载均衡.
新建相同的payment模块:
在这里插入图片描述
修改端口,把它们注册进Eureka server集群.
它们的Spring application name要相同,否则Eureka server认为它们不是同一个微服务.
对它们的调用也是通过http://+Spring application name,调用者微服务也要向Eureka server注册.

此时cloud-payment-service有两个实例:
在这里插入图片描述

输出端口号方便查看调用的是哪个:

@RestController
@Slf4j
public class PaymentController {
    @Autowired
    private PaymentService paymentService;
    @Value("${server.port}")   //获取配置文件中的值
    private String port;
    @PostMapping(value = "/payment/add")
    public CommonResult add(@RequestBody Payment payment){
        int result = paymentService.add(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 getById(@PathVariable("id") long id){
        Payment result = paymentService.getPaymentById(id);
        log.info("查询结果:"+result);
        if(result!=null){
            return new CommonResult(200,"查询成功,port:"+port,result);
        } else {
            return new CommonResult(444,"没有查到,ID:"+id,null);
        }
    }
}
远程调用

用RestTemplate 远程调用,需要加@LoadBalanced注解开启负载均衡.

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

customer向Eureka server调用该微服务:
在这里插入图片描述

@RestController
public class PaymentController {
    private final String url="http://CLOUD-PAYMENT-SERVICE";
    @Autowired       //从容器获取对象
    private RestTemplate restTemplate;
    @RequestMapping("/customer/payment/add")
    public CommonResult addPayment(Payment payment){
        return restTemplate.postForObject(url+"/payment/add",payment,CommonResult.class);
    }

    @RequestMapping("/customer/payment/get/{id}")
    public CommonResult getPayment(@PathVariable("id") long id){
        return restTemplate.getForObject(url+"/payment/get/"+id,CommonResult.class);
    }
}

每次刷新,轮番调用8001和8002,完成了负载均衡:
在这里插入图片描述
在这里插入图片描述

显示微服务信息

设置实例名和显示ip信息.
application.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/
  instance:
    instance-id: payment8001    #实例id
    prefer-ip-address: true     #显示ip

显示了实例id和ip:
在这里插入图片描述

服务发现

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

使用DiscoveryClient对象:

@RestController
@Slf4j
public class PaymentController {
    @Autowired
    private DiscoveryClient discoveryClient;
	//....

    @RequestMapping("/payment/discovery")
    public Object discovery(){
        List<String> services=discoveryClient.getServices();    //获取Eureka server中的服务
        for(String element:services){
            log.info(element);
        }
        //根据服务名获取实例
        List<ServiceInstance> instances=discoveryClient.getInstances("cloud-payment-service");
        for(ServiceInstance instance:instances){
            log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
        }
        return discoveryClient;
    }
}

启动类使用@EnableDiscoveryClient注解:

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

输出了所有服务和cloud-payment-service的实例:
在这里插入图片描述

Eureka的自我保护

保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务。
如果在Eureka Server的首页看到以下这段提示,则说明Eureka进入了保护模式: .
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT.
RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST To BE SAFE

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

禁止自我保护:
Eureka server加上配置:enable-self-preservation: false

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

  server:
    enable-self-preservation: false

关闭成功:
在这里插入图片描述
设置剔除失效服务时间间隔为2s(默认为90s):

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

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

Zookeeper

Zookeeeper的使用和Eureka的使用差不多,也是将微服务的提供者和消费者注册进Zookeeper,消费者可以发现、调用提供者.

一般在linux中使用Zookeeper,不过和Tomcat一样也可以在windows中使用,安装Zookeeper:
在这里插入图片描述

关闭防火墙:
在这里插入图片描述
查看ip,用于微服务与其通信:
在这里插入图片描述

注册与发现服务

新建微服务模块payment8004:
在这里插入图片描述
pom.xml引入Zookeeper依赖:
在这里插入图片描述
application.yml配置,注册到Zookeeper,Zookeeper端口号为2181:
在这里插入图片描述

启动类,使用@EnableDiscoveryClient允许服务发现:
在这里插入图片描述

发现服务和Eureka一样,使用Spring cloud的DiscoveryClient对象.

启动Zookeeper:
在这里插入图片描述
Zookeeper客户端连接:
在这里插入图片描述

解决包冲突:
pom中Zookeeper依赖中自带的包可能和实际使用的Zookeeper版本不一样:
在这里插入图片描述
上面为3.5.3,我们的Zookeeper为3.4.9
在这里插入图片描述
为了正常运行,用exclusion排除自带的包,自己添加对应版本:
在这里插入图片描述
启动后成功注册:
在这里插入图片描述

调用注册的服务

调用者也注册进Zookeeper:
在这里插入图片描述

使用RestTemplate对象调用,调用url为服务名:
在这里插入图片描述

Zookeeper没有自我保护机制,一旦未收到某个服务的心跳就立即剔除该服务.

Consul

Consul是一 开源的分布式服务发现和配置管理系统,由HashiCorp公司用Go语言开发。
提供了微服务系统中的服务治理、配置中心、控制总线等功能。这些功能中的每一个都可以根据需要单独使用也可以一起使用以构建全方位的服务网格,总之Consul提供了一种完整的服务网格解决方案。
功能:

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

下载地址:https://www.consul.io/downloads.html
中文教程:https://www.springcloud.cc/spring-cloud-consul.html

下载完后解压得到consul.exe,右键打开cmd

启动consul

consul agent -dev
在这里插入图片描述
访问:
http://localhost:8500
在这里插入图片描述

注册服务

创建Payment8006模块:
在这里插入图片描述

pom.xml引入consul依赖:

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

application.yml中配置consul:

server :
  port: 8006

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

  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}

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

启动类:

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

注册成功:
在这里插入图片描述
绿色表示服务注册,如果出现红色表示服务异常.

远程调用注册的服务

和前面两个框架调用方法一样.
新建customer-order模块:
在这里插入图片描述
注册进consul:
在这里插入图片描述
使用RestTemplate 对象调用

@RestController
public class PaymentController {
    private final String url="http://CLOUD-PAYMENT-SERVICE";
    @Autowired   
    private RestTemplate restTemplate;

    @RequestMapping("/customer/payment/get/{id}")
    public CommonResult getPayment(@PathVariable("id") long id){
        return restTemplate.getForObject(url+"/payment/consul/get/"+id,CommonResult.class);
    }
}

成功调用:
在这里插入图片描述

三个框架的区别

在这里插入图片描述

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

在这里插入图片描述
最多只能同时较好的满足两个。
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,因此,根据CAP原理将NoSQL数据库分成了满足CA原则、满足CP原则和满足AP原则三大类:

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

CP-满足一致性, 分区容忍性的系统,通常性能不是特别高。
当网络分区出现后,为了保证一致性,就必须拒接请求,否则无法保证一致性
结论:违背了可用性A的要求,只满足一致性和分区容错,即CP

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

服务调用与负载均衡

组件有Ribbon,和Eureka一样是Netflix公司的产品,貌似也将停更.别的组件还有Feign(已停更),OpenFeign,Spring cloud loadbalancer.

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

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

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

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

Ribbon

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

Eureka client2中整合了ribbon:
在这里插入图片描述
Resttemplate对象:
主要有getForObject(),postForObject(),getForEntity(),postForEntity()来远程调用.
xxForObject()返回json串,xxForEntity()返回ResponseEntity对象.
在这里插入图片描述

post请求的调用:

@RequestMapping("/customer/payment/add")
    public CommonResult addPayment(Payment payment){
        return restTemplate.postForObject(url+"/payment/add",payment,CommonResult.class);
    }
自带的负载均衡规则

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

在这里插入图片描述

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

自定义配置类配置负载均衡规则替换默认的.

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

启动类注解@SpringBootApplication包含了@ComponentScan,所以应新建包.
在这里插入图片描述
写自定义规则:
在这里插入图片描述

@Configuration
public class LoadRule {
    @Bean
    public IRule myRule(){
        return new RandomRule();
    }
}

启动类中注解使用:
服务名用大写,因为Eureka server中显示的是大写(尽管配置服务名时用的是小写):
在这里插入图片描述
否则无效.

@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = LoadRule.class)
public class OrderMain9000 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain9000.class,args);
    }
}

随机访问实例:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

轮询算法原理

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

OpenFeign

官网:https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle/#spring-cloud-openfeign
源码:https://github.com/spring-cloud/spring-cloud-openfeign
Feign是一个声明式WebService客户端。 使用Feign能让编写Web Service客户端更加简单。
它的使用方法是定义一个服务接口然后在上面添加注解。Feign也支 持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡

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

Feign集成了Ribbon
利用Ribbon维护了Payment的服务列表信息,組通过轮询实现了客户端的负载均衡。而与Ribbon不同的是, 通过feign只需要定义
服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用
在这里插入图片描述

服务调用
新建子模块

新建openfeign-customer-order9001模块:
在这里插入图片描述

pom.xml引入openFeign依赖:

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

yml中将其注册进Eureka服务器.

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://eureka7001.com:7001/eureka/

启动类使用@EnableFeignClients注解:

@SpringBootApplication
@EnableFeignClients
public class OpenfeignOrderMain9001 {
    public static void main(String[] args) {
        SpringApplication.run(OpenfeignOrderMain9001.class,args);
    }
}
新建Service接口

新建要调用的服务的接口:
在这里插入图片描述

使用@FeignClient注解,value为要调用的服务名.@GetMapping写要调用的地址.
直接在Service层调用其他服务.

@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentService {
    @GetMapping("/payment/get/{id}")  //调用的地址
    public CommonResult<Payment> getPaymentById(@PathVariable("id") long id);
}
控制类

在这里插入图片描述
service接口不用写实现类,直接使用.
注解使用

@RestController
public class PaymentController {
    @Autowired
    private PaymentService paymentService;

    @RequestMapping("/customer/feign/payment/get/{id}")
    public CommonResult<Payment> getPayment(@PathVariable("id") long id){
        return paymentService.getPaymentById(id);
    }
}

在这里插入图片描述

超时控制

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

payment设置睡眠3s:
在这里插入图片描述

customer-order中调用:
在这里插入图片描述
在这里插入图片描述
重启并访问.
由于超过1s,结国抛出异常:
在这里插入图片描述

设置超时时间

如果业务确实超过1s,可以在yml中设置超时时间
在这里插入图片描述

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

再次访问调用成功:
在这里插入图片描述

服务降级与熔断,限流

Hystrix(停更),Sentinel

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

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

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

服务降级(fallback)

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

原因:

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

服务熔断(circuit break)

提出者论文:martinfowler.com/bliki/CircuitBreaker.html

直接拒绝访问,然后调用服务降级的方法并返回友好提示.

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

服务限流(follow limit)

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

Hystrix服务降级

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

官网:https://github.com/Netflix/Hystrix/wiki/How-To-Use

创建子模块

在这里插入图片描述
pom.xml引入hystrix依赖:

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

application.yml配置,注册进Eureka server:

server :
  port: 8001

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

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

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://eureka7001.com:7001/eureka/

启动类:

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

service接口和实现类:
一个正常,一个超时.
在这里插入图片描述

public interface PaymentService {
    public String payment_info_ok(Integer id);
    public String payment_info_timeout(Integer id);

}
@Service
public class PaymentServiceImpl implements PaymentService {

    @Override
    public String payment_info_ok(Integer id) {
        return "线程名:"+Thread.currentThread().getName()+"  payment_ok,id:"+id;
    }

    @Override
    public String payment_info_timeout(Integer id) {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "线程名:"+Thread.currentThread().getName()+"  payment_timeout,id:"+id;
    }
}

控制类:
在这里插入图片描述

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

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

    @GetMapping("/payment/hystrix/timeout/{id}")
    public String payment_info_timeout(@PathVariable("id") Integer id){
        String re=paymentService.payment_info_timeout(id);
        log.info("****result:"+re);
        return re;
    }
}
JMeter压测

对/payment/hystrix/timeout/发20000个请求:
在这里插入图片描述在这里插入图片描述

此时/payment/hystrix/ok/请求也变慢了:
系统在处理timeout请求.
在这里插入图片描述

调用者也变卡顿

新建调用者模块:
在这里插入图片描述

openFeign调用:
服务接口

@Component
@FeignClient(value = "CLOUD-HYSTRIX-PAYMENT-SERVICE")   //要调用的服务名
public interface PaymentService {
    @GetMapping("/payment/hystrix/ok/{id}")
    public String payment_info_ok(@PathVariable("id") Integer id);
    @GetMapping("/payment/hystrix/timeout/{id}")
    public String payment_info_timeout(@PathVariable("id") Integer id);
}

控制类:

@RestController
public class PaymentController {
    @Autowired
    private PaymentService paymentService;

    @GetMapping("/customer/payment/hystrix/ok/{id}")
    public String payment_info_ok(@PathVariable("id") Integer id){
        return paymentService.payment_info_ok(id);
    }

    @GetMapping("/customer/payment/hystrix/timeout/{id}")
    public String payment_info_timeout(@PathVariable("id") Integer id){
        return paymentService.payment_info_timeout(id);
    }
}

压测服务提供者:
在这里插入图片描述
调用者也变卡:
在这里插入图片描述

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

对服务进行降级

一旦调用服务方法失败(或超时)并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法.

在payment的service层用@HystrixCommand注解:

@Service
public class PaymentServiceImpl implements PaymentService {

    @Override
    public String payment_info_ok(Integer id) {
        return "线程名:"+Thread.currentThread().getName()+"  payment_ok,id:"+id;
    }

    @Override
    @HystrixCommand(fallbackMethod = "paymentInfo_timeoutHandler",commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value ="3000")
    })
    public String payment_info_timeout(Integer id) {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "线程名:"+Thread.currentThread().getName()+"  payment_timeout,id:"+id;
    }

    public String paymentInfo_timeoutHandler(Integer id){
        return "线程名:"+Thread.currentThread().getName()+"  paymentInfo_timeoutHandler,稍后再试,id:"+id;
    }
}

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

@HystrixProperty(name=“execution.isolation.thread.timeoutInMilliseconds”,value ="3000"设置payment_info_timeout最多执行3s,超过则执行paymentInfo_timeoutHandler,不再等待.

启动类使用@EnableCircuitBreaker注解:

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

由于sleep了5s,超过了3s,所以执行了paymentInfo_timeoutHandler:
在这里插入图片描述
如果程序抛出异常也将执行paymentInfo_timeoutHandler:

    @Override
    @HystrixCommand(fallbackMethod = "paymentInfo_timeoutHandler",commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value ="3000")
    })
    public String payment_info_timeout(Integer id) {
        int num=1/0;
//        try {
//            Thread.sleep(5000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        return "线程名:"+Thread.currentThread().getName()+"  payment_timeout,id:"+id;
    }

在这里插入图片描述

服务调用者降级

上面对服务提供者降级,一般对服务调用者降级.
这里用OpenFeign调用,service层只是接口没有实现类,所以在控制层做服务降级.
启动类加@EnableCircuitBreaker注解.
服务提供者需要3s,这里设置等待1.5s.

@RestController
public class PaymentController {
    @Autowired
    private PaymentService paymentService;

    @GetMapping("/customer/payment/hystrix/ok/{id}")
    public String payment_info_ok(@PathVariable("id") Integer id){
        return paymentService.payment_info_ok(id);
    }

    @GetMapping("/customer/payment/hystrix/timeout/{id}")
    @HystrixCommand(fallbackMethod = "paymentInfo_timeoutHandler",commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value ="1500")
    })
    public String payment_info_timeout(@PathVariable("id") Integer id){
        return paymentService.payment_info_timeout(id);
    }

    public String paymentInfo_timeoutHandler(Integer id){
        return "线程名:"+Thread.currentThread().getName()+"  paymentInfo_timeoutHandler,payment系统繁忙,稍后再试,id:"+id;
    }
}

在这里插入图片描述

全局fallback method

如果每个服务都要写一个fallback method,那太麻烦了,可以使用默认的fallback method.

使用@DefaultProperties(defaultFallback = “paymentInfo_timeoutHandler”)注解,@HystrixCommand还是要的,不加表示该服务不降级:

@RestController
@DefaultProperties(defaultFallback = "global_fallbackMethod")
public class PaymentController {
    @Autowired
    private PaymentService paymentService;

    @GetMapping("/customer/payment/hystrix/ok/{id}")
    public String payment_info_ok(@PathVariable("id") Integer id){
        return paymentService.payment_info_ok(id);
    }

    @GetMapping("/customer/payment/hystrix/timeout/{id}")
//    @HystrixCommand(fallbackMethod = "paymentInfo_timeoutHandler",commandProperties = {
//            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value ="1500")
//    })
    @HystrixCommand(commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value ="1500")
    })
    public String payment_info_timeout(@PathVariable("id") Integer id){
        return paymentService.payment_info_timeout(id);
    }

    public String paymentInfo_timeoutHandler(Integer id){
        return "线程名:"+Thread.currentThread().getName()+"  paymentInfo_timeoutHandler,payment系统繁忙,稍后再试,id:"+id;
    }

    public String global_fallbackMethod(){
        return "one service has fallbacked";
    }
}

由于是全局的fallback方法,不能加参数.

在这里插入图片描述

服务提供者宕机的fallback

对于集群服务来说,即使一个节点宕机还有其他节点,所有节点都宕机的可能性不大.但有一种情况是服务访问量过高,所有节点的响应时间较长.

这里的服务调用框架为openFeign

实现service接口:
在这里插入图片描述
方法中对要调用的方法不可用时进行处理:

@Component
public class PaymentFallbackService implements PaymentService{

    @Override
    public String payment_info_ok(Integer id) {
        return "payment ok service 暂不可用";
    }

    @Override
    public String payment_info_timeout(Integer id) {
        return "payment timeout service 暂不可用";
    }
}

yml中feign开启hystrix:

#用于服务降級在炷@FeignCl ient中添加fallbackFactory属性值
feign:
  hystrix:
    enabled: true   #在Feign中开启Hystrix

@FeignClient中指定PaymentFallbackService来做fallback处理:

@Component
@FeignClient(value = "CLOUD-HYSTRIX-PAYMENT-SERVICE",fallback = PaymentFallbackService.class)   //要调用的服务名
public interface PaymentService {
    @GetMapping("/payment/hystrix/ok/{id}")
    public String payment_info_ok(@PathVariable("id") Integer id);
    @GetMapping("/payment/hystrix/timeout/{id}")
    public String payment_info_timeout(@PathVariable("id") Integer id);
}

正常访问:
在这里插入图片描述

关闭服务提供者:
在这里插入图片描述
在这里插入图片描述
重新开启并调用timeout:
在这里插入图片描述
由于设置等待时长最多1.5s,而提供者需要3s(模拟较繁忙),所以提示暂不可用.

Hystrix服务熔断

在Spring Cloud框架里,熔断机制通过Hystrix实现。 Hystrix会监控微服务间调用的状况,
当失败的调用到一定阈值,缺省是10秒内20次请求,50%的失败率,就会启动熔断机制。熔断机制的注解是@HystrixCommand.

熔断类型:

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

service层使用熔断:
在这里插入图片描述

熔断前会先降级,paymentCircuitBreaker_fallback为fallback方法.
这里id<0则抛出异常,模拟实际中可能会出现的异常

  //====服务熔断
    @Override
    @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"),// 失败率达到多少后跳闸
    })  //在10s内请求10次失败率为60%则熔断
    public String paymentCircuitBreaker(@PathVariable("id") Integer id){
        if(id < 0)
            throw new RuntimeException("******id不能负数");
        String serialNumber = UUID.randomUUID().toString();
        return Thread. currentThread(). getName()+"\t"+"调用成功,流水号: " + serialNumber;
    }

    public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
        return "id不能负数,请稍后再试,/(ToT)/~~ id: " +id;
    }

控制层调用:

    //服务熔断
    @GetMapping("/payment/circuit/{id}")
    public String paymentCircuitBreak(@PathVariable("id") Integer id){
        String re=paymentService.paymentCircuitBreaker(id);
        log.info("****result:"+re);
        return re;
    }

正确访问:
在这里插入图片描述
10s内10次错误无法:
在这里插入图片描述
已熔断,正确参数也不能访问:
在这里插入图片描述
再正确访问几次,熔断恢复:
在这里插入图片描述

Hystrix图形化监控工具

hystrix-dashboard可以监控各个微服务的情况.
新建模块:
在这里插入图片描述

pom.xml依赖:

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

spring-boot-starter-actuator依赖也是必要的,Spring的监控框架.

yml配置:
只有配一个端口.

server:
  port: 9001

启动类:
在这里插入图片描述
使用@EnableHystrixDashboard注解:

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

访问:
在这里插入图片描述

要监控的服务:

在这里插入图片描述

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

    /**
     *此配置是为了服务监控而配置,与服务 容错本身无关,springcloud升级后的坑
     *ServletRegistrat ionBean因为springboot的默认路径不是"/bystrix. 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;
    }
}

输入要监控服务的地址+hystrix.stream:
在这里插入图片描述
点击按钮进入监控:
在这里插入图片描述
分别正确访问和错误访问:
在这里插入图片描述
closeed表示熔断器未打开.

在这里插入图片描述
open表示熔断器已打开.

服务网关

Zuul,gateway

Spring Cloud Gateway

官网:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/

功能:

  • 反向代理
  • 鉴权
  • 流量控制
  • 熔断
  • 日志监控

Spring Cloud Gateway具有如下特性:

  • 基于Spring Framework 5, Project Reactor和Spring Boot2.0进行构建;
  • 动态路由:能够匹配任何请求属性;
  • 可以对路由指定Predicate (断言)和Filter (过滤器) ;
  • 集成Hystrix的断路器功能;
  • 集成Spring Cloud服务发现功能;
  • 易于编写的Predicate (断言)和Filter (过滤器) ;
  • 请求限流功能;
  • 支持路径重写。

Spring Cloud Gateway与Zuul的区别
在SpringCloud Finchley正式版之前, Spring Cloud推荐的网关是Netlix提供的Zuul:
1、 Zuul1.x, 是一个基于阻塞|/ 0的API Gateway
2、Zuul 1.x基于Servlet 2.5使用阻寒架构它不支持任何长连接(如WebSocket) Zuul的设计模式和Nginx较像,每次I/O操作都是从
工作线程中选择-个执行, 请求线程被阻塞到工作线程完成,但是差别是Nginx用C++实现,Zuul 用Java实现,而JVM本身会有第一次加载较慢的情况,使得Zuul的性能相对较差。
3、Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。Zuul 2.x的性能较Zuul 1.x有较大提升
。在性能方面,根据官方提供的基准测试, Spring Cloud Gateway的RPS (每秒请求数)是Zuul的1. 6倍。
4、Spring Cloud Gateway建立在Spring Framework 5、Project Reactor和Spring Boot2之上,使用非阻塞API。
5、Spring Cloud Gateway还支持WebSocket,并且与Spring紧密集成拥有更好的开发体验

servlet生命周期:
container启动时构造servlet对象并调用servlet init()进行初始化;
container运行时接受请求,并为每个请求分配一个线程(一般从线程池中获取空闲线程)然后调用service()。
container关闭时调用servlet destory()销毁servlet;

上述模式的缺点:
servlet是一个简单的网络IO模型,当请求进入servlet container时,servlet container就会为其绑定一个线程, 在并发不高的场景下这种模型是适用的。但是一旦高并发(如抽风用jemeter压),线程数量就会上涨,而线程资源代价是昂贵的(上线文切换,内存消耗大)严重影响请求的处理时间。
在一些简单业务场景下,不希望为每个request分配一个线程, 只需要1个或几个线程就能应对极大并发的请求,这种业务场景下servlet模型没有优势

所以Zuul 1.X是基于servlet之上的一个阻塞式处理模型,即spring实现了处理所有request请求的一 个servlet (DispatcherServlet) 并由该servlet阻塞式处理。所以Springcloud Zuul无法摆脱servlet模型的弊端

传统的Web框架,比如说: struts2, springmvc等都是基于Servlet API与Servlet容器基础之上运行的。
但是在Servlet3.1之后有了异步非阻塞的支持。而WebFlux是一 个典型非阻塞异步的框架,它的核心是基于Reactor的相关API实现的。相对于传统的web框架来说,它可以运行在诸如Netty, Undertow及支持Servlet3.1的容器上。非阻塞式+函数式编程(Spring5必须让你使
用java8)Spring WebFlux是Spring 5.0引入的新的响应式框架,区别于Spring MVC,它不需要依赖Servlet API,它是完全异步非阻塞的,并且基于Reactor来实现响应式流规范。

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

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

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

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

新建子模块

在这里插入图片描述
pom.xml中引入gateway依赖:

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

由于需要向Eureka server注册,所以还需引入Eureka client.不要引入spring boot starter web,否则启动报错.

gateway使用netty:
在这里插入图片描述

yml配置微服务地址:

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 #ayment_ route   #路由的ID,没有固定规则但要求唯一建议配合服务名
          uri: http://localhost:8001  #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/port #断言,路径相匹配的进行路由

8001提供的服务:
在这里插入图片描述
在这里插入图片描述

启动类:

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

访问:
在这里插入图片描述
在这里插入图片描述

路由配置

有两种方法,第一种在yml中配置,如上.
第二种为在代码中配置,
新建配置类:
在这里插入图片描述

@Configuration
public class GatewayConfig {
    @Bean
    public RouteLocator myRouteLocater(RouteLocatorBuilder routeLocatorBuilder){
        RouteLocatorBuilder.Builder routes=routeLocatorBuilder.routes();
        routes.route("payment_timeout",
                r->r.path("/guonei").uri("http://news.baidu.com/guonei")).build();
        return routes.build();
    }
}
动态路由

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

开启服务发现:

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      #lb表示load balance
          predicates:
            - Path=/payment/get/**   #断言,路径相匹配的进行路由

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

实现了网关的负载均衡:
在这里插入图片描述
在这里插入图片描述

常用predicates配置:

After设置地址在某个时间之后生效,Before,Between用法类似.
Cookie设置必需要的Cookie名和值.如果访问没有该Cookie则访问失败.
Header设置必要的请求头信息.
Host设置访问的主机名,不匹配则拒绝访问.
Method设置请求的方式
Query=username, \d+ 要有参数名username开且值还要是整数才能访问

spring:
  cloud:
    gateway:
      routes:
        - id: payment_ routh #payment_ route #路由的ID,设有固定规则但要求唯一, 建议配合服务名
          uri: lb://CLOUD-PAYMENT-SERVICE      #lb表示load balance
          predicates:
            - Path=/payment/get/**   #断言,路径相匹配的进行路由
			- After=2020-02-21T15:51:37.485+08:00[Asia/Shanghai]
			#- Between=2020-02-02T17:45:06.206+08:00[Asia/Shanghai],2020-03-25T18:59:06.206+08:00[Asia/Shanghai]
			#- Cookie=name,value
			#- Header=X-Request-Id,\d+ #请求头要有X-Request-Id属性并且值为整数的正则表达式
			#- Host=**.xxxx.com
			#- Method=GET
			#- Query=username, \d+	#要有参数名username开且值还要是整数才能访问




Filter

有单一过滤Gateway Filter,全局过滤GlobalFilter.

Gateway Filter:

spring:
  cloud:
    gateway:
      routes:
        - id: payment_ routh #payment_ route #路由的ID,设有固定规则但要求唯一, 建议配合服务名
          uri: lb://CLOUD-PAYMENT-SERVICE      #lb表示load balance
          predicates:
            - Path=/payment/get/**   #断言,路径相匹配的进行路由
		  filters:
			- AddRequestParameter=X-Request-Id,1024 #过糖器工厂会在匹配的请求头加上一对请求头,名称为X-Request-Id值为1024

自定义Filter:
在这里插入图片描述

@Component
@Slf4j
public class MyFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("*****come in gateway filter");
        String uname=exchange.getRequest().getQueryParams().getFirst("uname");
        if(uname==null){
            log.info("*****用户名为null");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);  //交给下一个过滤器
    }

    @Override
    public int getOrder() {     //设置为第一个过滤器
        return 0;
    }
}

不带uname参数拒绝访问:
在这里插入图片描述
加上参数:
在这里插入图片描述

服务配置

Config,Nacos

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

Spring Cloud Config

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

SpringCloud Config分为服务端和客户端两部分。
服务端也称为分布式配置中心,它是一个独立的微服务应用, 用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口.

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

功能:

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

在github中创建配置文件:
在这里插入图片描述
内容(先随便写点):

config:
	info: "master branch,springcloud-config/config-dev.yml version=1"

新建模块:
在这里插入图片描述

pom.xml中引入config server依赖:

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

yml配置github仓库地址:

server :
  port: 3344

spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        git:
          uri: https://github.com/Tyleryq/springcloud-config.git #GitHub上面的git仓库名字
          ###搜索目录
          search-paths:
            - springcloud-config
      ###读取分支
      label: master


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

启动类使用@EnableConfigServer注解:

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

访问获取配置信息:
在这里插入图片描述

config客户端

新建模块:
在这里插入图片描述

pom引入客户端依赖:

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

由于github上yml修改客户端要刷新,还需引入spring-boot-starter-actuator监控依赖.

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

server :
  port: 3355

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


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

控制类读取github上的配置并显示:

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

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

访问:
在这里插入图片描述

但是如果github上进行修改,客户端更新比较麻烦.可以使用Spring Cloud Bus消息总线动态刷新.
可以在github上的yml配置mysql,Redis等.

服务总线

Bus,Nacos

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

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

Spring Cloud Bus

Spring Cloud Bus是用来将分布式系统的节点与轻量级消息系统链接起来的框架,它整合了Java的事件处理机制和消息中间件的功能。
Spring Cloud Bus能管理和传播分布式系统间的消息,就像一个分布式执行器, 可用于广播状态更改、事件推送等,也可以当作微服务间的通信通道

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

erlang和rabbitmq安装与配置

erlang下载:
https://www.erlang-solutions.com/resources/download.html
这个地址下载比较快,官网下载较慢.

在这里插入图片描述
下载完后直接双击,等待安装完成.

在系统环境变量中添加path,直接添加bin路径:
E:\Program Files\erl10.3\bin;

RabbitMQ的下载:
https://dl.bintray.com/rabbitmq/all/rabbitmq-server/

我下的是3.7.14解压版:rabbitmq-server-windows-3.7.14.

将其sbin加入环境变量:
E:\rabbitmq-server-windows-3.7.14\sbin

安装插件,输入rabbitmq-plugins.bat enable rabbitmq_management:
在这里插入图片描述

启动,输入rabbitmq-server.bat或直接双击:
在这里插入图片描述
输入http://localhost:15672/访问:
在这里插入图片描述
输入账号密码:guest,guest登录:
在这里插入图片描述

RabbitMQ实现配置动态刷新

两种通知方式:
1)利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置口
2)利用消息总线触发一个服务端ConfigServer的/bus/refresh端点,而刷新所有客户端的配置

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

新建模块:
在这里插入图片描述

pom.xml导入依赖:

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

还需spring-boot-starter-actuator依赖

bootstrap.yml配置:

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

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

##rabbitmq相关配置,暴露bus刷新配置的端点
management:
  endpoints: #暴露bus辟新配置的端点
    web:
      exposure:
        include: "*"

控制层使用@RefreshScope配置,使用此注解,post访问http://localhost:3344/actuator/bus-refresh将刷新:

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

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

config服务端也做相同的依赖引入和配置
在这里插入图片描述
暴露的刷新地址为bus-refresh

spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        git:
          uri: https://github.com/Tyleryq/springcloud-config.git #GitHub上面的git仓库名字
          ###搜索目录
          search-paths:
            - springcloud-config
      ###读取分支
      label: master

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

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

服务端和客户端访问github配置:
在这里插入图片描述
在这里插入图片描述
github上修改配置并保存:
在这里插入图片描述

发送post请求进行配置服务器刷新:
在这里插入图片描述
这里只能用curl发,用jmeter发会返回415错误,不知道为什么,postman好像也可以.

客户端无需重启就能获取新的配置信息:
在这里插入图片描述

定点通知

在这里插入图片描述
发送请求(post)的url为:配置服务器地址:端口/actuator/bus-refresh/需通知的微服务名:端口

Spring Cloud Stream

Spring Cloud Stream是一个构建消息驱动微服务的框架。

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

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

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

Stream中的消息通信方式遵循了发布-订阅模式
通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。

官方源码:
https://github.com/spring-cloud/spring-cloud-stream
官网:https://spring.io/projects/spring-cloud-stream
中文指导手册:https://m.wang1314.com/doc/webapp/topic/20971999.html

常用注解

@Input
注解标识输入通道,通过该输入通道接收到的消息进入应用程序
@Output
注解标识输出通道,发布的消息将通过该通道离开应用程序
@StreamListener
监听队列,用于消费者的队列的消息接收
@EnableBinding
指信道channeI和exchange绑定在一-起

新建消息提供者模块

在这里插入图片描述
pom.xml引入stream rabbit依赖:

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

yml配置:

server :
  port: 8801

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

eureka:
  client:
    #表示是否将自己注册进Eurekaserver默认为true。
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负裁均衡
    fetchRegistry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/
  instance:
    lease-renewal-interval-in-seconds: 2 #设置心跳的时间问隔是30秒)
    lease-expiration-duration-in-seconds: 5 #如果现在超过5秒的间隔失效(默以是90秒)
    instance-id: send-8801.com #在信息列表时显示主机名称
    prefer-ip-address: true #访间的路径变为IP地址

发消息的接口:
在这里插入图片描述

不再使用@Service注解.@EnableBinding(Source.class) 设置其为信息发送方.

@EnableBinding(Source.class)     //定义信息推送管道
public class MessageProviderImpl implements IMessageProvider {
    @Autowired
    private MessageChannel output;
    @Override
    public String send() {
        String serial= UUID.randomUUID().toString();
        output.send(MessageBuilder.withPayload(serial).build());
        return serial;
    }
}

控制层使用该接口发送消息:

@RestController
public class SendController {
    @Autowired
    private IMessageProvider messageProvider;

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

rabbitmq可以看到自己配置的study exchange:
在这里插入图片描述
发送信息:
在这里插入图片描述

显示出波峰,发送成功:
在这里插入图片描述

新建消息消费模块

在这里插入图片描述
pom.xml引入stream rabbit依赖:

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

yml配置,和消息发送方类似,把output改为input:

server :
  port: 8802

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

eureka:
  client:
    #表示是否将自己注册进Eurekaserver默认为true。
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负裁均衡
    fetchRegistry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/
  instance:
    lease-renewal-interval-in-seconds: 2 #设置心跳的时间问隔是30秒)
    lease-expiration-duration-in-seconds: 5 #如果现在超过5秒的间隔失效(默以是90秒)
    instance-id: send-8801.com #在信息列表时显示主机名称
    prefer-ip-address: true #访间的路径变为IP地址

新建消息接收类:
在这里插入图片描述
@EnableBinding(Sink.class) 设置为消息接收者
@StreamListener(Sink.INPUT) 进行消息监听

@Component
@EnableBinding(Sink.class)  //设置为消息接收者
public class ReceiveMessage {
    @StreamListener(Sink.INPUT) //进行消息监听
    public void input(Message<String> message){
        System.out.println("消息消费者收到:"+message.getPayload());
    }
}

发送方发送信息:
在这里插入图片描述

收到消息:
在这里插入图片描述

消息分组
消息重复消费

比如在如下场景中,订单系统我们做集群部署,都会从RabbitMQ中获取订单信息,那如果一个订单同时被两个服务获取到,那么就会造成数据错误,我们得避免这种情况。这时我们就可以使用Stream中的消息分组来解决

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

新建相同的消息消费模块:
在这里插入图片描述

发送消息:
在这里插入图片描述
两个服务都收到消息:
在这里插入图片描述
在这里插入图片描述

消息消费者配置为同一组:
两个消息消费者yml中使用group:

      bindings: #服务的整合处理
        input: #这个名字是- - 个通道的名称
          destination: studyExchange #表示要使用的Exchange名称定义
          content-type: application/json #设置消息类型,本次为json, 文本则设置"“text/plain"
          binder: defaultRabbit #设置要绑定的消息服务的具体设置
          group: groupA

此时只有一个groupA:
在这里插入图片描述

发送消息:
在这里插入图片描述
只有一个服务实例接收消息:
在这里插入图片描述
在这里插入图片描述

消息持久化

使用了group后,不仅能分组,还能消息持久化.如果消息发送方发送了消息,而消息接收方宕机了,如果不使用group重启后将错过消息,使用group后重启能接收到错过的信息.

分布式请求链路跟踪

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

Spring Cloud Sleuth

官方源代码:https://github.com/spring-cloud/spring-cloud-sleuth

SpringCloud从F版起已不需要自己构建Zipkin Server了,只需调用jar包即可

Zipkin

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

在这里插入图片描述

下载地址:https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/
下载zipkin-server-2.12.9-exec.jar
在这里插入图片描述

启动:
在这里插入图片描述

访问界面:http://localhost:9411
在这里插入图片描述

服务提供者payment8001加入zipkin:
pom引入zipkin:

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

yml中配置zipkin:

spring:
  application:
    name: cloud-payment-service

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

服务调用者模块customer-order9000也做同样引入.

在这里插入图片描述

访问服务调用者:
在这里插入图片描述
zipkin跟踪到了调用情况:
在这里插入图片描述
在这里插入图片描述
点依赖可以查看服务依赖情况:
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值