springcloud(2/3)

3 篇文章 0 订阅
3 篇文章 1 订阅

14.Gateway

14.1.概述简介

14.1.1.官网

  1. 上一代zuul 1.X

    https://github.com/Netflix/zuul/wiki

  2. gateway

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

14.1.2.是什么

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

27200344586

概述

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

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

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

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

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

27200638085

14.1.3.GateWay的作用

  1. 反向代理
  2. 鉴权
  3. 流量控制
  4. 熔断
  5. 日志监控

14.1.4.微服务架构中网关在哪里

27201126039

14.1.5.我们为什么选择Gateway?

  1. neflix不太靠谱,zuul2.0一直跳票,迟迟不发布

    一方面因为Zuul1.0已经进入了维护阶段,而且Gateway是SpringCloud团队研发的,是亲儿子产品,值得信赖。
    而且很多功能Zuul都没有用起来也非常的简单便捷。

    Gateway是基于异步非阻塞模型上进行开发的,性能方面不需要担心。虽然Netflix早就发布了最新的 Zuul 2.x,
    但 Spring Cloud 貌似没有整合计划。而且Netflix相关组件都宣布进入维护期;不知前景如何?

    多方面综合考虑Gateway是很理想的网关选择。

  2. SpringCloud Gateway具有如下特性

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

    在SpringCloud Finchley 正式版之前,Spring Cloud 推荐的网关是 Netflix 提供的Zuul:

    1. Zuul 1.x,是一个基于阻塞 I/ O 的 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 Boot 2 之上, 使用非阻塞 API。
    5. Spring Cloud Gateway 还 支持 WebSocket, 并且与Spring紧密集成拥有更好的开发体验。

14.1.6.Zuul1.x模型

Springcloud中所集成的Zuul版本,采用的是Tomcat容器,使用的是传统的Servlet IO处理模型。

学过尚硅谷web中期课程都知道一个题目,Servlet的生命周期?servlet由servlet container进行生命周期管理。

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模型的弊端

14.1.7.GateWay模型

Gateway是基于WebFlux框架实现的,WebFlux是一个异步非阻塞的框架。

传统的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 来实现响应式流规范。

14.2.GateWay三大核心概念

14.2.1.Route(路由)

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

符合路由的转发规则,路由的作用就是进行路由转发。

14.2.2.Predicate(断言)

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

14.2.3.Filter(过滤)

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

14.2.4.总结

27205832979

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

14.3.Gateway工作流程

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

Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。

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

14.4.入门配置

14.4.1.新建项目cloud-gateway-gateway9527

14.4.2.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>cloud2021</artifactId>
        <groupId>com.stonebridge.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-gateway-gateway9527</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <!--gateway-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!--eureka-client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
        <dependency>
            <groupId>com.stonebridge.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </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>

注意:不可以引入spring-boot-starter-web和spring-boot-starter-actuator

14.4.3.主启动类

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

14.4.4.业务类

14.4.5.网关做路由映射

cloud-provider-payment8001看看controller的访问地址:

  • http://localhost:8001/payment/lb
  • http://localhost:8001/payment/get/31

14.4.6.在application.yaml增加配置

server:
  port: 9527

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

        - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001          #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/lb/**         # 断言,路径相匹配的进行路由
eureka:
  instance:
    hostname: cloud-gateway-service
  client: #服务提供者provider注册进eureka服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

14.4.7.测试

  • 启动7001

  • 启动8001

  • 启动9527网关

访问8001

27213901372

访问9527

27213712905

14.4.8.yaml配置说明

14.4.8.1.在配置文件yml中配置

见14.4.6

14.4.8.2.代码中注入RouteLocator的Bean

配置了一个id为path_route_atguigu的路由规则

当访问地址 http://localhost:9527/guonei时会自动转发到地址:http://news.baidu.com/guonei

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

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

测试

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

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

只需要在application.yaml进行配置即可

测试

启动注册中心,以及provider 集群

访问具体服务

http://localhost:9527/payment/lb

27221441324 27221453395

14.6.Predicate的使用

14.6.1.Predicate是什么

启动的gateway9527

Route Predicate Factories

Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础架构的一部分。

Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个Route Predicate工厂可以进行组合

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

所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑and。

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

14.6.2.常用的Route Predicate

14.6.2.1.After Route Predicate

配置规则:必须要在该时间以后才能访问

predicates:
       - Path=/payment/lb/**         # 断言,路径相匹配的进行路由
       - After=2020-02-05T15:10:03.685+08:00[Asia/Shanghai]         # 断言,路径相匹配的进行路由

补充生成时间串:

public class Test {
    public static void main(String[] args) {
        ZonedDateTime zbj = ZonedDateTime.now(); // 默认时区
        System.out.println(zbj);
    }
}
14.6.2.2.Before Route Predicate

配置规则:必须要在该时间以前才能访问

predicates:
	- Path=/payment/lb/**         # 断言,路径相匹配的进行路由
	- Before=2020-02-05T15:10:03.685+08:00[Asia/Shanghai]         # 断言,路径相匹配的进行路由
14.6.2.3.Between Route Predicate

配置规则:必须要在该段时间内以前才能访问

predicates:
	- Path=/payment/lb/**         # 断言,路径相匹配的进行路由
	- Between=2020-02-02T17:45:06.206+08:00[Asia/Shanghai],2020-03-25T18:59:06.206+08:00[Asia/Shanghai]
14.6.2.4.Cookie Route Predicate

Cookie Route Predicate需要两个参数,一个是 Cookie name ,一个是正则表达式。
路由规则会通过获取对应的 Cookie name 值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行

配置规则:携带的cookie必须满足该匹配条件

predicates:
    - Path=/payment/lb/**         # 断言,路径相匹配的进行路由
    - Cookie=username,zzyy
  1. 不带cookies访问

  2. 带上cookies访问

    中文乱码处理:https://blog.csdn.net/leedee/article/details/82685636

14.6.2.5.Header Route Predicate

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

配置规则:携带的Header 必须满足该匹配条件

predicates:
	- Path=/payment/lb/**         # 断言,路径相匹配的进行路由
    - Header=X-Request-Id, \d+  # 请求头要有X-Request-Id属性并且值为整数的正则表达式
14.6.2.6.Host Route Predicate

配置规则:访问的HOST必须满足该匹配条件

predicates:
	- Path=/payment/lb/**         # 断言,路径相匹配的进行路由
    - Host=**.atguigu.com

curl http://localhost:9588/paymentInfo -H “Host: www.atguigu.com”

curl http://localhost:9588/paymentInfo -H “Host: news.atguigu.com”

正确:curl http://localhost:9527/payment/lb -H “Host: www.atguigu.com”
正确:curl http://localhost:9527/payment/lb -H “Host: java.atguigu.com”
错误:curl http://localhost:9527/payment/lb -H “Host: java.atguigu.net”

14.6.2.7.Method Route Predicate

配置规则:访问方法相匹配

predicates:
	- Path=/payment/lb/**         # 断言,路径相匹配的进行路由
    - Method=GET
14.6.2.8.Path Route Predicate

配置规则:访问的路径要匹配

predicates:
	- Path=/payment/lb/**         # 断言,路径相匹配的进行路由
14.6.2.9.Query Route Predicate

配置规则:要有满足参数名的参数且要满足匹配条件

predicates:
	- Path=/payment/lb/**         # 断言,路径相匹配的进行路由
    - Query=username, \d+  # 要有参数名username并且值还要是整数才能路由

正确:http://localhost:9527/payment/lb?username=31

错误:http://localhost:9527/payment/lb?username=-31

14.7.Filter的使用

14.7.1.基本概念

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

01143117093

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

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

14.7.2.Spring Cloud Gateway的Filter

  1. 生命周期,Only Two

    • pre
    • post
  2. 种类,Only Two

    • GatewayFilter

      https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#the-addrequestparameter-gatewayfilter-factory

    • GlobalFilter

      01144646175

14.7.3.常用的GatewayFilter

AddRequestParameter

14.7.4.自定义过滤器

自定义全局GlobalFilter

  1. 两个主要接口介绍

    implements GlobalFilter,Ordered
    
  2. 作用

    过滤器的作用就是在挡在所有微服务之前进行权限管理

  3. 案例代码

    @Component //必须加,必须加,必须加
    @Slf4j
    public class MyLogGateWayFilter implements GlobalFilter, Ordered {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            log.info("******************come in MyLogGateWayFilter***************" + new Date());
            //获取请求的参数;getRequest()获取ServerHttpRequest
            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);
        }
    
        /**
         * 加载过滤器的顺序,值越小,优先级越高
         *
         * @return
         */
        @Override
        public int getOrder() {
            return 0;
        }
    }
    
  4. 测试

    • 正确访问

      01151608688

      01151642722

    • 错误访问

15.SpringCloud Config分布式配置中心

15.1.概述

15.1.1.分布式系统面临的—配置问题

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

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

  1. 多个application.yaml统一的管理
  2. 多个微服务链接多个或者一个数据库
  3. 开发上线的时候开发、测试、生成环境的配置

希望一处修改处处生效

15.1.2.是什么

是什么

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

中心化的外部配置总管所有的微服务的配置,所有的微服务的公有的配置放在配置中心、私有的放在自己的配置中心。

15.1.3.怎么用

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

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

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

15.1.4.具体功能

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

15.1.5.与GitHub整合配置

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

15.1.6.官网

https://cloud.spring.io/spring-cloud-static/spring-cloud-config/2.2.1.RELEASE/reference/html/

15.2.Config服务端配置与测试

15.2.1.创建SpringCloud Config

用你自己的账号在GitHub上新建一个名为springcloud-config的新Repository

15.2.2.由上一步获得刚新建的git地址

https://github.com/stonebridege/springcloud-config.git

15.2.3.clone到本地

本地硬盘目录上新建git仓库并clone

git clone git@github.com:zzyybs/springcloud-config.git
02005616964

表示多个环境的配置文件

保存格式必须为UTF-8

如果需要修改,此处模拟运维人员操作git和github

git add .
git commit -m "init yml"
git push origin master

15.2.4.新建Module模块cloud-config-center-3344

它即为Cloud的配置中心模块cloudConfig Center

15.2.5.pom.xml

最主要:spring-cloud-config-server

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

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

15.2.6.application.yaml

server:
  port: 3344

spring:
  application:
    name: cloud-config-center #注册进Eureka服务器的微服务名
  cloud:
    config:
      server:
        git:
          uri: https://github.com/stonebridege/springcloud-config.git #GitHub上面的git仓库名字
          username: stonebridege
          password: 3f448795
          search-paths: springcloud-config
          skip-ssl-validation: true
          default-label: master
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka

15.2.7.主启动类

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

15.2.8.windows下修改hosts文件,增加映射

15.2.9.测试

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

启动微服务Eureka和3344,访问http://config-3344.com:3344/master/config-dev.yml

15.2.10.配置文件读取规则

配置如下

server:
  port: 3344

spring:
  application:
    name: cloud-config-center #注册进Eureka服务器的微服务名
  cloud:
    config:
      server:
        git:
          uri: https://github.com/stonebridege/springcloud-config.git #GitHub上面的git仓库名字
          username: stonebridege
          password: 3f448795
          search-paths: springcloud-config
          skip-ssl-validation: true
          default-label: master
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka
  1. /{label}/{application}-{profile}.yml

    • master分支
      1. http://config-3344.com:3344/master/config-dev.yml
      2. http://config-3344.com:3344/master/config-test.yml
      3. http://config-3344.com:3344/master/config-prod.yml
    • dev分支
      1. http://config-3344.com:3344/dev/config-dev.yml
      2. http://config-3344.com:3344/dev/config-test.yml
      3. http://config-3344.com:3344/dev/config-prod.yml
  2. /{application}-{profile}.yml

    1. http://config-3344.com:3344/config-dev.yml
    2. http://config-3344.com:3344/config-test.yml
    3. http://config-3344.com:3344/config-prod.yml
    4. http://config-3344.com:3344/config-xxxx.yml(不存在的配置)
  3. /{application}/{profile}[/{label}]

    1. http://config-3344.com:3344/config/dev/master
    2. http://config-3344.com:3344/config/test/master
    3. http://config-3344.com:3344/config/test/dev
  4. 重要配置细节总结

    /{name}-{profiles}.yml
     
    /{label}-{name}-{profiles}.yml
     
    label:分支(branch)
    name :服务名
    profiles:环境(dev/test/prod)
    

15.2.11.已实现的模块

成功实现了用SpringCloud Config通过GitHub获取配置信息

15.3.Config客户端配置与测试

15.3.1.新建客户端cloud-config-client-3355

15.3.2.pomxml

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

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

15.3.3.bootstrap.yml

15.3.3.1.是什么

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

Spring Cloud会创建一个“Bootstrap Context”,作为Spring应用的Application Context的父上下文。初始化的时候,Bootstrap Context负责从外部源加载配置属性并解析配置,可以理解为先从Config Server加载总的统一的配置文件,加上application.yaml配置文,合并为一个配置。这两个上下文共享一个从外部获取的Environment

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

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

15.3.3.2.bootstrap.yaml
server:
  port: 3355

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

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

说明

15.3.4.主启动

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

15.3.5.业务类

@Controller
public class ConfigClientController {

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

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

15.3.6.测试

  1. 启动Config配置中心3344微服务并自测

    • http://config-3344.com:3344/master/config-prod.yml

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

  2. 启动3355作为Client准备访问

    • http://localhost:3355/configInfo

      02151703810

15.3.7.分布式配置的动态刷新问题

问题随时而来,分布式配置的动态刷新问题

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

执行步骤:

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

  2. 刷新3344,发现ConfigServer配置中心立刻响应,且返回为修改的最新的配置信息

    02153705417
  3. 刷新3355,发现ConfigClient客户端没有任何响应,且返回为原来的配置信息

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

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

15.4.Config客户端之动态刷新

运维人员修改config-dev.yml配置并提交到GitHub中,访问ConfigServer配置中心立刻响应,且返回为修改的最新的配置信息,访问ConfigClient客户端没有任何响应,且返回为原来的配置信息。避免每次更新配置都要重启客户端微服务3355,因此需要进行Config客户端动态刷新的配置。

15.4.1.动态刷新

15.4.1.1.修改ConfigClient客户端3355模块
  1. pom.xml引入actuator

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
  2. 修改bootstrap.yml,暴露监控端口

    # 暴露监控端点
    management:
      endpoints:
        web:
          exposure:
            include: "*"
    
  3. 业务类Controller增加@RefreshScope

    @Controller
    @RefreshScope
    public class ConfigClientController {
    
        @Value("${config.info}")
        private String configInfo;
    
        @RequestMapping(value = "/configInfo")
        @ResponseBody
        public String getConfigInfo() {
            return configInfo;
        }
    }
    
  4. 此时修改github,并访问3344,3355

    http://localhost:3355/configInfo

    3344,可以显示修改的内容,3355不能显示

  5. 刷新

    需要运维人员发送Post请求刷新3355

    curl -X POST "http://localhost:3355/actuator/refresh"
    
  6. 再次访问3344,3355

    http://localhost:3355/configInfo

    成功实现了客户端3355刷新到最新配置内容

15.4.2.手动刷新的问题

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

16.SpringCloud Bus消息总线

16.1.概述

SpringCloud Config分布式配置中心只能实现手动刷新。Spring Cloud Bus 配合 Spring Cloud Config 使用可以实现配置的动态刷新。

16.1.1.目前配置中心的问题

  1. 多个微服务客户端动态刷新配置,需要逐个手动刷新。
  2. 广播多个微服务刷新需要能否统一或者差异化的通知刷新。

16.1.2.是什么

自动版的动态刷新是SpringCloud Config分布式配置中心的完美配合,因此SpringCloud Config和SpringCloud Bus是最佳组合。

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

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

02174105704

实现步骤:

  1. 运维更新config,push updated config,推送到github。
  2. 配置中心此时可以获得更新的信息。
  3. 刷新一个微服务,其他已经订阅的微服务会受到感染,进行刷新操作。局域网广播达到全部更新的。

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

16.1.3.能实现的功能

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

此时将更新退给Config Server中心,其他的客户端全部被广播。

16.1.4.为何被称为总线

什么是总线

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

基本原理

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

16.2.RabbitMQ环境配置

**rabbitmq与erlang的官方匹配度建议:**https://www.rabbitmq.com/which-erlang.html#compatibility-matrix

16.2.1.安装Erlang

下载地址:https://www.erlang.org/downloads/23.3

配置环境变量

16.2.2.RabbitMQ

下载地址:https://www.rabbitmq.com/download.html

16.2.3.配置RabbitMQ

  1. 进入RabbitMQ安装目录下的sbin目录

  2. 输入以下命令启动管理功能

    rabbitmq-plugins enable rabbitmq_management
    
  3. 可视化插件

16.3.docker安装rabbitMQ

  1. docker安装RabbitMQ

    docker search rabbitmq
    
  2. 安装RabbitMQ

    docker pull rabbitmq
    

    这里是直接安装最新的,如果需要安装其他版本在rabbitmq后面跟上版本号即可

  3. 启动RabbitMQ

    docker run -d --hostname my-rabbit --name rabbit -p 15672:15672 -p 5672:5672 rabbitmq
    
  4. 安装插件

    1. 先执行docker ps 拿到当前的镜像ID

      docker ps 
      
    2. 进入容器

      docker exec -it 镜像ID /bin/bash
      
    3. 安装插件

      rabbitmq-plugins enable rabbitmq_management
      
    4. 退出

      exit
      
  5. 访问

    LInux地址+15672访问。这里的用户名和密码默认都是guest

    http://192.168.174.137:15672/#/

16.4.Bus动态刷新全局广播

必须先具备良好的RabbitMQ环境

16.4.1.创建新模块3366

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

16.4.1.1.新建cloud-config-client-3366
16.4.1.2.pom.xml
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
16.4.1.3.bootstrap.yml
server:
  port: 3366

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

#服务注册到eureka地址
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka
# 暴露监控端点
management:
  endpoints:
    web:
      exposure:
        include: "*"
16.4.1.4.主启动类
@EnableEurekaClient
@SpringBootApplication
public class ConfigClientMain3366 {
    public static void main(String[] args) {
        SpringApplication.run(ConfigClientMain3366.class, args);
    }
}
16.4.1.5.controller
@Controller
@RefreshScope
public class ConfigClientController {

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

    @RequestMapping(value = "/configInfo")
    @ResponseBody
    public String getConfigInfo() {
        return configInfo;
    }
}

16.4.2.设计思想

16.4.2.1.触发一个客户端刷新

利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置

16.4.2.2.触发Config Server刷新(推荐)

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

16.4.2.3.技术选型

触发Config Server刷新的架构显然更加适合,触发一个客户端刷新不适合的原因如下

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

16.4.3.具体实现

目标:一次修改,广播通知,处处生效

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

    1. pom.xml

      <!--添加消息总线RabbitMQ支持-->
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-bus-amqp</artifactId>
      </dependency>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
      
    2. application.yaml

      server:
        port: 3355
      
      spring:
        application:
          name: config-client
        cloud:
          #Config客户端配置
          config:
            label: master #分支名称
            name: config #配置文件名称
            profile: dev #读取后缀名称   上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
            uri: http://localhost:3344 #配置中心地址k
        #rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口
        rabbitmq:
          host: 192.168.174.143
          port: 5672
          username: guest
          password: guest
      #服务注册到eureka地址
      eureka:
        client:
          service-url:
            defaultZone: http://localhost:7001/eureka
      # 暴露监控端点
      management:
        endpoints:
          web:
            exposure:
              include: "*"
      
  2. 给cloud-config-client-3355客户端添加消息总线支持

    1. pom.xml

      <!--添加消息总线RabbitMQ支持-->
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-bus-amqp</artifactId>
      </dependency>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
      
    2. bootstrap.yml

      server:
        port: 3366
      
      spring:
        application:
          name: config-client
        cloud:
          #Config客户端配置
          config:
            label: master #分支名称
            name: config #配置文件名称
            profile: dev #读取后缀名称   上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
            uri: http://localhost:3344 #配置中心地址k
        #rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口
        rabbitmq:
          host: 192.168.174.143
          port: 5672
          username: guest
          password: guest
      #服务注册到eureka地址
      eureka:
        client:
          service-url:
            defaultZone: http://localhost:7001/eureka
      # 暴露监控端点
      management:
        endpoints:
          web:
            exposure:
              include: "*"
      
  3. 给cloud-config-client-3366客户

    1. pom.xml

      <!--添加消息总线RabbitMQ支持-->
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-bus-amqp</artifactId>
      </dependency>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
      
    2. bootstrap.yml

      server:
        port: 3366
      
      spring:
        application:
          name: config-client
        cloud:
          #Config客户端配置
          config:
            label: master #分支名称
            name: config #配置文件名称
            profile: dev #读取后缀名称   上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
            uri: http://localhost:3344 #配置中心地址k
        #rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口
        rabbitmq:
          host: 192.168.174.143
          port: 5672
          username: guest
          password: guest
      #服务注册到eureka地址
      eureka:
        client:
          service-url:
            defaultZone: http://localhost:7001/eureka
      # 暴露监控端点
      management:
        endpoints:
          web:
            exposure:
              include: "*"
      

16.4.4.测试

  1. 启动后访问各个接口,看能否正常返回数据

    • 配置中心:http://config-3344.com:3344/config-dev.yml

    • 客户端:

      1. http://localhost:3355/configInfo

      2. http://localhost:3366/configInfo

  2. 运维工程师修改github的配置文件

    执行命名POST刷新配置中心

    curl -X POST "http://localhost:3344/actuator/bus-refresh"
    
  3. 再次访问

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

    • http://localhost:3366/configInfo

  4. 登录RabbitMQ

16.5.Bus动态刷新定点通知

目标:不想全部通知,只想定点通知

16.5.1.基本概念

简单一句话:指定具体某一个实例生效而不是全部

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

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

16.5.2.示例

我们这里以刷新运行在3355端口上的config-client为例

只通知3355,不通知3366

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

16.5.3.通知总结All

17.SpringCloud Stream消息驱动

17.1.消息驱动概述

17.1.1.是什么

企业中分为前端、后端、以及大数据平台。比如电商网站中,后端中使用RabbitMQ,大数据平台使用的是Kafka。会导致一个系统存在两种MQ,切换、维护、开发都很麻烦。

有没有一种技术让我们不再关注MQ的细节,我们只需要一种适配绑定的方式,自动的给我们在各种MQ内切换,屏蔽底层差异。

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

17.1.1.1.什么是SpringCloudStream

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

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

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

目前仅支持RabbitMQ、Kafka。

17.1.1.2.官网

https://spring.io/projects/spring-cloud-stream#overview

Spring Cloud Stream 是一个框架,用于构建与共享消息系统连接的高度可扩展的事件驱动微服务。

该框架提供了一个灵活的编程模型,该模型建立在已经建立和熟悉的 Spring 习惯用法和最佳实践之上,包括对持久发布/订阅语义、消费者组和有状态分区的支持。

Spring Cloud Stream中文指导手册:https://m.wang1314.com/doc/webapp/tohttps://stonebridge.oss-cn-shanghai.aliyuncs.com/springcloud/20971999.html

17.1.2.设计思想

17.1.2.1.标准MQ
03142715881

消息发送,以某种格式封装消息到我们的队列,订阅的服务就来里面取。

生产者/消费者之间靠消息媒介传递信息内容Message

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

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

17.1.2.2.为什么用Spring Cloud Stream

比方说我们用到了RabbitMQ和Kafka,由于这两个消息中间件的架构上的不同,像RabbitMQ有exchange,kafka有Topic和Partitions分区。Spring Cloud Stream将不同平台进行翻译对话,去除差异化。

这些中间件的差异性导致我们实际项目开发给我们造成了一定的困扰,我们如果用了两个消息队列的其中一种,后面的业务需求,我想往另外一种消息队列进行迁移,这时候无疑就是一个灾难性的,一大堆东西都要重新推倒重新做,因为它跟我们的系统耦合了,这时候springcloud Stream给我们提供了一种解耦合的方式。

stream凭什么可以统一底层差异?

在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接与消息中间件进行信息交互的时候,

由于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性

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

通过向应用程序暴露统一的Channel通道,使得应用程序不需要再考虑各种不同的消息中间件实现。

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

Binder

INPUT对应于消费者

OUTPUT对应于生产者

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

03154536244

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

Binder可以生成Binding,Binding用来绑定消息容器的生产者和消费者,它有两种类型,INPUT和OUTPUT,INPUT对应于消费者,OUTPUT对应于生产者。

17.1.2.3.Stream中的消息通信方式

遵循了发布-订阅模式

Topic主题进行广播

  • 在RabbitMQ就是Exchange
  • 在Kakfa中就是Topic

17.1.3.Spring Cloud Stream标准流程套路

官方架构图

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

17.1.4.编码API和常用注解

17.2.案例说明

RabbitMQ环境已经准备好了

工程中新建三个子模块

  1. cloud-stream-rabbitmq-provider8801, 作为生产者进行发消息模块
  2. cloud-stream-rabbitmq-consumer8802,作为消息接收模块
  3. cloud-stream-rabbitmq-consumer8803 作为消息接收模块

17.3.消息驱动之生产者

17.3.1.新建Module

17.3.2.pom.xml

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-stream-rabbit</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>

17.3.3.application.yaml

server:
  port: 8801

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

因为访问的rabbitMQ部署在192.168.174.144的docker上

启动时报:org.springframework.amqp.AmqpConnectException: java.net.ConnectException: Connection refused: connect。

解决方案

增加spring.rabbitmq的配置

17.3.4.主启动类StreamMQMain8801

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

17.3.5.业务类

  1. 发送消息接口

    public interface IMessageProvider {
        String send();
    }
    
  2. 发送消息接口实现类

    @EnableBinding(Source.class) // 可以理解为是一个消息的发送管道的定义
    public class MessageProvider implements IMessageProvider {
        @Resource
        private MessageChannel output; // 消息的发送管道
    
        @Override
        public String send() {
            String serial = UUID.randomUUID().toString();
            this.output.send(MessageBuilder.withPayload(serial).build()); // 创建并发送消息
            System.out.println("***serial: " + serial);
            return serial;
        }
    }
    
  3. Controller

    @Controller
    public class SendMessageController {
        @Resource
        private IMessageProvider messageProvider;
    
        @RequestMapping(value = "/sendMessage")
        @ResponseBody
        public String sendMessage() {
            return messageProvider.send();
        }
    }
    

17.3.6.测试

启动7001eureka,启动rabbitmq

http://localhost:8801/sendMessage

http://localhost:15672/

17.4.消息驱动之消费者

17.4.1.新建消费者module

cloud-stream-rabbitmq-provider8801

17.4.2.pom.xml

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!--基础配置-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

17.4.3.application.yaml

server:
  port: 8802

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

避免启动时报:org.springframework.amqp.AmqpConnectException: java.net.ConnectException: Connection refused: connect。

增加spring.rabbitmq的配置

17.4.4.主启动类

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

17.4.5.业务类

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

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

17.4.6.测试8801发送8802接收消息

http://localhost:8801/sendMessage

17.5.分组消费与持久化

17.5.1.依照8802,clone出来一份运行8803

17.5.2.启动

17.5.3.问题1:有重复消费问题

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

03193351370

故障现象:重复消费

导致原因:默认分组group是不同的,组流水号不一样,被认为不同组,可以消费。

解决方案:自定义配置分为同一组解决。

17.5.3.1.生产实际案例

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

03193546098

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

17.5.3.2.解决方案

分组和持久化属性group

17.5.4.分组

未分组时

03194502999

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

8802/8803都变成不同组,group两个不同,导致都会消费因此将其放置在一个组中。8802/8803实现了轮询分组,每次只有一个消费者8801模块的发的消息只能被8802或8803其中一个接收到,这样避免了重复消费。

  • 8802修改YML

    03222034489
  • 8803修改YML

    03222225965

同一个组的多个微服务实例,每次只会有一个拿到

03222424997

17.5.5.问题2:持久化

通过上述,解决了重复消费问题,再看看持久化

执行一下步骤:

  1. 停止8802/8803并去除掉8802的分组group: atguiguA。8803的分组group: atguiguA没有去掉

  2. 8801先发送4条消息到rabbitmq

  3. 先启动8802,无分组属性配置,后台没有打出来消息

    03222625885
  4. 再启动8803,有分组属性配置,后台打出来了MQ上的消息

18.SpringCloud Sleuth分布式请求链路跟踪

18.1.概述

18.1.1.为什么会出现这个技术?

需要解决哪些问题?

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

例如订单系统调用库存系统、调用支付系统、调用积分系统。

18.1.2.是什么

https://github.com/spring-cloud/spring-cloud-sleuth

Spring Cloud Sleuth提供了一套完整的服务跟踪的解决方案

在分布式系统中提供追踪解决方案并且兼容支持了zipkin

18.1.3.解决

假设现在产品调用库存模块,发送链路数据后,无论谁调用谁,或者互相调用。ZIPKIN就会记录下来,最终会以网页的形式展示出来。

18.2.搭建链路监控步骤

18.2.1.zipkin

18.2.1.1.下载

https://repo1.maven.org/maven2/io/zipkin/java/zipkin-server/2.12.9/

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

18.2.1.1.运行jar
java -jar zipkin-server-2.12.9-exec.jar
18.2.1.2.运行控制台

http://localhost:9411/zipkin/

术语

  1. 完整的调用链路

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

    同一Trace ID的所有请求构成请求链路

  2. 图解

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

  3. 名词解释

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

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

18.2.2.服务提供者

修改cloud-provider-payment8001

  1. pom.xml

    引入spring-cloud-starter-zipkin

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

    spring:
      application:
        name: cloud-payment-service
      zipkin:
        base-url: http://localhost:9411
      sleuth:
        sampler:
          #采样率值介于 0 到 1 之间,1 则表示全部采集
         probability: 1
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource            # 当前数据源操作类型
        driver-class-name: org.gjt.mm.mysql.Driver              # mysql驱动包
        url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false
        username: root
        password: 123456
    
  3. 业务类PaymentController

    @RequestMapping("/payment/zipkin")
    @ResponseBody
    public String paymentZipkin() {
        return "hi ,i'am paymentzipkin server fall back,welcome to atguigu,O(∩_∩)O哈哈~";
    }
    

18.2.3.服务消费者(调用方)

cloud-consumer-order80

  1. pom.xml

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

    04110618677
    spring:
        application:
            name: cloud-order-service
        zipkin:
          base-url: http://localhost:9411
        sleuth:
          sampler:
            probability: 1
    
  3. 业务类OrderController

    @RequestMapping("/consumer/payment/zipkin")
    @ResponseBody
    public String paymentZipkin() {
        String result = restTemplate.getForObject(PAYMENT_URL + "/payment/zipkin/", String.class);
        return result;
    }
    

18.2.4.测试

依次启动eureka7001/8001/80。80调用8001几次测试下

http://localhost/consumer/payment/zipkin

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

查看依赖关系

原理

表示全部采集 probability: 1 datasource: type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型 driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包 url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: 123456 ```
  1. 业务类PaymentController

    @RequestMapping("/payment/zipkin")
    @ResponseBody
    public String paymentZipkin() {
        return "hi ,i'am paymentzipkin server fall back,welcome to atguigu,O(∩_∩)O哈哈~";
    }
    

18.2.3.服务消费者(调用方)

cloud-consumer-order80

  1. pom.xml

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

    04110618677
    spring:
        application:
            name: cloud-order-service
        zipkin:
          base-url: http://localhost:9411
        sleuth:
          sampler:
            probability: 1
    
  3. 业务类OrderController

    @RequestMapping("/consumer/payment/zipkin")
    @ResponseBody
    public String paymentZipkin() {
        String result = restTemplate.getForObject(PAYMENT_URL + "/payment/zipkin/", String.class);
        return result;
    }
    

18.2.4.测试

依次启动eureka7001/8001/80。80调用8001几次测试下

http://localhost/consumer/payment/zipkin

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

查看依赖关系

原理

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值