SpringCloud-2020版学习笔记

  • Post author:
  • Post category:其他




SpringCloud



什么是微服务架构?

1、微服务架构是一种架构模式,它提倡将单一的应用程序划分成一组小的服务,服务之间相互协调,互相配合,为用户提供最终价值。

2、每个服务使用一个单独运行的进程,服务之间采用轻量级的通信机制相互协作(常用:基于HTTP协议的RESTFUL API)

3、服务的管理应当避免采用统一的、集中式的服务管理机制



SpringCloud



什么是SpringCloud?

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

springcloud架构图:

在这里插入图片描述

当前环境版本:

在这里插入图片描述

当前版本升级和维护情况:

在这里插入图片描述


约定大于配置大于编码

依赖包Management与dependencies的区别:

在这里插入图片描述

1、management与dependencies区别

management:管理版本依赖:常存在于父类工程或者项目顶层pom中

只负责定义不做引用,子类项目才会进行引用



SpringCloud 项目环境搭建

​ ·Rundashboard:其他版本IDEA中未出现这个tools时在项目中的.idea下的workspace文件中插入一下代码

<option name="configurationTypes">
	<set>
  	<option value="SpringBootApplicationConfigurationType"/>
  </set>
</option>


一、父类项目

数据库:搭建payment数据库并根据需要创建表

父类项目pom文件:统一管理子项目中的依赖 版本

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

    <groupId>com.wzs.springcloud</groupId>
    <artifactId>SpringCloud</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <modules>
        <module>cloud-provider-pay-8001</module>
        <module>Spring-Cloud-Api</module>
        <module>cloud-consumer-order-80</module>
    </modules>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <junit.version>4.12</junit.version>
        <log4j.version>1.2.17</log4j.version>
        <lombok.version>1.18.24</lombok.version>
        <mysql.version>8.0.18</mysql.version>
        <druid.verison>1.1.16</druid.verison>
        <mybatis.spring.boot.verison>1.3.0</mybatis.spring.boot.verison>
    </properties>

    <!--dependencyManagement :父类定义好后,子类可直接继承-->
    <!--子模块继承之后,提供作用:锁定版本+子module,不用写groupId和version-->
    <dependencyManagement>
        <dependencies>
            <!--前三个 是标配 -->
            <!--spring boot 2.2.2-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.2.2.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--spring cloud Hoxton.SR1-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--spring cloud alibaba 2.1.0.RELEASE-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.1.0.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>


            <!-- MySql -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.30</version>
<!--                <version>${mysql.version}</version>-->
            </dependency>
            <!-- Druid -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>${druid.verison}</version>
            </dependency>
            <!-- mybatis-springboot整合 -->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>${mybatis.spring.boot.verison}</version>
            </dependency>
            <!--lombok-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
            </dependency>
            <!--junit-->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>${junit.version}</version>
            </dependency>
            <!-- log4j -->
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>${log4j.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <fork>true</fork>
                    <addResources>true</addResources>
                </configuration>
            </plugin>
        </plugins>

    </build>

</project>


Model:


cloud-data-api:

​ pom文件

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

    <artifactId>Spring-Cloud-Api</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>


</project>

​ 实体类:

//Bean

package com.wzs.springcloud.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.io.Serializable;


@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class PayMent implements Serializable {
    private long id;
    private String serial;
    private String dataSource;
    public PayMent(String serial,String dataSource){
        this.serial=serial;
        this.dataSource=dataSource;
    }
}


//封装返回信息实体
package com.wzs.springcloud.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class CommentResult<T> implements Serializable {
    private int Code;
    private String message;
    private T data;
    public CommentResult(int code,String message){
        this.Code=code;
        this.message=message;
    }
}



Server-provider:

​ pom文件

<dependencies>
        <dependency>
            <groupId>com.wzs.springcloud</groupId>
            <artifactId>Spring-Cloud-Api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <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>

配置文件application.yml

server:
  port: 8001

spring:
  application:
    name: payment-server-8001
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/payment?useUnicode=true&characterEncoding=UTF-8&useSSL=true
    username: root
    password: wuzeshun425
logging:
  level:
    root: info
    com:
      wzs: debug
mybatis:
  type-aliases-package: com.wzs.springcloud.pojo
  mapper-locations: mapper/*.xml


业务类

//dao/mapper
package com.wzs.springcloud.dao;

import com.wzs.springcloud.pojo.PayMent;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

@Mapper
public interface PayMapper {
    public PayMent queryById(@Param("id") long id);

    public List<PayMent> queryAll();

    public int create(PayMent payment);
}

//service
package com.wzs.springcloud.server;

import com.wzs.springcloud.pojo.PayMent;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface PayServer {
    public PayMent queryById(long id);

    public List<PayMent> queryAll();

    public int create(PayMent payment);
}

//serviceImpl
package com.wzs.springcloud.server.impl;

import com.wzs.springcloud.dao.PayMapper;
import com.wzs.springcloud.pojo.PayMent;
import com.wzs.springcloud.server.PayServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
@Service
public class PayServerImpl implements PayServer {
    @Autowired
    private PayMapper payMapper;
    @Override
    public PayMent queryById(long id) {
        return payMapper.queryById(id);
    }

    @Override
    public List<PayMent> queryAll() {
        return payMapper.queryAll();
    }

    @Override
    public int create(PayMent payment) {
        return payMapper.create(payment);
    }
}
//controller
package com.wzs.springcloud.controller;

import com.wzs.springcloud.pojo.CommentResult;
import com.wzs.springcloud.pojo.PayMent;
import com.wzs.springcloud.server.PayServer;
import com.wzs.springcloud.server.impl.PayServerImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@Slf4j
public class PayController {
    @Autowired
    private PayServerImpl payserver;
    @GetMapping("/get/{id}")
    public CommentResult getPayMent(@PathVariable("id")long id){
        log.info("into select byid");
        PayMent payment= payserver.queryById(id);
        if(!StringUtils.isEmpty(payment)){
            log.info("select success");
           return  new CommentResult<>(200,"支付状态信息为:",payment);
        }
        else {
            log.info("select failed");
            return new CommentResult<>(404,"not find source",id);
        }

    }

    @GetMapping("/get/list")
    public CommentResult getPayMentAll(){
        log.info("into select allpayment");
        List<PayMent> payMentList = payserver.queryAll();
        if(!StringUtils.isEmpty(payMentList)){
            log.info(" select allpayment success");
            return  new CommentResult<>(200,"支付状态信息为:",payMentList);
        }
        else {
            log.info(" select allpayment failed");
            return new CommentResult<>(404,"not find source","noinfo");
        }

    }

    @PostMapping("/create")
    public CommentResult CreatePayMent( @RequestBody PayMent payMent){
        log.info("into create payment");
        int result= payserver.create(payMent);
        if(result>0){
            log.info("create payment success");
            return  new CommentResult<>(200,"添加成功:",result);
        }
        else {
            log.info(" create payment failed");
            return new CommentResult<>(404,"insert failed",result);
        }

    }

}

Mapper文件:

<?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.wzs.springcloud.dao.PayMapper">

    <resultMap id="resMap" type="Payment">
        <result column="id" property="id"/>
        <result column="serial" property="serial" />
        <result column="serial" property="dataSource"/>
    </resultMap>
    <select id="queryById" resultType="PayMent" parameterType="long">
        select * from paystatue where id=#{id};
    </select>

    <select id="queryAll" resultMap="resMap">
        select id,serial,data_source from paystatue
    </select>

    <insert id="create" parameterType="PayMent" useGeneratedKeys="true" keyProperty="id">
        insert into dept(serial,data_source)values (#{serial},#{data_source});
    </insert>
</mapper>

启动类:

package com.wzs.springcloud;

import com.wzs.springcloud.pojo.PayMent;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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



server-consumer

pom文件

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

    <artifactId>cloud-consumer-order-80</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.wzs.springcloud</groupId>
            <artifactId>Spring-Cloud-Api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

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

</project>

配置文件:application.yml

server:
  port: 80

业务类

//具体业务类
package com.wzs.springcloud.controller;

import com.wzs.springcloud.pojo.CommentResult;
import com.wzs.springcloud.pojo.PayMent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;


@RestController
@Slf4j
public class OrderConsumerController {
    @Autowired
    private RestTemplate restTemplate;

    private static final String HTTP_URL="http://localhost:8001/";

    @GetMapping("/consumer/get/{id}")
    public CommentResult getById(@PathVariable("id") long id){
        log.info("into consumer selectOne");
        return restTemplate.getForObject(HTTP_URL+"get/"+id, CommentResult.class);
    }

    @GetMapping("/consumer/get/list")
    public CommentResult getAll(){
        log.info("into consumer selectAll");
        return restTemplate.getForObject(HTTP_URL+"get/list", CommentResult.class);
    }
    @GetMapping("/consumer/create")
    public CommentResult createPayment(){
        log.info("into consumer create");
        return restTemplate.getForObject(HTTP_URL+"create", CommentResult.class);
    }
}
//RestTemplate配置类
package com.wzs.springcloud.config;

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

@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate getRestemplate(){
        return new RestTemplate();
    }
}

启动类

package com.wzs.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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



服务注册与发现



Eureka



什么是服务治理

SpringCloud 封装NetFlix公司开发的Eureka模块来实现

服务治理

传统的RPC远程调用框架,管理每个服务与服务之间的依赖关系比较复杂,需要使用

服务治理

来简化管理服务之间的依赖关系,实现服务调用、负载均衡、容错,服务注册与发现等



什么是服务注册

Dubbo与Eureka框架对比

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-64f1aReO-1662615655936)(C:\Users\WZS\AppData\Roaming\Typora\typora-user-images\image-20220820212552920.png)]

Eureka

采用C/S架构



Eureka server 提供服务注册功能,是服务注册中心

,在服务注册表中存放服务提供者所提供的服务信息;

服务提供者通过Eureka client将自己的服务信息注册到Eureka Server,并维持心跳,来告知服务注册中心自己的服务状态是健康的;

服务消费者,通过Eureka Server服务注册中心,获取到具体服务的信息(服务通信地址),以此来完成自己的业务,即远程服务调用

维护人员可以通过Eureka server 来查看服务是否存可用

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

pom文件

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

    <artifactId>SpringCloud-Eureka-7001</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>

</project>

配置文件:application.yml

server:
  port: 7001

eureka:
  instance:
    # Eureka服务端的实例名字
    hostname: eureka701.com
  client:

    # 表示是否向 Eureka 注册中心注册自己(这个模块本身是服务器,所以不需要)
    register-with-eureka: false
    # fetch-registry如果为false,则表示自己为注册中心,客户端的化为 ture
    fetch-registry: false
    # Eureka监控页面~
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka



业务类

启动类

package com.wzs.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer //开启Eureka功能
public class Eurreka7001 {
    public static void main(String[] args) {
        SpringApplication.run(Eurreka7001.class,args);
    }
}



自我保护机制

·说白了就是在某一时刻服务注册中心很久没有接收到某个服务的心跳时,不会直接将该服务注销(剔除出去),而是保留该服务的信息,等待其恢复正常后,就会退出自我保护机制

·场景:由于网络或者其他原因造成服务暂时与服务注册中心断连,但该服务本身是健康的,因此不应该直接将其从服务中心干掉

·属于AP原则

·禁止保护机制【不建议做】:服务注册中心的yml中添加以下配置

#关闭自我保护机制
server-self-preservation: false
#时间间隔2s,就是说超过2s未接收到某个服务的心跳包,就会剔除该服务
eviction-interval-timer-in-ms: 2000

·provider的yml更改

#server 向注册中心发送心跳的间隔
lease-renewal-interval-in-seconds: 1
#服务中心最后一次收到心跳后,由于网路原因造成server无法发送心跳到达服务注册中心,服务中心等待到一段时间后就会将该服务剔除(默认:90s)
lease-expiration-duration-in-seconds: 2



Eureka服务集群原理

原理图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZGyTzEFK-1662615655937)(C:\Users\WZS\AppData\Roaming\Typora\typora-user-images\1.png)]


问题:微服务RPC远程服务调用最核心的是什么



高可用

,试想你的注册中心只有一个chly one,一旦出现故障就会导致整个服务崩盘,即会导致整个服务环境不可用,所以

解决办法:搭建Eureka注册中心集群,实现

负载均衡+故障容错



集群配置

1、Eureka集群

​ ·新增一个Eureka server注册服务中心

​ ·分别在各自的yml文件中关联对方

#7001
eureka:
  instance:
    # Eureka服务端的实例名字
    hostname: eureka701.com
  client:

    # 表示是否向 Eureka 注册中心注册自己(这个模块本身是服务器,所以不需要)
    register-with-eureka: false
    # fetch-registry如果为false,则表示自己为注册中心,客户端的化为 ture
    fetch-registry: false
    # Eureka监控页面~
    service-url:
      defaultZone: http://eureka702.com:7002/eureka
      
#7002
eureka:
  instance:
    # Eureka服务端的实例名字
    hostname: eureka702.com
  client:

    # 表示是否向 Eureka 注册中心注册自己(这个模块本身是服务器,所以不需要)
    register-with-eureka: false
    # fetch-registry如果为false,则表示自己为注册中心,客户端的化为 ture
    fetch-registry: false
    # Eureka监控页面~
    service-url:
      defaultZone: http://eureka701.com:7001/eureka

2、SERVER Provider集群

​ ·新增几个服务提供者

​ ·在各自的yml中设置相同的服务名称,表示它们都是提供一组服务的集群

#8001的yml更改
server:
  port: 8001

spring:
  application:
    name: payment-server-provider
eureka:
  client:
    service-url:
      defaultZone: http://eureka701.com:7001/eureka/,http://eureka702.com:7002/eureka/
  instance:
    instance-id: payment-server-provider-8001
    prefer-ip-address: true
#8002的yml更改
server:
  port: 8002

spring:
  application:
    name: payment-server-provider
eureka:
  client:
    service-url:
      defaultZone: http://eureka701.com:7001/eureka/,http://eureka702.com:7002/eureka/
  instance:
    instance-id: payment-server-provider-8002
    prefer-ip-address: true

3、consumer在集群配置中的改变

​ ·yml的更改

server:
  port: 80

eureka:
  client:
  	fetch-registry: true
    service-url:
      defaultZone: http://eureka701.com:7001/eureka/,http://eureka702.com:7002/eureka/

​ ·业务类的更改

//controller

//    private static final String HTTP_URL="http://localhost:8001/";
    //不在关注端口号,通过服务名称来回去服务通信地址
    private static final String HTTP_URL="http://PAYMENT-SERVER-PROVIDER/";

//config
@Configuration
public class RestTemplateConfig {
    @Bean
  //添加负载均衡注解,因为集群配置不在向原来单机模式一样只通过单一的通信地址获取服务,而是在一组服务集群中寻找合适的服务来提供业务的实现
    @LoadBalanced 
    public RestTemplate getRestemplate(){
        return new RestTemplate();
    }
}



服务提供者名称信息修改

只需要在yml中做如下修改

  instance:
    instance-id: payment-server-provider-8002
    prefer-ip-address: true


服务发现Discovery

·说白了就是获取服务注册中心中服务注册表内的服务信息以及指定的某个服务的信息

provider修改:

@Autowired
    private DiscoveryClient discoveryClient;
//discovery方法
 @GetMapping("/get/discover")
    public Object discover(){
        //服务列表
        List<String> servers = discoveryClient.getServices();
        System.out.println("discover=>service"+servers);
        //具体服务信息
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-DEPT");
        for (ServiceInstance instance : instances) {
            System.out.println(
                    instance.getHost() + "\t" + // 主机名称
                            instance.getPort() + "\t" + // 端口号
                            instance.getUri() + "\t" + // uri
                            instance.getServiceId() // 服务id
            );
        }
        return this.discoveryClient;

    }
//启动类添加注解
@EnableDiscoveryClient



Zookeeper服务注册与发现



什么是Zookeeper服务注册中心

·Zookeeper是一个分布式协调工具,可以实现注册中心功能

·关闭linux服务防火墙,启动Zookeeper服务器

·Zookeeper服务器替代Eureka服务器,zk为服务注册中心



服务提供者


服务节点是临时还是持久

·Zookeeper服务节点是临时的,也就是说,当某个服务在一段时间内没有向Zookeeper发送心跳,就会被剔除(CP原则)



服务消费者

在这里插入图片描述



Consul



什么是Consul?

分布式的服务发现和配置管理,有hashCorp公司用Go语言开发的

·提供微服务系统中的

服务治理,配置中心、控制总线

等功能。

·每个功能可以单独使用,也可以一起使用以构建全方位的服务网格

·也就是说:Consul是一套

服务网格解决方案

·优点:

​ ·基于raft协议、比较简洁;

​ ·支持健康检查;

​ ·同时支持HTTP和DNS协议;

​ ·支持跨域中心的WAN集群;

​ ·提供界面化图形 可跨平台



Consul可以提供那些服务

1、服务发现:提供HTTP和DNS两种发现方式

2、健康监测:支持多种方式;HTTP、TCP、Docker、Shell脚本定制化

3、Key-value存储:Key、Value的存储方式

4、多数据中心:Consul支持多数据中心

5、可视化web界面



启动Consul

1、下载并解压文件

2、在解压文件下,打开Cmd窗口,通过命令:consul –version 查看版本信息

启动开发环境:consul agent -dev



三个注册中心的异同点

在这里插入图片描述

AP:保证系统高可用,即允许数据存在一定误差范围,允许返回数据以前某个时刻的旧值

CP:强调数据的一致性,不允许有范围上的偏差,一旦出现就会停止服务或者返回Error



项目搭建

1、model

​ cloud-procider-server-consul-8003

​ server-consumer-consul-80

2、pom

<!--在原来的基础上添加以下依赖-->
<!--consul-->
<dependency>
 <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>

3、yml

#两个的区别在于服务端口和服务名称不同,其他的一样
server:
  port: 80

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

4、启动类:@EnableDiscovery注解

5、配置类:

//利用RestTemplate来实现RPC调用
//config
 @Bean
    @LoadBalanced
    public RestTemplate getTemplate(){
        return  new RestTemplate();
    }
//controller
@RestController
public class ConsumerConsulController {

    @Autowired
    private RestTemplate template;

    private static  final  String GET_URL="http://cloud-server-provider/";

    @GetMapping("/consumer/consul")
    public String paymentConsumer(){
        return template.getForObject(GET_URL+"/consul/server",String.class);
    }
}
//provider controller
    @Value("${server.port}")
    private int server_port;
    @GetMapping("/consul/server")
    public String payMentConsul(){
        return "consul server provider port"+server_port+ UUID.randomUUID().toString();
    }



负载均衡



Ribbon



什么是Ribbon?

·Ribbon是一套基于Netflix Ribbon实现的 客户端负载工具

·主要功能:提供客户端软件的负载均衡算法和服务调用

·说白了就是在消费者发出服务请求的时候,Ribbon会干这么一件事:它将从注册中心的服务列表中获取已经注册的服务信息,然后通过某种规则来筛选合适的服务通信地址,来完成客户端的请求



Ribbon能做些什么

·负载均衡(Load Balance):将用户请求平均的分配到多个服务上,以达到系统的高可用;常见负载均衡软件:Nginx、LVS,硬件F5等

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

​ ·进程内的LB:将LB逻辑集成到消费方,消费方从服务注册中心获取服务信息列表,从中选择合适的服务来完成某次请求;

​ ·Ribbon就是属于进程内的LB,它是一个类库,集成与消费方进程,消费方通过它连接到合适的服务提供方



Ribbon本地负载均衡客户端与Nginx服务端负载均衡的区别

Nginx是服务器负载均衡,客户端所有请求都会交给Nginx,由它实现请求转发;也就是说负载均衡从客户端转交给我们的服务端去做了

Ribbon是本地负载均衡,在调用服务接口的时候,从注册中心上获取注册信息列表,并加载到JVM本地,从而在本地实现RPC远程服务调用技术。说白了就是在消费方(本地)配合RestTemplate来完成远程服务调用的组件



Ribbon的工作方式

一、先选择EurekaServer,优先选择同一个区域内负载较少的server

二、根据用户指定的策略,从server取到的服务注册列表中选择一个服务地址来完成本次请求

Ribbon提供了多种策略:轮询,随机,根据响应时间加权


eureka-client依赖中已经包含了ribbon的相关依赖不需要再导入依赖了



RestTemplate使用

·getForObject:返回对象为响应实体数据转化成的对象,基本上可以理解为Json

·getForEntity:返回对象为ResponseEntity对象,包含响应中的一些重要信息,比如响应头,响应状态码,响应体等

·GET

·POST



Ribbon负载均衡策略

IRule:根据特定的算法从服务列表中选取一个恰当的服务来完成本次请求

IRule实现类图:

在这里插入图片描述

常见策略:

在这里插入图片描述

替换策略:

警告:

这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的Ribbpn客户端所共享,达不到特殊化定制的目的了。

1、新建package

2、编写配置类

3、启动类上添加@RibbonClient(name=server-name,configuration=xxx.class)注解

编写自己的负载均衡策略:

​ 负载均衡算法原理:

实际调用服务器位置下标 = rest接口请求次数 % 服务器集群总数量



每次服务重启后rest接口计数从1开始

在这里插入图片描述

​ 1、消费方配置类去掉注解@loadbalance

​ 2、编写LoadBalance接口

//编写LoadBalance接口
interface LoadBalance{
  //获取服务列表
  ServiceInstance instance(List<ServiceInstance> serviceInstance);
  //
}
//编写实现类
@Component
class MyLb implements LoadBalance{
  
  private AtomicInteger atomcInteger = new AtomicInteger(0);
  //返回rest次数
  public final int getAndIncrement(){
    
    int current;
    int next;
    do{
      current = this.atomcInteger.get();
      next = current >= 2147483647 ? 0: current+1;
    }while(!this.atomcInteger.compareAndSet(current,next));//自旋,CAS并判断是否更新成功
   return next;
    
  }
  
  @Override
  public ServiceInstance instance(List<ServiceInstance> serviceInstance){
    
    int index=getAndIncrement() % serviceInstance.size();
    return serviceInstance.get(index);
    
  }
  
  
}

​ 3、

@GetMapping("/consumer/port")
    public String getPort(){
        List<ServiceInstance> serviceInstanceList = discoveryClient.getInstances("PAYMENT-SERVER-PROVIDER");
        ServiceInstance ServiceInstance = myLB.getInstance(serviceInstanceList);
        if(ServiceInstance==null || serviceInstanceList.size() <=0){
            return null;
        }
        URI uri = ServiceInstance.getUri();

        return restTemplate.getForObject(uri+"/get/port",String.class);
    }



OpenFeign



什么是OpenFeign?

·feign是一个声明式的WebServeice客户端,使用feign可以简化WebService 客户端的编写

·使用方法:编写服务接口,添加注解;feign也支持可拔插式的编码器和解码器。Spring Cloud对feign进行了封装。使其支持SpringMvc标准注解和HttpMessageConverters;feign可以和Eureka、ribbon组合使用以支持负载均衡

·feign本身不具备负载均衡功能



OpenFeign功能

·ribbon+RestTemplate实现服务远程调用时,利用RestTemplate对http请求进行封装处理,构建一套模板化的调用方法。

·在实际的开发场景中,对服务依赖的调用可能不止一个,也就是说

一个接口往往会在多处被调用,所以通常会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用

·feign在此基础上做了进一步的封装,由它来帮助我们定义和实现依赖服务接口的定义。也就是说:

我们需要在消费方创建一个接口并添加feign注解来对它进行配置,完成服务提供方的接口绑定;简化原来的rpc调用方式的开发量

·feign集成了ribbon,

因此可以利用ribbon来维护服务注册中心获取的注册列表信息,通过轮询的方式实现客户端的负载均衡

;与ribbon不同的是,feign只需要定义服务绑定接口且以声明式的方法,显得更加优雅简单

feign与open feign区别

在这里插入图片描述



OpenFeign实战

消费方改变:

//XML
 <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
//YML
server:
  port: 80

eureka:
  client:
    service-url:
      defaultZone: http://eureka701.com:7001/eureka/,http://eureka702.com:7002/eureka/
    register-with-eureka: false
spring:
  application:
    name: consumer-server-80
//CONTROLLER
      
@RestController
@Slf4j
public class FeignCotroller {
    @Autowired
    private ProviderService providerService;

    @GetMapping("/feign/get/{id}")
    public CommentResult getPaymentInfo(@PathVariable("id") long id){
        System.out.println("进入了这个方法");
        return providerService.getPayMent(id);
    }
}
//ADD SERVICE INTERFACE
@Component
//开启feign服务接口绑定
@FeignClient(value = "PAYMENT-SERVER-PROVIDER")
public interface ProviderService {
    @GetMapping("/get/{id}")
    CommentResult getPayMent(@PathVariable("id")long id);
}



OpenFeign超时控制

·因为feign集成了ribbon,所以其每个服务调用等待时间是1s,超时就会报错

·为避免上述的情况,我们需要在yml文件中进行配置;

//YML
ribbon:
  ReadTimeout: 5000
  ConnectTimeout: 5000
//TEST method
    //service
@GetMapping("/timeout/port")
    public String getPort();
	//controller
 @GetMapping("/timeout/port")
    String getPort() {
        return providerService.getPort();
    }
//注意在提供者中也要编写对应的方法


OpenFeign日志打印功能

·feign提供了日志打印功能,可以通过配置调整日志级别,从而了解feign中http的请求细节

·也就是说

对feign接口调用情况进行监控和输出

日志级别:

none:默认的,不显示任何日志
basic:记录请求方法、url、响应状态码以及执行时间
headers:在上一个级别的基础上添加了对请求和响应头的信息的输出
full:在上述级别基础上添加请求和响应的正文以及元数据,说白了就是输出一次request的所有信息

配置变更:

logging:
  level:
		//需要开启日志的全限定类路径
    com.wzs.springcloud.service.ProviderService: debug
//config
@Configuration
public class LogConfig {
    @Bean
    Logger.Level feignLogLevel(){//设置日志等级
        return Logger.Level.FULL;
    }

}

也可以直接在yaml中配置:feign.client.config.default.loggerLevel: FULL,其中”default”可以换成FeignClient中配置的name属性,也可以直接用default,对应的是FeignClientProperties类中的config属性。该类为Feign自动配置类引入的配置项类



断路由

分布式系统面临的问题

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

在这里插入图片描述

服务雪崩

​ 对个微服务调用的时候,假设服务A调用服务B和服务C,微服务B和C又调用其他的微服务,这就是**“扇出”**,

如果扇出的链路上某个微服务的调用时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃

,也就是“雪崩效应”

对于

高流量的应用

来说,

单一的后端依赖

可能会导致所有

服务器上的所有资源都在几秒钟内饱和

。比失败更糟糕的是,这些应用程序还可能

导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障

。这些都表示需要对故障和延迟进行

隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。


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



Hystrix



什么是Hystrix?

Hystrix是一个用于

处理分布式系统的延迟和容错的开源库

,Hystrix能够保证

在一个依赖出现问题的情况下,不会导致整体服务失败,避免联级故障,提高了分布式系统的弹性

;也就是说Hystrix是用于解决依赖调用失败所带来的问题

“断路由”:是一种开关机制,当某个服务单元发生故障之后,通过断路由的故障监控,

向调用方返回一个符合预期的、可处理的备选响应,而不是长时间的等待或者抛出无法处理的异常

;保障调用方线程不会被长时间占用,避免分布式系统崩溃



Hystrix可以做什么?

服务降级

服务熔断

接近实时监控



Hystrix重点概念

服务降级:fallback

​ ·服务器忙,请稍后再试,不然客户端等待立即返回一个友好提示,fallback

那些情况会发生服务降级?

​ ·程序运行异常

​ ·超时

​ ·服务熔断触发服务降级

​ ·线程池/信号量打满也会导致服务降级

服务熔断:break

​ ·说白了就是某个服务访问量达到最大后,直接拒绝访问,然后通过调用服务降级的方法返回友好提示

​ ·服务降级——》服务熔断——》恢复调用链路

服务限流:flowlimit

​ 秒杀高并发等操作,严禁一窝蜂的过来拥挤,一秒钟N个,有序进行;说白了就是为了保护服务器不被打满



Hystrix实例


Jmeter压力测试

开启Jmeter进行压力测试时,tomcat会被打满,正常的服务端口也会变慢,因为大多数的资源都被利用去处理另一个高并发的服务

在这里插入图片描述



服务降级

程序运行异常或者服务处理超时都能进行服务降级



降级配置:

服务提供端

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

·添加@HystrixCommand注解,标注fallback方法

   /**
     * 配置服务降级,并设置服务处理时限,3秒内为正常,超过3秒就会启动服务降级策略
     * @param id
     * @return  状态info
     */
    @HystrixCommand(fallbackMethod = "timeoutHandler",commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
    })
    public String getPayment_timeout(long id){
        int time= 5;
        try {

           TimeUnit.SECONDS.sleep(time);
        }catch (Exception e){
            e.printStackTrace();
        }

        return "当前线程:"+Thread.currentThread().getName()+"查询id为:"+id+"查询状态为:timeoute!"+"损耗时间为:"+time;
    }

    /**
     * 备选方法,服务降级开启后调用的方法,也就是说当正常的服务不可用或者超时后就会调用该方法,快速的做出响应和反馈
     * 而不是让出错的服务一直占用资源等待,将服务耗死
     * @param id
     * @return
     */
    public String timeoutHandler(long id){
        return "当前系统业务繁忙请稍后再试!Thanks♪(・ω・)ノ";
    }
}

·启动类添加@EnableCircuitBreaker注解

客户端:

yml:开启Hystrix

server:
  port: 80

eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka701.com:7001/eureka
#开启Hystrix
feign:
  hystrix:
    enabled: true

启动类;@EnableHystrix

业务类:

  @GetMapping("/consumer/get/payment/timeout/{id}")
	//开启服务降级,设置等待时间
    @HystrixCommand(fallbackMethod = "timeoutHandler",commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1000")
    })
    public String getPayment_timeout(@PathVariable("id") long id) {
            return pService.getPayment_timeout(id);
    }
	//服务降级备选方法
    public String timeoutHandler(@PathVariable("id") long id){
        return "服务器繁忙稍后再试!(。・_・。)ノI’m sorry~!请求id:"+id;
    }

·存在问题

服务异常情况:

在这里插入图片描述

​ 要是每个业务都存在一个备选方法(兜底方法),会造成代码膨胀

·问题解决:

​ 代码膨胀:@DefaultProperties(defaultFallback=“”)统一的服务降级处理方法;通用的和独享的分开,避免代码膨胀

@RestController
@Slf4j
@DefaultProperties(defaultFallback = "Global_timeoutHandler")
public class ConsumerHystrixCotroller {



    @Autowired
    @Qualifier("com.wzs.springcloud.service.PaymentProviderService")
    private PaymentProviderService pService;

    @GetMapping("/consumer/get/payment/ok/{id}")
  	//开启未指定备选方法的服务降级,会调用defaultFallback指定的备选方法
    @HystrixCommand
    public String getPayment_ok(@PathVariable("id") long id){
        log.info("in the method hystrix ok");
        return pService.getPayment_ok(id);
    }

 /**
     * 全局fallback方法
     * @return
     */
    public String Global_timeoutHandler(){
        return "服务器繁忙稍后再试!(。・_・。)ノI’m sorry~!";
    }

​ 代码混乱:新建一个实现接口的类,以实现统一的fallback

在这里插入图片描述

@Service
@FeignClient(value = "SERVICE-HYSTRIX-PROVIDER",fallback = PaymentFallback.class)
public interface PaymentProviderService {
    @GetMapping("/get/payment/ok/{id}")
    String getPayment_ok(@PathVariable("id")long id);

    @GetMapping("/get/payment/timeout/{id}")
    String getPayment_timeout(@PathVariable("id")long id);
}

//服务降级fallback类
import org.springframework.stereotype.Component;

@Component
public class PaymentFallback implements PaymentProviderService{
    @Override
    public String getPayment_ok(long id) {
        return "当前getPayment_ok服务繁忙,请稍后重试!(。・_・。)ノI’m sorry~!";
    }

    @Override
    public String getPayment_timeout(long id) {
        return "当前getPayment_timeout服务繁忙,请稍后重试!(。・_・。)ノI’m sorry~!";
    }
}



服务熔断

概念:类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法返回给客户端提示信息



熔断机制:

熔断机制是

应对雪崩效应的一种微服务保护机制

。当扇出链路的某个微服务

出错不可用或者响应时间太长时

,会进行

服务降级

,进而

熔断

该节点服务的调用,快速

返回提示信息

的响应信息


当检测到该节点微服务调用响应正常后,恢复调用链路

springcloud中的熔断机制是通过Hystrix实现的,它会监控服务间的调用情况,

当失败的调用达到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制



熔断机制注解@HystrixCommand

在这里插入图片描述



熔断实例

service provider:

//SERVICE
    /**
     * 服务熔断
     * id是否合法
     * @param id
     * @return
     */
    @HystrixCommand(fallbackMethod = "Error",commandProperties = {
            @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),// 是否开启断路器
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),// 请求次数
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),// 时间窗口期
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),// 失败率达到多少后跳闸
    })
    public String isTrueId(long id){
        if(id>0){
            return "this id is true! id:"+id+"uuid:"+ IdUtil.simpleUUID();
        }
        else {
            throw new RuntimeException("id is false,because id's value small of 0");
        }

    }
		//fallback 方法
    public String Error(long id){
        return "service error ,please try again after 10s!id:"+id;
    }

//controller
 @GetMapping("/get/payment/istureId/{id}")
    public String isTureId(@PathVariable("id")long id){

        return payserver.isTrueId(id);
    }


当调用失败率过高就会打开熔断机制,当正确率恢复到一定频次后又可以恢复链路调用



断路由类型


熔断打开

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


熔断关闭

:熔断关闭不会对服务进行熔断;服务健康状态


熔断半开

:部分请求根据规则调用当前服务,如果请求成功且符合规则认为当前服务恢复正常,关闭熔断;服务修复状态



官网流程:

在这里插入图片描述



断路由的三个重要参数:

​ 1、快照时间窗:断路由确定是否打开

需要统计一些请求和错误数据

,而统计的时间范围就是快照时间窗,默认为

最近的10秒

​ 2、请求总数阈值:在快照时间窗内,必须

满足请求总数阈值

才有资格熔断。

默认为20

,意味着在10秒内,如果该Hystrix命令调用次数不足20次,即使所有的请求都超时或者失败,断路由都不会打开

​ 3、错误百分比阈值:当请求总数在快照时间窗内超过了阈值,如30次调用,且在这30次调用中发生15次及以上的错误或者异常,就会打开断路由,默认百分比为50%;


熔断开启和关闭条件:

​ ·当满足一定阈值的时候(10s内20次request)

​ ·失败率达到一定的时候(10s内50%错误率)

a 以上阈值会打开断路由,开启断路由的时候所有的请求都不会进行转发

b·一段时间之后(15s),断路由处于半开状态,会让其中一个请求进行转发;如果成功,断路由会关闭,若失败,继续开启;

期间将会重复a-b阶段

注意事项:

​ 1、再次请求调用开启断路由的服务接口时,将不会在调用主逻辑,而是直接调用降级fallback,通过断路由,实现了自动的发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果

​ 2、主逻辑恢复:

断路由打开后

,主逻辑会

被熔断不在被调用

,Hystrix会

启动一个休眠时间窗

,在这个时间窗内。

降级逻辑作为主逻辑

;当时间窗

到期

,断路由就会转变为

半开状态



释放一次请求到原来的主逻辑上

,若本次

请求成功

返回正确数据,那么

断路由将闭合,主逻辑恢复

;反之,继续打开状态,休眠重新计时


all配置:

在这里插入图片描述



服务限流


工作流程

查看源图像



服务监控HystrixDashBoard

概念:Hystrix提供了准实时的调用监控(HystrixDashboard),Hystrix会持续地记录所有通过Hystrix发起的请求执行信息,以统计报表和图的形式展示给用户

注意:

​ 1、使用HystrixDashboard 需要项目提供spring-boot-starter-actuator依赖,并且在启动类中编写ServletRegeditrationBean,并将其注入IOC容器;

​ 2、仪表盘在服务调用后展现

cloud-hystrix-dashboard-9001

pom

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

yml

server:
	port: 9001

starter


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

service provider:

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

    /**
     * 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的玩法,
     * servletRegistrationBean因为springboot的默认路径不是"/hystrix.stream"
     * 只要在自己的项目里配置上下面的servlet就可以了
     *
     * 编写Servlet:HystrixMetricsStreamServlet
     * 注册Servlet:new ServletRegistrationBean(hystrixMetricsStreamServlet)
     * 设置启动级别:servletRegistrationBean.setLoadOnStartup(1);
     * 设置url(访问路径):servletRegistrationBean.addUrlMappings("/hystrix/stream");
     * 设置Servletname: servletRegistrationBean.setName("HystrixStream");
     * @return
     */
    @Bean
    public ServletRegistrationBean servletRegistration(){
        HystrixMetricsStreamServlet hystrixMetricsStreamServlet = new HystrixMetricsStreamServlet();
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(hystrixMetricsStreamServlet);
        servletRegistrationBean.setLoadOnStartup(1);
        servletRegistrationBean.addUrlMappings("/hystrix.stream");
        servletRegistrationBean.setName("HystrixStream");
        return servletRegistrationBean;
    }
}

仪表盘说明:

在这里插入图片描述



路由网关



GateWay



什么是gateway

·cloud网关组件在1.x版本中采用的是Zuul网关,在2.x版本以后,由于走了的升级一直跳票,springcloud自己研发了一个网关组件gateway代替zuul

·gateway

是在spring生态系统之上构建的API网关服务

,基于spring5,springboot 2,Project Reactor等技术

·gateway是为了

提供一种简单有效的统一的API路由管理方式,以及

提供一些强大的过滤功能**如:熔断、限流、重试等

·gateway是基于webflux框架实现的,而webflux框架底层则使用了高性能的reactor模式通信框架Netty,相比zull提高了网关的性能

·说白了就是一个使用了webflux中的reactor-netty响应式的编程组件,底层是netty通讯框架的 cloud网关路由组件

cloud官网:

在这里插入图片描述



可以做什么

·反向代理

·鉴权

·熔断

·日志监控等

企业系统架构层次:

在这里插入图片描述



特性

​ ·基于spring framework5,Project reactor和spring boot 2.0构建,稳定性更强

​ ·动态路由:能够匹配任何请求属性

​ ·可以对路由指定Predicate(断言)和Filter(过滤器),且编写简单

​ ·集成Hystrix的断路由功能

​ ·集成SpringCloud 服务发现功能

​ ·请求限流功能

​ ·支持轻路重写



Gateway与Zuul区别

·Zuul.x是基于阻塞I/O的API Gateway

·Zull 1.x基于

Servlet 2.5使用阻塞框架

实现,

不支持任何长连接

,zull的设计模式和Nginx较像,

每次I/O都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成

,差别是Nginx是用c++实现的,Zull是用java实现的;

JVM本身会有第一次加载较慢的情况,因此zull性能比较差

·Zull 2.x 基于netty非阻塞和支持长连接,cloud为整合,rps(每秒请求数)提升1.6倍

·Gateway是基于spring framework5,Project reactor和spring boot 2.0构建,使

用非阻塞API

·Gateway还

支持WebSocket

,并且与spring紧密集成拥有更好的开发体验



Zuul 1.x模型

Springcloud中所集成的Zuul版本,采用的是

Tomcat容器

,使用的是

传统的Servlet lO处理模型



Servlet的生命周期:servlet由

servlet container进行生命周期管理

container

启动时构造

servlet对象并

调用servlet init()进行初始化

;

container

运行时接受请求

,并为每个请求

分配一个线程(一般从线程池中获取空闲线程)然后调用service()

.container关闭时调用servlet. destory()销毁servlet;

在这里插入图片描述

上述模式的缺点:

当请求进入servlet container时,servlet container就会为其

绑定一个线程

,在

并发不高的场景

下这种模型是适用的。但是一旦

高并发(比如抽风用jemeter压),线程数量就会上涨,而线程资源代价是昂贵的(上线文切换,内存消耗大、严重影响请求的处理时间

在一些

简单

业务场景下,

不希望

为每个request分配一个线程,只需要

1个或几个线程

就能应对极大并发的请求,这种业务场景下servlet模型没有优势

Zul 1.X是基于serlet之上的一个阻塞式处理模型,

即spring实现了处理所有request请求的一个servlet (DispatcherServlet)并由该servlet阻塞式处理

,所以Zull无法摆脱

Servlet模型的弊端



Gateway模型

·传统的web框架都是

基于Servlet API与Servlet容器基础上运行的

(Struts2,springMvc等)

·Servlet 3.1之后

有了异步非阻塞的支持



webflux是一个典型的非阻塞异步的框架

,其核心是

基于Reactor的相关API实现的

。相对于传统的web框架来说,它可以运行在诸如Netty,Undertow及支持Servlet 3.1的容器上。非阻塞+函数式编程(s5+J8)

·spring webflux是spring5.0引入的新响应式框架,区别在于springMvc,他不需要依赖Servlet API,是完全异步非阻塞的,且基于Reactor来实现响应式流规范。



三大核心概念

route(路由):路由是构建网关的基础模块,

它由ID,目标URI,一系列的断言和过滤器组成,若果断言为true则匹配该路由

Predicate(断言):开发人员匹配HTTP请求中的所有内容,

若请求域断言相匹配则进行路由

filter(过滤):指的是spring框架中Gateway的实例,使用过滤器,可以

在请求被路由前或者之后对请求进行修改

总体:web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。predicate就是匹配条件,filter就是拦截器,再结合目标uri就可以实现一个具体的路由了

在这里插入图片描述



Gateway工作流程

流程图:

在这里插入图片描述

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

·Handler通过指定的过滤器链将请求发送到实际的服务执行业务逻辑,然后返回

·过滤器之间用虚线分开是因为过滤器可能会发生在发送代理请求之前(“pre”)或者之后(“post”)执行业务逻辑

·filter在“pre”类型的过滤器可以进行参数校验、流量监控、日志输出、协议转换等

·filter在“post”类型的过滤器中可以进行响应内容、响应头的修改、日志输出、流量监控等


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



入门配置

路由映射:

YML:

server:
  port: 9527

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

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

网关路由配置方式:

​ ·在yml中配置;同上

​ ·Java编码,Bean注入

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


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



微服务名实现动态网关路由


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

cloud:
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行
      routes:
        - id: toute1 #路由ID,没有固定规则但要求要唯一,建议配合事务名
          uri: lb://payment-server-provider #匹配后服务提供的路由地址
          predicates:
            - Path=/get/** #断言,也就是匹配条件,路径相匹配的进行路由



Predicate的使用


是什么?

启动Gateway查看后台日志:

在这里插入图片描述



Route Predicate Factories

·Gateway将

路由匹配作为 webflux HandlerMapping基础架构的一部分

·Gateway

包括很多内置的Route Predicate Factories

,这些

Predicate都与HTTP请求的不同属性匹配,多个Route Predicate工厂可以进行组合

,通过

逻辑and连接

·Gateway创建Route对象的时候,使用RoutePredicateFactory创建Predicate对象,Predicate对象可以赋值给Route



常用的Route Predicate

在这里插入图片描述

补充:javatime:ZoneDateTime类获取当前时区的时间

cookie:Cookie Route Predicate需要两个参数,

一个是Cookie name ,一个是正则表达式。


路由规则会通

过获取对应的Cookie name值和正则表达式去匹配

,如果匹配上就会执行路由,如果没有匹配上则不执行

​ 带cookie:

在这里插入图片描述

​ 不带cookie

在这里插入图片描述

Header:两个参数,一个属性名和一个正则表达式,这个属性值和正则表达式匹配则执行

Host

Method

Path

Query



Filter的使用


是什么

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

Gateway内置了许多过滤器,它们都由Gateway Filter的工厂来产生



生命周期,种类

生命周期:

​ pre:前

​ post:后

种类

​ ·GatewayFilter

​ ·GlobalFilter

常用过滤器:

在这里插入图片描述



自定义过滤器

自定义全局GlobalFilter

主要接口:GlobalFilter,Ordered

能做什么:全局日志记录,统一网关鉴权

public class GateWayFilter implements GlobalFilter, Ordered{
  
  public Mono<void> filter(ServerWebExchange exchange, GatewayFilterChain chain){
    log.info("my filter:"+new Date());
    String uname=exchange.getRequest().getQueryParams().getFirst("uname");//获取request中的参数
    if(uname==null){
      log.info("this user not be availabled ");
      exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);//过滤条件满足,筛掉当前不合法的请求
      return exchange.getResponse().setComplete();//返回提示信息
    }
    return chain.filter(exchange);//放行合法的请求
  }
  public int getOrder(){
    return 0; //过滤级别,值越小过滤等级越高,优先考虑
  }
}



分布式服务配置



CloudConfig分布式配置中心



什么是Cloudconfig?


分布式系统配置存在问题:

​ 微服务意味着将

单体应用中的业务拆分成一个个子服务

,每个服务的粒度相对较小,因此系统中会

出现大量的服务

。由于每个服务

都需要必要的配置信息才能运行

,所以一套

集中式的、动态的配置管理

设施是必不可少的

SpringCloud提供了ConfigServer来解决这个问题

配置原理图:

在这里插入图片描述

概念:SpringCloudConfig微服务架构中的微服务

提供集中化的外部配置支持

,配置服务器为各个不同的微服务应用的所有环境提供一个

中心化的外部配置



如何使用

·config分为

服务端和客户端

两部分

·服务端也称为

分布式配置中心

,它

是一个独立的微服务应用

,用来

连接配置服务器

并为客户端

提供获取配置信息,加密/解密信息等访问接口

·客户端:通过

指定的配置中心来管理应用资源,以及与业务相关的配置内容

,并在

启动的

时候从配置中心获取和加载配置信息,配置服务器

默认采用git来存储配置信息

,有助于对环境配置进行版本管理,且可以通过git客户端工具来更好的管理和访问配置内容



有什么功能

·

集中管理配置文件

·

不同环境不同配置,动态化的配置更新,分环境部署

(dev,test,prod,beat,release)

·运行期间

动态调整配置

,不需要在每个服务部署的及其上编写配置文件,服务会

向配置中心拉取配置自己的信息

·当配置发生改变时,服务

不需要重启就可以感知配置的变化并自动更新使用新的配置

·降配置信息

以rest接口的形式暴露

·与github整合



案例实战


服务端配置

步骤:

​ 1、创建一个github账号,并创建一个远程仓库Repository

​ 2、获取仓库的git地址

​ 3、在本地磁盘上创建一个git本地仓库文件并clone到本地

​ 4、创建config—server model

​ 5、改pom

​ 6、写yml

​ 7、主启动类

​ 8、添加新的host映射(选做)

​ 9、测试model与git是否正常连接,收发数据

​ 10、配置读取规则

实例:

pom:

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

yml:

server:
  port: 3344

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

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

启动类:


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

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



常用获取配置信息的方式:

·/{label}/{application}-{profile}.yml

·{application}-{profile}.yml

·/{application}/{profile}/{label}

注意:label指的是拉取的分支,application:指的是文件名称,profile:指的是环境名

在这里插入图片描述

示例图:

在这里插入图片描述



客户端配置

步骤:

1、新建model

2、pom

    <dependencies>
        <dependency>
            <groupId>com.wzs.springcloud</groupId>
            <artifactId>Spring-Cloud-Api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>


    </dependencies>

3、yml

server:
  port: 3355

spring:
  application:
    name: config-client
  cloud:
    config:
      label: main #分支
      name: config #配置文件名称
      profile: dev #配置环境
      uri: http://localhost:3344 #拉取配置文件地址
eureka:
  client:
    service-url:
      defaultZone: http://eureka701.com:7001 

4、主启动

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

5、业务类

@RestController
public class ServiceInfoController {
    @Value("${spring.cloud.config.info}")//属性注入,相当于bean标签下的set或者构造器
    private  String configInfo;

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


}

6、测试:略


存在问题:

运维人员

在远程仓库修改了配置文件

,configserver端可以拿到最新的配置信息,而客户端人

保持原来的配置信息



无法及时有效的更新

配置信息,需要客户端

重启或者重新加载

,每次修改都需要客户端重启导致体验感极差



bootstrap与application的区别:

·application是

用户级别

的资源配置项

·bootstrap是

系统级别

的资源配置项,

优先级更高

·SpringCloud会创建一个“bootstrap context”,作为spring应用的‘application context“的

父上下文



初始化

的时候,’bootstrap context‘负责从

外部资源加载配置属性并解析配置

,这两个上下文

共享一个

从外部获取的**’environment‘**

·bootstrap

属性有高优先级

,默认情况下,它们

不会被本地配置覆盖



bootstrap context



application context

有着不同的约定,所以新增一个bootstrap.yml文件,

保证二者配置分离

注意:

远程拉取配置项,把client端原来的application配置文件修改为bootstrap配置文件是关键



客户端动态刷新

1、修改client(pom中要包含actuator依赖)

2、修改yml

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

3、业务类添加@RefreshScope注解

4、维护人员修改后发送

post请求

,通知client刷新、修改

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

在这里插入图片描述

新问题:当存在多个客户端时,每次修改都需要单独的通知各个客户端刷新配置,这项工程及其庞大,急需一个

一次修改、一次通知、全部刷新

、的方法



消息总线BUS



Bus是什么

·分布式自动刷新配置功能

·springcloudBus配合springcloudConfig使用可以实现配置的动态刷新

·Bus支持两种代理消息:RabbitMq 、Kafka

·Bus是将

分布式系统的节点与轻量级消息系统链接

起来的框架。

整合了java的事件处理机制和中间件功能

架构图:

在这里插入图片描述



Bus能做什么

·Bus能

管理和传播分布式系统间的消息

,就像一个

分布式执行器

,可用于

广播状态的更改、事件推送

等;也可当做

微服务间的通信通道

工作原理图:

在这里插入图片描述



什么是总线

·在微服务架构系统中,通常会使用

轻量级的消息代理

来构建一个

共用的消息主题

,并让系统中

所有微服务实例都连接上来

。由于该主题

产生的消息会被所有的实例监听和消费

,所以称它为

消息总线

·总线上的实例可以方便地广播一些需要让其他连接在该主题上的实例都知道的消息



基本原理

configClient实例

都监听MQ中同一个topic(默认是springCloudBus)

,当一个

服务刷新数据的时候

,它会把这个

信息放入到Topic

中,这样

其他监听该topic的服务也能得到通知,然后去更新自身的配置



RabbitMQ环境配置

安装Erlang

安装rabbitMQ



Bus动态刷新全局广播通知

步骤:

​ 1、配置rabbitMQ环境:官方网站下载对应的Erl与rabbitMQ版本安装即可

​ 2、添加新clientmodel

​ 3、服务端配置总线支持

​ 4、客户端添加总线支持

​ 5、测试

消息推送的两种方式:

​ 1、利用总线触发一个客户端的/bus/refresh,从而刷新所有客户端的配置

​ 2、利用总线触发一个服务端的/bus/refesh,从而刷新客户端的配置

两种方式的工作原理图:

1、感染一个客户端从而感染全部的方式

在这里插入图片描述

​ 2、感染服务端从而感染全部的方式

在这里插入图片描述

两种方式的对比,显然方式二更加合适

​ 1、方式一打破了微服务的职责单一性,因为微服务本身是业务模块,它本不应该承担配置的刷新职责

​ 2、破坏了微服务各节点的对等性

​ 3、有一定局限性;如在微服务迁移时,它的网络地址常常会发生变化,此时如果想要做到自动刷新,就会增加更多的修改

实战案例:

service

pom

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

YML添加的部分

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


management: #暴露bus刷新端口
  endpoints: #暴露bus刷新配置的端点
    web:
      exposure:
        include: "bus-refresh"

client

pom

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

yml添加的部分

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


management: #暴露bus刷新端口
  endpoints: #暴露bus刷新配置的端点
    web:
      exposure:
        include: "*"

curl命令

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


动态刷新的定点通知

·针对指定的实例生效而不是全部

·方式:

http://localhost:端口号/actuator/bus-refresh/{destination}

·

bus/refresh请求不再发送到具体实例上所以需要通过destination来指定需要更新的具体实例或者服务




我没跑出来不知道为什么,解决的小伙伴可以@我

工作原理图:

在这里插入图片描述



消息驱动 stream



为什么引入cloud Stream?

·MQ(消息中间件)

​ ·ActiveMQ

​ ·rabbitMQ

​ ·RocketMQ

​ ·Kafka

·解决痛点

​ ·各种不同的MQ的应用场景以及企业要求不同,导致技术人员在这些MQ的学习、技术转换十分痛苦难受,

​ ·Stream的出现让这些技术人员看到了希望,Stream让技术开发者

可以不再关注具体的MQ细节

,用

一种适配绑定的方式,自动的在各种MQ内切换



Stream是什么?

·

Stream是一个构建消息驱动微服务的架构

·

应用程序

与Stream中的

binder(绑定)对象

交互,是通过

inputs或者outputs

实现的;

​ ·通过

配置来binding(绑定

),Stream的binder对象

负责与消息中间件交互

·也就是说只要清楚如何与Stream交互,就可以方便的使用消息驱动的方式来实现各种MQ的切换



如何实现消息事件驱动?

​ ·通过使用

Spring integration



连接


代理中间件

的方式实现消息事件驱动

Stream为一些

供应商的消息中间件提供了个性化的自动化配置实现

,引用了

发布订阅,消费组,分区的三个核心概念


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

【目前仅支持RabbitMQ,kafka】



设计思想


标准Mq

在这里插入图片描述

·生产者/消费者之间靠消息媒介传递消息内容(Message),

·消息必须走特定的通道,消息通道MessageChannel

·消息通道里的消息如何被消费呢,谁负责收发处理——消息通道MessageChannel的子接口SubscribableChannel由MessageHandler消息处理器所订阅



引入Stream后MQ

比如我们使用了RabbitMQ和Kafka两种中间件,由于二者的架构不同,像rabbitMQ有exchange,Kafka有Topic和Partitions分区

这些中间件的差异会给我们在实际的项目开发中造成困扰,如要是我们用了两个消息队列的其中一种,而在后面的业务需求我们想

往另一种消息队列进行迁移

的时候,

一大堆东西需要推倒重做

,因为它跟我们的系统耦合了,这时候Stream给我们提供了一种解耦的方式

在这里插入图片描述



如何屏蔽底层差异

·在无绑定器这个概念的时候,SpringBoot应用要

直接

与消息中间件

进行信息交互

·由于各消息中间件

构建的初衷不同

,它们的

实现细节就会存在较大的差异

,不易于在各MQ内切换

·通过绑定器

Binder

作为

中间层

,完美的实现了

应用程序



消息中间件细节

之间的

解耦和隔离

·通过向应用程序

暴露统一的Channel通道

,使得应用程序不需要考虑各种

不同的消息中间件的实现



Binder

·Binder可以生成Binding,

Binding用来绑定消息容器的生产者和消费者

,它有两种类型,INPUT和OUTPUT

​ ·

INPUT对应消费者



·OUTPUT对应生产者

框架图:

image-20220322170240853

·Stream中的消息通信方式遵循了发布-订阅模式

·Topic主题进行广播:对于rabbitMQ就是Exchange;对于Kafka就是Topic;



Stream使用的标准流程

流程图:

在这里插入图片描述

·Binder:较

方便的连接中间件,屏蔽差异

·Channel:通道,是队列Queue的一种抽象,在消息通讯系统中就是

实现存储和转发的媒介,通过Channel队列进行配置

·Source和Sink:简单的理解为参照对象是Stream自身,Stream

发布消息的就是输出,接受消息的就是输入



编码API和常用注解

在这里插入图片描述



案例说明

新建的model

在这里插入图片描述



生产者消息驱动

pom

<dependencies>
        <dependency>
            <groupId>com.wzs.springcloud</groupId>
            <artifactId>Spring-Cloud-Api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
            <version>2.0.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
</dependencies>

yml

server:
  port: 8801

spring:
  application:
    name: cloud-stream-provider
  cloud:
    stream:
      default-binder: rabbit # 设置要绑定的消息服务的具体设置
      bindings: # 服务的整合处理
        output: # 这个名字是一个通道的名称
          destination: studyExchange # 表示要使用的Exchange名称定义
          content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
          binder: rabbit
  rabbitmq:
      host: localhost
      port: 5672
      username: guest
      password: guest



eureka:
  client: # 客户端进行Eureka注册的配置
    service-url:
      defaultZone: http://eureka701.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
public interface MessageSend {
    public String  send();

}

//impl
  import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.MessageChannel;

import javax.annotation.Resource;
import java.util.UUID;

@EnableBinding(Source.class)
public class MessageSendImpl implements MessageSend {

    /**
     * 消息的发送管道
     */
    @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;
    }
}

//controller
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;


@RestController
public class SendController {
    @Resource
    private MessageSend messageSend;
    @GetMapping("/send")
    public String sendMessage(){
        return messageSend.send();
    }
}

启动类

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

消费者消息驱动

pom:与生产者一致

yml

server:
  port: 80

spring:
  application:
    name: cloud-stream-consumer
  cloud:
    stream:
      default-binder: rabbit # 设置要绑定的消息服务的具体设置
      bindings: # 服务的整合处理
        input: # 这个名字是一个通道的名称
          destination: studyExchange # 表示要使用的Exchange名称定义
          content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
          binder: rabbit
  rabbitmq:
      host: localhost
      port: 5672
      username: guest
      password: guest



eureka:
  client: # 客户端进行Eureka注册的配置
    service-url:
      defaultZone: http://eureka701.com:7001/eureka
  instance:
    lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
    lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
    instance-id: consumer-80.com  # 在信息列表时显示主机名称
    prefer-ip-address: true     # 访问的路径变为IP地址

业务类

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;


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

    @StreamListener(Sink.INPUT)
    public void ConsumerMessage(Message<String> message){
        System.out.println( "正在消费消息的端口port:"+port+"\t"+"消费的消息是:"+message.getPayload());
    }
}

启动类

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



分组消费与持久化

新增一个同类型的消费者服务应用并启动所有服务测试结果如下:

在这里插入图片描述



存在问题:

​ ·消息重复消费问题:一条消息被多个具备相同业务的服务接受并响应,由上图可知

​ 解决:分组持久化属性group

​ ·消息持久化问题



生产实际案例

​ ·假如

订单系统做集群部署

,集群中的各个微服务都会从

rabbitMQ获取订单信息

,如果

一个订单同时被两个服务获取到,就会造成数据错误

,就会对用户造成不好的影响,我们需要避免这种情况;

​ ·解决方案:对这些个消费者进行分组,即利用Stream的消息分组来解决


在这里插入图片描述



消息分组为什么能够解决重复消费问题呢?

​ ·在Stream中处于同一个group中的

多个消费者是竞争关系

,这保证了某条消息

只会被其中的一个应用消费一次



·不同组是可以全面消费的(重复消费)



·同组则会产生竞争,只有其中一个可以消费

​ 实例:

在这里插入图片描述



分组:

​ 为什么分组?

​ ·为了实现微服务的高可用和负载均衡,我们会部署多个服务实例,而我们希望

生产者所生产的消息发送给具体的微服务时,该消息只被该类型中的其中的一个微服务应用消费

,上述的事例却

出现了同一类微服务应用重复消费消息的行为

,Stream消息分组可以很好的解决这个问题

​ 原理:组内成员,消息只能被其中一员消费;非同组成员可以完全消费

yml:

binder: rabbit
group: consumerA

·统一分组后,事例中的两个服务实例,每次只有其中一个对消息进行消费,从而避免了重复消费的问题



持久化:避免消息丢失

​ 1、停掉两个消费者实例,并将其中一个从分组中剔除,生产者继续发送消息

​ 2、重启两个消费者实例

​ 3、情况:

未剔除的消费者,任然可以消费生产者刚刚产生了的消息,而不在分组内的消费者是接收不到消息的



Sleuth分布式请求链路跟踪



什么是sleuth?

问题:

​ 在微服务架构中,

一个

有客户端发起的

请求

在后端系统中会

经过多个不同的服务节点调用



协同产生最后请求的结果

,每一个前端请求都会

形成一条复杂的分布式调用链路

,链路中的

任何一环出现高延时或者错误

都会引起

整个请求最后失败

环境图:

在这里插入图片描述

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

官方图示:

在这里插入图片描述



搭建链路监控步骤

​ 1、zipkin下载、运行

​ 2、服务提供者

​ 3、服务消费者

provider&consumer都添加一下配置

​ pom

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

​ yml

spring:
  zipkin:
    base-url: http://localhost:9411
  sleuth:
    sampler:
      probability: 1 #采样率,一般鉴于0到1之间

​ 4、访问监控链路ip:http://localhost:9411/zipkin

zipkin-server-2.23.18-exec.jar包下载地址:链接:https://pan.baidu.com/s/1huimOtTW3nf7pSvEzPVPFg

提取码:nefp

zipkin运行命令:java -jar zipkin-server-2.23.18-exec.jar

完整的调用链路:

​ 下图表示一请求链路的完整结构,一条链路通过Trace id唯一标识,Span标识发起请求信息,个Span通过parent id 关联起来


在这里插入图片描述

上图的拆分:

在这里插入图片描述

·Trace:类似于树形结构的Span集合,表示一条调用链路,存在唯一标识

·span:表示调用链路来源,通俗理解就是一次请求信息


githubdemo



B战视频——周阳老师



版权声明:本文为qq_38690873原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。