当前位置: 首页 > news >正文

南京网站开发就业培训课程专业seo排名优化费用

南京网站开发就业培训课程,专业seo排名优化费用,cms系统主要做什么,网站建设江阴微服务架构理念 关于微服务的概念、理念及设计相关内容#xff0c;并没有特别严格的边界和定义#xff0c;某种意义上说#xff0c;适合的就是最好的#xff0c;在之前的文章中有过详细的阐述#xff0c;微服务[v1.0.0][Spring生态概述]、微服务[设计与运行]、微服务[v1.…微服务架构理念 关于微服务的概念、理念及设计相关内容并没有特别严格的边界和定义某种意义上说适合的就是最好的在之前的文章中有过详细的阐述微服务[v1.0.0][Spring生态概述]、微服务[设计与运行]、微服务[v1.0.0][服务调用]、微服务[开发生命周期]、微服务[面临的挑战]、架构师修炼系列【微服务】 Spring Cloud基础架构和概念 构建微服务架构其实是一件很复杂的事情很多企业都不具备这个能力这不只是技术能力的问题往往并非因为技术问题导致的困难在实际工作中时间、人力、成本、各维度的压力都有可能导致微服务体系的建设并不是很顺利更无法做到彻底即便是技术驱动的公司也存在诸多困难 为了构建微服务体系业内有不少的组件可以使用Spring Cloud就是其中典型之一它是有Pivotal团队维护的而这个团队并没有自己造轮子而是借鉴当前若干企业在长期实践中经过考验的优秀的分布式产品将这些产品以Spring Boot的形式进行了封装因此学习Spring Cloud的基础是Spring Boot 在Spring Cloud封装的分布式产品中是以Netflix为核心的该公司有大量的分布式经验也有很多优秀的产品但随着时代的发展该公司很多组件都停止了更新或者更新缓慢因此Pivotal开始从Spring Cloud中去除Netflix组件但耗费巨大只是无论用的组件借鉴了哪里其包含的思想确是相似的学好一种或者一套例如Netflix组件明确他们的设计思想和应用场景再看其他的同类组件也是大同小异 Spring Cloud融入了很多组件而这些组件由各个公司进行开发和维护版本十分凌乱且更新不一致为了对这些组件进行统一管理Pivotal团队决定使用伦敦地铁站的站名作为版本名如下表格所示 Spring Cloud架构和组件 Spring Cloud最重要的是其架构和组件架构是方向组件是细节 服务:服务实例:服务调用:服务治理中心:网关:业务服务:基础服务: 服务治理和服务发现 在Spring Cloud中服务治理一般是通过Netflix Eureka完成Pivotal团队将其以Spring Boot的形式封装为Spring Cloud Netflix Eureka以便能够快速简单的使用为了展示微服务治理和服务发现创建如下项目 微服务治理中心-Eureka 首先引入相关依赖(因为Eureka是在Web环境下运行的因此也会引入spring-boot-starter-web)如下所示 !-- 引入Spring Boot的Webstarter依赖它为构建Web应用程序提供了必需的组件包括Servlet容器和Spring Web MVC。 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency!-- 引入Spring Cloud的Eureka Server starter依赖用于实现服务注册与发现的功能。 --!-- 它基于Netflix Eureka提供了微服务架构中服务之间互相发现和通信的能力。 --dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-eureka-server/artifactId/dependency然后修改eureka-server模块的启动类代码如下 package com.scd.eurekaserver;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;/*** Eureka Server应用程序入口类。* 使用SpringBootApplication注解标记这是一个Spring Boot应用程序* 同时通过EnableEurekaServer注解启用Eureka Server功能使得当前应用程序成为一个Eureka注册中心。*/ SpringBootApplication EnableEurekaServer public class EurekaServerApplication {/*** 程序入口方法。* 使用SpringApplication.run方法启动Spring Boot应用程序* 参数为当前类和应用程序启动参数。** param args 应用程序启动参数*/public static void main(String[] args) {SpringApplication.run(EurekaServerApplication.class, args);} } 添加相关配置到eureka-server模块的配置文件如下所示 # 定义Spring应用名称它是一个服务的名称一个服务可拥有多个实例 spring:application:name: eureka-server # 启动端口 server:port: 1001 eureka:client:# 服务自身就是治理中心所以这里设置为false取消注册register-with-eureka: false# 取消服务获取至于服务获取后续会进行讨论fetch-registry: falseinstance:# 服务治理中心服务器IPhostname: 192.168.3.115启动eureka-server模块 然后浏览器访问http://localhost:1001/即可看到如下页面 服务发现 Eureka服务治理中心是不会主动发现服务的具体的服务实例会通过发送REST请求去Eureka服务治理中心进行注册、续约和下线等操作接下来将Customer和Goods模块注册给Eureka服务治理中心首先引入Eureka客户端依赖 !-- 引入Eureka客户端依赖用于实现服务发现功能 --dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-eureka-client/artifactId/dependencyEureka的服务治理中心相当于服务端eureka-client相当于客户端具体的服务实例添加了eureka-client依赖之后通过发送REST请求到服务端建立联系然后修改这两个模块的的配置文件 # Spring应用名称服务名称 spring:application:name: customer # 请求URL,指向Eureka服务治理中心 eureka:client:serviceUrl:defaultZone: http://localhost:1001/eureka/instance:# 服务实例主机hostname: 192.168.3.115# 服务端口 server:port: 3001# Spring应用名称服务名称 spring:application:name: goods # 请求URL,指向Eureka服务治理中心 eureka:client:serviceUrl:defaultZone: http://localhost:1001/eureka/instance:# 服务实例主机hostname: 192.168.3.115# 服务端口 server:port: 2001在旧版本的Spring Cloud中还需要使用注解EnableEurekaClient来驱动Eureka客户端在新版本中已经不需要只需要依赖spring-cloud-starter-netflix-eureka-client即可也就是说并不需要修改任何启动类的内容启动模块大概30s即可完成服务注册启动Goods模块和Customer模块后访问页面http://localhost:1001/,如下所示 页面上出的红色警告是Eureka服务端做的自我保护机制如果要去掉需要在Eureka Server模块添加配置项enable-self-preservation: false, 默认为true # 定义Spring应用名称它是一个服务的名称一个服务可拥有多个实例 spring:application:name: eureka-server# 启动端口 server:port: 1001eureka:# 服务器配置段用于定义服务器的行为和特性server:# 是否启用自我保护模式# 自我保护模式是一种机制用于在服务器负载过高时自动限制某些操作以保护服务器免于崩溃enable-self-preservation: falseclient:# 服务自身就是治理中心所以这里设置为false取消注册register-with-eureka: false# 取消服务获取至于服务获取后续会进行讨论fetch-registry: falseinstance:# 服务治理中心服务器IPhostname: 192.168.3.115服务高可用 一个服务可能存在多个服务实例服务治理中心也可能存在多个实例多个服务治理中心加上一个服务多个实例将大大提升服务能力加下来看如何操作首先使用IDEA的功能通过不同的端口启动同一个服务的多个服务实例 首先给这个运行的实例添加运行端口如图所示 软后复制另一个运行配置 将运行端口改为1002 然后用相同的方法将Goods和Customer服务都复制两个运行实例出来 这样配置完后server.port就会作为命令行参数输入到Spring Boot项目它将覆盖application.yml配置文件里的端口配置还没完还需要修改服务治理中的配置如下所示 # 定义Spring应用名称它是一个服务的名称一个服务可拥有多个实例 spring:application:name: eureka-server# 启动端口 # server: # port: 1001eureka:# 服务器配置段用于定义服务器的行为和特性server:# 是否启用自我保护模式# 自我保护模式是一种机制用于在服务器负载过高时自动限制某些操作以保护服务器免于崩溃enable-self-preservation: falseclient:# 服务自身就是治理中心所以这里设置为false取消注册register-with-eureka: false# 取消服务获取至于服务获取后续会进行讨论fetch-registry: falseserviceUrl:# Eureka服务端相互注册defaultZone: http://localhost:1001/eureka/,http://localhost:1002/eureka/instance:# 服务治理中心服务器IPhostname: 192.168.3.115注释掉了端口配置实际上不注释掉也会被命令行覆盖添加了defaultZone: http://localhost:1001/eureka/,http://localhost:1002/eureka/两个地址分别指向两个Eureka服务治理中心这两个服务治理中心可相互注册 还要修改两个服务的配置如下所示 # Spring应用名称服务名称 spring:application:name: goods# 请求URL,指向Eureka服务治理中心 eureka:client:serviceUrl:defaultZone: http://localhost:1001/eureka/,http://localhost:1002/eureka/instance:# 服务实例主机hostname: 192.168.3.115# 服务端口 server:port: 2001 # Spring应用名称服务名称 spring:application:name: customer# 请求URL,指向Eureka服务治理中心 eureka:client:serviceUrl:defaultZone: http://localhost:1001/eureka/,http://localhost:1002/eureka/instance:# 服务实例主机hostname: 192.168.3.115# 服务端口 server:port: 3001然后重新启动各模块如下所示 再访问页面http://localhost:1002/和http://localhost:1001/如下页面所示 如此便实现了高可用注册到服务治理中心的服务实例当服务治理中心判断有挂掉的就会自动将其踢掉但再次前提是服务治理中本身没有挂掉,因此它本身也需要是高可用的因此还需要让他们之间能够互相发现就需要将配置register-with-eureka: true设置为true 然后重启服务治理中心模块便能看到他们之间互相注册为服务实例如此当一个挂掉另一个也能监控到 Eureka工作原理 Eureka服务端和客户端由客户端发送Rest请求完成服务注册、续约、下线等操作的发送请求的是Eureka的客户端而不是服务端Rest请求的地址是通过eureka.client.serviceUrl.defaultZone生成;两个服务治理中心之间是对等关系不是主从关系 服务注册一个服务实例要被服务治理中心发现首先需要注册客户端把相关的信息以REST请求的方式注册到服务治理中心这里值得注意的配置项是spring.application.name,Eureka会通过这个配置项去区分服务实例属于哪个服务相同的服务实例应该有相同的业务能力服务注册不是服务启动后就注册默认情况下服务启动后40s才会发起注册如果需要改变它需要通过配置项eureka.client.initial-instance-info-replication-interval-seconds:40来改变 服务续约在服务实例启动后可能会出现下线、故障等不可用的情况Eureka为了监测这些实例是否可用要求实例每隔一段时间对Eureka发送请求以告知服务治理中心是否可用状态这个过程被称为续约(Renew),如果迟迟没有续约那么服务治理中心会认为该实例不可用然后Eureka就会将其剔除。默认情况下30s一次续约90s不续约就会被剔除如果需要改变它则需要通过配置项eureka.instance.lease-expiration-duration-in-seconds:90和eureka.instance.lease-renewal-interval-in-seconds:30来改变 服务下线当一个服务正常下线时会向Eureka发送Rest请求告知下线然后Eureka会将其剔除 服务调用Ribbon 在系统整个体系中看各服务之间是需要通过写作来完成某一个任务的在Spring Cloud中是以Rest请求作为主要的服务调用方式的Spring Cloud将Netflix Ribbon封装成Spring Cloud Netflix Ribbon作为服务调用的组件并将Spring Cloud Netflix Ribbon和第三方开源工具OpenFeign(也是Netflix Ribbon开发的)结合封装成为Spring Cloud OpenFeign作为声明式服务调用以简化开发过程 Ribbon也被称为客户端负载均衡这里的客户端是相对于Eureka服务端而言的主要有如下几个特点 将单机服务变为多机服务从而降低单机压力提高系统的吞吐和服务能力当发现一些故障实例时可以屏蔽这些故障实例让系统继续工作通过负载均衡来实现实例的伸缩性在业务膨胀时增加实例在业务缩减时减少实例负载均衡器可以度量服务的质量在执行服务调用时剔除故障多、服务性能差的实例侧面提升服务调用感知上的成功率和性能 负载均衡的基础有两点一个是服务实例清单也就是从哪里选取服务实例执行服务调用另一个是负载均衡策略也就是如何从服务实例清单里选取可用的服务实例 在服务调用过程中存在三个角色即服务提供者、服务消费者、服务治理中心 Ribbon实例 引入相关依赖spring-cloud-starter-netflix-ribbon实际上在引入了spring-cloud-starter-netflix-eureka-client时项目会自动依赖无需单独在pom文件中单独添加spring-cloud-starter-netflix-ribbon 在Customer模块中编写controller代码如下所示 package com.scd.customer.controller;import com.scd.common.pojo.CustomerPojo; import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletRequest;RestController RequestMapping(/customer) public class CustomerController {GetMapping(/name/{id})public String getCustomerName(PathVariable(id) Long id, HttpServletRequest request) {String customerName customer_name_ id;System.out.println(服务端口 request.getServerPort());return customerName;} }修改Goods模块的启动类 package com.scd.goods.main; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate;import java.time.Duration;// 定义扫描包 SpringBootApplication(scanBasePackages com.scd.goods) public class GoodsApplication {// 执行负载均衡,启动Ribbon默认的负载均衡策略来选择可用的服务实例完成服务调用LoadBalanced// 装配为Bean方便之后注入Beanpublic RestTemplate restTemplate() {return new RestTemplate();}public static void main(String[] args) {SpringApplication.run(GoodsApplication.class, args);} }然后编写服务接口和服务类 package com.scd.goods.facade;public interface CustomerFacade {public String getCustomerName(Long id);public String timeout(Long id);public String exception(Long id); } package com.scd.goods.facade.impl;import com.scd.goods.facade.CustomerFacade; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; /*** 定义一个服务类该类用于处理特定的业务逻辑。* 使用Service注解表明该类是一个服务层对象通常由Spring框架管理其生命周期* 并可以进行依赖注入。这个类的设计是为了提供一系列的服务操作以支持应用程序的运行。*/ Service public class CustomerFacadeImpl implements CustomerFacade {Autowiredprivate RestTemplate restTemplate null;Overridepublic String getCustomerName(Long id) {// 这里的BUYER代表客户服务此时RestTemplate会自动负载均衡String urlhttp://CUSTOMER/customer/name/{id};// 服务REST风格调用String name restTemplate.getForObject(url, String.class, id);return name;} }然后写一个控制器来验证 package com.scd.goods.controller;import com.scd.goods.facade.CustomerFacade; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;RestController RequestMapping(/goods) public class CustomerCallController {Autowiredprivate CustomerFacade customerFacade null;GetMapping(/customer/name/{id})public String getCustomerName(PathVariable(id) Long id) {return customerFacade.getCustomerName(id);} }然后重启Goods和Customer服务访问地址http://localhost:2001/goods/customer/name/1如下所示被负载均衡了 Ribbon工作原理 前面说到负载均衡需要处理的两个问题一个是从哪里选取服务实例另一个问题是如何从可选的服务实例中选取第一个问题很好解释Eureka服务端有可用的服务实例的清单在Eureka的机制中客户端默认会每隔30s向Eureka发送请求获取其他服务的实例清单并将其副本保存在本地这样就获得了服务清单也可以通过配置修改eureka.client.fetch-registry:true和eureka.client.registry-fetch-interval-seconds:30每隔30秒获取一次Eureka服务端的服务实例清单其中包括了可用和不可用的 第二个问题就是如何选取实例的策略问题了Ribbon中定义了几个主要的接口 IClientConfig:提供客户端配置功能默认实现类DefaultClientConfigImplIRule:提供具体的负载均衡策略默认实现类ZoneAvoidanceRuleIPing:通过PING命令验证服务实例是否可用默认实现类DummyPingServerListT extends Server:用于获取服务实例清单默认实现类ConfigurationBasedServerListServerListFilterT extends Server:根据服务的可用性和性能排除一些有故障的和性能低的服务实例然后得到一个可用性较高的服务实例清单默认实现类ZonePreferenceServerListFilterILoadBalancer:按照策略选取服务实例默认实现类ZoneAwareLoadBalancerServerListUpdater: 它会根据一定的策略来更新服务实例清单默认实现类PollingServerListUpdater 其中大部分是无需定制化的常变动的是IRule接口该接口的结构大致如下 可以看到该接口下有若干策略其中常用几个如下 BestAvailableRule先探测服务实例是否可用如果可用则选择当前被分配请求最少的那个WeightedResponseTimeRule根据数据统计分析服务实例响应时间并分配一个权重值(weight),对于响应时间短的服务实例有更大的概率被分配请求反之则被分配请求的概率会减少RetryRule重试服务策略在一个特定的时间戳内如果当前被分配的服务实例不可用则通过子策略(默认是轮询)来选定可用的服务实例RoundRobinRule轮询选择服务实例通过下标轮询服务实例列表从而选择一个服务实例ZoneAvoidanceRule默认实现策略它会通过可用性和性能两重过滤选择可用且性能较高的服务实例 在抽象类CommonClientConfigKey定义了大量配置项部分代码如下 /*** 定义了与负载均衡相关的配置键。* p* NFLoadBalancerClassName: 负载均衡器的类名。* NFLoadBalancerRuleClassName: 负载均衡规则的类名。* NFLoadBalancerPingClassName: 负载均衡器ping的类名。* NFLoadBalancerPingInterval: 负载均衡器ping的间隔时间单位秒。* NFLoadBalancerMaxTotalPingTime: 负载均衡器ping的最大总时间单位毫秒。* NFLoadBalancerStatsClassName: 负载均衡器统计信息的类名。* NIWSServerListClassName: Netty服务器列表的类名。* ServerListUpdaterClassName: 服务器列表更新器的类名。* NIWSServerListFilterClassName: Netty服务器列表过滤器的类名。** author Unknown*/ public class ConfigurationKeys {/*** 负载均衡器的类名配置键。*/public static final IClientConfigKeyString NFLoadBalancerClassName new CommonClientConfigKey(NFLoadBalancerClassName) {};/*** 负载均衡规则的类名配置键。*/public static final IClientConfigKeyString NFLoadBalancerRuleClassName new CommonClientConfigKey(NFLoadBalancerRuleClassName) {};/*** 负载均衡器ping的类名配置键。*/public static final IClientConfigKeyString NFLoadBalancerPingClassName new CommonClientConfigKey(NFLoadBalancerPingClassName) {};/*** 负载均衡器ping的间隔时间配置键单位秒。*/public static final IClientConfigKeyInteger NFLoadBalancerPingInterval new CommonClientConfigKey(NFLoadBalancerPingInterval) {};/*** 负载均衡器ping的最大总时间配置键单位毫秒。*/public static final IClientConfigKeyInteger NFLoadBalancerMaxTotalPingTime new CommonClientConfigKey(NFLoadBalancerMaxTotalPingTime) {};/*** 负载均衡器统计信息的类名配置键。*/public static final IClientConfigKeyString NFLoadBalancerStatsClassName new CommonClientConfigKey(NFLoadBalancerStatsClassName) {};/*** Netty服务器列表的类名配置键。*/public static final IClientConfigKeyString NIWSServerListClassName new CommonClientConfigKey(NIWSServerListClassName) {};/*** 服务器列表更新器的类名配置键。*/public static final IClientConfigKeyString ServerListUpdaterClassName new CommonClientConfigKey(ServerListUpdaterClassName) {};/*** Netty服务器列表过滤器的类名配置键。*/public static final IClientConfigKeyString NIWSServerListFilterClassName new CommonClientConfigKey(NIWSServerListFilterClassName) {};例如商品Goods服务调用客户Customer服务可以在商品Goods服务中配置如下负载均衡策略 # CUSTOMER为Eureka服务端的另外一个服务名称 CUSTOMER:ribbon:# 配置负载均衡策略为BestAvailableRuleNFLoadBalancerRuleClassName: com.netflix.loadbalancer.BestAvailableRule配置单个客户端可以这样也可以进行全局配置编写一个Ribbon配置类如下代码所示 package com.scd.goods.config;import com.netflix.loadbalancer.BestAvailableRule; import com.netflix.loadbalancer.IPing; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.PingUrl; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;/*** Ribbon配置类用于设置Ribbon客户端的负载均衡策略和服务器健康检查规则。*/ Configuration public class RibbonConfig {/*** 配置负载均衡策略使用{link BestAvailableRule}该规则会选择最少连接的服务器。* return 返回一个BestAvailableRule实例*/Beanpublic IRule rule() {return new BestAvailableRule();}/*** 配置Ribbon的服务器心跳监测策略使用{link PingUrl}通过HTTP GET请求检测服务器是否可用。* return 返回一个PingUrl实例*/Beanpublic IPing ribbonPing() {return new PingUrl();} } 这样就可以配置全局性的Ribbon了而且这个方式比使用yaml文件的配置级别高会覆盖yaml文件的相同配置项的配置Ribbon还提供了注解RibbonClients /*** 组件扫描并配置多个Ribbon客户端指定不同的服务名及其对应的自定义配置类* 同时设定默认的全局Ribbon配置类。* RibbonClients 注解用于批量配置RibbonClient可以为不同的服务定制化负载均衡策略等配置。* - value数组包含多个RibbonClient注解分别定义不同服务的配置,name属性指定了微服务的名称configuration属性指定了该服务专用的配置类。* - defaultConfiguration指定所有RibbonClient共用的默认配置类。*/ RibbonClients(value{RibbonClient(nameCUSTOMER, configurationCustomerRibbonConfig.class), // 定义CUSTOMER服务的特定配置RibbonClient(nameUSER, configurationUserRibbonConfig.class), // 定义USER服务的特定配置},defaultConfigurationRibbonConfig.class // 设置所有RibbonClient的默认全局配置类 ) 这样微服务调用就会按照自定义的负载均衡策略来路由服务实例执行服务调用了 断路器Hystrix Hystrix的使用 通常情况下一个服务调用另一个服务完成某个业务功能他们之间便产生了依赖而且是一个普遍现象系统在运转过程中无法完全保证一个服务实例一直是可用的状态 而且如果调用关系设计或者服务划分不合理往往还会出现因为某一个服务不可用导致整条调用链路上的服务均出现不可用再严重一些就是整个服务架构不可用导致服务器雪崩效应 当服务提供者不可用而服务消费者仍旧进行大量的调用势必会出现很多请求超时此时断路器的作用就会将服务熔断从而阻止服务消费者对服务提供者进行调用从而避免大量线程积压导致服务消费者自身也变成不可用从而断路器起到了保护服务消费者的作用 Hystrix的作用之一就是熔断此外还包括服务降级、缓存、线程池、异步等等 首先在服务消费者(Goods)模块引入该断路器的依赖 !-- 引入Spring Cloud Netflix Hystrix提供断路器功能用于容错管理 --dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-hystrix/artifactId/dependency引入了该依赖后便可以使用注解EnableCircuitBreaker驱动断路器工作 package com.scd.goods.main;import io.github.resilience4j.timelimiter.TimeLimiterConfig; import io.github.resilience4j.timelimiter.TimeLimiterRegistry; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate;import java.time.Duration;// 定义扫描包 SpringBootApplication(scanBasePackages com.scd.goods) // 驱动断路器工作Hystrix EnableCircuitBreaker public class GoodsApplication {... ... }这样就可以驱动Hystrix的工作在Goods模块使用了Hystrix了只需要加入一个注解HystrixCommand即可使用Hystrix这个注解通过Spring AOP技术将方法包装为一个Hystrix命令然后执行 在服务调用过程中常出现的两种故障一个是超时一个是异常看一下断路器如何使用首先在Customer模块的CustomerController中加入超时和异常两个方法代码如下 // 最大休眠时间为3秒private static Long MAX_SLEEP_TIME 3000L;GetMapping(/timeout/{id})public String testTimeout(PathVariable(id) Long id) {try {// 随机产生不超过3秒的时间戳long sleepTime (long) (Math.random()*MAX_SLEEP_TIME);// 线程休眠Thread.sleep(sleepTime);} catch (Exception ex) {ex.printStackTrace();}return test timeout;}GetMapping(/exception/{id})public String testException(PathVariable(id) Long id) {throw new RuntimeException(当前尚未开发该方法);}在Hystrix中默认超时时间为1s因此Goods模块在经过Hystrix调用Customer会很大概率出现超时然后修改Goods模块的调用代码添加调用方法如下代码所示 package com.scd.goods.facade.impl;import com.scd.goods.facade.CustomerFacade; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate;/*** 定义一个服务类该类用于处理特定的业务逻辑。* 使用Service注解表明该类是一个服务层对象通常由Spring框架管理其生命周期* 并可以进行依赖注入。这个类的设计是为了提供一系列的服务操作以支持应用程序的运行。*/ Service public class CustomerFacadeImpl implements CustomerFacade {Autowiredprivate RestTemplate restTemplate null;Overridepublic String getCustomerName(Long id) {//这里的CUSTOMER代表客户服务此时RestTemplate会自动负载均衡String urlhttp://CUSTOMER/customer/name/{id};// 服务REST风格调用String name restTemplate.getForObject(url, String.class, id);return name;}/*** 测试超时调用* 使用Hystrix通过Spring AOP将方法捆绑为一个Hystrix命令去执行并指定了降级方法* param id 参数* return 服务调用结果或者降级结果* */HystrixCommand( fallbackMethod fallback, threadPoolKey customer-pool-1)Overridepublic String timeout(Long id) {//这里的CUSTOMER代表客户服务此时RestTemplate会自动负载均衡String urlhttp://CUSTOMER/customer/timeout/{id};// 服务REST风格调用String name restTemplate.getForObject(url, String.class, id);return name;}/*** 测试异常调用* param id 参数* return 调用结果或者降级结果*/HystrixCommand(fallbackMethod fallback)Overridepublic String exception(Long id) {//这里的CUSTOMER代表客户服务此时RestTemplate会自动负载均衡String urlhttp://CUSTOMER/customer/exception/{id};// 服务REST风格调用String name restTemplate.getForObject(url, String.class, id);return name;}/*** 降级方法* param id 参数* param ex 异常对象* return 降级结果*/public String fallback(Long id, Throwable ex) {System.out.println(服务调用失败参数为 id);System.out.println(异常信息是 ex.getMessage());return 服务调用失败我是降级服务;} }代码中使用了HystrixCommand注解它会使用Spring AOP技术将方法包装为一个Hystrix命令去执行同时还配置了一个fallback属性执行了fallback方法fallback方法有两个参数id和ex因为被注解方法都有id参数因此fallback也必须有id参数而ex不是必须的。 通常情况下当我们时间紧迫、资源不足难以完成任务时会选择去做最主要的任务而不是全做在微服务中这样的思想就叫做服务降级而fallback方法就是降级方法服务降级的过程如下图所示 然后在控制器代码中加入调用方法如下所示 package com.scd.goods.controller;import com.scd.goods.facade.CustomerFacade; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;RestController RequestMapping(/goods) public class CustomerCallController {Autowiredprivate CustomerFacade customerFacade null;GetMapping(/customer/name/{id})public String getCustomerName(PathVariable(id) Long id) {return customerFacade.getCustomerName(id);}GetMapping(/customer/timeout/{id})public String timeout(PathVariable(id) Long id) {return customerFacade.timeout(id);}GetMapping(/customer/exception/{id})public String exception(PathVariable(id) Long id) {return customerFacade.exception(id);} }重启客户端服务并访问地址http://localhost:2001/goods/customer/timeout/1和http://localhost:2001/goods/customer/exception/1便有可能的到如下页面 在Hystrix中有三种状态默认为CLOSED状态在此状态下会放心服务调用OPEN状态下当执行服务调用失败比率达到50%时或者10s内超过20次请求时断路器的状态就会从CLOSED变为OPEN熔断服务调用执行降级方法HALF_OPEN状态断路器保持OPEN状态5s后会将断路器修改为此状态在此状态下允许尝试一次服务调用如果成功则将断路器状态修改为CLOSED放行服务调用否则继续保持OPEN状态熔断服务调用 在进行调用时候Hystrix会自动分配资源给用户执行服务调用默认是一个大小为10的线程池当然也可以使用信号量有时候不想用默认配置例如默认超时时间1s、失败率50%才将断路器改为OPEN状态等等可以通过HystrixCommand的配置项进行配置如下所示 import org.springframework.cloud.netflix.hystrix.HystrixCommand; import org.springframework.cloud.netflix.hystrix.HystrixCommandGroupKey; import org.springframework.cloud.netflix.hystrix.HystrixThreadPoolKey; import org.springframework.stereotype.Component;Component public class MyService {/*** 示例方法展示HystrixCommand的高级配置。* 包括自定义线程池、超时设置、断路器配置等。** return 服务调用的结果或降级响应*/HystrixCommand(commandKey MyServiceCommand,groupKey HystrixCommandGroupKey.Factory.asKey(MyServiceGroup),threadPoolKey HystrixThreadPoolKey.Factory.asKey(MyServiceThreadPool),threadPoolProperties {HystrixProperty(name coreSize, value 20), // 自定义线程池核心线程数HystrixProperty(name maxQueueSize, value 100) // 队列最大长度},commandProperties {HystrixProperty(name execution.isolation.thread.timeoutInMilliseconds, value 2000), // 超时时长HystrixProperty(name circuitBreaker.requestVolumeThreshold, value 5), // 断路器触发请求阈值HystrixProperty(name circuitBreaker.errorThresholdPercentage, value 50), // 错误百分比阈值超过则打开断路器HystrixProperty(name circuitBreaker.sleepWindowInMilliseconds, value 5000) // 断路器休眠时间窗},fallbackMethod fallbackMethod)public String advancedConfiguredCall() {// 远程服务调用逻辑...return Advanced configured remote service response;}/*** 降级处理方法。** return 降级返回信息*/private String fallbackMethod() {return Advanced config: Service unavailable, fallback response.;} } import org.springframework.cloud.netflix.hystrix.HystrixCommand; import org.springframework.cloud.netflix.hystrix.HystrixCommandGroupKey; import org.springframework.cloud.netflix.hystrix.HystrixThreadPoolKey; import org.springframework.stereotype.Component;Component public class MyService {/*** 示例方法展示如何使用Hystrix信号量进行并发限制。** return 服务调用的结果或降级响应*/HystrixCommand(commandKey MyServiceSemaphoreCommand,groupKey HystrixCommandGroupKey.Factory.asKey(MyServiceGroup),commandProperties {HystrixProperty(name execution.isolation.strategy, value SEMAPHORE), // 使用信号量进行并发控制HystrixProperty(name execution.isolation.semaphore.maxConcurrentRequests, value 100), // 最大并发请求数HystrixProperty(name execution.timeout.enabled, value false), // 信号量模式下通常禁用超时因为它是非阻塞的HystrixProperty(name circuitBreaker.requestVolumeThreshold, value 5), // 断路器触发请求阈值HystrixProperty(name circuitBreaker.errorThresholdPercentage, value 50), // 错误百分比阈值超过则打开断路器HystrixProperty(name circuitBreaker.sleepWindowInMilliseconds, value 5000) // 断路器休眠时间窗},fallbackMethod semaphoreFallbackMethod)public String semaphoreControlledCall() {// 远程服务调用逻辑...return Semaphore controlled remote service response;}/*** 适用于信号量控制方法的降级处理。** return 降级返回信息*/private String semaphoreFallbackMethod() {return Semaphore limit reached, fallback response.;} }设置了execution.isolation.strategy为SEMAPHORE这意味着Hystrix将使用信号量而非线程池来控制并发。同时还设置了execution.isolation.semaphore.maxConcurrentRequests来定义最大的并发请求数。此外因为信号量模式通常用于非阻塞操作所以我们也禁用了执行超时(execution.timeout.enabledfalse)以避免不必要的复杂性 舱壁隔离 舱壁隔离(Bulkhead Isolation)也有称作隔板隔离的例如这样一个调用关系 例如这样一个调用关系所有的服务调用都共享一个线程池就会出现线程池被占满了再有服务调用的时候就会挂起或者拒绝这样虽然服务提供者是可用状态但仍然无法完成调用于是改设计为 这样即便线程池1被占满了也不会影响调用商品服务 引入舱壁隔离也会带来问题就是大量线程的交互 切换线程状态会消耗性能相对而言Hystrix的舱壁隔离性能还算不错它还是通过注解HystrixCommand来支持舱壁隔离的HystrixCommand存在三个配置项 commandKey MyServiceCommand, groupKey HystrixCommandGroupKey.Factory.asKey(MyServiceGroup), threadPoolKey HystrixThreadPoolKey.Factory.asKey(MyServiceThreadPool),可以通过这3个配置项修改服务调用的线程池路径 通常一个服务类可以存在一个或者多个标注HystrixCommand的方法一般都在同一个组别里同一个线程池下要改变这个情况只需要通过这3个配置配置多个组别和线程池 commandKey MyServiceCommand1, groupKey HystrixCommandGroupKey.Factory.asKey(MyServiceGroup1), threadPoolKey HystrixThreadPoolKey.Factory.asKey(MyServiceThreadPool-1),Hystrix仪表盘 先建个Dashboard模块引入如下依赖 dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-eureka-client/artifactId/dependencydependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-hystrix-dashboard/artifactId/dependency然后修改一下该模块的配置文件 # Spring应用名称微服务名称 spring:application:name: dashboard# 请求URL指向Eureka服务治理中心 eureka:client:serviceUrl:defaultZone : http://localhost:1001/eureka/,http://localhost:1002/eureka/instance:# 实例服务器名称hostname: 192.168.3.115修改该模块的启动类驱动仪表盘工作 /*** com.scd.dashboard包下的主类用于启动Spring Boot应用并启用Hystrix仪表板功能。* 此类利用Spring Boot的自动配置特性以及Spring Cloud的Hystrix Dashboard支持* 提供了一个监控微服务中Hystrix命令执行情况的可视化界面。*/ package com.scd.dashboard;import org.springframework.boot.SpringApplication; // 引入Spring Boot的启动器 import org.springframework.boot.autoconfigure.SpringBootApplication; // 标记为自动配置的Spring Boot应用 import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard; // 启用Hystrix仪表板功能SpringBootApplication EnableHystrixDashboard public class DashboardApplication {/*** 应用的主入口点使用SpringApplication.run方法启动Spring Boot应用。* 参数args - 应用启动时传入的命令行参数*/public static void main(String[] args) {SpringApplication.run(DashboardApplication.class, args); // 运行Spring Boot应用}} Hystrix仪表盘只是一个平台还需要给它添加监控指标为此在Goods模块也就是服务消费者模块引入spring boot actuator dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId/dependency这样仪表盘就有了监控指标的数据来源然后将actuator的端点暴露(默认是不暴露的监控不到)修改Goods模块的配置文件 management:endpoints:web:exposure:# 暴露的端点如果配置为“*”则代表全部暴露include : *然后重启Goods的两个模块同时用相同的方法使用4001和4002两个端口启动两个DashBoard模块启动完成后访问地址http://localhost:4001/hystrix,如下页面所示 在第一个输入框中输入http://localhost:2001/actuator/hystrix:stream后边两个输入框一个是延迟可以输入2000另一个输入Goods2001即可然后点击Monitor Stream按钮即可进入监控页面然后不停的刷新Goods服务在2001端口的两个服务请求即可看到监控上的数据变化 然而这只是能监控到一个服务实例还不能监控多个服务实例Netflix提供了工具Turbine通过它可以聚集各个服务的监测数据首先在DashBoard模块引入依赖 dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-turbine/artifactId/dependency然后在该模块的启动类添加这个工具的注解驱动工具 /*** com.scd.dashboard包下的主类用于启动Spring Boot应用并启用Hystrix仪表板功能。* 此类利用Spring Boot的自动配置特性以及Spring Cloud的Hystrix Dashboard支持* 提供了一个监控微服务中Hystrix命令执行情况的可视化界面。*/ package com.scd.dashboard;import org.springframework.boot.SpringApplication; // 引入Spring Boot的启动器 import org.springframework.boot.autoconfigure.SpringBootApplication; // 标记为自动配置的Spring Boot应用 import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard; // 启用Hystrix仪表板功能 import org.springframework.cloud.netflix.turbine.EnableTurbine;SpringBootApplication EnableHystrixDashboard EnableTurbine public class DashboardApplication {/*** 应用的主入口点使用SpringApplication.run方法启动Spring Boot应用。* 参数args - 应用启动时传入的命令行参数*/public static void main(String[] args) {SpringApplication.run(DashboardApplication.class, args); // 运行Spring Boot应用}} 在DashBoard模块配置文件中添加该工具的配置 turbine:# 配置聚合服务名称app-config: GOODS# 指定集群名称表达式注意不是字符串cluster-name-expression: new String(default)# 设置为true可以让同一主机上的服务通过主机名与端口号的组合进行区分。# 如果它为false则会以host来区分不同的服务默认值为truecombine-host-port: true然后重启DashBoard模块的两个实例再次访问地址http://localhost:4001/hystrix 在第一个输入框中添加http://localhost:4001/turbine.stream另外两个分别输入2000和Goods即可然后不停的刷新Goods服务的几个请求http://localhost:2002/goods/customer/exception/1、http://localhost:2001/goods/customer/exception/1、http://localhost:2001/goods/customer/timeout/1和http://localhost:2002/goods/customer/timeout/1便可看到监控到两个端口的数据了 在DashBoard的日志中也能看到 服务调用OpenFeign Ribbon在服务调用时以使用RestTemplate为主并以编码的形式完成为了简化开发Spring Cloud还提供了Spring Cloud OpenFeign这样的声明式调用Spring Cloud OpenFeign是基于Github OpenFeign开发的而Github OpenFeign使用的是自己的注解为了更为简单Spring Cloud OpenFeign在封装Github OpenFeign的同时提供了基于Spring MVC的注解来支持声明式服务调用。 首先在Goods模块中引入Spring Cloud OpenFeign的依赖 dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-openfeign/artifactId/dependency然后在Customer模块的CustomerController中添加一个POST请求 /*** 新增客户信息POST请求带请求体* param customer 客户信息请求体* return 信息*/PostMapping(/info)public String newCustomer(RequestBody CustomerPojo customer) {return 创建成功;} }这个方法使用了请求体RequestBodyCustomerPojo是Common模块的pojo代码如下 package com.scd.common.pojo;public class CustomerPojo {private Long id;private String name;private String note;public Long getId() {return id;}public void setId(Long id) {this.id id;}public String getName() {return name;}public void setName(String name) {this.name name;}public String getNote() {return note;}public void setNote(String note) {this.note note;} } 然后就可以使用OpenFeign进行声明式调用了代码如下 package com.scd.goods.facade;import com.scd.common.pojo.CustomerPojo; import com.scd.goods.facade.impl.CustomerOpenFeignFacadeImpl; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody;/*** 定义了对客户服务的远程调用接口。* 使用OpenFeign框架来代理对customer服务的HTTP请求。声明为OpenFeign客户端即服务消费者*/ FeignClient(valuecustomer public interface CustomerOpenFeignFacade {/*** 根据客户ID获取客户名称。* param id 客户的唯一标识ID。* return 客户的名称。*/GetMapping(/customer/name/{id})public String getCustomerName(PathVariable(id) Long id);/*** 模拟一个因为超时而可能失败的请求。* param id 客户的唯一标识ID。* return 客户的相关信息。*/GetMapping(/customer/timeout/{id})public String timeout(PathVariable(id) Long id);/*** 模拟一个因为异常而可能失败的请求。* param id 客户的唯一标识ID。* return 客户的相关信息。*/GetMapping(/customer/exception/{id})public String exception(PathVariable(id) Long id);/*** 创建一个新的客户。* param customer 客户的详细信息。* return 创建成功的提示信息。*/PostMapping(/customer/info)public String newCustomer(RequestBody CustomerPojo customer); } package com.scd.goods.facade.impl; import com.scd.common.pojo.CustomerPojo; import com.scd.goods.facade.CustomerOpenFeignFacade; import org.springframework.stereotype.Component;/*** 客户OpenFeign接口的实现类。* 该类实现了CustomerOpenFeignFacade接口提供了与客户相关的远程服务调用功能。*/ Component public class CustomerOpenFeignFacadeImpl implements CustomerOpenFeignFacade {/*** 根据客户ID获取客户名称。* param id 客户的唯一标识ID。* return 客户的名称或者错误信息。*/Overridepublic String getCustomerName(Long id) {return 获取客户名称失败;}/*** 模拟一个超时的接口调用。** param id 客户的唯一标识ID用于标识具体的客户。* return 超时后的错误信息。*/Overridepublic String timeout(Long id) {return 服务调用超时了;}/*** 模拟一个发生异常的接口调用。* param id 客户的唯一标识ID用于指定相关的客户。* return 异常发生后的错误信息。*/Overridepublic String exception(Long id) {return 服务调用异常;}/*** 创建一个新的客户。* param customer 客户的信息包括名称等。* return 创建结果或者错误信息。*/Overridepublic String newCustomer(CustomerPojo customer) {return 创建客户信息失败;} }在接口代码里都加了注解很像Spring MVC但并不是在开发Spring MVC的控制器只是在声明服务调用首先在接口上标注了FeignClient并且设置值customer,这是一个服务名称指向客户服务很显然这是为了调用客户服务所做的的声明式调用紧接着就是GetMapping和PostMapping分别对应GET请求和POST请求的服务调用 修改启动类添加注解驱动OpenFeign代码如下 package com.scd.goods.main;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.openfeign.EnableFeignClients;// 定义扫描包 SpringBootApplication(scanBasePackages com.scd.goods) // 驱动断路器工作Hystrix EnableCircuitBreaker EnableFeignClients(//扫描装配OpenFeign接口到IoC容器中basePackagescom.scd.goods) public class GoodsApplication {...... }然后再编写一个控制器代码如下 package com.scd.goods.controller;import com.scd.common.pojo.CustomerPojo; import com.scd.goods.facade.CustomerOpenFeignFacade; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;RestController RequestMapping(/openfeign) public class CustomerOpenFeignController {Qualifier(com.scd.goods.facade.CustomerOpenFeignFacade)Autowiredprivate CustomerOpenFeignFacade customerOpenFeignFacade null;GetMapping(/customer/name/{id})public String getCustomerName(PathVariable(id) Long id) {return customerOpenFeignFacade.getCustomerName(id);}GetMapping(/customer/timeout/{id})public String timeout(PathVariable(id) Long id) {return customerOpenFeignFacade.timeout(id);}GetMapping(/customer/exception/{id})public String exception(PathVariable(id) Long id) {return customerOpenFeignFacade.exception(id);}GetMapping(/customer/info)public String newCustomer() {CustomerPojo customerPojo new CustomerPojo();customerPojo.setId(1L);customerPojo.setName(customer_name_1);customerPojo.setNote(note_1);return customerOpenFeignFacade.newCustomer(customerPojo);} } 然后重启项目并访问地址http://localhost:2001/openfeign/customer/info 代码相对更清晰一些相比Ribbon而言 OpenFeign中使用Hystrix 在OpenFeign中使用Hystrix非常简单只需要在Goods模块的配置文件中设置feign.hystrix.enabled:true即可 # 配置Feign的熔断器功能 feign:circuitbreaker:enabled: true# 启用熔断器功能以提供对服务故障的保护# 设置为true表示熔断器功能启用false表示禁用# 配置Hystrix的熔断器功能hystrix:enabled: true# 启用Hystrix熔断器功能以提供对服务故障的保护# 设置为true表示Hystrix熔断器功能启用false表示禁用这样OpenFeign就会将所有的OpenFeign加入到Hystrix机制中这个配置项默认为false避免造成Hystrix的滥用当将该配置项设置为true时实现服务降级首先要有一个OpenFeign客户端接口的实现类来作为降级逻辑的提供者如下代码所示 package com.scd.goods.facade.impl;import com.scd.common.pojo.CustomerPojo; import com.scd.goods.facade.CustomerOpenFeignFacade; import org.springframework.stereotype.Component;/*** 客户OpenFeign接口的实现类。* 该类实现了CustomerOpenFeignFacade接口提供了与客户相关的远程服务调用功能。*/ Component public class CustomerOpenFeignFacadeImpl implements CustomerOpenFeignFacade {/*** 根据客户ID获取客户名称。* param id 客户的唯一标识ID。* return 客户的名称或者错误信息。*/Overridepublic String getCustomerName(Long id) {return 获取客户名称失败;}/*** 模拟一个超时的接口调用。** param id 客户的唯一标识ID用于标识具体的客户。* return 超时后的错误信息。*/Overridepublic String timeout(Long id) {return 服务调用超时了;}/*** 模拟一个发生异常的接口调用。* param id 客户的唯一标识ID用于指定相关的客户。* return 异常发生后的错误信息。*/Overridepublic String exception(Long id) {return 服务调用异常;}/*** 创建一个新的客户。* param customer 客户的信息包括名称等。* return 创建结果或者错误信息。*/Overridepublic String newCustomer(CustomerPojo customer) {return 创建客户信息失败;} } 要使这个实现类成为降级逻辑类需要满足以下三个条件 这个降级实现类可以实现OpenFeign客户端接口的各个方法将当前类装配到Spring IoC容器中在OpenFeign客户端接口的FeignClient中指明降级逻辑类 改造一下OpenFeign的客户端接口添加降级逻辑类 package com.scd.goods.facade;import com.scd.common.pojo.CustomerPojo; import com.scd.goods.facade.impl.CustomerOpenFeignFacadeImpl; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody;/*** 定义了对客户服务的远程调用接口。* 使用OpenFeign框架来代理对customer服务的HTTP请求。声明为OpenFeign客户端即服务消费者* 当customer服务不可用时会自动切换到降级处理类CustomerOpenFeignFacadeImpl。*/ FeignClient(valuecustomer,fallback CustomerOpenFeignFacadeImpl.class) public interface CustomerOpenFeignFacade {/*** 根据客户ID获取客户名称。* param id 客户的唯一标识ID。* return 客户的名称。*/GetMapping(/customer/name/{id})public String getCustomerName(PathVariable(id) Long id);/*** 模拟一个因为超时而可能失败的请求。* param id 客户的唯一标识ID。* return 客户的相关信息。*/GetMapping(/customer/timeout/{id})public String timeout(PathVariable(id) Long id);/*** 模拟一个因为异常而可能失败的请求。* param id 客户的唯一标识ID。* return 客户的相关信息。*/GetMapping(/customer/exception/{id})public String exception(PathVariable(id) Long id);/*** 创建一个新的客户。* param customer 客户的详细信息。* return 创建成功的提示信息。*/PostMapping(/customer/info)public String newCustomer(RequestBody CustomerPojo customer); } 重启Goods模块然后在访问http://localhost:2001/openfeign/customer/timeout/1, 刷新几次就会出现如下页面即走到了降级处理类中 网关Zuul 网关是请求各个服务实例的入口通过它可以实现对请求的过滤和转发在微服务体系中可以称之为服务端负载均衡网关分为硬件网关和软件网关软件网关又分为传统网关和API网关传统网关类似于Nginx可以通过配置即可使用如果要用到比较复杂的场景中就需要引入OpenResty(NginxLua), 而API网关比传统网关功能更强大更便利Spring Cloud Netflix Zuul就是一种API网关它以Spring Boot的形式封装了Netflix Zuul采用Java实现网关的各种功能 新建一个模块名为Zuul并引入如下依赖 !-- Spring Boot Web Starter提供Web应用程序的基础支持包括Spring MVC和TomcatWeb服务器 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency!-- Spring Cloud Netflix Eureka Client使应用程序能够作为Eureka服务注册表的客户端进行注册和发现 --dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-eureka-client/artifactId/dependency!-- Spring Cloud Netflix Zuul提供边缘服务和路由功能作为进入微服务架构的统一入口 --dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-zuul/artifactId/dependency然后改造启动类,代码如下 /*** ZuulApplication 类作为Spring Boot应用程序的入口点。* 启用了Zuul网关代理功能用于路由和过滤微服务请求。*/ package com.scd.zuul;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.zuul.EnableZuulProxy;SpringBootApplication(scanBasePackages com.scd.zuul) EnableZuulProxy public class ZuulApplication {/*** 程序的入口方法使用SpringApplication.run()启动Spring Boot应用。* param args 命令行参数*/public static void main(String[] args) {SpringApplication.run(ZuulApplication.class, args);} }添加EnableZuulProxy来驱动Zuul工作然后修改Zuul模块的配置文件如下所示 # 定义Spring应用名称它是一个服务的名称一个服务可拥有多个实例 spring:application:name: zuul# 向端口为1001和1002的Eureka服务治理中心注册 eureka:client:serviceUrl:defaultZone: http://localhost:1001/eureka, http://localhost:1002/eureka# Zuul的配置 zuul:# 路由配置routes:# 买家服务customer-service:# 请求拦截路径配置使用ANT风格path: /customer-api/**# 通过一个URL配置url: http://localhost:3001/# 商品服务配置goods-service:# 请求拦截路径配置使用ANT风格path: /goods-api/**service-id: goodszuul.routes.xxx配置项是配置路由的也就是请求地址的转发规则它所要配置的是一个MapString, ZuulProperties.ZuulRoute对象所以必然有key和对应的内容配置里存在两个key一个是customer-service另一个是goods-service而path配置的是请求所拦截的路径而url便是转发路径也就是说假如启动Zuul的端口是5001然后访问的地址是http://localhost:5001/goods-api/goods/customer/name/1时它将会路由转发到http://localhost:3001/goods/customer/name/1上这样便可访问到真实的服务实例但这样做没办法实现负载均衡因为除了3001端口还有3002端口的实例为了解决这个问题可以参考zuul.routes.goods-service的配置path依旧是请求的拦截路径而service-id将服务名配置为goods指向商品服务此时zuul会自动实现负载均衡 自动Zuul模块并访问地址http://localhost:5001/customer-api/customer/name/1和http://localhost:5001/goods-api/goods/customer/name/1,会得到如下页面 网关过滤器 Zuul作为Netflix开源的微服务网关其核心功能之一就是通过过滤器Filter来实现请求的预处理和后处理。Zuul过滤器主要分为以下几种类型每种类型在请求处理的不同阶段执行特定的任务 Pre(前置)过滤器:在请求被路由到目标服务之前执行用于身份验证、请求校验、日志记录、添加或修改请求头等操作。Routing(路由)过滤器:负责将请求路由到正确的后端服务这个过程包括构建请求、发送请求到后端服务并接收响应。Post(后置)过滤器:在收到后端服务的响应后执行但在响应被发送回客户端之前可用于响应数据的处理、修改响应头、日志记录、收集统计信息等Error(错误)过滤器:当在处理请求过程中发生任何错误时执行用于捕获异常、进行错误处理、记录日志或向客户端返回友好的错误信息。 Route(路由相关)过滤器:虽然有时会单独提及但其实可以看作是Routing和Post两类过滤器的综合专注于处理与路由直接相关的逻辑包括成功路由后的处理及路由失败的错误处理。 每个过滤器都具有明确的执行顺序由其filterType和filterOrder属性共同决定。开发者可以根据需求自定义过滤器灵活地扩展Zuul的功能 Zuul内部有很多过滤器它们之间形成了责任链 其中pre过滤器在路由到源服务器之前进行route过滤器路由到源服务器执行post过滤器在源服务器之后执行error过滤器在路由到源服务器错误时执行其中error是post过滤器的一种 在Zuul中提供了抽象类ZuulFilter来定义过滤器这个抽象类有几个重要的方法 filterType过滤器类型可以是pre、route、post、error等filterOrder返回一个整数代表过滤器在责任链中的顺序shouldFilter是否启用过滤器可以根据条件确定过滤器是否拦截请求run过滤器的具体逻辑来自于IZuulFilter接口它是过滤器的核心方法返回一个Object对象如果返回null则继续责任链后续正常逻辑 网关过滤器实例 通过继承该抽象类实现自定义过滤器例如验证码机制假如在redis中存了一个这样的验证码code1:123456789,为了使用redis需要在Zuul模块引入Redis相关依赖并在配置文件中配置Redis如下所示 !-- 加入Spring Boot的Redis依赖 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId!--排除同步Redis客户端Lettuce--exclusionsexclusiongroupIdio.lettuce/groupIdartifactIdlettuce-core/artifactId/exclusion/exclusions/dependency!--加入Redis客户端Jedis--dependencygroupIdredis.clients/groupIdartifactIdjedis/artifactId/dependency# 定义Spring应用名称它是一个服务的名称一个服务可拥有多个实例 spring:application:name: zuuldata:redis:host: 192.168.3.115password: 123456789jedis:pool:max-active: 20max-wait: 2000min-idle: 5max-idle: 15# 向端口为1001和1002的Eureka服务治理中心注册 eureka:client:serviceUrl:defaultZone: http://localhost:1001/eureka, http://localhost:1002/eureka# Zuul的配置 zuul:# 路由配置routes:# 买家服务customer-service:# 请求拦截路径配置使用ANT风格path: /customer-api/**# 通过一个URL配置url: http://localhost:3001/# 商品服务配置goods-service:# 请求拦截路径配置使用ANT风格path: /goods-api/**service-id: goods然后过滤器检测验证码代码如下 package com.scd.zuul.filter;import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.apache.http.HttpStatus; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils;import java.util.HashMap; import java.util.Map;// 装配过滤器Zuul会自动加入到责任链中 Component public class VerificationFilter extends ZuulFilter {// 注入StringRedisTemplateAutowiredprivate StringRedisTemplate redisTemplate null;//验证码键和值的参数名称private final static String VERIFICATION_KEY_PARAM_NAME validateKey;private final static String VERIFICATION_CODE_PARAM_NAME validateCode;/*** 获取过滤器类型。* 本方法重写了父类方法旨在明确指定当前过滤器的类型为前置过滤器。* 返回的字符串值对应于过滤器常量中的PRE_TYPE表示该过滤器将在处理链的开始阶段被应用。* return 过滤器类型返回FilterConstants.PRE_TYPE表示前置过滤器。*/Overridepublic String filterType() {return FilterConstants.PRE_TYPE;}/*** 设置过滤器的执行顺序。* 此方法定义了过滤器在过滤器链中的优先执行顺序。返回值越小过滤器将越早被执行。* 通过返回值设置为FilterConstants.PRE_DECORATION_FILTER_ORDER减1* 确保当前过滤器在所有预装饰过滤器之前执行因为它的顺序比预装饰过滤器的标准顺序还要靠前。* return 返回一个整数表示此过滤器的执行顺序。*/Overridepublic int filterOrder() {return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;}/*** 判断是否应该进行过滤。* 本方法用于决定是否对当前请求进行验证码验证。只有在请求参数中同时存在验证码密钥和验证码代码时* 才会进行验证流程否则忽略验证。* return boolean 返回true表示需要进行过滤即进行验证码验证返回false表示不需要。*/Overridepublic boolean shouldFilter() {// 获取验证码密钥参数String key getParam(VERIFICATION_KEY_PARAM_NAME);// 获取验证码代码参数String code getParam(VERIFICATION_CODE_PARAM_NAME);// 判断验证码密钥和验证码代码是否都不为空不为空则表示需要进行过滤return !StringUtils.isEmpty(key) !StringUtils.isEmpty(code);}/*** 根据参数名从请求中获取参数值。* 优先从请求参数中获取值如果参数不存在则尝试从请求头中获取。* 这样设计是为了确保可以从多种途径获取必要的请求数据以提高代码的灵活性和鲁棒性。* param name 参数名* return 参数值。如果参数既不存在于请求参数中也不存在于请求头中则返回空字符串。*/private String getParam(String name) {// 获取当前请求的上下文对象用于后续从中获取请求参数或头信息RequestContext cxt RequestContext.getCurrentContext();// 尝试从请求参数中获取指定名称的参数值String param cxt.getRequest().getParameter(name);// 如果请求参数中不存在该参数则尝试从请求头中获取if (StringUtils.isEmpty(param)) {param cxt.getRequest().getHeader(name);}// 返回获取到的参数值如果既未从参数中获取到也未从头信息中获取到则返回空字符串return param;}/*** 覆盖父类的run方法执行验证码验证逻辑。* 如果验证码有效过滤器放行请求否则设置错误响应体并放行。* return 如果验证码验证通过返回null表示放行无论验证是否通过实际都会返回null。* throws ZuulException 当发生与验证码验证相关的异常时抛出。*/Overridepublic Object run() throws ZuulException {String key getParam(VERIFICATION_KEY_PARAM_NAME); // 获取验证码键String code getParam(VERIFICATION_CODE_PARAM_NAME); // 获取请求中的验证码String redisCode redisTemplate.opsForValue().get(key); // 从Redis获取存储的验证码/*** 验证请求中的验证码与Redis中的验证码是否一致。* 如果一致执行后续逻辑如果不一致跳过并执行错误处理。*/if (code.equals(redisCode)) {// 验证码匹配放行请求return null;}// 验证码不匹配或验证失败执行错误处理setErrorBody(); // 设置错误响应体return null; // 返回null表示请求已被处理}private void setErrorBody() {// 获取请求内容对象RequestContext ctx RequestContext.getCurrentContext();// 不再放行路由ctx.setSendZuulResponse(false);// 设置响应码为401-未签名ctx.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);// 设置响应类型为JSON类型ctx.getResponse().setContentType(MediaType.APPLICATION_JSON_VALUE);// 设置编码类型ctx.getResponse().setCharacterEncoding(UTF-8);// 响应结果MapString, String result new HashMap();result.put(success, false);result.put(message, 验证码错误请检查您的输入);// 将result转换为JSON媒体类型ObjectMapper mapper new ObjectMapper();String body null;try {// 转变为JSON字符串body mapper.writeValueAsString(result);} catch (JsonProcessingException e) {e.printStackTrace();}// 设置响应体,准备响应请求ctx.setResponseBody(body);} }过滤器继承了抽象类ZuulFilter 且覆盖率抽象类的方法Component表示启动程序之后将其装配到Spring IoC容器中这样Zuul会自动识别它为过滤器并且装载到过滤器的责任链中,其他内容注释很清楚 然后重启Zuul模块并访问连接http://localhost:5001/customer-api/customer/name/1?validateKeycode1validateCode123456789 http://localhost:5001/customer-api/customer/name/1?validateKeycode1validateCode12345 便能看到过滤器效果 网关Gateway Netflix的Zuul一直不能很好地支持响应式编程后来Spring Cloud推出自己的网关GatewayGateway和传统的组件不同它依赖于Spring Boot和Spring WebFlux采用的是响应式编程(Reactive Programming) 新建一个Gateway模块并引入如下依赖 !-- 引入Gateway --dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-gateway/artifactId/dependency!-- 引入服务发现 --dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-eureka-client/artifactId/dependency需要注意的几点 Gateway用的是Spring WebFlux技术它和spring-boot-starter-web的会产生冲突因此只能引入其一Gateway只支持Netty容器引入Tomcat或者Jetty等同期在运行期间会引发异常如果在新建模块或者项目的时候选择了war模式则需要删除spring-boot-starter-web和IDE生成的ServletInitializer.java并将打包形式改为jar否则会引发错误 通过代码配置路由 然后修改启动类添加路由规则代码如下 package com.scd.gateway;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.context.annotation.Bean;SpringBootApplication public class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);}/*** 创建路由规则* param builder -- 路由构造器* return 路由规则*/Beanpublic RouteLocator customRouteLocator(RouteLocatorBuilder builder) {return builder.routes()// 配置路由// route方法两个参数第一个是id第二个断言.route(customer, r - r.path(/customer-api/**)// 过滤器删除一个层级再匹配地址.filters(f-f.stripPrefix(1))// 转发到具体的URI.uri(http://localhost:3001))// 基于服务发现的路由.route(goods, r-r.path(/goods-api/**)// 过滤器删除一个层级再匹配地址.filters(f-f.stripPrefix(1)// 约定以“lb://{service-id}”为格式.uri(lb://goods))// 创建.build();} }customRouteLocator方法回返回一个RouteLocator对象这是Gateway的路由对象将其装配到Spring IoC容器后Gateway就会将其加载并作为路由规则customRouteLocator方法中有两个路由规则都是以.route()方法开头每个route()方法代表一个路由它有两个参数一个是字符串参数作为路由id另一个是断言代码中是一个lambda表达式这里的断言作用是路由匹配判断哪些请求和这个路由匹配path方法表示采用路径匹配当出现了与ANT风格表达式/customer-api/**匹配的路径时就会启用这个路由接着是filters方法它代表过滤器也就是在Gateway执行路由之前或者之后可以加入一些逻辑这里的stripPrefix方法表示删除匹配地址中的一个层级如果配置的匹配路径是/customer-api/**,那么请求地址是http://localhost:6001/customer-api/customer/name/1,通过Gateway的路由源服务器地址就是http://localhost:3001/customer-api/customer/name/1显然这是不能匹配到源服务器里的而这里的stripPrefix方法代表删除一个层级就是删除了路径中的/customer-api,这样路由到源服务器的地址就是http://localhost:3001/customer/name/1了这才是能找到服务的地址uri方法配置的则是源服务器的路径是真实的源服务器地址customRouteLocator方法中第二个route方法和第一个不同的是uri方法配置的内容这里配置的是lb://goods,在Gateway中约定格式lb://{service-id}为服务发现的路由通过它就可以将请求匹配到商品(Goods)服务的各个可用实例从而实现负载均衡在注册服务治理中心的环境中经常会用到服务发现的路由方式 通过配置文件配置路由 除了这种编码路由的方式Gateway还提供了配置的方法在Gateway项目的application.yml文件写入如下配置和上面的代码等价 # Spring应用微服务名称 spring:application:name: gatewaycloud:gateway:# 开始配置路径routes:# 路径匹配- id: customer# 转发URIuri: http://localhost:3001# 断言配置predicates:- Path/customer-api/**# 过滤器配置filters:- StripPrefix1# 路径匹配- id: goods# 转发URIuri: lb://goods# 断言配置predicates:- Path/goods-api/**# 过滤器配置filters:- StripPrefix1# 向端口为1001和1002的Eureka服务治理中心注册 eureka:client:serviceUrl:defaultZone: http://localhost:1001/eureka, http://localhost:1002/eureka# 配置启动端口 server:port: 6001logging:level:root: info路由断言工厂 Gateway中几个比较重要的概念 断言(Predicate):断言用于检查请求是否与路由匹配只有在相互匹配时才会使用路由过滤器(Filter):通过过滤器我们可以在执行路由前后加入自己的逻辑Gateway存在全局过滤器和局部过滤器路由(Route):路由是一个最基本的组件它由ID、目标URI、断言集合和过滤器集合等组成当断言判断为true时才会匹配到路由一个路由可以存在多个断言也可以存在多个过滤器 在Gateway的源码中已经提供了很多过滤器和断言供我们使用例如之前代码中的path和stripPrefix都是Gateway提供的断言和过滤器. Gateway的断言是通过它的路由断言工厂提供的这些断言工厂都实现了接口RoutePredicateFactoryC源码如下所示 package org.springframework.cloud.gateway.handler.predicate;import java.util.function.Consumer; import java.util.function.Predicate; import org.springframework.cloud.gateway.handler.AsyncPredicate; import org.springframework.cloud.gateway.support.Configurable; import org.springframework.cloud.gateway.support.NameUtils; import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; import org.springframework.cloud.gateway.support.ShortcutConfigurable; import org.springframework.web.server.ServerWebExchange;/*** 路由断言工厂接口用于创建匹配网关中路由的断言。* 它扩展了ShortcutConfigurable与Configurable接口支持配置快捷方式及带有泛型C的可配置功能。* param C 配置类型用于断言的特定配置。*/ FunctionalInterface public interface RoutePredicateFactoryC extends ShortcutConfigurable, ConfigurableC {/*** 断言配置中的模式键。*/String PATTERN_KEY pattern;/*** 应用给定的配置来创建一个断言。* param consumer 配置断言语句的消费者。* return 基于给定配置匹配路由的断言。*/default PredicateServerWebExchange apply(ConsumerC consumer) {C config this.newConfig();consumer.accept(config);this.beforeApply(config);return this.apply(config);}/*** 应用给定的配置来创建一个异步断言。* param consumer 配置异步断言语句的消费者。* return 基于给定配置异步匹配路由的AsyncPredicate。*/default AsyncPredicateServerWebExchange applyAsync(ConsumerC consumer) {C config this.newConfig();consumer.accept(config);this.beforeApply(config);return this.applyAsync(config);}/*** 获取配置类的类型。* return 配置类的类型。* throws UnsupportedOperationException 如果此方法未实现。*/default ClassC getConfigClass() {throw new UnsupportedOperationException(getConfigClass() 方法未实现);}/*** 创建配置的新实例。* return 配置的新实例。* throws UnsupportedOperationException 如果此方法未实现。*/default C newConfig() {throw new UnsupportedOperationException(newConfig() 方法未实现);}/*** 在应用配置前调用的钩子方法。* param config 将要应用的配置。*/default void beforeApply(C config) {}/*** 根据给定的配置创建一个断言。* param config 用于断言的配置。* return 基于给定配置匹配路由的断言。*/PredicateServerWebExchange apply(C config);/*** 根据给定的配置创建一个异步断言。* param config 用于AsyncPredicate的配置。* return 基于给定配置异步匹配路由的AsyncPredicate。*/default AsyncPredicateServerWebExchange applyAsync(C config) {return ServerWebExchangeUtils.toAsyncPredicate(this.apply(config));}/*** 返回断言的名称该名称已规范化适用于路由中使用。* return 断言的名称。*/default String name() {return NameUtils.normalizeRoutePredicateName(this.getClass());} } 它有很多实现类通过这些实现类实现了多种断言 这些类的命名非常关键其规则都是XXXRoutePredicateFactory这个规则很重要例如之前的用到的path方法就意味着它是使用PathRoutePredicateFactory生成的断言在配置文件中的- Path/customer-api/**也是使用PathRoutePredicateFactory生成的断言 如果想使用Query断言可以这样改造代码如下所示 package com.scd.gateway;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.context.annotation.Bean;SpringBootApplication public class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);}/*** 创建路由规则* param builder -- 路由构造器* return 路由规则*/Beanpublic RouteLocator customRouteLocator(RouteLocatorBuilder builder) {return builder.routes()// 配置路由// route方法两个参数第一个是id第二个断言.route(customer, r - r.path(/customer-api/**)// 需要存在请求参数“id” 才匹配路由.and().query(id)// 需要存在请求参数“id” 且是数字才匹配路由.and().query(id, ^[0-9]*$)// 过滤器删除一个层级再匹配地址.filters(f-f.stripPrefix(1))// 转发到具体的URI.uri(http://localhost:3001))// 基于服务发现的路由.route(goods, r-r.path(/goods-api/**)// 过滤器删除一个层级再匹配地址.filters(f-f.stripPrefix(1)// 添加响应头.addResponseHeader(response-header, response-value))// 约定以“lb://{service-id}”为格式.uri(lb://goods))// 创建.build();} }通过query()添加了一个参数断言判断存在参数id才会匹配路由而第二个query()又判断了存在参数id且是一个数字才会匹配路由在配置文件中也可以做同样的事情 # Spring应用微服务名称 spring:application:name: gatewaycloud:gateway:# 开始配置路径routes:# 路径匹配- id: customer# 转发URIuri: http://localhost:3001# 断言配置predicates:- Path/customer-api/**- Queryid- Queryid, ^[0-9]*$# 过滤器配置filters:- StripPrefix1# 路径匹配- id: goods# 转发URIuri: lb://goods# 断言配置predicates:- Path/goods-api/**# 过滤器配置filters:- StripPrefix1# 向端口为1001和1002的Eureka服务治理中心注册 eureka:client:serviceUrl:defaultZone: http://localhost:1001/eureka, http://localhost:1002/eureka# 配置启动端口 server:port: 6001logging:level:root: info过滤器工厂 过滤器允许在路由前后执行自己的逻辑Gateway提供了许多过滤器工厂来产生过滤器,这些过滤器工厂都实现了接口GatewayFilterFactoryC,源码如下 package org.springframework.cloud.gateway.filter.factory;import java.util.function.Consumer; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.support.Configurable; import org.springframework.cloud.gateway.support.HasRouteId; import org.springframework.cloud.gateway.support.NameUtils; import org.springframework.cloud.gateway.support.ShortcutConfigurable; import org.springframework.http.server.reactive.ServerHttpRequest;/*** GatewayFilter工厂接口用于创建GatewayFilter实例。* 提供了配置GatewayFilter的灵活性支持通过函数式接口进行配置。** param C 配置类型的参数化类型。*/ FunctionalInterface public interface GatewayFilterFactoryC extends ShortcutConfigurable, ConfigurableC {/*** 配置名称的键。*/String NAME_KEY name;/*** 配置值的键。*/String VALUE_KEY value;/*** 根据给定的routeId和配置消费者创建并应用GatewayFilter。** param routeId 路由ID用于配置GatewayFilter。* param consumer 配置消费者用于定制GatewayFilter的配置。* return 应用了配置的GatewayFilter实例。*/default GatewayFilter apply(String routeId, ConsumerC consumer) {C config this.newConfig();consumer.accept(config);return this.apply(routeId, config);}/*** 创建并应用GatewayFilter使用配置消费者进行配置。** param consumer 配置消费者用于定制GatewayFilter的配置。* return 应用了配置的GatewayFilter实例。*/default GatewayFilter apply(ConsumerC consumer) {C config this.newConfig();consumer.accept(config);return this.apply(config);}/*** 获取配置类的Class对象。* 默认实现抛出UnsupportedOperationException异常表明该方法未实现。** return 配置类的Class对象。*/default ClassC getConfigClass() {throw new UnsupportedOperationException(getConfigClass() not implemented);}/*** 创建一个新的配置实例。* 默认实现抛出UnsupportedOperationException异常表明该方法未实现。** return 配置的实例。*/default C newConfig() {throw new UnsupportedOperationException(newConfig() not implemented);}/*** 根据给定的配置创建并应用GatewayFilter。** param config 配置实例用于定制GatewayFilter。* return 应用了配置的GatewayFilter实例。*/GatewayFilter apply(C config);/*** 根据当前工厂类的名称生成并返回标准化的过滤器名称。** return 标准化的过滤器名称。*/default String name() {return NameUtils.normalizeFilterFactoryName(this.getClass());}/*** 修改ServerHttpRequest的构建器。* 默认实现抛出UnsupportedOperationException异常表明该方法未实现。** param request 原始请求。* return 修改后的请求构建器。* deprecated 该方法已过时。*/Deprecateddefault ServerHttpRequest.Builder mutate(ServerHttpRequest request) {return request.mutate();} } 这些过滤器工厂类都继承了接口同样的也需要特别注意这些工厂的命名规则XXXGatewayFilterFactory,以AddResponseHeaderGatewayFilterFactory为例修改之前的代码如下 package com.scd.gateway;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.context.annotation.Bean;SpringBootApplication public class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);}/*** 创建路由规则* param builder -- 路由构造器* return 路由规则*/Beanpublic RouteLocator customRouteLocator(RouteLocatorBuilder builder) {return builder.routes()// 配置路由// route方法两个参数第一个是id第二个断言.route(customer, r - r.path(/customer-api/**)// 需要存在请求参数“id” 才匹配路由.and().query(id)// 需要存在请求参数“id” 且是数字才匹配路由.and().query(id, ^[0-9]*$)// 过滤器删除一个层级再匹配地址.filters(f-f.stripPrefix(1))// 转发到具体的URI.uri(http://localhost:3001))// 基于服务发现的路由.route(goods, r-r.path(/goods-api/**)// 过滤器删除一个层级再匹配地址.filters(f-f.stripPrefix(1)// 添加响应头.addResponseHeader(response-header, response-value))// 约定以“lb://{service-id}”为格式.uri(lb://goods))// 创建.build();}} 也可以通过配置文件实现如下所示 # Spring应用微服务名称 spring:application:name: gatewaycloud:gateway:# 开始配置路径routes:# 路径匹配- id: customer# 转发URIuri: http://localhost:3001# 断言配置predicates:- Path/customer-api/**- Queryid- Queryid, ^[0-9]*$# 过滤器配置filters:- StripPrefix1# 路径匹配- id: goods# 转发URIuri: lb://goods# 断言配置predicates:- Path/goods-api/**# 过滤器配置filters:- AddResponseHeaderresponse-header, response-value- StripPrefix1# 向端口为1001和1002的Eureka服务治理中心注册 eureka:client:serviceUrl:defaultZone: http://localhost:1001/eureka, http://localhost:1002/eureka# 配置启动端口 server:port: 6001logging:level:root: info主要用来给客户端响应的时候添加一个响应头 自定义过滤器 在Gateway中过滤器分为全局过滤器和局部过滤器全局过滤器对所有路由有效局部过滤器对指定的路由有效局部过滤器只需要实现GateFilter接口即可而全局过滤器需要实现GlobalFilter接口, 并将其装配到Spring IoC容器中 改造一下之前的customRouteLocator方法加入自定义局部过滤器如下所示 package com.scd.gateway;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.context.annotation.Bean; import org.springframework.http.MediaType; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import reactor.core.publisher.Mono;SpringBootApplication public class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);}/*** 创建路由规则* param builder -- 路由构造器* return 路由规则*/Beanpublic RouteLocator customRouteLocator(RouteLocatorBuilder builder) {return builder.routes()// 配置路由// route方法两个参数第一个是id第二个断言.route(customer, r - r.path(/customer-api/**)// 需要存在请求参数“id” 才匹配路由.and().query(id)// 需要存在请求参数“id” 且是数字才匹配路由.and().query(id, ^[0-9]*$)// 过滤器删除一个层级再匹配地址.filters(f-f.stripPrefix(1))// 转发到具体的URI.uri(http://localhost:3001))// 基于服务发现的路由.route(goods, r-r.path(/goods-api/**)// 过滤器删除一个层级再匹配地址.filters(f-f.stripPrefix(1).filter(myGatewayFilter()))// 约定以“lb://{service-id}”为格式.uri(lb://goods))// 创建.build();}// 开发局部过滤器private GatewayFilter myGatewayFilter() {return (exchange, chain) - {System.out.println(我是局部过滤器逻辑);// 获取请求对象ServerHttpRequest request exchange.getRequest();// 增加请求头信息request request.mutate().header(request-header, my-request-header).build();/**错误增加请求头的代码因为request.getHeaders()返回的是只读请求头不可修改*/// request.getHeaders().add(header, myheader);// 获取请求参数String id request.getQueryParams().getFirst(id);// 调用filter方法让过滤器责任链向下继续执行MonoVoid mono chain.filter(exchange);// 获取应答对象ServerHttpResponse response exchange.getResponse();// 响应类型为JSONresponse.getHeaders().setContentType(MediaType.APPLICATION_JSON);// 添加响应头response.getHeaders().add(response-header, my-response-header);return mono;};} }.filter(myGatewayFilter())):filter方法调用了自定义的myGatewayFilter()局部过滤器方法返回的过滤器通过这样给路由添加过滤器在myGatewayFilter()方法中使用了正则表达式创建局部过滤器它需要实现的方法是MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain);,其中参数exchange是一个请求交互对象我们可以通过它来获取请求对象(ServerHttpRequest)正如代码中用它设置了请求头并获取了参数当然也可以通过参数exchange获取应答对象(ServerHttpResponse),正如代码中用它设置了响应类型和响应头而chain是一个过滤器责任链通过它的filter方法继续执行下一层次的过滤器// request.getHeaders().add(header, myheader);注释掉的这个方法在运行中会出现问题request.getHeaders()返回的是一个只读的请求头对象不可以写入这个写法设置请求头会抛异常表示不支持该方法 在Gateway中还可以使用全局过滤器全局过滤器对所有路由有效使用它非常简单在启动类中添加如下全局过滤器代码如下所示 // 定义全局过滤器Beanpublic GlobalFilter globalFilter() {return (exchange, chain) - {System.out.println(我是全局过滤器);MonoVoid mono chain.filter(exchange);return mono;};}方法标注了Bean, 代表会将其返回的全局过滤器(GlobalFilter接口对象)装配到SpringIoC容器中此时Gateway会自动识别将其装配到过滤器的责任链中在globalFilter方法中通过Lambda表达式创建了GlobalFilter接口对象其参数和局部过滤器GatewayFilter接口一致 最终启动类如下代码所示 package com.scd.gateway;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.context.annotation.Bean; import org.springframework.http.MediaType; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import reactor.core.publisher.Mono;SpringBootApplication public class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);}/*** 创建路由规则* param builder -- 路由构造器* return 路由规则*/Beanpublic RouteLocator customRouteLocator(RouteLocatorBuilder builder) {return builder.routes()// 配置路由// route方法两个参数第一个是id第二个断言.route(customer, r - r.path(/customer-api/**)// 过滤器删除一个层级再匹配地址.filters(f-f.stripPrefix(1))// 转发到具体的URI.uri(http://localhost:3001))// 基于服务发现的路由.route(goods, r-r.path(/goods-api/**)// 过滤器删除一个层级再匹配地址.filters(f-f.stripPrefix(1).filter(myGatewayFilter()))// 约定以“lb://{service-id}”为格式.uri(lb://goods))// 创建.build();}// 开发局部过滤器private GatewayFilter myGatewayFilter() {return (exchange, chain) - {System.out.println(我是局部过滤器逻辑);// 获取请求对象ServerHttpRequest request exchange.getRequest();// 增加请求头信息request request.mutate().header(request-header, my-request-header).build();/**错误增加请求头的代码因为request.getHeaders()返回的是只读请求头不可修改*/// request.getHeaders().add(header, myheader);// 获取请求参数String id request.getQueryParams().getFirst(id);// 调用filter方法让过滤器责任链向下继续执行MonoVoid mono chain.filter(exchange);// 获取应答对象ServerHttpResponse response exchange.getResponse();//响应类型为JSONresponse.getHeaders().setContentType(MediaType.APPLICATION_JSON);// 添加响应头response.getHeaders().add(response-header, my-response-header);return mono;};}// 定义全局过滤器Beanpublic GlobalFilter globalFilter() {return (exchange, chain) - {System.out.println(我是全局过滤器);MonoVoid mono chain.filter(exchange);return mono;};}} 用6001端口启动Gateway模块然后访问http://localhost:6001/customer-api/customer/name/1和http://localhost:6001/goods-api/goods/customer/name/1会得到如下页面 并且在Gateway的控制台能够看到如下输出说明网关配置生效 断路器Resilience4j Netflix公司已经不再维护Netflix HystrixSpring Cloud推荐使用Resilience4j、Alibaba Sentinel。其中Resilience4j是Spring Cloud推荐的主流熔断器它是一款参考Netflix Hystrix开发的容错工具且更为强大和灵活当前Spring Cloud官方提供了spring-cloud-starter-circuitbreaker-resilience4j包不过功能还是算强大Resilience4j自己提供的resilience4j-spring-boot2包更强一些 在Goods模块引入该依赖 dependencygroupIdio.github.resilience4j/groupIdartifactIdresilience4j-spring-boot2/artifactIdversion1.2.0/version/dependencyResilience4j包含了Hystrix的各种功能还给出了Hystrix的限速器resilience4j-spring-boot2会依赖resilience4j各种包如下所示 在Resilience4j内部使用环形数组统计和分析请求环形数组存在两个指针头(header)和尾(tail),当header (tail 1) mod buffer.length 时说明环形数组已满此时它会丢弃旧的请求当headertail时说明环形数组为空可以自由操作 一个环形数组可以放1024个二进制单位当服务调用成功时Resilience4j就会在对应的位存放0失败则存放1因此Resilience4j可以通过这些数据分析服务调用情况 Resilience4j采用注册机的形式各个组件可以注册在注册机里比如断路器注册机(CircuitBreakerRegistry)可以注册多个断路器(CircuitBreaker), 同样限速器注册机(RateLimiterRegistry)可以注册多个限速器(RateLimiter) 断路器CircuitBreaker resilience4j-spring-boot2中可以通过配置使用断路器注册机如下所示 # 配置Resilience4J的断路器模块 resilience4j:# 配置断路器配置的断路器会注册到断路器注册机CircuitBreakerRegistry中# 配置特定后端服务的断路器设置circuitbreaker:backends:# 针对名为“customer”的后端服务配置断路器customer:# 在断路器打开状态下需要等待的时间间隔之后会尝试进入半打开状态 默认为60秒wait-duration-in-open-state: 5s# 当错误率超过此阈值时断路器将打开# 当请求失败比例达到30%时打开断路器默认为50%failure-rate-threshold: 30# 在半打开状态下允许通过的请求数量用于判断是否应该关闭断路器# 在半打开状态下至少尝试5次才重新关闭断路器permitted-number-of-calls-in-half-open-state: 5resilience4j.circuitbreaker.*代表配置resilience4j的断路器注册机resilience4j.circuitbreaker.backends.*配置的是一个注册机下的断路器它是一个MapString, InstanceProperties类型customer是其中一个键后面的内容就是InstanceProperties对象的属性 在讲到Hystrix的时候知道它有3中状态Open、HALF-OPEN、ClOSEDresilience4j也大体适用这三种状态只是条件不同具体的看配置中的注释即可 然后编写服务调用类代码如下 package com.scd.goods.facade;public interface R4jCustomerFacade {public String exception(Long id);public String getCustomerName(Long id);public String timeout(Long id);public String getCustomerNameWithBulkhead(Long id); } package com.scd.goods.facade.impl;import com.scd.goods.facade.CustomerOpenFeignFacade; import com.scd.goods.facade.R4jCustomerFacade; import io.github.resilience4j.bulkhead.Bulkhead; import io.github.resilience4j.bulkhead.BulkheadRegistry; import io.github.resilience4j.circuitbreaker.CircuitBreaker; import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; import io.github.resilience4j.ratelimiter.RateLimiter; import io.github.resilience4j.ratelimiter.RateLimiterRegistry; import io.github.resilience4j.timelimiter.TimeLimiter; import io.github.resilience4j.timelimiter.TimeLimiterRegistry; import io.vavr.CheckedFunction0; import io.vavr.control.Try; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service;import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.function.Supplier;Service public class R4jCustomerFacadeImpl implements R4jCustomerFacade {// 注入OpenFeign客户端接口Qualifier(com.scd.goods.facade.CustomerOpenFeignFacade)Autowiredprivate CustomerOpenFeignFacade customerOpenFeignFacade null;// 注入断路器注册机Autowiredprivate CircuitBreakerRegistry circuitBreakerRegistry null;Overridepublic String exception(Long id) {// 获取名为“customer”的断路器,在application.yml中配置的CircuitBreaker customerCb circuitBreakerRegistry.circuitBreaker(customer);// 描述事件并捆绑断路器准备发送CheckedFunction0String decoratedSupplier CircuitBreaker.decorateCheckedSupplier(customerCb, () - {return customerOpenFeignFacade.exception(id);});// 尝试获取结果TryString result Try.of(decoratedSupplier)// 服务降级.recover(ex-{System.out.println(发生异常了请查看异常信息 ex.getMessage());return 发生异常请查看后台日志;});return result.get();} } 为了避免冲突要将配置文件中的feign.hystrix.enabled和feign.circuitbreaker.enabled设置为false禁用Hystrix,然后编写一个控制器代码如下 package com.scd.goods.controller;import com.scd.goods.facade.R4jCustomerFacade; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;RestController RequestMapping(/r4j) public class R4jController {Autowiredprivate R4jCustomerFacade r4JCustomerFacade null;GetMapping(/customer/exception/{id})public String exception(PathVariable(id) Long id) {return r4JCustomerFacade.exception(id);} }然后重启Goods模块并访问http://localhost:2001/r4j/customer/exception/1,得到如下页面 限速器RateLimiter 限速器在微服务体系中常用任何服务都有并发上限从而避免请求高于服务的承受能力大致路线跟断路器相似首先在Goods模块的配置文件中添加限速器的注册配置如下所示 # 配置限速器ratelimiter:instances:# 配置命名为“customer”的限速器customer:# 时间戳内限制通过的请求数默认为50limit-for-period: 1# 配置时间戳单位毫秒。默认值为500 nslimit-refresh-period: 1s# 超时时间timeout-duration: 1s然后添加调用限速器方法如下代码所示 package com.scd.goods.facade.impl;import com.scd.goods.facade.CustomerOpenFeignFacade; import com.scd.goods.facade.R4jCustomerFacade; import io.github.resilience4j.bulkhead.Bulkhead; import io.github.resilience4j.bulkhead.BulkheadRegistry; import io.github.resilience4j.circuitbreaker.CircuitBreaker; import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; import io.github.resilience4j.ratelimiter.RateLimiter; import io.github.resilience4j.ratelimiter.RateLimiterRegistry; import io.github.resilience4j.timelimiter.TimeLimiter; import io.github.resilience4j.timelimiter.TimeLimiterRegistry; import io.vavr.CheckedFunction0; import io.vavr.control.Try; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service;import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.function.Supplier;Service public class R4jCustomerFacadeImpl implements R4jCustomerFacade {// 注入OpenFeign客户端接口Qualifier(com.scd.goods.facade.CustomerOpenFeignFacade)Autowiredprivate CustomerOpenFeignFacade customerOpenFeignFacade null;// 注入断路器注册机Autowiredprivate CircuitBreakerRegistry circuitBreakerRegistry null;Overridepublic String exception(Long id) {// 获取名为“customer”的断路器,在application.yml中配置的CircuitBreaker customerCb circuitBreakerRegistry.circuitBreaker(customer);// 描述事件并捆绑断路器准备发送CheckedFunction0String decoratedSupplier CircuitBreaker.decorateCheckedSupplier(customerCb, () - {return customerOpenFeignFacade.exception(id);});// 尝试获取结果TryString result Try.of(decoratedSupplier)// 服务降级.recover(ex-{System.out.println(发生异常了请查看异常信息 ex.getMessage());return 发生异常请查看后台日志;});return result.get();}// 注入限速器注册机Autowiredprivate RateLimiterRegistry rateLimiterRegistry null;Overridepublic String getCustomerName(Long id) {// 获取名称为“customer的限速器,在配置文件application.yml中配置的RateLimiter customerRl rateLimiterRegistry.rateLimiter(customer);// 描述事件并且绑定限速器准备发送CheckedFunction0String decoratedSupplier RateLimiter.decorateCheckedSupplier(customerRl, () - {return customerOpenFeignFacade.getCustomerName(1L);});// 尝试获取结果TryString result Try.of(decoratedSupplier)// 服务降级.recover(ex-{System.out.println(超速了 ex.getMessage());return 超速了请观察后台日志;});return result.get();} }在控制器中添加对应方法代码如下 package com.scd.goods.controller;import com.scd.goods.facade.R4jCustomerFacade; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;RestController RequestMapping(/r4j) public class R4jController {Autowiredprivate R4jCustomerFacade r4JCustomerFacade null;GetMapping(/customer/name/{id})public String getCustomerName(Long id) {return r4JCustomerFacade.getCustomerName(id);}GetMapping(/customer/exception/{id})public String exception(PathVariable(id) Long id) {return r4JCustomerFacade.exception(id);}} 然后重启服务并访问地址http://localhost:2001/r4j/customer/name/1 舱壁隔离 和Hystrix一样Resilience4j也提供了舱壁隔离只是Resilience4j提供的是基于信号量的方式舱壁隔离是一些微服务调用可以被隔离到一个线程池或者信号量中从而防止错误向外蔓延缩小故障范围同时还可以独立优化线程池或信号量更具灵活性 首先添加舱壁隔离的配置 # 舱壁隔离bulkhead:backends:# 舱壁名称为“customercustomer:# 允许最大并发线程数max-concurrent-calls: 10# 等待时间在高并发下建议设置为0秒# 这样就可以快速终止并丢弃请求避免线程积压导致系统崩溃max-wait-duration: 0s和断路器和限速器一样舱壁隔离也是注册机机制Spring Boot会创建舱壁注册机并将配置的舱壁注册到注册机内所以只需要注入舱壁注册机通过名称获取舱壁即可添加服务调用类代码如下 // 舱壁注册机Autowiredprivate BulkheadRegistry bulkheadRegistry null;Overridepublic String getCustomerNameWithBulkhead(Long id) {// 获取名为“customer”的舱壁在application.yml中配置的名称Bulkhead bulkhead bulkheadRegistry.bulkhead(customer);// 描述事件且绑定舱壁CheckedFunction0String decoratedSupplier Bulkhead.decorateCheckedSupplier(bulkhead,() - {return customerOpenFeignFacade.getCustomerName(id);});// 发送请求TryString result Try.of(decoratedSupplier).recover(ex - { // 降级服务return 线程错误;});return result.get();}然后在控制器中添加对应的方法代码如下 package com.scd.goods.controller;import com.scd.goods.facade.R4jCustomerFacade; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;RestController RequestMapping(/r4j) public class R4jController {Autowiredprivate R4jCustomerFacade r4JCustomerFacade null;GetMapping(/customer/name/{id})public String getCustomerName(Long id) {return r4JCustomerFacade.getCustomerName(id);}GetMapping(/customer/bulkhead/name/{id})public String getCustomerNameWithBulkhead(PathVariable(id) Long id) {return r4JCustomerFacade.getCustomerNameWithBulkhead(id);}GetMapping(/customer/exception/{id})public String exception(PathVariable(id) Long id) {return r4JCustomerFacade.exception(id);}} 其他的和前面的组件一样测试 限时器TimeLimiter 首先引入依赖如下所示 dependencygroupIdio.github.resilience4j/groupIdartifactIdresilience4j-timelimiter/artifactIdversion1.2.0/version/dependency一些长期得不到响应的服务调用往往会占用资源所以对服务调用加入限时十分必要Resilience4j也提供了限时器(TimeLimiter)但是在resilience4j-spring-boot2包的1.2.0版本还不支持像之前那样的配置方式但是可以通过编码来实现首先改造一下启动类代码,创建限时器注册机如下所示 package com.scd.goods.main;import io.github.resilience4j.timelimiter.TimeLimiterConfig; import io.github.resilience4j.timelimiter.TimeLimiterRegistry; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate;import java.time.Duration;// 定义扫描包 SpringBootApplication(scanBasePackages com.scd.goods) // 驱动断路器工作Hystrix EnableCircuitBreaker EnableFeignClients(//扫描装配OpenFeign接口到IoC容器中basePackagescom.scd.goods) public class GoodsApplication {// 执行负载均衡,启动Ribbon默认的负载均衡策略来选择可用的服务实例完成服务调用LoadBalanced// 装配为Bean方便之后注入Beanpublic RestTemplate restTemplate() {return new RestTemplate();}/*** 创建时间限制器注册机TimeLimiterRegistry)* return 时间限制器注册机*/Beanpublic TimeLimiterRegistry timeLimiterRegistry() {// 限时器配置TimeLimiterConfig timeLimiterConfig TimeLimiterConfig.custom()// 设置超时时间为1s超时就取消服务调用避免积压.timeoutDuration(Duration.ofSeconds(1))// 一旦超时则将来不再运行默认值也为true.cancelRunningFuture(true).build();return TimeLimiterRegistry.of(timeLimiterConfig);}public static void main(String[] args) {SpringApplication.run(GoodsApplication.class, args);}} 然后编写限时器代码在服务类中添加限时器代码如下所示 // 限时器注册机Autowiredprivate TimeLimiterRegistry timeLimiterRegistry null;Overridepublic String timeout(Long id) {// 单线程池ExecutorService executorService Executors.newSingleThreadExecutor();// 获取或者创建命名为“customer”限时器TimeLimiter timeLimiter timeLimiterRegistry.timeLimiter(customer);// 描述事件SupplierFutureString supplier ()- executorService.submit(()- customerOpenFeignFacade.timeout(id));// 将事件和限时器绑定CallableString call TimeLimiter.decorateFutureSupplier(timeLimiter, supplier);return Try.ofCallable(call) // 执行事件尝试获取结果.recover(ex-{ // 降级逻辑return 服务调用超时了;}).get(); // 获取结果}然后在控制器中添加调用代码如下所示 package com.scd.goods.controller;import com.scd.goods.facade.R4jCustomerFacade; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;RestController RequestMapping(/r4j) public class R4jController {Autowiredprivate R4jCustomerFacade r4JCustomerFacade null;GetMapping(/customer/name/{id})public String getCustomerName(Long id) {return r4JCustomerFacade.getCustomerName(id);}GetMapping(/customer/bulkhead/name/{id})public String getCustomerNameWithBulkhead(PathVariable(id) Long id) {return r4JCustomerFacade.getCustomerNameWithBulkhead(id);}GetMapping(/customer/timeout/{id})public String timeout(PathVariable(id) Long id) {return r4JCustomerFacade.timeout(id);}GetMapping(/customer/exception/{id})public String exception(PathVariable(id) Long id) {return r4JCustomerFacade.exception(id);} }重启服务并访问连接http://localhost:2001/r4j/customer/timeout/1即可测试
http://www.tj-hxxt.cn/news/222265.html

相关文章:

  • 企业建站模板手机高端设计网站建设
  • 网站如何做担保交易平台wordpress无法添加媒体
  • wordpress子域名网站福田园岭网站建设
  • 甘肃找人做网站多少钱文创产品设计公司
  • 餐饮设计网站建设互联网网站开发
  • 网站开发的编程软件成都哪里有seo公司
  • 美食网站的建设开题报告个人建设网站成本
  • app制作企业seo01网站
  • 做某健身房网站的设计与实现100款软件app免费下载大全
  • 网站建设的发展前景翻译做网站
  • 宁化网站建设网站数据分析工具有哪些
  • 网站建设合同概念深圳商业网站建设哪家
  • 哪里做网站的网站开发制作合同
  • 阿里云服务器上如何做网站南宁世尊商贸网站建设
  • 容桂免费网站建设公司可以做网站的域名后缀
  • 淮北市矿业工程建设公司网站公共交通公司网站建设方案
  • 郑州网站制作报价网页设计的目的
  • 如何制作有关西安的网站应用最广网站建设技术
  • 制作网站需要多少钱查看网站的目录文件夹权限设置
  • 价值30万的网站建设桂林漓江介绍
  • 南安市住房和城乡建设部网站域名免费
  • 免费建官方网站网站开发规划书
  • 怎样查网站的注册地点免费企业黄页查询官网
  • 国外室内设计网站推荐给个网站能用的2022
  • 深圳市龙华区观澜街道梧州seo
  • pc网站转换成wapwordpress多语言企业网站
  • 企业酒店的网站建设wordpress 5.2.2中文版
  • 关于幼儿建设网站ppt模板下载洛阳恒凯做的网站有哪些
  • cnzz如何查询某个网站频道的流量公司网站设计解决方案
  • 做网站找雷鸣做长海报的网站