# cloud 学习 **Repository Path**: dayu007/cloud2020 ## Basic Information - **Project Name**: cloud 学习 - **Description**: No description available - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-03-24 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # SpringCloud 学习 ​ @auth: dayu @email: dayu007@foxmail.com ------ # OpenFeign ## 概述 Feign是一个声明式的Web服务客户端,让编写Web服务客户端变的非常容易。 **只需要创建一个接口并在接口上添加一些注解即可 ** 前面在使用Ribbon+ RestTemplate时,利用RestTemplate对http请求的封装处理, 形成了套模版化的调用方法。 但是在实际开发中,由于对服务依赖的调用可能不止一处, 往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装。这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。 在Feign的实现下我们只需创建一个接口并使用注解的方式来配置它(以前是 Dao接口.上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。 ## 集成Ribbon 利用Ribbon维护了Payment的服务列表信息,并且通过**轮询**实现了客户端的负载均衡。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用 编码和调用规则 - 启动项激活openfeign功能 @EnableFeignClients - 调用的地方需要加上FeginClient注解,和调用服务的名称,调用的方法不可以写错,保持和服务端的方法一致。 ### 设置客户端超时时间 在yml配置文件中配置超时时间 ```yaml #OpenFeign默认支持Ribbon ribbon: #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间 ReadTimeout: 5000 #指的是建立连接后,从服务器读取到可用资源的时间 ConnectTimeout: 5000 ``` ## 日志打印 自定义一个类,设置日志打印的等级,在yml文件中指定打印特定的作用域 ```java @Configuration public class FeignConfig { @Bean Logger.Level feignLoggerLevel(){ return Logger.Level.FULL; } } ``` ```yaml logging: level: #feign日志以什么级别开启哪个接口的日志 com.atguigu.springcloud.service.OrderFeignService: debug ``` # Hystrix(熔断,降级) ## 概述 Hystrix是一个用于处理分布式系统的延迟和容错的开源库, 在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。 **“断路器”**本身是一种开关装置, 当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),**向调用方返回一个符合预期的、可处理的备选响应(FallBack)**, 而不是长时间的等待或者抛出调用方无法处理的异常, 这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了敌障在分布式系统中的蔓延,乃至雪崩。 ## 服务降级 设置自身调用超时时间的峰值,峰值内可以正常运行,超时了需要有兜底的方法处理,作为服务降级fallback ### 单独配置 ​ 在个方法上单独配置服务降级,方法名上添加注解`@HystrixCommand` ```java @HystrixCommand(fallbackMethod = "paymentInfoTimeoutHandler", commandProperties = { @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value = "3000")}) //1、fallbackMethod指定降级的方法,当方法超时,异常时触发该方法 //2、HystrixProperty设置相关属性,超时时间超过3秒则触发 ``` ### 默认配置 写一个默认的处理方法,当不特定指定降级方法的时候触发该默认方法 - 步骤一:类名上添加默认降级方法 - 方法名上写上注解 @HystrixCommand - yml文件添加Hystrix功能 - 启动类上添加开启Hystrix功能注解 ```java @DefaultProperties(defaultFallback = "paymentGlobalFallback") public class ASDF{ @GetMapping("/consumer2/payment/timeout/{id}") @HystrixCommand public String getTimeoutWithHystrixCommand(@PathVariable("id") Long id) { int a=10/0; return result; } public String paymentGlobalFallback(){ return "消费者客户端的默认处理方法,你调用的方法出现了故障或者超时了,等会再试试吧"; } } ``` ```yaml server: port: 80 eureka: client: register-with-eureka: true service-url: defaultZone: http://eureka7001.com:7001/eureka # 设置openfeign客户端超时时间,OpenFeign默认支持Ribbon ribbon: ReadTimeout: 6000 ConnectTimeout: 6000 feign: hystrix: enabled: true ``` ```java @SpringBootApplication @EnableFeignClients @EnableHystrix public class HystrixOrder80 { public static void main(String[] args) { SpringApplication.run(HystrixOrder80.class, args); } } ``` ### 问题: - 降级的方法和原方法在同一个类中,代码混乱 - 单独配置的降级方法,代码膨胀 ### 处理方法 为客户端FeignClient调用的接口写一个实现类,专门处理服务降级 ```java @Component @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = OrderHystrixServiceImpl.class) public interface OrderHystrixService { @GetMapping("/payment/get/{id}") public String getOk(@PathVariable("id") Long id); @GetMapping("/payment/timeout/{id}") public String getTimeout(@PathVariable("id") Long id); } ``` 实现类的方法 ```java /** * @USER: dayu * @DATE: 2020/3/27 * @DESCRIPTION: 实现feign调用接口,用于写服务降级 */ @Component public class OrderHystrixServiceImpl implements OrderHystrixService { @Override public String getOk(Long id) { System.out.println("OrderHystrixServiceImpl.getOk"); return "=====OrderHystrixServiceImpl.getOk======服务降级了"; } @Override public String getTimeout(Long id) { System.out.println("OrderHystrixServiceImpl.getTimeout"); return "=====OrderHystrixServiceImpl.getTimeout======服务降级了,等待结果。。。。。"; } } ``` ## 服务熔断 ### 熔断机制概述 熔断机制是应对雪崩效应的一-种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时, 会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。 在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败, 就会启动熔断机制。熔断机制的注解是@HystrixCommand. ### demo案例 #### 1、改pom文件 ```xml org.springframework.cloud spring-cloud-starter-netflix-hystrix org.springframework.cloud spring-cloud-starter-netflix-eureka-client com.atgugui.springcloud cloud-commons 1.0-SNAPSHOT org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.projectlombok lombok org.springframework.boot spring-boot-devtools runtime true org.springframework.boot spring-boot-starter-test test ``` #### 2、service类中写熔断方法 ```java import cn.hutool.core.util.IdUtil; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.PathVariable; import java.util.concurrent.TimeUnit; /** * @USER: dayu Administrator * @DATE: 2020/3/26 * @DESCRIPTION: */ @Service public class PaymentHystrixService { @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = { @HystrixProperty(name = "circuitBreaker.enabled",value = "true"),//是否开启断路器 @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),//请求次数。使用统计信息做出打开/关闭决定之前,必须在statisticalWindow内发出的请求数 @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),//电路跳闸后的毫秒数,然后允许重试 @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60")//不能使电路跳闸的“标记”的百分比 }) public String paymentCircuitBreaker(Long id){ if(id<0){ throw new RuntimeException("id 不能为负数!!!!"); } //生成一个唯一的流水号 String serialNumber = IdUtil.simpleUUID(); return Thread.currentThread().getName()+" 调用成功,流水号:"+serialNumber; } //服务熔断的方法 public String paymentCircuitBreaker_fallback(Long id){ return "id 不能为负数,请稍后再试试: id:"+id; } } ``` #### 3、写controller调用方法 ```java @GetMapping("/payment/circuit/{id}") public String paymentCircuitBreaker(@PathVariable("id") Long id){ String result = paymentHystrixService.paymentCircuitBreaker(id); log.info("result:"+result); return result; } ``` #### 4、测试 在第三步中,注解的一些参数信息里,写明了方法打开断路器 @HystrixProperty里面的属性都在`com.netflix.hystrix.HystrixCommandProperties`类中可以找到默认的方法属性和名词解释 ```java @HystrixProperty(name = "circuitBreaker.enabled",value = "true"),//是否开启断路器 @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),//请求次数。使用统计信息做出打开/关闭决定之前,必须在statisticalWindow内发出的请求数 @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),//电路跳闸后的毫秒数,然后允许重试 @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60")//不能使电路跳闸的“标记”的百分比(失败率超过60%就打开断路器,进入断路器方法) ``` ### 总结 ![1585295684279](assets/1585295684279.png) ### 熔断类型 1. 熔断打开 请求不在进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到设定的时长时进入半熔断状态 2. 熔断关闭 熔断关闭不会对服务进行熔断 3. 熔断半开 部分请求根据规则调用当前服务,如果请求成功,且符合规则,则认为当前服务恢复正常,关闭熔断 #### 熔断器在什么状态下起作用 ![1585296154925](assets/1585296154925.png) #### 断路器开启或者关闭的条件 1. 当满足一定的阀值的时候(默认10秒内超过20个请求次数) 2. 当失败率达到一定的时候( 默认10秒内超过50%的请求失败) 3. 到达以上阀值,断路器将会开启 4. 当开启的时候,所有请求都不会进行转发 5. 一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启。复4和5 #### 断路器打开之后 1. 再有请求调用的时候,将不会调用主逻辑,而是直接调用降级fallback。通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。 2. 原来的主逻辑要如何恢复呢? 对于这一-问题, hystrix也为我们实现了自动恢复功能。 当断路器打开,对主逻辑进行熔断之后,hystrix会启动- 个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。 ## Hystrix仪表盘图形展示 ### 代码案例 1. 创建module,pom.xml配置 ```xml org.springframework.cloud spring-cloud-starter-netflix-hystrix-dashboard org.springframework.boot spring-boot-starter-actuator ``` 2. 启动类上激活仪表盘 ```java @SpringBootApplication @EnableHystrixDashboard public class HystrixDashboard9001 { public static void main(String[] args) { SpringApplication.run(HystrixDashboard9001.class, args); } } ``` 3. 所有的provide微服务要添加监控依赖配置 ```xml org.springframework.boot spring-boot-starter-actuator ``` 4. 启动9001启动类,监控微服务出现豪猪的主页就对了 5. 配置微服务项目 - 在微服务pom中添加3中指定的依赖 - 在启动项或者配置类中添加@Bean ```java /** *此配置是为了服务监控而配置,与服务容错本身无关, springcloud 升级后的坑 *ServletRegistrationBean因为springboot的默认路径不是"/hystrix. stream", *只要在自己的项目里配置上:下面的servlet就可以了 */ @Bean public ServletRegistrationBean getServlet(){ HystrixMetricsStreamServlet hystrixMetricsStreamServlet = new HystrixMetricsStreamServlet(); ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(hystrixMetricsStreamServlet); servletRegistrationBean.setLoadOnStartup(1); servletRegistrationBean.addUrlMappings("/hystrix.stream"); servletRegistrationBean.setName("HystrixMetricsStreamServlet"); return servletRegistrationBean; } ``` ### 测试地址 ### 怎么看 - 7色 其中颜色分别对应数据显示 ![1585300062147](assets/1585300062147.png) - 一圈 ![1585300120358](assets/1585300120358.png) 实心圆:共有两种含义。它通过颜色的变化代表了实例的健康程度,它的健康度从**绿色<黄色<橙色<红色递减**。 该实心圆除了颜色的变化之外,它的大小也会根据实例的请求流量发生变化,**流量越大该实心圆就越大**。所以通过该实心圆的展示,就可以在大量的实例中快速的发现**故障实例和高压力实例**。 - 一线 曲线:用来记录2分钟内流量的相对变化,可以通过它来观察到流量的上升和下降趋势。 - 整图说明 ![1585300283961](assets/1585300283961.png) # GateWay服务网关 ## 概述 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 链的方式提供了网关基本的功能,例如:安全,监控指标,和限流。 ## 作用 - 反向代理 - 鉴权 - 熔断 - 日志监控 - 。。。。 ## 网管的位置 ![1585302224403](assets/1585302224403.png) ## 相关疑问: ### Zuul了为什么选择gateway? 1. neflix不太靠谱,zuul2.0一直跳票,迟迟不发布 一方面因为Zuul1.0已经进入了维护阶段,而且Gateway是SpringCloud团队研发的, 是亲儿子产品,值得信赖。 而且很多功能Zuul都没有用起来也非常的简单便捷。 Gateway是基于异步非阻塞模型上进行开发的,性能方面不需要担心。虽然Netflix早就发布了最新的Zuul 2.x, 但Spring| Cloud貌似没有整合计划。而且Netflix相关组件都宣布进入维护期;不知前景如何? 多方面综合考虑Gateway是很理想的网关选择。 2. SpringCloud Gateway具有如下特性重 Spring Cloud Gatewaly具有如下特性: 基于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, 是-个基于阻塞|/ 0的API Gateway 2、Zuul 1.x基于Servlet 2.5使用阻塞架构它不支持任何长连接(如WebSocket) Zuul的设计模式和Nginx较像,每次I/ 0操作都是从 工作线程中选择一个执行, 请求线程被阻塞到工作线程完成,但是差别是Nginx用C++实现,Zuul 用Java实现,而JVM本身会有第 一次加载较慢的情况,使得Zuul的性能相对较差。 3、Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。Zuul 2.x的性能较Zuul 1.x有较大提升 。在性能方面,根据官方提供的基准测试, Spring Cloud Gateway的RPS (每秒请求数)是Zuul的1.6倍。 4、Spring Cloud Gateway建立在Spring Framework 5、Project Reactor和Spring Boot2之上,使用非阻塞API。 ### Zuul 1.x模型 Springcloud中所集成的Zuul版本,采用的是Tomcat容器,使用的是传统的Servlet IO处理模型。 学过尚硅谷web中期课程都知道一个题目, Servlet的生 命周期?servlet由servlet container进行生命周期管理。 container启动时构造servlet对象并调用servlet init0进行初始化; container运行时接受请求,为每个请求分配一个线程(- 般从线程池中获取空闲线程) 然后调用service0。 container关闭时调用servlet destory0销毁servlet; ![1585302684529](assets/1585302684529.png) **上述模式的缺点:** servlet是一 个简单的网络I0模型,当请求进入servlet container时, servlet container就会为其绑定一 个线程,在并发不高的场景下这种模型是适用 的。但是一旦高并发(此如抽风用jemeter压), 线程数量就会.上涨,而线程资源代价是昂贵的(上线文切换,内存消耗大)严重影响请求的处理时间。 在一 些简单业务场景下,不希望为每个request分配一 个线程,只需要1个或几个线程就能应对极大并发的请求,这种业务场景下servlet模型没有优势 所以Zuul 1.X是基于servlet之 上的一个阻塞式处理模型,即spring实现了处理所有request请求的一 个servlet (DispatcherServlet) 并由该servlet阻 塞式处理处理。所以Springcloud Zuul无法摆脱servlet模型的弊端 ### GateWay模型 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来实现响应式流规范。 ## GateWay三大核心概念 - Route路由 路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤组成,如果断言为true则匹配该路由 - Predicate断言 参考的是Java8的java.util.function.Predicate 开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由 - Filter过滤 ​ 指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。 - **总体** web请求,通过一些匹配条件, 定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。 predicate就是我们的匹配条件; 而filter,就可以理解为一个无所不能的拦截器。 有了这两个元素,再加上目标uri;,就可以实现个具体的路由了 ## GateWay工作流程 - 官网总结 ![1585303469007](assets/1585303469007.png) - **核心逻辑** ​ 路由转发和执行过滤器链 ## GateWay代码demo ### 1、创建module,写pom.xml ```xml org.springframework.cloud spring-cloud-starter-gateway org.springframework.cloud spring-cloud-starter-netflix-eureka-client com.atgugui.springcloud cloud-commons 1.0-SNAPSHOT org.projectlombok lombok org.springframework.boot spring-boot-devtools runtime true org.springframework.boot spring-boot-starter-test test ``` ### 2、写application.yml ```yaml server: port: 9527 spring: application: name: cloud-gateway cloud: gateway: discovery: locator: enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由 routes: - id: payment-route #路由的ID,没有固定的规则但要求唯一,建议配合服务器 # uri: http://localhost:8001 # 匹配后提供服务的路由地址 uri: lb://cloud-payment-service # 匹配后提供服务的路由地址,实现负载均衡 predicates: - Path=/payment/get/** #断言,路径相匹配的才可进行路由 - id: payment-route2 #路由的ID,没有固定的规则但要求唯一,建议配合服务器 # uri: http://localhost:8001 # 匹配后提供服务的路由地址 uri: lb://cloud-payment-service # 匹配后提供服务的路由地址,实现负载均衡 predicates: - Path=/payment/lb/** #断言,路径相匹配的才可进行路由 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://eureka7001.com:7001/eureka instance: hostname: cloud-gateway-service ``` 访问说明: ![1585556794745](assets/1585556794745.png) ### 3、写启动类 ```java @SpringBootApplication @EnableEurekaClient public class GateWayMain9527 { public static void main(String[] args) { SpringApplication.run(GateWayMain9527.class, args); } } ``` ### 4、测试启动 启动eureka和service服务,然后启动路由service,在步骤2中配置了路由的规则,服务端8001端口下的两个路径添加了断言,只有相匹配的路径在可以由该9527路由转发, 测试项目启动了8001,访问效果如下 ![1585556669510](assets/1585556669510.png) 通过网关测试的结果如下: ![1585556709390](assets/1585556709390.png) 由此一个网关demo完成。 ## GateWay的两种网关配置 1. yml配置 (上面介绍了) 2. 代码中注入RouteLocator的Bean ### 代码中注入RouteLocator的Bean ```java 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; /** * @USER: dayu * @DATE: 2020/3/30 * @DESCRIPTION: 代码注入方式自定义网关 */ @Configuration public class GateWayConfig { @Bean public RouteLocator route(RouteLocatorBuilder routeLocatorBuilder){ //id唯一需要自定义,patterns为路径,uri为路由的地址 return routeLocatorBuilder.routes() .route("my_route2",r->r.path("/explore/**").uri("https://gitee.com/explore")) .route("my_route2",r->r.path("/s/**").uri("http://baijiahao.baidu.com/s")) .build(); } } ``` 启动gateway,下面是路由效果 ![1585561237338](assets/1585561237338.png) ### 路由Predicate配置: 1. After Route Predicate 2. Before Route Predicate 3. Between Route Predicate 4. Cookie Route Predicate 5. Header Route Predicate 6. Host Route Predicate 7. Method Route Predicate 8. Path Route Predicate 9. Query Route Predicate After,Before,Between都是配置服务的生效时间范围,在指定的时间范围内才可以正常调用服务。 Cookie设置,是调用放必须要指定正确的cookie Host Route Predicate yml的demo如下: ```yaml spring: application: name: cloud-gateway cloud: gateway: discovery: locator: enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由 routes: - id: payment-route #路由的ID,没有固定的规则但要求唯一,建议配合服务器 uri: lb://cloud-payment-service # 匹配后提供服务的路由地址 predicates: - Path=/payment/get/** #断言,路径相匹配的才可进行路由 - id: payment-route2 #路由的ID,没有固定的规则但要求唯一,建议配合服务器 uri: lb://cloud-payment-service # 匹配后提供服务的路由地址 predicates: - Path=/payment/lb/** #断言,路径相匹配的才可进行路由 #断言,路径相匹配的进行路由,时间的格式由 ZonedDateTime.now() 打印出来的本地时间格式 # - After=2020-03-30T17:57:45.963+08:00[Asia/Shanghai] # #断言,路径相匹配的进行路由 # - Before=2020-03-31T17:57:45.963+08:00[Asia/Shanghai] # #断言,路径相匹配的进行路由 - Between=2020-03-30T17:57:45.963+08:00[Asia/Shanghai],2020-03-31T20:57:45.963+08:00[Asia/Shanghai] #调用方式 : curl http://localhost:9527/payment/lb --cookie "username=zzyy" - Cookie=username , zzyy #调用方式 : curl http://localhost:9527/payment/lb -H "X-Request-Id:123" - Header=X-Request-Id, \d+ # 请求头要有X- Request-Id属性并且值为整数的正则表达式 # - Host=**.atguigu.com # - Method=GET # - Query=username, \d+ #要有参数名username并且值还要是整数才能路由 ``` Predicate就是为了匹配规则,为了让请求找到对应的匹配进行路由。 ### Filter配置 路由过滤器可用于修改进入的HTTP请求和返回HTTP响应,路由过滤器只能指定路由进行使用。 Spring Cloud Gateway 内置了多种路由过滤器,他们都是由GatewayFilter工厂产生。 #### 生命周期 1. pre 2. post #### 种类 1. GatewayFilter 2. GlobalFilter #### 自定义过滤器 ```java import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * @USER: dayu * @DATE: 2020/3/31 * @DESCRIPTION:自定义gateway的全局过滤器 */ @Component @Slf4j public class MyGlobalFilter implements GlobalFilter, Ordered { @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { String uname = exchange.getRequest().getQueryParams().getFirst("uname"); if (uname==null){ log.info("用户名为null,非法用户,o(╥﹏╥)o"); exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } @Override public int getOrder() { return 0; } } ``` # 分布式配置中心(SpringCloud Config) **分布式服务配置问题:** 微服务意味着要将单体应用中的业务拆分成一个个 子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以套集中式的、动态的配置管理设施是必不可少的。 SpringCloud提供了ConfigServer来解决这个问题, 我们每一 个微服务自己带着一个application.yml, 上百个配置文件的管理... ## 概述 SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器**为各个不同微服务应用**的所有环境提供了一个中心化的外部配置。 SpringCloud Config分为**服务端**和**客户端**两部分。 服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口 客户端则是通过指定的配置中心来管理应用资源,以吸与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并组可以通过git客户端I具来方便的管理和访问配置内容 ## 作用 - 集中管理配置文件 - 不同环境不同配置,动态化的配置更新,分环境部署比如dev/test/prod/beta/release - 运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统- -拉取配置 自己的信息 - 当配置发生变动时,服务不需要重启即可感知到配置的变化并应用新的配置 - 将配置信息以REST接口的形式暴露 ## 与GitHub整合配置 由于SpringCloud Config默认使用Git来存储配置文件(也有其它方式,比如支持SVN和本地文件),但最推荐的还是Git,而且使用的是http/https访问的形式 ## 服务端配置和测试 1. 用你自己的账号在GitHub上新建一一个名为springcloud-config的新Repository 2. 由上一步获得刚新建的git地址git@github.com:dayu007/springcloud-config.git 3. 本地硬盘目录上新建git仓库并clone 4. 此时在本地D盘符下D:/SpringCloud2020/springcloud-config 5. 新建Module模块cloud-config-center-3344它即为Cloud的配置中心模块cloudConfig Center 6. POM口 ```xml cloud2020 com.atgugui.springcloud 1.0-SNAPSHOT 4.0.0 cloud-config3344 org.springframework.cloud spring-cloud-config-server com.atgugui.springcloud cloud-commons 1.0-SNAPSHOT org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.projectlombok lombok org.springframework.boot spring-boot-devtools org.springframework.boot spring-boot-starter-test test ``` 7. YML口 ```yaml server: port: 3344 eureka: client: fetch-registry: true register-with-eureka: true service-url: defaultZone: http://eureka7001.com:7001/eureka spring: application: name: cloud-config-center cloud: config: server: git: #git远程目录 uri: git@github.com:dayu007/springcloud-config.git #搜索目录 search-path: - springcloud-config label: master ``` 8. 主启动类 ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.config.server.EnableConfigServer; /** * @USER: dayu * @DATE: 2020/3/31 * @DESCRIPTION: springcloud-config主启动类,需要激活configServer */ @SpringBootApplication @EnableConfigServer public class CloudConfigApplication3344 { public static void main(String[] args) { SpringApplication.run(CloudConfigApplication3344.class, args); } } ``` 9. windows'下修改hosts文件,增加映射 ```tex 127.0.0.1 config-3344.com ``` 10. 测试通过Config微服务是否可以从GitHub.上获取配置内容 - 启动配置类 - 访问:http://config-3344.com:3344/master/config-dev.yml 11. 配置读取规则 - **/{label}/{application}-{profile}.yml** - master分支 http://config-3344.com:3344/master/config-dev.yml http://config-3344.com:3344/master/config-test.yml http://config-3344.com:3344/master/config-prod.yml - dev分支 http://config-3344.com:3344/dev/config-dev.yml http://config-3344.com:3344/dev/config-test.yml http://config-3344.com:3344/dev/config-prod.yml - **/{application)-{profile}.yml** ​ http://config-3344.com:3344/config-dev.yml ​ http://config-3344.com:3344/config-test.yml ​ http://config-3344.com:3344/config-prod.yml ​ http://config-3344.com:3344/config-xxx.yml(不存在的配置) - **/{application}/{profle}[/{label}** ​ http://config-3344.com:3344/config/dev/master ​ http://config-3344.com:3344/config/test/master 12. 总结 ````tex label:分支(branch) name :服务名 profiles:环境(dev/test/prod) ```` ## 客户端配置和测试 1. 新建cloud-config-client-3355 2. POM ```xml cloud2020 com.atgugui.springcloud 1.0-SNAPSHOT 4.0.0 cloud-config-client3355 org.springframework.cloud spring-cloud-starter-config com.atgugui.springcloud cloud-commons 1.0-SNAPSHOT org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.projectlombok lombok org.springframework.boot spring-boot-devtools org.springframework.boot spring-boot-starter-test test ``` 3. bootstrap.yml applicaiton. yml是用户级的资源配置项 bootstrap. yml是系统级的,优先级更加高 Spring Cloud会创建一个"Bootstrap Context" , 作为Spring应用的Application Context的父上下文。初始化的时候,Bootstrap Context'负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的Environment'。 Bootstrap属性有高优先级,默认情况下,它们不会被本地配置覆盖。Bootstrap context和Application Context'有着不同的约定,所以新增了一个bootstrap.yml文件, 保证Bootstrap Context和Application Context配置的分离。 **要将Client模块下的application.yml文件改为bootstrap.yml,这是很关键的**,因为bootstrap.ymI是比application.yml先加载的。bootstrap.yml优先级高于application.yml bootstrap. yml具体demo ```yaml server: port: 3355 eureka: client: service-url: defaultZone: http://eureka7001.com:7001/eureka spring: application: name: cloud-config-client cloud: config: label: master #分支名称 name: config #配置文件名称 profile: dev #读取的文件后缀 #上述3个综合起来就是:master分支上config-dev.yml的配置文件被读取 #读取的uri是http://config-3344.com:3344/master/config-dev.yml uri: http://localhost:3344 ``` 4. 修改config-dev.ym|配置并提交到GitHub中,比如加个变量age或者版本号version 5. 主启动类 ```java @SpringBootApplication @EnableEurekaClient public class CloudConfigClient3355 { public static void main(String[] args) { SpringApplication.run(CloudConfigClient3355.class, args); } } ``` 6. 业务类 ```java package com.atguigu.springcloud.controller; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * @USER: dayu * @DATE: 2020/3/31 * @DESCRIPTION: */ @RestController @RefreshScope //用于运维人员手动post通知客户端刷新配置 public class ConfigClientController { //读取GitHub上的配置信息 @Value("${config.info}") private String configInfo; @GetMapping("/configInfo") public String getRemoteConfigInfo() { return configInfo; } } ``` 7. 测试 - 启动Config配置中心3344微服务并自测 ​ http://config-3344.com:3344/master/config-prod.yml ​ http://config-3344.com:3344/master/config-dev.yml ![1585647161548](assets/1585647161548.png) - 启动3355作为Client准备访问 http://localhost:3355/configInfo ​ ![1585647174696](assets/1585647174696.png) 8. 成功实现了客户端3355访问SpringCloud Config3344通过GitHub获取配置信息 ### 分布式配置的动态刷新问题 当运维人员在GitHub修改了config配置文件的时候,服务端3344获取的数据是及时从GitHub拉取下来的,但是3355端口的客户端获取的数据还是没有变化,只有重启3355服务才可以获取到新的数据信息。。。 #### 具体问题: - Linux运维修改GitHub.上的配置文件内容做调整 - 刷新3344,发现ConfigServer配置中心立刻响应 - 刷新3355,发现ConfigClient客户端没有任何响应 - 3355没有变化除非自己重启或者重新加载 - 难到每次运维修改配置文件,客户端都需要重启? ?噩梦 在客户端的controller类上添加注解@RefreshScope //用于运维人员手动post通知客户端刷新配置 ![1585646813651](assets/1585646813651.png) ​ 如上图,3344获取的是动态的数据,3355获取的不是 ### 解决步骤: - 修改3355模块 - POM引入actuator监控 - ```xml org.springframework.boot spring-boot-starter-actuator ``` - 修改YML,暴露监控端口 - ```yaml #暴露监控端口 management: endpoints: web: exposure: include: "*" ``` - 添加**@RefreshScope**到业务类Controller修改 - ```java @RestController @RefreshScope //用于运维人员手动post通知客户端刷新配置 public class ConfigClientController ``` - 此时修改github---> 测试3344 --->测试3355 - 发送post请求给指定客户端3355,告诉他刷新 ```shell curl -X POST "http://localhost:3355/actuator/refresh" ``` 此时再刷新3355,发现获取到的值就是刚刚变更过的值了 ### 还有问题: + 假如有多个微服务客户端3355/3366/3377。。 + 每个微服务都要执行一次post请求, 手动刷新? + 可否广播,一次通知,处处生效? + 我们想大范围的自动刷新,求方法 以上问题的具体在SpringCloud Bus来解决。 # 消息总线 SpringCloud Bus ## 概述 上一讲解的加深和扩充,实现分布式自动刷新的功能,SpringCloud Bus配合SpringCloud Config实现配置的动态刷新。 支持的两种代理消息: - Rabbitmq - Kafka ### 能干嘛: ![1585704349167](assets/1585704349167.png) **SpringCloud Bus能管理和传播分布式系统之间的消息,就像一个分布式执行器,用在广播状态的更改,消息的推送等,也可以当做微服务之间的通信通道。** ![1585704722792](assets/1585704722792.png) ### 什么是总线? 在微服务架构的系统中,通常会使用 轻量级的消息代理来构建一个 公用的消息主题,并让系统中的所有微服务都连接上来。由于 主题中产生的消息会被所有实例监听和和消费,所以称为消息总线。在总线上的各个实例,都可以广播一些需要让其他连接在该主题上的实例都知道消息。 ### 基本原理 ConfigClient实例都会监听MQ中同一个topic(默认springCloudBus),当一个服务刷新数据的时候,会把这个消息放到topic中,这样监听其他同一topic的服务就能得到通知,然后去更新自身的配置。 ## RabbitMQ环境配置 - 安装Erlang,安装RabbitMQ,下载地址:https://download.csdn.net/download/dayu0007/11216192 - 进入RabbitMQ安装目录下的sbin目录 命令行输入一下命令 ```shell rabbitmq-plugins enable rabbitmq_management ``` - 输入以下命令启动管理功能 - 访问地址查看是否安装成功: http://localhost:15672 输入账号密码并登录: guest guest ## SpringCloud Bus动态刷新全局广播 1. 必须先具备良好的RabbitMQ环境 2. 演示广播效果,增加复杂度,再以3355为模板再制作一个3366 - pom.xml ```xml cloud2020 com.atgugui.springcloud 1.0-SNAPSHOT 4.0.0 cloud-config-client3366 org.springframework.cloud spring-cloud-starter-config com.atgugui.springcloud cloud-commons 1.0-SNAPSHOT org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.projectlombok lombok org.springframework.boot spring-boot-devtools org.springframework.boot spring-boot-starter-test test ``` - boostrap.yml ```yaml server: port: 3366 #注册到eureka地址 eureka: client: service-url: defaultZone: http://eureka7001.com:7001/eureka spring: application: name: cloud-config-client cloud: config: label: master #分支名称 name: config #配置文件名称 profile: dev #读取后缀的名称 #上述三个综合起来就是:读取master分支上的config-dev.yml文件, #就是: http://config3344.com:3344/master/config-dev.yml uri: http://localhost:3344 #配置中心的地址 #暴露监控端点 management: endpoints: web: exposure: include: "*" ``` - 主启动 ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; /** * @USER: dayu * @DATE: 2020/4/1 * @DESCRIPTION: */ @SpringBootApplication @EnableEurekaClient public class CloudConfigClient3366 { public static void main(String[] args) { SpringApplication.run(CloudConfigClient3366.class, args); } } ``` - controller ```java @RestController @RefreshScope public class ConfigClientController { @Value("${server.port}") private String serverPort; @Value("${config.info}") private String configInfo; @GetMapping("/configInfo") public String configInfo(){ return "serverPort:"+serverPort+"\t,config.info:"+configInfo; } } ``` 3. **设计思想** 1. 利用消息总线触发一个客户端/bus/refresh,而刷新所有 客户端的配置 ![1585709296779](assets/1585709296779.png) 2. 利用消息总线触发一个服务端ConfigServer的/bus/refresh端点,而刷新所有 ![1585709427938](assets/1585709427938.png) 3. 第二个架构显然更加适合,图一不适合的原因如下 - 打破了微服务的职责单一性,因为微服务本身是业务模块,它本不应该承担配置刷新的职责。 - 破坏了微服务各节点的对等性。 - 有一定的局限性。例如,微服务在迁移时,它的网络地址常常会发生变化,此时如果想要做到自动刷新,那就会增加更多的修改 4. 给cloud-config-center- 3344配置中心服务端添加消息总线支持 - pom ```xml org.springframework.cloud spring-cloud-starter-bus-amqp ``` - yml ```yaml server: port: 3344 eureka: client: fetch-registry: true register-with-eureka: true service-url: defaultZone: http://eureka7001.com:7001/eureka spring: application: name: cloud-config-center cloud: config: server: git: #git远程目录 uri: git@github.com:dayu007/springcloud-config.git #搜索目录 search-path: - springcloud-config label: master #新增以下配置 #rabbitmq相关配置 rabbitmq: host: localhost username: guest password: guest port: 5672 ###rabbitmq配置后,暴露bus配置的端点 management: endpoints: web: exposure: include: "bus-refresh" ``` 5. 给cloud-config -client-3355,cloud-config-client-3366客户端添加消息总线支持,两个服务修改相同 - 改pom.xml ```xml org.springframework.cloud spring-cloud-starter-bus-amqp ``` - 改yml ```yaml #新增rabbitmq相关配置 rabbitmq: host: localhost username: guest password: guest port: 5672 ``` 6. 测试 启动eureka服务器,启动config server服务器3344 启动config client服务器,3355,3366 访问master数据 ​ ​ 更改GitHub上master分支上的配置再次访问数据,会发现config客户端数据没有更新 ​ 此时手动通知config配置中心服务端,达到一次修改,广播通知,处处生效 ```shell curl -X POST "http://localhost:配置中心端口/actuator/bus-refresh" #精确通知指定的config客户端,SpringCloud Bus动态刷新定点通知 curl -X POST "http://localhost:配置中心端口/actuator/bus-refresh/{destination}" #如 curl -X POST "http://localhost:3344/actuator/bus-refresh/cloud-config-client:3355" ``` ​ 再次访问,config所有客户端数据更改。 ​ **通知总结** 当运维GitHub远程修改了配置文件,需要post一个请求给配置中心服务端(config Server),配置中心会发送一个topic给消息中间件,所有订阅了这个topic的配置中心客户端监听到消息之后会去服务端拉取新的配置信息。流程如下图 ![1585713533262](assets/1585713533262.png) # SpringCloud Stream(消息驱动) ## 概述 ### 是什么 ​ **屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型** ​ 官方定义Spring Cloud Stream是一个构建消息驱动微服务的框架。 ​ 应用程序通过**inputs或者outputs**与Spring Cloud Stream中binder对象交互。 ​ 过我们配置来**binding**(绑定),而Spring Cloud Stream的binder对象负责与消息中间件交互。所以,我们只需要搞清楚如何与Spring Cloud Stream交互就可以方便使用消息驱动的方式。 通过使用**Spring Integration来连接消息代理中间件以实现消息事件驱动**。Spring Cloud Stream为一些供应商的消息中间件产品提供了个性化的自动化配置实现,引用了发布订阅、消费组、分区的三个核心概念。 **目前仅支持RabbitMQ、Kafka.** ### 设计思想 - 标准MQ - 生产者/消费者之间靠消息媒介传递信息内容——Message - 消息必须走特定的通道 - 消息通道MessageChannel - 消息通道里的消息如何被消费呢,谁负责收发处理 - 消息通道MessageChannel的子接口SubscribableChannel,由MessageHandler消息处理器所订阅 - Stream中的消息通信方式遵循了发布-订阅模式 这些中间件的差异性导致我们实际项目开发给我们造成了一定的困扰,我们如果用了两个消息队列的其中一种,后面的业务需求,我想往另外一种消息队列进行适移,这时候无疑就是一个灾难性的,一大堆东西都要重新推倒重新做,因为它跟我们的系统耦合了,这时候springcloud Stream给我们提供了一种解耦合的方式。 - 在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接与消息中间件进行信息交互的时候, 由于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性 通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离。 通过向应用程序暴露统一的Channel通道, 使得应用程序不需要再考虑各种不同的消息中间件实现。 通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。 - Binder 通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件之间的隔离。 ![1585721202910](assets/1585721202910.png) - SpringCloud Stream 标准流程套路 ![1585721422613](assets/1585721422613.png) - Binder - 方便连接中间件,屏蔽差异 - Channel - 通道,是队列Queue的一种抽象,在消息系统中实现存储和转发的媒介,通过Channel对队列进行配置 - Source和Sink 消息的生产和消费者 - 常用API和注解 ![1585721654561](assets/1585721654561.png) ## 案例说明 创建3个module,一个生产者,两个消费者 ### 生产者(8801) #### pom.xml ```xml cloud2020 com.atgugui.springcloud 1.0-SNAPSHOT 4.0.0 cloud-stream-rabbitmq-provider8801 com.atgugui.springcloud cloud-commons 1.0-SNAPSHOT org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.cloud spring-cloud-starter-stream-rabbit org.projectlombok lombok org.springframework.boot spring-boot-devtools org.springframework.boot spring-boot-starter-test test ``` #### application.yml ```yaml server: port: 8801 spring: application: name: cloud-stream-provider cloud: stream: binders: #此处定义要绑定的rabbitmq的信息; defaultRabbit: #表示自定义的名称,用于bindings整合 type: rabbit #消息组件的类型 enviroment: #rabbitmq相关的环境参数 spring: rabbitmq: host: localhost port: 5672 username: guest password: guest bindings: #绑定器进行整合处理 #output用于消息发送者(important!) output: #这个名字是一个通道的名称 destination: studyExchange #自定义的Exchange名称 content-type: application/json #设置消息类型,本次为json,如果设置文本,这用 "text/plain" binder: defaultRabbit #设置要绑定的消息服务的具体位置 eureka: client: service-url: defaultZone: http://eureka7001.com:7001/eureka instance: lease-expiration-duration-in-seconds: 5 #超时时间设置5s,(默认90s) lease-renewal-interval-in-seconds: 2 #设置心跳间隔时间(默认30s) instance-id: send-8801.com #eureka信息列表展示的主机名称 prefer-ip-address: true #访问的路径变为ip地址 ``` #### 启动类 ```java @SpringBootApplication public class StreamMQMain8801 { public static void main(String[] args) { SpringApplication.run(StreamMQMain8801.class, args); } } ``` #### 消息的接口 创建一个消息接口用于发送消息 ```java public interface IMessageProvide { String send(); } ``` #### 服务的实现类 消息的发送者 ```java import com.atguigu.springcloud.service.IMessageProvide; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.cloud.stream.messaging.Source; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.support.MessageBuilder; import javax.annotation.Resource; import java.util.UUID; /** * @USER: dayu * @DATE: 2020/4/1 * @DESCRIPTION: */ @EnableBinding(Source.class) //定义消息管道 @Slf4j public class MessageProvideImpl implements IMessageProvide { @Resource MessageChannel output; //消息发送通道 @Override public String send() { String serial = UUID.randomUUID().toString(); output.send(MessageBuilder.withPayload(serial).build()); log.info("serial:"+serial); return serial; } } ``` ### 消费者(8802) #### 创建新的module #### pom.xml jar包引用和生产者是一样的 ```xml cloud2020 com.atgugui.springcloud 1.0-SNAPSHOT 4.0.0 cloud-stream-rabbitmq-consumer8802 com.atgugui.springcloud cloud-commons 1.0-SNAPSHOT org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.cloud spring-cloud-starter-stream-rabbit org.projectlombok lombok org.springframework.boot spring-boot-devtools org.springframework.boot spring-boot-starter-test test ``` #### application.yml ```yaml server: port: 8802 spring: application: name: cloud-stream-consumer cloud: stream: binders: #此处定义要绑定的rabbitmq的信息; defaultRabbit: #表示自定义的名称,用于bindings整合 type: rabbit #消息组件的类型 enviroment: #rabbitmq相关的环境参数 spring: rabbitmq: host: localhost port: 5672 username: guest password: guest bindings: #绑定器进行整合处理 input: #这个名字是一个通道的名称,表示消费端 destination: studyExchange #自定义的Exchange名称 content-type: application/json #设置消息类型,本次为json,如果设置文本,这用 "text/plain" binder: defaultRabbit #设置要绑定的消息服务的具体位置 group: groupB #特别注意的是:group!!!! #group可以避免消息重复消费,需要给消息添加分组,同一个组中消息不会重复消费,属于竞争关系 #group 分组还有一个很重要的功能就是消息的持久化,当服务宕机或重启时,如果消息发送方发送了消息,这些消息会被消费。如果没有group分组,则服务重启或者宕机时消息会丢失。 eureka: client: service-url: defaultZone: http://eureka7001.com:7001/eureka instance: lease-expiration-duration-in-seconds: 5 #超时时间设置5s,(默认90s) lease-renewal-interval-in-seconds: 2 #设置心跳间隔时间(默认30s) instance-id: receive-8802.com #eureka信息列表展示的主机名称 prefer-ip-address: true #访问的路径变为ip地址 ``` #### 启动类 ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @USER: dayu * @DATE: 2020/4/1 * @DESCRIPTION: */ @SpringBootApplication public class StreamConsumer8802 { public static void main(String[] args) { SpringApplication.run(StreamConsumer8802.class, args); } } ``` #### 业务类 ```java import lombok.extern.slf4j.Slf4j; 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; /** * @USER: dayu * @DATE: 2020/4/1 * @DESCRIPTION: */ @EnableBinding(Sink.class) @Slf4j public class ReceiveMessageListenerController { @Value("${server.port}") private String serverPort; @StreamListener(Sink.INPUT) public void input(Message message) { String payload = message.getPayload(); log.info("消费者1号,接收到消息:" + payload + ",serverPort:" + serverPort); } } ``` 复制一个消费者8803,和上一个一样,只需要修改端口和`instance-id: receive-8802.com #eureka信息列表展示的主机名称`,用于测试消息的消费,一个stream的注解,@StreamListener,@EnableBinding(),就可以操作消息中间件,不需要其他额外的配置。过程比较清爽。 ## 重点问题 1. Stream 消息重复消费 application.yml中配置group可以避免消息重复消费,需要给消息添加分组,同一个组中消息不会重复消费,属于竞争关系 2. Stream消息持久化 group 分组还有一个很重要的功能就是消息的持久化,当服务宕机或重启时,如果消息发送方发送了消息,这些消息会被消费。如果没有group分组,则服务重启或者宕机时消息会丢失。 # SpringCloud Sleuth(分布式请求链路跟踪) ## 概述 ### 是什么 - Spring Cloud Sleuth提供了-套完整的服务跟踪的解决方案 - 在分布式系统中提供追踪解决方案并且兼容支持了zipkin ## 链路监控搭建 ### zipkin #### 下载并运行jar ​ SpringCloud从F版起已不需要自己构建Zipkin Server了,只需调用jar包即可 ​ 下载地址:https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/ ​ 下载:zipkin-server-2.12.9-exec.jar 运行显示如下界面表示正常运行 ![1585735234082](assets/1585735234082.png) 访问本地浏览器显示正常访问: ![1585735228603](assets/1585735228603.png) #### 术语 - 完整的调用链路 - 表示一请求链路,-条链路通过Trace ld唯-标识, Span标识发起的请求信息,各span通过parent id关联起来 - 上图what - ![1585735454929](assets/1585735454929.png) - 名词解释 - Trace:类似于树结构的Span集合,表示一条调用链路,存在唯一标识 - span:表示调用链路来源,通俗的理解span就是一次请求信息 ## 案例demo 在消费者和生产者上添加sleuth和zipkin的依赖 ```xml org.springframework.cloud spring-cloud-starter-zipkin ``` 在yaml文件上添加配置 ```yaml #分布式链路追踪设置采样率 sleuth: sampler: probability: 1 #分布式链路追踪设置zipkin地址 zipkin: base-url: http://localhost:9411/ ``` 启动消费者和生产者,调用方法后,访问出现如下界面就对了 ![1585795077541](assets/1585795077541.png) # SpringCloud Alibaba ## 主要功能 - **服务限流降级**:默认支持 WebServlet、WebFlux, OpenFeign、RestTemplate、Spring Cloud Gateway, Zuul, Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。 - **服务注册与发现**:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。 - **分布式配置管理**:支持分布式系统中的外部化配置,配置更改时自动刷新。 - **消息驱动能力**:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。 - **分布式事务**:使用 @GlobalTransactional 注解, 高效并且对业务零侵入地解决分布式事务问题。。 - **阿里云对象存储**:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。 - **分布式任务调度**:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。 - **阿里云短信服务**:覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。 ## 组件 **Sentinel**:把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。 **Nacos**:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 **RocketMQ**:一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。 **Dubbo**:Apache Dubbo™ 是一款高性能 Java RPC 框架。 **Seata**:阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。 **Alibaba Cloud ACM**:一款在分布式架构环境中对应用配置进行集中管理和推送的应用配置中心产品。 **Alibaba Cloud OSS**: 阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。 **Alibaba Cloud SchedulerX**: 阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。 **Alibaba Cloud SMS**: 覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。 # Nacos ## cap模型对比 ![1585795839049](assets/1585795839049.png) ## 安装 - 要有jdk1.8和maven的安装环境 - 下载 - 地址:找到一个稳定的版本下载 - 本demo中用的版本号 Nacos 1.1.4 - 下载解压进入bin目录,运行startup.cmd到如下页面就成功了 ![1585796201914](assets/1585796201914.png) - 访问nacos页面 用户名和密码都是 nacos ![1585796307644](assets/1585796307644.png) ## 服务中心的对比 **Nacos支持AP和CP模式的切换** C是所有节点在同一时间看到的数据是一致的; 而A的定义是所有的请求都会收到响应。 何时选择使用何种模式? 一般来说, 如果不需要存储服务级别的信息且服务实例是通过nacos client注册,并能够保持心跳上报,那么就可以选择AP模式。当前主流的服务如Spring cloud和Dubbo服务,都适用于AP模式,AP模式为了服务的可能性而减弱了一致性, 因此AP模式 下只支持注册临时实例。 如果需要在服务级别编辑或者存储配置信息,那么CP是必须,K8S服务和DNS服务则适用于CP模式。 CP模式下则支持注册持久化实例,此时则是以Raft协议为集群运行模式,该模式下注册实例之前必须先注册服务,如果服务不存在,则会返回错误。 ```shell curl -X PUT '$NACOS_ SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP' ``` ## Nacos服务端演示 创建9001服务端的module ### 1、导入jar包 pom.xml ```xml com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.atgugui.springcloud cloud-commons 1.0-SNAPSHOT org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.projectlombok lombok org.springframework.boot spring-boot-devtools runtime true org.springframework.boot spring-boot-starter-test test ``` ### 2、写yaml ```yaml server: port: 9001 spring: application: name: nacos-provider-payment cloud: nacos: discovery: #配置Nacos地址 server-addr: 127.0.0.1:8848 management: endpoints: web: exposure: include: "*" ``` ### 3、启动类 ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @USER: dayu * @DATE: 2020/4/2 * @DESCRIPTION: */ @SpringBootApplication public class NacosPayment9001 { public static void main(String[] args) { SpringApplication.run(NacosPayment9001.class, args); } } ``` ### 4、业务类 ```java import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; /** * @USER: dayu * @DATE: 2020/4/2 * @DESCRIPTION: */ @RestController public class PaymentController { @Value("${server.port}") private String port; @GetMapping("/payment/get/{id}") public String paymentPort(@PathVariable("id") Integer id){ return "nacos registry, id :"+id+",port :"+port; } } ``` ### 5、测试 - 启动nacos - 启动启动类 - 输入``显示正常调用就对了 - ![1585903076473](assets/1585903076473.png) ### 6、创建项目9002 复制一个和9001同样的服务端做集群操作,只修改一个端口 ### 7、总结 - nacos做服务注册中心,服务发现,不需要想eureka那样创建一个服务端项目,只需要启动nacos的jar包即可 - yaml配置文件上需要配置Nacos地址 ## Nacos消费者端演示 创建一个消费者module ### 1、pom.xml ```xml com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.atgugui.springcloud cloud-commons 1.0-SNAPSHOT org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator ``` ### 2、application.yml ```yaml server: port: 80 spring: application: name: nacos-consumer-payment cloud: nacos: discovery: #配置Nacos地址 server-addr: 127.0.0.1:8848 nacos-serever: server-url: http://nacos-provider-payment ``` ### 3、启动类 ```java @SpringBootApplication public class NacosConsumer83 { public static void main(String[] args) { SpringApplication.run(NacosConsumer83.class, args); } @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } } ``` ### 4、业务类 ```java @RestController public class ConsumerNacosController { @Resource RestTemplate restTemplate; @Value("${nacos-serever.server-url}") private String SERVER_URL; @GetMapping("/consumer/payment/get/{id}") public String payment(@PathVariable("id") Integer id) { String result = restTemplate.getForObject(SERVER_URL+"/payment/get/"+id, String.class); System.out.println(result); return result; } } ``` ### 5、测试 启动项目,启动9001,9002项目,访问如下地址 ![1585903750740](assets/1585903750740.png) **nacos 集成了ribbon,项目实现了负载均衡**。 ## Nacos服务配置中心 Nacos同springcloud-config一样,在项目初始化时,要保证先从配置中心进行配置拉取,拉取配置之后,才能保证项目的正常启动。 springboot中配置文件的加载是存在优先级顺序的,**bootstrap.yml优先级高于application.yml** ## Nacos配置 ### 客户端demo 创建一个端口为3377的客户端项目 #### 1、pom配置 ```xml com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.atgugui.springcloud cloud-commons 1.0-SNAPSHOT org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.projectlombok lombok org.springframework.boot spring-boot-devtools runtime true org.springframework.boot spring-boot-starter-test test ``` #### 2、yml配置 这里需要两个yml配置文件,bootstrap.yml,application.yml springboot中配置文件的加载是存在优先级顺序的,**bootstrap.yml优先级高于application.yml** - bootstrap.yml - ```yml server: port: 3377 spring: cloud: nacos: discovery: server-addr: localhost:8848 #指定Nacos为服务中心地址 config: server-addr: localhost:8848 #指定Nacos为配置中心地址 file-extension: yaml #指定配置文件的格式--yaml格式 group: DEFAULT_GROUP namespace: 30857e04-b3d6-4156-9a3f-102b8d3529c5 #nacos的命名空间,不写就是默认的命名空间 application: name: nacos-config-client #${spring.application.name}-${spring.profile.active}.${file-extension} ``` - application.yml - ```yaml spring: profiles: active: dev # active: info ``` #### 3、主启动 ```java @SpringBootApplication @EnableDiscoveryClient public class NacosClient3377 { public static void main(String[] args) { SpringApplication.run(NacosClient3377.class, args); } } ``` #### 4、业务类 ```java import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * @USER: dayu * @DATE: 2020/4/3 */ @RestController @RefreshScope //动态监听并拉取修改的配置 这个注解非常重要!! public class PaymentNacosController { //读取Nacos配置中心文件的数据 @Value("${config.info}") private String configInfo; @GetMapping("/payment/configInfo") public String configInfo() { return configInfo; } } ``` #### 5、nacos配置文件 nacos控制面板中国现实配置列表,添加配置文件 ![1585904783761](assets/1585904783761.png) 文件名的匹配规则在yml配置文件中写了。 ```shell 文件名必须严格遵守这个规则 #${spring.application.name}-${spring.profile.active}.${file-extension} ``` #### 5.测试 启动项目。访问 ### Data Id,命名空间和分组的关系 Nacos控制面板中的对应的位置如下: ![1585902045018](assets/1585902045018.png) client客户端的配置文件如下:bootstrap.yml ![1585902111728](assets/1585902111728.png) application.yml ```yaml spring: profiles: active: dev # active: info ``` ## Nacos单机数据库转MySQL 1、修改conf目录application.properties 配置文件,在文件最底部添加数据库配置 ```properties spring.datasource.platform=mysql db.num=1 db.url.0=jdbc:mysql://11.162.196.16:3306/自定义database?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true db.user=msyql的用户 db.password=mysql的password ``` 2、将conf文件夹下有个nacos-mysql.sql的文件导入MySQL数据库 重启nacos即可 ## Nacos集群配置 ### 1、架构图 ![1586312073235](assets/1586312073235.png) nacos集群需要**至少3个nacos节点**,部署的前提条件是**jdk1.8+,Maven3.2+** ### 2、文件配置 - 解压目录nacos/的conf目录下,有配置文件cluster.conf,请每行配置成ip:port。(请配置3个或3个以上节点) ```conf # ip:port 200.8.9.16:8848 200.8.9.17:8848 200.8.9.18:8848 ``` - 配置统一数据源 参照**单机数据库转MySQL**,配置到mysql即可 # Sentinel 分布式系统的流量防卫兵 ## Sentinel 是什么? 随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。 Sentinel 具有以下特征: - **丰富的应用场景**:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。 - **完备的实时监控**:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。 - **广泛的开源生态**:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。 - **完善的 SPI 扩展点**:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。 Sentinel 的主要特性: ![1586338714725](assets/1586338714725.png) Sentinel 的开源生态: ![1586338737392](assets/1586338737392.png) Sentinel 分为两个部分: - 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。 - 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。 ## Sentinel下载运行和安装 从GitHub官网上下载 下载 `sentinel-dashboard-1.7.0.jar`,直接运行jar包即可 运行的前提条件: - jdk1.8 - sentinel的默认端口8080不被占用 运行 ```shell java -jar sentinel-dashboard-1.7.0.jar ``` 浏览器访问,用户和密码都是 **sentinel** ![1586412110987](assets/1586412110987.png) ## Sentinel初始化监控 配合Nacos使用 ### 1、启动Nacos 进入nacos的bin目录,启动nacos ### 2、启动Sentinel ### 3、创建module工程,并启动项目 创建cloudalibaba-sentinel-service8401,父类是cloud2020 ​ 1、pom依赖 ```xml com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.alibaba.csp sentinel-datasource-nacos com.alibaba.cloud spring-cloud-starter-alibaba-sentinel org.springframework.cloud spring-cloud-starter-openfeign com.atgugui.springcloud cloud-commons 1.0-SNAPSHOT org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.projectlombok lombok org.springframework.boot spring-boot-devtools runtime true org.springframework.boot spring-boot-starter-test test ``` 2、application.yml ```yaml server: port: 8401 spring: application: name: cloudalibaba-sentinel-service cloud: nacos: discovery: #Nacos服务注册中心地址 server-addr: 127.0.0.1:8848 sentinel: transport: #配置sentinel dashboard 地址 dashboard: 127.0.0.1:8080 #默认端口8719,假如被占用会自动从8719开始依次加1扫描,直至找到未占用的端口 port: 8719 management: endpoints: web: exposure: include: '*' ``` ​ 3、启动类和业务类 ```java @EnableDiscoveryClient @SpringBootApplication public class CloudSentinelApplication8401 { public static void main(String[] args) { SpringApplication.run(CloudSentinelApplication8401.class, args); } } //测试监控方法 @RestController public class FlowLimitController { @GetMapping("/show1") public String testA(){ return "limitService.show1()"; } @GetMapping("/show2") public String testB(){ return "limitService.show2()"; } } ``` 4、 访问项目路径后,再查看sentinel控制台就可以发现访问的路径都被监控了,如图 ![1586412619100](assets/1586412619100.png) ## Sentinel流控 当 QPS 超过某个阈值的时候,则采取措施进行流量控制。流量控制的效果包括以下几种:**直接拒绝**、**Warm Up**、**匀速排队**。对应 `FlowRule` 中的 `controlBehavior` 字段。 > 注意:若使用除了直接拒绝之外的流量控制效果,则调用关系限流策略(strategy)会被忽略。 #### 直接拒绝 **直接拒绝**(`RuleConstant.CONTROL_BEHAVIOR_DEFAULT`)方式是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出`FlowException`。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。具体的例子参见 [FlowQpsDemo](https://github.com/alibaba/Sentinel/blob/master/sentinel-demo/sentinel-demo-basic/src/main/java/com/alibaba/csp/sentinel/demo/flow/FlowQpsDemo.java)。 #### Warm Up Warm Up(`RuleConstant.CONTROL_BEHAVIOR_WARM_UP`)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。详细文档可以参考 [流量控制 - Warm Up 文档](https://github.com/alibaba/Sentinel/wiki/限流---冷启动),具体的例子可以参见 [WarmUpFlowDemo](https://github.com/alibaba/Sentinel/blob/master/sentinel-demo/sentinel-demo-basic/src/main/java/com/alibaba/csp/sentinel/demo/flow/WarmUpFlowDemo.java)。 通常冷启动的过程系统允许通过的 QPS 曲线如下图所示: ![](assets/qps曲线.png) #### 匀速排队 匀速排队(`RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER`)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。详细文档可以参考 [流量控制 - 匀速器模式](https://github.com/alibaba/Sentinel/wiki/流量控制-匀速排队模式),具体的例子可以参见 [PaceFlowDemo](https://github.com/alibaba/Sentinel/blob/master/sentinel-demo/sentinel-demo-basic/src/main/java/com/alibaba/csp/sentinel/demo/flow/PaceFlowDemo.java)。 该方式的作用如下图所示: ![](assets/时间轴.png) 这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。 名词解释 - 资源名:唯一名称,默认请求路径 - 针对来源: Sentine可以针对调用者进行限流,填写微服务名,默认default (不区分来源) - 阈值类型/单机阈值: - QPS (每秒钟的请求数量) :当调用该api的QPS达到阈值的时候,进行限流 - 线程数:当调用该api的线程数达到阈值的时候,进行限流 - 是否集群:不需要集群 - 流控模式: - 直接: api达到限流条件时,直接限流 - 关联:当关联的资源达到阈值时,就限流自己 - 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流) [api级别的针对来源] - 流控效果: - 快速失败:直接失败,抛异常 - Warm Up:根据codeFactor (冷加载因子,默认3)的值,从阈值/codeFactor, 经过预热时长,才达到设置的QPS阈值 - 排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效 **流控相关操作解释:** **1、快速失败——直接** ![1586413197381](assets/1586413197381.png) 测试: ​ 快速访问,报错**`Blocked by Sentinel (flow limiting)`** **2、快速失败——关联** 当管联资源达到阈值是,限流自己 如下截图,当/show2资源每秒请求大于1时候,就限流/show1方法。 ![1586917829678](assets/1586917829678.png) **3、预热** 默认的coldFactor为3,请求的QPS从(threshold/3)开始,经过预热时长逐渐升到设定的QPS阈值。 ___ 案例:阈值为10,预热时长为5秒 系统初始化阈值为10/3约等于3,经过5秒时间后阈值才慢慢上升到10。而不是直接为10 ![1586919884827](assets/1586919884827.png) 如果该成关联的话,也是一个道理,监控关联资源,达到阈值限制自己 **3、排队等待** 匀速排队(`RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER`)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。详细文档可以参考 [流量控制 - 匀速器模式](https://github.com/alibaba/Sentinel/wiki/流量控制-匀速排队模式),具体的例子可以参见 [PaceFlowDemo](https://github.com/alibaba/Sentinel/blob/master/sentinel-demo/sentinel-demo-basic/src/main/java/com/alibaba/csp/sentinel/demo/flow/PaceFlowDemo.java)。 案例: 设置含义:资源名设置每秒请求阈值10次,超过就要排队,排队的超时时间是200000毫秒 ![1586922790280](assets/1586922790280.png) 这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。 ## Sentinel 熔断降级 ### 降级策略 我们通常用以下几种方式来衡量资源是否处于稳定的状态: - 平均响应时间 (`DEGRADE_GRADE_RT`):当 1s 内持续进入 N 个请求,对应时刻的平均响应时间(秒级)均超过阈值(`count`,以 ms 为单位),那么在接下的时间窗口(`DegradeRule` 中的 `timeWindow`,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 `DegradeException`)。注意 Sentinel 默认统计的 RT 上限是 4900 ms,**超出此阈值的都会算作 4900 ms**,若需要变更此上限可以通过启动配置项 `-Dcsp.sentinel.statistic.max.rt=xxx` 来配置。 - 异常比例 (`DEGRADE_GRADE_EXCEPTION_RATIO`):当资源的每秒请求量 >= N(可配置),并且每秒异常总数占通过量的比值超过阈值(`DegradeRule` 中的 `count`)之后,资源进入降级状态,即在接下的时间窗口(`DegradeRule` 中的 `timeWindow`,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 `[0.0, 1.0]`,代表 0% - 100%。 - 异常数 (`DEGRADE_GRADE_EXCEPTION_COUNT`):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 `timeWindow` 小于 60s,则结束熔断状态后仍可能再进入熔断状态。 下图表示一秒钟处理的线程数为5个。超过就降级。 (用JMeter并发请求进行压力测试,就可以) ![1586941270480](assets/1586941270480.png) 下图表示show2的方法一秒以内执行的异常比例达到20%就降级,报自带的异常提示Blocked by Sentinel (flow limiting) ![1586943374543](assets/1586943374543.png) 下图表示:showD方法在时间窗口1s内异常数超过3时,就降级 ![1586943526270](assets/1586943526270.png) ## Sentinel热点key 何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如: - 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制 - 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制 热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。 代码: ```java @GetMapping("/hotKey") @SentinelResource(value = "hotKey", defaultFallback = "deal_hotKey") public String hotKey(@RequestParam(value = "key1", required = false) String key1, @RequestParam(value = "key2", required = false) String key2) { log.info("hotKey"); return "limitService.hotKey()"; } /* *当热点key达到限定阈值时,时指定的兜底方法 */ public String deal_hotKey() { log.info("deal_hotKey,测试异常数"); return "deal_hotKey \t o(╥﹏╥)o"; } ``` sentinel设置:表示/hotKey方法的第一个参数key1在时间窗口1s内的请求数不超过1 ![1586999788852](assets/1586999788852.png) 添加成功之后,找到添加项后面的编辑可以添加参数例外项 如下表示:当第一个参数key1的值为aa时,每秒的阈值为10 ![1586999675764](assets/1586999675764.png) Sentinel系统规针对整个系统的控制 ![1587005670866](assets/1587005670866.png) ## Sentinel自定义限流处理方法 ```java /** * 根据资源名限流 * @return */ @GetMapping("/byResource") @SentinelResource(value = "byResource",blockHandler = "handlerException") public CommonResult byResource(){ return new CommonResult(200,"按资源名称测试OK \t O(∩_∩)O",new Payment(20200416l,"serial0001")); } public CommonResult handlerException(BlockException blockException){ return new CommonResult(444,blockException.getClass().getCanonicalName()+"\t服务不可用\t o(╥﹏╥)o"); } /** * 根据url限流 * @return */ @GetMapping("/byUrl") @SentinelResource(value = "byUrl") public CommonResult byUrl(){ return new CommonResult(200,"按url测试OK \t O(∩_∩)O",new Payment(20200412l,"serial0002")); } /** * 根据自定义方法限流 * @return */ @GetMapping("/customBlockerHandler") @SentinelResource(value = "customBlockerHandler", blockHandlerClass = CustomBlockerHandler.class, blockHandler = "handlerException001") public CommonResult customBlockerHandler(){ return new CommonResult(200,"根据自定义方法限流 \t O(∩_∩)O",new Payment(2020l,"serial0003")); } /** * @USER: dayu * @DATE: 2020/4/16 * @DESCRIPTION: 自定义处理限流异常 */ public class CustomBlockerHandler { public static CommonResult handlerException001(BlockException exception){ return new CommonResult(4444,exception.getClass().getCanonicalName()+"\t 按客户定义 global 1 号方法 "); } public static CommonResult handlerException002(BlockedException exception){ return new CommonResult(4444,exception.getClass().getCanonicalName()+"\t 按客户定义 global 2 号方法"); } } ``` ## Sentinel熔断配置 **1、创建项目9003,9004两个provider子工程** pom.xml的核心依赖 ```xml com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.atgugui.springcloud cloud-commons 1.0-SNAPSHOT org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator ``` application.yml配置文件简单配置一下,端口对应修改一下就好 ```yaml server: port: 9004 spring: application: name: nacos-payment-provider cloud: nacos: discovery: server-addr: localhost:8848 management: endpoints: web: exposure: include: '*' ``` 业务类启动类 ```java @SpringBootApplication @EnableDiscoveryClient public class Payment9004 { public static void main(String[] args) { SpringApplication.run(Payment9004.class, args); } } /** * 业务类,为了简化操作,模拟从数据库读取数据,这里测试就用了hashMap代替了一下 */ @RestController public class PaymentController { @Value("${server.port}") String port; public static HashMap hashMap=new HashMap<>(); static { hashMap.put(1L,new Payment(1L,"b17f24ff026d40949c85a24f4f375d42")); hashMap.put(2L,new Payment(2L,"b17f24ff026d40949c85a24f4f375d43")); hashMap.put(3L,new Payment(3L,"b17f24ff026d40949c85a24f4f375d44")); } @GetMapping("/paymentSQL/{id}") public CommonResult paymentSQL(@PathVariable("id")Long id){ Payment payment= hashMap.get(id); return new CommonResult<>(200,"from sql :server.port="+port,payment); } } ``` **2、创建Consumer84的消费者工程** pom.xml的引用 ```xml com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.alibaba.csp sentinel-datasource-nacos com.alibaba.cloud spring-cloud-starter-alibaba-sentinel org.springframework.cloud spring-cloud-starter-openfeign com.atgugui.springcloud cloud-commons 1.0-SNAPSHOT org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator ``` application.yml ```yaml server: port: 84 spring: application: name: nacos-payment-consumer cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport: #配置Sentinel dashboard地址 dashboard: localhost:8080 #默认端口8719,加入被占用了会自动从8719开始依次+1,直至找到未占用的端口 port: 8719 #provider的服务名,这里选择配置文件的方式读取 nacos-serever: server-url: http://nacos-payment-provider ``` 启动类和业务类 ```java /** * @USER: dayu * @DATE: 2020/4/16 * @DESCRIPTION: */ @SpringBootApplication @EnableDiscoveryClient public class NacosOrder84 { public static void main(String[] args) { SpringApplication.run(NacosOrder84.class, args); } @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } } @RestController @Slf4j public class ConsumerController { @Value("${nacos-serever.server-url}") private String LOAD_SERVER; @Resource RestTemplate restTemplate; @GetMapping("/consumer/paymentSQL/{id}") // @SentinelResource(value = "fallback") // @SentinelResource(value = "fallback",fallback = "handlerFallback")// 兜底异常处理 // @SentinelResource(value = "fallback",blockHandler = "blockHandlerPayment") //流控处理 @SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandlerPayment", exceptionsToIgnore = {IllegalArgumentException.class})//当两个限制同时使用的情况下,如果不限流-兜底处理,如果达到限流阈值,走blockHandler方法. //exceptionsToIgnore表示忽略指定的异常,如果程序跑出指定异常的话,不再有fallback兜底方法 public CommonResult paymentSQL(@PathVariable("id") Long id) { CommonResult result = restTemplate.getForObject(LOAD_SERVER + "/paymentSQL/" + id, CommonResult.class); if (id == 4) { throw new IllegalArgumentException("IllegalArgumentException,非法参数异常"); } else if (result.getData() == null) { throw new NullPointerException("NullPointerException,该ID没有对应的订单,空指针异常"); } return result; } public CommonResult handlerFallback(@PathVariable("id") Long id, Throwable e) { Payment payment = new Payment(id, "null"); return new CommonResult(444, "兜底异常处理:" + e.getMessage(), payment); } public CommonResult blockHandlerPayment(@PathVariable Long id, BlockException exception) { Payment payment = new Payment(id, "null"); return new CommonResult(445, "blockHandlerPayment 异常处理:" + exception.getClass().getCanonicalName(), payment); } } ``` Sentinel流控前提是:启动nacos,sentinel,以及9003,9004,84端口的项目。 访问方法显示效果,出现兜底方法 ![1587024054645](assets/1587024054645.png) 访问方法,在sentinel面板中设置流控或者降级方法 ![1587023984666](assets/1587023984666.png) 流控或者降级设置之后的显示效果 ![1587024091819](assets/1587024091819.png) 注意的:异常忽略 ![1587022213974](assets/1587022213974.png) ## Sentinel服务熔断openfeign 以下对84端口项目修改: pom.xml新增openfeign引用,上面已经添加了 yaml文件中新增 ``` yaml #设置sentinel的feign支持 feign: sentinel: enabled: true ``` 业务类添加feign调用 ```java /** * @USER: dayu * @DATE: 2020/4/16 * @DESCRIPTION: */ @FeignClient(value = "nacos-payment-provider",fallback = ConsumerServiceImpl.class) public interface ConsumerService { @GetMapping("/paymentSQL/{id}") public CommonResult paymentSQL(@PathVariable("id")Long id); } /** *服务熔断或者服务不可用时候的处理方法 */ @Component public class ConsumerServiceImpl implements ConsumerService { @Override public CommonResult paymentSQL(Long id) { return new CommonResult<>(444444,"服务熔断降级的处理方法",new Payment(id,null)); } } //在ConsumerController中添加openfeign调用 //=========open-feign============ @Resource ConsumerService consumerService; @GetMapping("/consumer/feign/paymentSQL/{id}") public CommonResult paymentSQL2(@PathVariable("id") Long id) { return consumerService.paymentSQL(id); } ``` 启动9003.9004项目,启动84项目 访问 ![1587029954708](assets/1587029954708.png) 当provide项目宕机时,再次访问,显示的效果如下,已触发了熔断 ![1587029913511](assets/1587029913511.png) 服务熔断框架,Sentinel配合openfeign或者Hystrix配合openfeign ## Sentinel持久化配合 将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址, sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel 上的流控规则持续有效 1、修改8401项目,使8401中的指定方法持久化到nacos中 pom.xml中添加sentinel和nacos的持久化引用 ```xml com.alibaba.csp sentinel-datasource-nacos ``` application中添加sentinel读取nacos配置,指定持久化数据的位置 ```yaml server: port: 8401 spring: application: name: cloudalibaba-sentinel-service cloud: nacos: discovery: #Nacos服务注册中心地址 server-addr: 127.0.0.1:8848 sentinel: transport: #配置sentinel dashboard 地址 dashboard: 127.0.0.1:8080 #默认端口8719,假如被占用会自动从8719开始依次加1扫描,直至找到未占用的端口 port: 8719 #指定sentinel读取nacos中dataId,sentinel设置都在这个配置中 datasource: dsl: nacos: server-addr: localhost:8848 dataId: cloudalibaba-sentinel-service groupId: DEFAULT_GROUP data-type: json rule-type: flow management: endpoints: web: exposure: include: '*' ``` Nacos配置列表新增配置dataId 是cloudalibaba-sentinel-service,配置和yaml保持一一致 ```json [ { "resource":"/byUrl", "limitApp":"default", "grade":1, "count":1, "strategy":0, "controlBehavior":0, "clusterMode":false } ] 解释: resource:资源名称; limitApp:来源应用; grade:阈值类型,0表示线程数,1表示QPS; count:单机阈值; strategy:流控模式,I0表示直接,1表示关联,2表示链路; controlBehavior:流控效果,0表示快速失败,1表示Warm Up, 2表示排队等待; clusterMode:是否集群。 ``` 上面的配置表示,资源名为/byUrl的阈值超过1就快速失败 启动项目项目,访问http://localhost:8401/byUrl当超过每秒1次访问就自动流控了,表明持久化成功。 ![1587092664757](assets/1587092664757.png) # Seata分布式事务