SpringCloud组件整合使用。(Eureka Ribbon Hystrix OpenFeign Gateway)
SpringCloudNetflix概述
Eureka
Eureka服务端配置
eureka.client.service-url.defaultZone值的格式
Eureak集群
Eureka客户端配置
定时续约和拉取注册表
服务端的自我保护机制
完整配置项
手动清理已经关闭的服务
远程关闭服务
服务平滑上下线
Ribbon
自定义负载均衡策略
DefaultRibbonConfig被@SpringBootApplication组合注解扫描到
配置文件方式自定义负载均衡策略
@RibbonClients自定义全局的负载均衡策略
内置负载均衡策略
不依赖Eureka使用Ribbon
OpenFeign
openFeign的使用
@SpringQueryMap表单提交需要带这个
超时设置
Hystrix(还需要深入的学习,难点)
方法级别的降级处理
服务级别的降级处理的两种实现
hystrix中的超时设置
隔离策略
Fallback也有限制
熔断器
Dashboard监控中心
Turbine集群监控
Turbine分组集群监控
Gateway
扩展写法
同时匹配到多个route
11个Route
route之 - Before
route之 - After
route之 - Between
route之 - Cookie
route之 - Header
route之 - Host(get不到用法)
route之 - Method
route之 - Path
route之 - Query
route之 - RemoteAddr
route之 - Weight
31个Filter
filter之 - AddRequestHeader
filter之 - AddRequestParameter
filter之 - AddResponseHeader
filter之 - DedupeResponseHeader
filter之 - Hystrix
filter之 - CircuitBreaker
filter之 - FallbackHeaders
filter之 - MapRequestHeader
filter之 - PrefixPath
filter之 - PreserveHostHeader
filter之 - RequestRateLimiter
filter之 - RedirectTo
filter之 - RemoveRequestHeader
filter之 - RemoveResponseHeader
filter之 - RemoveRequestParameter
filter之 - RewritePath
filter之 - RewriteLocationResponseHeader
filter之 - RewriteResponseHeader
filter之 - SaveSession
filter之 - SecureHeaders
filter之 - SetPath
filter之 - SetRequestHeader
filter之 - SetResponseHeader
filter之 - SetStatus
filter之 - StripPrefix
filter之 - Retry
filter之 - RequestSize
filter之 - SetRequestHost
filter之 - ModifyRequestBody
filter之 - ModifyResponseBody
filter之 default-filters
以上yml配置都可以用JAVA编程方式实现
全局过滤器
SpringCloudNetflix概述
官方文档地址: https://cloud.spring.io/spring-cloud-netflix/2.2.x/reference/html
SpringCloudNetflix可以通过自动配置将.properties或.yml的属性绑定到SpringEnvironment,也可以
使用Spring编程方式(注解)构建。Netflix组件提供的服务有Eureka(服务发现)Hystrix(断路器)Ribbon(负载均衡)
Zuul(路由本博客不做研究,官方已经不推荐使用了,后边会补充官方推荐的Gateway)另外Netflix和spring cloud不知道
啥原因,Netflix的组件正在被spring cloud慢慢替换。Gateway就是第一个(PS:真心学不动)。
Eureka
注册中心是分布式项目中不可缺少的一环,不同的业务模块拆分为独立的微服务(springboot项目)服务于服务之间需要互相调用,功能
相同的服务又要做集群负载均衡。若有A,B,C业务服务组成一个系统,A,B,C每个负载均衡1分,则该系统共有6个微服务。服务之间互相调用
一般为内部调用,其实也可以理解为A服务Http访问B服务,但B服务有两个此时IP+Port的方式就不灵了。所以我们需要将每个微服务都注册
到注册中心,并且周期的向注册中心发送心跳,证明自己活着。当服务自身的状态发生了更改也要告诉注册中心,例如A服务中的其中一台主机
需要下架服务(即暂不提供服务)。服务之间需要互相调用,则需要到注册中心获取注册表,例如A调用B则需要获取B服务的实例IP+Port,当然
具体的选择哪一个B服务取决于负载均衡策略。同时注册中心也要负责管理每个服务的当前状态,若发现有不可以用的服务,负责将其清理出注册中心。
若B服务挂掉了一台,则注册中心维护的注册表中不会有该服务,若有其他服务要访问B服务则只会获取到仅存活的一个B服务的IP+Port。
主流的注册中心有Zookeeper和Eureka,需要注意的是Zookeeper是CP,Eureka是AP。CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、
可用性(Availability)、分区容错性(Partition tolerance)。CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。
Eureka的AP特性决定了多个Eureka组成的集群中,每个注册中心的注册表可能会不同,这一点特别重要。
Eureka服务端配置
EurekaServer的基本配置以下三步,1 导入依赖 spring-cloud-starter-netflix-eureka-server 2 yml配置 3 Springboot启动类增加注解@EnableEurekaServer
其实eureka的配置有很多,官方文档只提供了最基础的配置,按照以下配置可以快速构建eureka服务端。对于yml配置:eureka.instance.hostname 此处填写的为
当前服务器的真实IP。eureka.client.register-with-eureka 若为true则表示将该服务注册到注册中心,对于eureka服务端可以配置为false,以下配置为true只是
为了在页面可以看到。eureka.client.fetch-registry 如果为false则表示不从注册中心集群中拉去注册表,身为服务端也是不需要拉去的,只有客户端需要拉去注册表。
eureka.client.service-url.defaultZone 此处要填写当前此处要填写eureka服务端集群的中所有eureka的地址。若为单机eureka则只需填自己的即可。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.dfsn.cloud</groupId> <artifactId>erueka-7001</artifactId> <version>1.0-SNAPSHOT</version> <properties> <java.version>1.8</java.version> <spring-cloud.version>Hoxton.SR1</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> </project>
server:
port: 7001
spring:
application:
name: eureka
eureka:
instance:
hostname: 127.0.0.1
client:
register-with-eureka: true
fetch-registry: false
service-url:
defaultZone: http://localhost:7001/eureka
package com.dfsn.cloud.eureka; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer public class EurekaServer_7001 { public static void main(String[] args) { SpringApplication.run(EurekaServer_7001.class, args); } }
启动项目后可以访问eureka控制查看是否配置成功。
eureka.client.service-url.defaultZone值的格式
请注意 eureka.client.service-url.defaultZone的值固定格式为 http://ip:port/上下文路径/eureka
以上代码值为 http://localhost:7001/eureka 是因为当前的项目并没有配置server.servlet.context-path上下文名称。
server: port: 7001 servlet: context-path: /e1 spring: application: name: eureka eureka: instance: hostname: 127.0.0.1 client: register-with-eureka: true fetch-registry: false service-url: defaultZone: http://localhost:7001/e1/eureka
Eureak集群
Eureka多个节点集群没有主从之分,每个node之间都是平等的。
在配置上,只需要在属性 eureka.client.service-url.defaultZone 中添加其他eureka节点的地址即可。有几个节点,就增加几个。
server: port: 7001 spring: application: name: eureka eureka: instance: hostname: 127.0.0.1 client: register-with-eureka: true fetch-registry: false service-url: defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
server: port: 7002 spring: application: name: eureka eureka: instance: hostname: 127.0.0.1 client: register-with-eureka: true fetch-registry: false service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka
Eureka客户端配置
客户端只需要导入对应依赖配置yml即可。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.dfsn.cloud</groupId> <artifactId>consumer-8001</artifactId> <version>1.0-SNAPSHOT</version> <properties> <java.version>1.8</java.version> <spring-cloud.version>Hoxton.SR1</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <!-- 没有该配置,devtools 不生效 --> <fork>true</fork> </configuration> </plugin> </plugins> </build> </project>
package com.dfsn.cloud.consumer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Consumer_8001 { public static void main(String[] args) { SpringApplication.run(Consumer_8001.class, args); } }
需要注意的是做负载均衡的代码属性 spring.application.name 需要相同,eureka.client.register-with-eureka 需要为 true 表示需要将此服务注册到注册中心(我们需要的不就是这个吗?)eureka.client.fetch-registry 需要为 true
表示从注册中心拉去注册表(获取其他服务的ip+port)
下面附录一份较为详细的配置,属性名是 .properties的配置方式,若你的配置文件是 .yml 把 . 替换成 : 换行缩进 :后的值留一个空格就可以了。
定时续约和拉取注册表
对于Eureka客户端来说,心跳续约和拉取注册表是两个必须有而且需要我们关注的配置。
eureka.instance.lease-renewal-interval-in-seconds 定时续约单位是秒默认值是 30秒
eureka.instance.lease-expiration-duration-in-seconds 告诉服务端,如果我多少秒没有发送心跳续约则证明我宕机默认值是 90秒。
eureka.client.registry-fetch-interval-seconds 定时获取注册表单位是秒默认值是 30秒
服务端的自我保护机制
自我保护机制是Eureka服务端的一种机制,在15分钟内如果服务端得到的续约数不满足一定比例默认85%
则服务端认为是自己出了问题,则不会将没有收到心跳的客户端从注册表中删除。
eureka.server.enable-self-preservation 自我保护机制开关 默认是 true
eureka.server.renewal-percent-threshold 失去连接比例 默认是 0.85
在生产环境应该避免关闭掉自我保护机制,这样服务宕机后才能最快速度从注册表中删除。
除此之外,如果需要最快的清理没有续约的服务,还需要设置
eureka.server.eviction-interval-timer-in-ms 多少毫秒清除过期服务。默认 60000毫秒
完整配置项
Eureka Client 配置项(eureka.client.*)
org.springframework.cloud.netflix.eureka.EurekaClientConfigBean
参数名称 说明 默认值 eureka.client.enabled 用于指示Eureka客户端已启用的标志 true eureka.client.registry-fetch-interval-seconds 指示从eureka服务器获取注册表信息的频率(s) 30 eureka.client.instance-info-replication-interval-seconds 更新实例信息的变化到Eureka服务端的间隔时间,(s) 30 eureka.client.initial-instance-info-replication-interval-seconds 初始化实例信息到Eureka服务端的间隔时间,(s) 40 eureka.client.eureka-service-url-poll-interval-seconds 询问Eureka Server信息变化的时间间隔(s),默认为300秒 300 eureka.client.eureka-server-read-timeout-seconds 读取Eureka Server 超时时间(s),默认8秒 8 eureka.client.eureka-server-connect-timeout-seconds 连接Eureka Server 超时时间(s),默认5秒 5 eureka.client.eureka-server-total-connections 获取从eureka客户端到所有eureka服务器的连接总数,默认200个 200 eureka.client.eureka-server-total-connections-per-host 获取从eureka客户端到eureka服务器主机允许的连接总数,默认50个 50 eureka.client.eureka-connection-idle-timeout-seconds 连接到 Eureka Server 空闲连接的超时时间(s),默认30 30 eureka.client.registry-refresh-single-vip-address 指示客户端是否仅对单个VIP的注册表信息感兴趣,默认为null null eureka.client.heartbeat-executor-thread-pool-size 心跳保持线程池初始化线程数,默认2个 2 eureka.client.heartbeat-executor-exponential-back-off-bound 心跳超时重试延迟时间的最大乘数值,默认10 10 eureka.client.serviceUrl.defaultZone 可用区域映射到与eureka服务器通信的完全限定URL列表。每个值可以是单个URL或逗号分隔的备用位置列表。 eureka.client.use-dns-for-fetching-service-urls 指示eureka客户端是否应使用DNS机制来获取要与之通信的eureka服务器列表。当DNS名称更新为具有其他服务器时,eureka客户端轮询eurekaServiceUrlPollIntervalSeconds中指定的信息后立即使用该信息。 false eureka.client.register-with-eureka 指示此实例是否应将其信息注册到eureka服务器以供其他服务发现,默认为false true eureka.client.prefer-same-zone-eureka 实例是否使用同一zone里的eureka服务器,默认为true,理想状态下,eureka客户端与服务端是在同一zone下 true eureka.client.log-delta-diff 是否记录eureka服务器和客户端之间在注册表的信息方面的差异,默认为false false eureka.client.disable-delta 指示eureka客户端是否禁用增量提取 false eureka.client.fetch-remote-regions-registry 逗号分隔的区域列表,提取eureka注册表信息 eureka.client.on-demand-update-status-change 客户端的状态更新到远程服务器上,默认为true true eureka.client.allow-redirects 指示服务器是否可以将客户端请求重定向到备份服务器/集群。如果设置为false,则服务器将直接处理请求。如果设置为true,则可以将HTTP重定向发送到具有新服务器位置的客户端。 false eureka.client.availability-zones.* 获取此实例所在区域的可用区域列表(在AWS数据中心中使用)。更改在运行时在registryFetchIntervalSeconds指定的下一个注册表获取周期生效。 eureka.client.backup-registry-impl 获取实现BackupRegistry的实现的名称,该实现仅在eureka客户端启动时第一次作为后备选项获取注册表信息。 对于需要额外的注册表信息弹性的应用程序,可能需要这样做,否则它将无法运行。 eureka.client.cache-refresh-executor-exponential-back-off-bound 在发生一系列超时的情况下,它是重试延迟的最大乘数值。 10 eureka.client.cache-refresh-executor-thread-pool-size 缓存刷新线程池初始化线程数量 2 eureka.client.client-data-accept 客户端数据接收的名称 full eureka.client.decoder-name 解码器名称 eureka.client.dollar-replacement eureka服务器序列化/反序列化的信息中获取“$”符号的替换字符串。默认为“_-” eureka.client.encoder-name 编码器名称 eureka.client.escape-char-replacement eureka服务器序列化/反序列化的信息中获取“_”符号的的替换字符串。默认为“__“ eureka.client.eureka-server-d-n-s-name 获取要查询的DNS名称来获得eureka服务器,此配置只有在eureka服务器ip地址列表是在DNS中才会用到。默认为null null eureka.client.eureka-server-port 获取eureka服务器的端口,此配置只有在eureka服务器ip地址列表是在DNS中才会用到。默认为null null eureka.client.eureka-server-u-r-l-context 表示eureka注册中心的路径,如果配置为eureka,则为http://ip:port/eureka/,在eureka的配置文件中加入此配置表示eureka作为客户端向注册中心注册,从而构成eureka集群。此配置只有在eureka服务器ip地址列表是在DNS中才会用到,默认为null null eureka.client.fetch-registry 客户端是否获取eureka服务器注册表上的注册信息,默认为true true eureka.client.filter-only-up-instances 是否过滤掉非up实例,默认为true true eureka.client.g-zip-content 当服务端支持压缩的情况下,是否支持从服务端获取的信息进行压缩。默认为true eureka.client.property-resolver 属性解析器 eureka.client.proxy-host 获取eureka server 的代理主机名 null eureka.client.proxy-password 获取eureka server 的代理主机密码 null eureka.client.proxy-port 获取eureka server 的代理主机端口 null eureka.client.proxy-user-name 获取eureka server 的代理用户名 null eureka.client.region 获取此实例所在的区域(在AWS数据中心中使用)。 us-east-1 eureka.client.should-enforce-registration-at-init client 在初始化阶段是否强行注册到注册中心 false eureka.client.should-unregister-on-shutdown client在shutdown情况下,是否显示从注册中心注销 true
服务实例配置项(eureka.instance.*)
org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean
参数名称 说明 默认值 eureka.instance.appname 注册到注册中心的应用名称 unknown eureka.instance.a-s-g-name 注册到注册中心的应用所属分组名称(AWS服务器) null eureka.instance.app-group-name 注册到注册中心的应用所属分组名称 null eureka.instance.data-center-info 指定服务实例所属数据中心 eureka.instance.instance-enabled-onit 指示是否应在eureka注册后立即启用实例以获取流量 false eureka.instance.non-secure-port http通信端口 80 eureka.instance.non-secure-port-enabled 是否启用HTTP通信端口 ture eureka.instance.secure-port HTTPS通信端口 443 eureka.instance.secure-port-enabled 是否启用HTTPS通信端口 false eureka.instance.secure-virtual-host-name 服务实例安全主机名称(HTTPS) unknown eureka.instance.virtual-host-name 该服务实例非安全注解名称(HTTP) unknown eureka.instance.secure-health-check-url 该服务实例安全健康检查地址(URL),绝对地址 eureka.instance.lease-renewal-interval-in-seconds 该服务实例向注册中心发送心跳间隔(s) 30 eureka.instance.lease-expiration-duration-in-seconds 指示eureka服务器在删除此实例之前收到最后一次心跳之后等待的时间(s) 90 eureka.instance.metadata-map.* eureka.instance.ip-address 该服务实例的IP地址 null eureka.instance.prefer-ip-address 是否优先使用服务实例的IP地址,相较于hostname false eureka.instance.status-page-url 该服务实例的状态检查地址(url),绝对地址 null eureka.instance.status-page-url-path 该服务实例的状态检查地址,相对地址 /actuator/info eureka.instance.home-page-url 该服务实例的主页地址(url),绝对地址 eureka.instance.home-page-url-path 该服务实例的主页地址,相对地址 / eureka.instance.health-check-url 该服务实例的健康检查地址(url),绝对地址 null eureka.instance.health-check-url-path 该服务实例的健康检查地址,相对地址 /actuator/health eureka.instance.instance-id 该服务实例在注册中心的唯一实例ID eureka.instance.hostname 该服务实例所在主机名 eureka.instance.namespace 获取用于查找属性的命名空间。 在Spring Cloud中被忽略。eureka eureka.instance.environment 该服务实例环境配置 eureka.instance.default-address-resolution-order 默认地址解析顺序 eureka.instance.initial-status 该服务实例注册到Eureka Server 的初始状态 up eureka.instance.registry.default-open-for-traffic-count 【Eureka Server 端属性】默认开启通信的数量 1 eureka.instance.registry.expected-number-of-renews-per-min 【Eureka Server 端属性】每分钟续约次数 1
Eureka Server 配置项(eureka.server.*)
org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean
参数名称 说明 默认值 eureka.server.enable-self-preservation 启用自我保护机制,默认为true true eureka.server.eviction-interval-timer-in-ms 清除无效服务实例的时间间隔(ms),默认1分钟 60000 eureka.server.delta-retention-timer-interval-in-ms 清理无效增量信息的时间间隔(ms),默认30秒 30000 eureka.server.disable-delta 禁用增量获取服务实例信息 false eureka.server.log-identity-headers 是否记录登录日志 true eureka.server.rate-limiter-burst-size 限流大小 10 eureka.server.rate-limiter-enabled 是否启用限流 false eureka.server.rate-limiter-full-fetch-average-rate平均请求速率 100 eureka.server.rate-limiter-throttle-standard-clients 是否对标准客户端进行限流 false eureka.server.rate-limiter-registry-fetch-average-rate 服务注册与拉取的平均速率 500 eureka.server.rate-limiter-privileged-clients 信任的客户端列表 eureka.server.renewal-percent-threshold 15分钟内续约服务的比例小于0.85,则开启自我保护机制,再此期间不会清除已注册的任何服务(即便是无效服务) 0.85 eureka.server.renewal-threshold-update-interval-ms 更新续约阈值的间隔(分钟),默认15分钟 15 eureka.server.response-cache-auto-expiration-in-seconds 注册信息缓存有效时长(s),默认180秒 180 eureka.server.response-cache-update-interval-ms 注册信息缓存更新间隔(s),默认30秒 30 eureka.server.retention-time-in-m-s-in-delta-queue 保留增量信息时长(分钟),默认3分钟 3 eureka.server.sync-when-timestamp-differs 当时间戳不一致时,是否进行同步 true eureka.server.use-read-only-response-cache 是否使用只读缓存策略 true
Eureka集群配置
参数名称 说明 默认值 eureka.server.enable-replicated-request-compression 复制数据请求时,数据是否压缩 false eureka.server.batch-replication 节点之间数据复制是否采用批处理 false eureka.server.max-elements-in-peer-replication-pool 备份池最大备份事件数量,默认1000 1000 eureka.server.max-elements-in-status-replication-pool 状态备份池最大备份事件数量,默认1000 1000 eureka.server.max-idle-thread-age-in-minutes-for-peer-replication 节点之间信息同步线程最大空闲时间(分钟) 15 eureka.server.max-idle-thread-in-minutes-age-for-status-replication 节点之间状态同步线程最大空闲时间(分钟) 10 eureka.server.max-threads-for-peer-replication 节点之间信息同步最大线程数量 20 eureka.server.max-threads-for-status-replication 节点之间状态同步最大线程数量 1 eureka.server.max-time-for-replication 节点之间信息复制最大通信时长(ms) 30000 eureka.server.min-available-instances-for-peer-replication 集群中服务实例最小数量,-1 表示单节点 -1 eureka.server.min-threads-for-peer-replication 节点之间信息复制最小线程数量 5 eureka.server.min-threads-for-status-replication 节点之间信息状态同步最小线程数量 1 eureka.server.number-of-replication-retries 节点之间数据复制时,可重试次数 5 eureka.server.peer-eureka-nodes-update-interval-ms 节点更新数据间隔时长(分钟) 10 eureka.server.peer-eureka-status-refresh-time-interval-ms 节点之间状态刷新间隔时长(ms) 30000 eureka.server.peer-node-connect-timeout-ms 节点之间连接超时时长(ms) 200 eureka.server.peer-node-connection-idle-timeout-seconds 节点之间连接后,空闲时长(s) 30 eureka.server.peer-node-read-timeout-ms 几点之间数据读取超时时间(ms) 200 eureka.server.peer-node-total-connections 集群中节点连接总数 1000 eureka.server.peer-node-total-connections-per-host 节点之间连接,单机最大连接数量 500 eureka.server.registry-sync-retries 节点启动时,尝试获取注册信息的次数 500 eureka.server.registry-sync-retry-wait-ms 节点启动时,尝试获取注册信息的间隔时长(ms) 30000 eureka.server.wait-time-in-ms-when-sync-empty 在Eureka服务器获取不到集群里对等服务器上的实例时,需要等待的时间(分钟) 5
手动清理已经关闭的服务
发送Delete请求 http://ip:port/eureka/apps/应用的服务名和端口。例如:
http://127.0.0.1:7001/eureka/apps/CONSUMER/FTS-SZ-9073186.szwb.fsc.cntaiping.com:consumer:8001
可以在手动关掉服务进程后发送这个请求,立即手动的清理注册表。
远程关闭服务
远程关闭其实是SpringBoot的功能,在这里提到只是为了引入下面的服务平滑上下线。
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
shutdown:
enabled: true
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
导入依赖后,配置yml配置。Post访问 http://localhost:8001/actuator/shutdown 即可停止服务,但是这种方式再次开启服务
需要手动到服务器启动。
服务平滑上下线
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
shutdown:
enabled: true
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
配置如上,POST访问 http://localhost:9001/actuator/service-registry {"status":"DOWN"}下线{"status":"UP"}上线
这种方式下线后,Eureka会标记该服务暂时不可用,其他服务拉去注册表时,就不会在拉到这个服务。
Ribbon
Ribbon是一个客户端负载均衡器,SpringCloud生态中,zuul依赖Ribbon,使用RestTemplate通过服务名调用也要用到Ribbon,包括OpenFeign内部调用也使用的有Ribbon。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency>
一般来说,我们不需要单独引入Ribbon依赖,eureka中引入的有,zuul中也有。
package com.dfsn.cloud.producer.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class ProducerController { @GetMapping(value = "producerHandler") public String handler() { return "producer-9001"; } }
package com.dfsn.cloud.producer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication public class Producer_9001 { public static void main(String[] args) { SpringApplication.run(Producer_9001.class, args); } }
server: port: 9001 spring: application: name: producer eureka: instance: hostname: 127.0.0.1 client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.dfsn.cloud</groupId> <artifactId>producer-9001</artifactId> <version>1.0-SNAPSHOT</version> <properties> <java.version>1.8</java.version> <spring-cloud.version>Hoxton.SR1</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> </project>
将以上代码复制两份,创建两个producer项目,构成一个服务提供者集群,同时注册到Eureka集群中。
package com.dfsn.cloud.consumer.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class ConsumerController { @GetMapping(value = "consumerHandler") public String handler() { return "consumer-8001"; } @Autowired private RestTemplate restTemplate; @GetMapping(value = "to") public String toProducer(){ String forObject = restTemplate.getForObject("http://localhost:9001/producerHandler", String.class); return forObject; } }
package com.dfsn.cloud.consumer.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class CreateBean { @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
在consumer中通过RestTemplateAPI访问producer
那问题来了producer服务是一个集群,要搞负载均衡的,在代码中写死了ip:port是不行的。现在我们可以借助Ribbon的注解
@LoadBalanced 将这个注解放到 RestTemplate的bean上。使用RestTemplate的方法传递url参数时IP和端口写消费者集群的名称
也就是 producer服务中配置的 spring.application.name 的值,两个producer的值相同,这样就可以做到负载均衡了。
package com.dfsn.cloud.consumer.config; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class CreateBean { @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } }
package com.dfsn.cloud.consumer.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class ConsumerController { @GetMapping(value = "consumerHandler") public String handler() { return "consumer-8001"; } @Autowired private RestTemplate restTemplate; @GetMapping(value = "to") public String toProducer(){ String forObject = restTemplate.getForObject("http://producer/producerHandler", String.class); return forObject; } }
自定义负载均衡策略
当我们使用@LoadBalanced注解注释RestTemplate后,通过服务名调用其他微服务,Ribbon则使用它自己的负载均衡策略为我们
找到一个服务使用。下面我们来做一个自定义的负载均衡策略。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.dfsn.cloud</groupId> <artifactId>order-9100</artifactId> <version>1.0-SNAPSHOT</version> <properties> <java.version>1.8</java.version> <spring-cloud.version>Hoxton.SR1</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> </project>
server:
port: 9100
spring:
application:
name: order
eureka:
instance:
hostname: 127.0.0.1
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
package com.dfsn.cloud.order; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Order_9100 { public static void main(String[] args) { SpringApplication.run(Order_9100.class, args); } }
首先我们在创建两个order服务,作为order集群。现在我们有producer两台服务,order两台服务。
1 在consumer中使用RestTemplate的方式调用两个服务。
package com.dfsn.cloud.consumer.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.util.concurrent.ThreadLocalRandom; @RestController public class ConsumerController { @GetMapping(value = "consumerHandler") public String handler() { return "consumer-8001"; } @Autowired private RestTemplate restTemplate; @GetMapping(value = "toproducer") public String toProducer(){ String forObject = restTemplate.getForObject("http://producer/producerHandler", String.class); return forObject; } @GetMapping(value = "toorder") public String toOrder(){ String forObject = restTemplate.getForObject("http://order/orderHandler", String.class); return forObject; } }
2 创建一个针对producer的配置类。该类使用@RibbonClient注解注释 name为producer的服务名,configuration为一个自定义的负载均衡策略。
package com.dfsn.cloud.consumer.config; import org.springframework.cloud.netflix.ribbon.RibbonClient; import org.springframework.context.annotation.Configuration; @Configuration @RibbonClient(name = "producer",configuration = com.dfsn.cloud.DefaultRibbonConfig.class) public class MyRibbonClient { }
3 创建自定义的负载均衡策略。该负载均衡策略需要一个IRule的实例Bean,我这里使用的是RandomRule的子类,RandomRule是Ribbon提供的实现类。
MyRult类只覆盖chooseRandomInt(int serverCount)方法,该方法返回一个整数,根据返回值从已有的服务中取出一个服务。我这里写死的原因是为了
测试它到底能不能生效,如果剩下则producer服务每次访问都是一个不会负载均衡。请注意!DefaultRibbonConfig类不能被@SpringBootApplication
组合注解扫描到,否则将被认为是全局的负载均衡策略,也就是说,order也会使用该策略。下图是我的文件目录。
package com.dfsn.cloud; import com.dfsn.cloud.consumer.config.MyRule; import com.netflix.loadbalancer.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class DefaultRibbonConfig { @Bean public IRule ribbonRule() { return new MyRule(); } }
package com.dfsn.cloud.consumer.config; import com.netflix.loadbalancer.RandomRule; public class MyRule extends RandomRule { @Override protected int chooseRandomInt(int serverCount) { return 0; } }
从结果看,order还是走的Ribbon的负载均衡策略而producer则每次都是同一个,表示自定义的负载均衡策略生效。
其次除了IRule可以重写,官方文档中还提到了其他的几个类也可以重新。IPing是一个定时任务
用于定时去查询集群服务的状态,当然默认的这些都是先从Eureka中获取的数据。
https://cloud.spring.io/spring-cloud-netflix/2.2.x/reference/html/#spring-cloud-ribbon
DefaultRibbonConfig被@SpringBootApplication组合注解扫描到
将目录调整为DefaultRibbonConfig可以被@SpringBootApplication扫描到,会发现无论是producer还是order都会使用自定义的负载均衡策略。也就是说当前自定义的负载均衡策略变成了全局的。
配置文件方式自定义负载均衡策略
对于单个服务使用注解的方式自定义策略还是有些麻烦的。1.2之后的版本支持在yml或者properties中配置负载均衡器
server:
port: 8001
spring:
application:
name: consumer
eureka:
instance:
hostname: 127.0.0.1
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
producer:
ribbon:
NFLoadBalancerRuleClassName: com.dfsn.cloud.consumer.config.MyRule2
package com.dfsn.cloud.consumer.config;
import com.netflix.loadbalancer.RandomRule;
public class MyRule2 extends RandomRule {
@Override
protected int chooseRandomInt(int serverCount) {
System.out.println("MyRule2");
return 1;
}
}
配置文件的格式为 服务名.ribbon.NFLoadBalancerRuleClassName=负载均衡类 并不需要在写其他的配置类,也无需关心是否被SpringBootApplication扫描到因为不需要这个配置类了。
只需要直接指定负载均衡的策略类即可。经测试,使用配置文件的优先级低于使用注解的方式。注意配置文件的方式,只能重写5个类。
@RibbonClients自定义全局的负载均衡策略
如果你想为所有服务都使用自定义的负载均衡策略使用该注解即可。它的defaultConfiguration参数接收一个负载均衡策略配置类,也就是那个不能配@SpringBootApplication扫描到的类。
package com.dfsn.cloud.consumer.config; import com.dfsn.cloud.DefaultRibbonConfig; import org.springframework.cloud.netflix.ribbon.RibbonClients; @RibbonClients(defaultConfiguration ={DefaultRibbonConfig.class} ) public class AllRibbonClient { }
内置负载均衡策略
不依赖Eureka使用Ribbon
上面我们自定义负载均衡策略,其实也是建立在Eureka服务发现的基础上,思考一下,如果没有Eureka,我们只是指定了负载均衡策略,请求能转发到对应的服务器吗?如果可以那IP:Port是怎么确定的?
server: port: 8001 spring: application: name: consumer eureka: instance: hostname: 127.0.0.1 client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka producer: ribbon: NFLoadBalancerRuleClassName: com.dfsn.cloud.consumer.config.MyRule2 order: ribbon: NFLoadBalancerRuleClassName: com.dfsn.cloud.consumer.config.MyRule2 ribbon: eureka: enabled: false
ribbon.eureka.enabled=false进制Ribbon使用Eureka,通过浏览器访问会失败。
server: port: 8001 spring: application: name: consumer eureka: instance: hostname: 127.0.0.1 client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka producer: ribbon: NFLoadBalancerRuleClassName: com.dfsn.cloud.consumer.config.MyRule2 listOfServers: 127.0.0.1:9001,127.0.0.1:9002 order: ribbon: NFLoadBalancerRuleClassName: com.dfsn.cloud.consumer.config.MyRule2 listOfServers: 127.0.0.1:9100,127.0.0.1:9101 ribbon: eureka: enabled: false
给每个服务配置 listOfServers属性,该属性的值为微服务集群的物理IP:Port。以上配置可以完全不依赖Eureka。
OpenFeign
openFeign不在SpringCloudNetflix中,而是单独的一个项目 https://cloud.spring.io/spring-cloud-openfeign/2.2.x/reference/html
openFeign的使用
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
@SpringBootApplication
@EnableFeignClients
public class Consumer_8001 {
public static void main(String[] args) {
SpringApplication.run(Consumer_8001.class, args);
}
}
@FeignClient("order") public interface OrderService { @GetMapping(value = "orderHandler") public String handler(); }
package com.dfsn.cloud.consumer.feigns; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; @FeignClient("producer") public interface ProducerService { @GetMapping(value = "producerHandler") public String handler(); }
@Autowired private OrderService orderService; @Autowired private ProducerService producerService; @GetMapping(value = "toproducer2") public String toProducer2() { String forObject = producerService.handler(); return forObject; } @GetMapping(value = "toorder2") public String toOrder2() { String forObject = orderService.handler(); return forObject; }
导入依赖后在启动类上加上注解@EnableFeignClients注解。创建两个接口,接口上使用@FeignClient注解,value属性是服务提供者的名字,就是注册到Eureka
的名字。接口中的方法是服务提供者的方法,除了没有方法体,剩余的需要和服务提供者的方法一模一样。在Controller中注入接口,然后调用接口的方法。
这样就像是调用本地方法一样。
@SpringQueryMap表单提交需要带这个
请注意!以上的代码片段服务提供者接收JSON格式参数,调用者的feign也需要带@RequestBody注解。
如果是表单提交按道理是不需要带任何的注解,但是这是不行的。你必须在feign客户端给方法使用
@SpringQueryMap注解。否则参数绑定失败。
@PostMapping(value = "oder") public OrderVo order(@RequestBody OrderVo orderVo) { System.out.println("postjson"); return orderVo; } @PostMapping(value = "oder2") public OrderVo order2(OrderVo orderVo) { System.out.println("post表单"); return orderVo; } @GetMapping(value = "oder3") public OrderVo order3(OrderVo orderVo) { System.out.println("get表单"); return orderVo; } @DeleteMapping(value = "oder4") public OrderVo order4(OrderVo orderVo) { System.out.println("delete表单"); return orderVo; } @PutMapping(value = "oder5") public OrderVo order5(OrderVo orderVo) { System.out.println("put表单"); return orderVo; }
@PostMapping(value = "oder") public OrderVo order(@RequestBody OrderVo orderVo); @PostMapping(value = "oder2") public OrderVo order2(@SpringQueryMap OrderVo orderVo); @GetMapping(value = "oder3") public OrderVo order3(@SpringQueryMap OrderVo orderVo); @DeleteMapping(value = "oder4") public OrderVo order4(@SpringQueryMap OrderVo orderVo); @PutMapping(value = "oder5") public OrderVo order5(@SpringQueryMap OrderVo orderVo);
@GetMapping(value = "addorder") public OrderVo addorder() { OrderVo orderVo = new OrderVo(); orderVo.setId(1); orderVo.setName("方便面"); return orderService.order(orderVo); } @GetMapping(value = "addorder2") public OrderVo addorder2() { OrderVo orderVo = new OrderVo(); orderVo.setId(1); orderVo.setName("方便面"); return orderService.order2(orderVo); } @GetMapping(value = "addorder3") public OrderVo addorder3() { OrderVo orderVo = new OrderVo(); orderVo.setId(1); orderVo.setName("方便面"); return orderService.order3(orderVo); } @GetMapping(value = "addorder4") public OrderVo addorder4() { OrderVo orderVo = new OrderVo(); orderVo.setId(1); orderVo.setName("方便面"); return orderService.order4(orderVo); } @GetMapping(value = "addorder5") public OrderVo addorder5() { OrderVo orderVo = new OrderVo(); orderVo.setId(1); orderVo.setName("方便面"); return orderService.order5(orderVo); }
超时设置
feign: client: config: order: connectTimeout: 2000 readTimeout: 5000
@GetMapping(value = "orderHandler") public String handler() throws Exception { Thread.sleep(3000); return "order-9100"; }
@GetMapping(value = "producerHandler") public String handler()throws Exception { Thread.sleep(3000); return "producer-9001"; }
以上代码设置order服务的连接超时为2000毫秒相应超时为5000毫秒。在order和producer中线程休眠3000毫秒。
最终order不会发生超时而producer则会发生超时。如果要设置全局的超时时间则将order(具体的服务)替换成default。
请注意这个超时时间仅仅是远程调用的时间,并不包含调用者本身代码的消耗时间。
Hystrix(还需要深入的学习,难点)
熔断与降级。这里需要搞清楚熔断和降级的概念。
降级:当服务发生异常时做出的补偿机制就是降级。说白了,我们程序中写的try{}catch(){}也是一种降级手段
只是为了在出现错误时反馈给用户一个更好的体验。
熔断:当触发某个条件时,熔断该请求,例如请求时间过长会影响整体微服务的响应时间。当某个服务频繁出错,
这些情况都可以预先设置熔断。熔断以后请求链路就断了,最终和有异常,而熔断后的补偿就是降级。
方法级别的降级处理
1 导入依赖spring-cloud-starter-netflix-hystrix
2 在启动类增加@EnableCircuitBreaker或者@EnableHystrix两个注解任意一个就行
3 在需要降级处理的方法上加@HystrixCommand注解属性 fallbackMethod 指定本类的一个方法,该方法是降级方法。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
package com.dfsn.cloud.consumer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.netflix.hystrix.EnableHystrix; import org.springframework.cloud.openfeign.EnableFeignClients; @EnableFeignClients @SpringBootApplication @EnableHystrix @EnableCircuitBreaker public class Consumer_8001 { public static void main(String[] args) { SpringApplication.run(Consumer_8001.class, args); } }
@GetMapping(value = "toorder2") @HystrixCommand(fallbackMethod = "toOrder2Fallback") public String toOrder2() throws Exception { int i=1/0; String forObject = orderService.handler(); return forObject; } public String toOrder2Fallback() throws Exception { return "我是降级处理哦"; }
请注意!方法级别的降级处理无论是在远程调用中异常或者是方法本身异常,都会发生降级处理,这一点很重要。
服务级别的降级处理的两种实现
服务级别的降级处理需要在feign中开启使用hystrix
feign.hystrix.enable=true
1 fallbackFactory
feign: hystrix: enabled: true
package com.dfsn.cloud.consumer.feigns; import feign.hystrix.FallbackFactory; import org.springframework.stereotype.Component; @Component public class OrderServiceFallbackFactory implements FallbackFactory<OrderService> { @Override public OrderService create(Throwable throwable) { return new OrderService() { @Override public String handler() { throwable.printStackTrace(); return "fallbackFacotry降级"; } }; } }
package com.dfsn.cloud.consumer.feigns; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; @FeignClient(value = "order", fallbackFactory = OrderServiceFallbackFactory.class) public interface OrderService { @GetMapping(value = "orderHandler") public String handler(); }
2 fallback
feign: hystrix: enabled: true
package com.dfsn.cloud.consumer.feigns; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; @FeignClient(value = "order", fallback = OrderServiceFallback.class) public interface OrderService { @GetMapping(value = "orderHandler") public String handler(); }
package com.dfsn.cloud.consumer.feigns; import org.springframework.stereotype.Component; @Component public class OrderServiceFallback implements OrderService{ @Override public String handler() { return "fallback降级"; } }
以上两种方法都可以实现降级处理,区别是fallbackFactory可以获取具体的异常信息。这里需要注意,无论是以上哪一种都只会对远程
调用过程中出错的错误进行降级。而方法级别是方法内任意一行异常都会触发降级。以下截图中有fallbackfactory降级处理。
toorder2有单独的method降级。toorder2和toorder3都会在第一行抛出一个算术异常。而toorder2最终走了方法级别的降级,toorder3
没有走降级。toorder3使用的是服务级别的降级,但是这个异常并不是调用服务产生的所以不会被降级。
hystrix中的超时设置
hystrix.command.default.execution.timeout.enabled 属性默认为true有超时设置。
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 设置超时时间默认是1000毫秒
同时需要注意在openFeign中也有超时时间 feign.client.config.default.connectTimeout和readTimeout两个时间
如果在配置文件中同时设置了hystrix和openfeign则哪个先超时就是超时。(谁的时间短,谁为主。)
隔离策略
信号量隔离:对依赖的调用所使用的线程仍为请求线程,即不会为依赖请求再新创建新 的线程。但系统会为每种依赖分配一定数量的信号量,而每个依赖请求分配一个信号号。
当对该依赖的调用请求数量达到上限后再有请求,则该请求阻塞。所以对某依赖的并发 量取决于为该依赖所分配的信号数量。
hystrix.command.default.execution.isolation.strategy=SEMAPHORE
hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests=10
使用JMeter测试从第四个请求开始返回的就是降级结果了。
server:
port: 8001
spring:
application:
name: consumer
eureka:
instance:
hostname: 127.0.0.1
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
feign:
hystrix:
enabled: true
client:
config:
default:
connectTimeout: 2000
readTimeout: 3000
hystrix:
command:
default:
execution:
timeout:
enabled: true
isolation:
thread:
timeoutInMilliseconds: 3000
strategy: SEMAPHORE
semaphore:
maxConcurrentRequests: 3
线程隔离 thread:Hystrix 的默认隔离策略。系统会创建一个依赖线程池,为每个依赖请 求分配一个独立的线程,而每个依赖所拥有的线程数量是有上限的。
当对该依赖的调用 请求数量达到上限后再有请求,则该请求阻塞。所以对某依赖的并发量取决于为该依赖 所分配的线程数量。
hystrix: threadpool: default: coreSize: 5 maxQueueSize: -1 command: default: execution: timeout: enabled: true isolation: thread: timeoutInMilliseconds: 3000 strategy: THREAD
hystrix.command.default.execution.isolation.strategy=THREAD
hystrix.threadpool.default.coreSize=可用线程数默认为10
hystrix.threadpool.default.maxQueueSize=等待队列个数默认为-1
hystrix.threadpool.default.queueSizeRejectionThreshold=可热修改的队列默认为5如果maxQueueSize为-1则不生效。
Fallback也有限制
以上测试无论是超时还是信号量不够用或者是异常都会最走到降级方法。但降级方法也有默认的并发数量最多10
hystrix.command.default.fallback.enabled 设置是否开启fallback默认是开启的。
hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests 设置回退方法的并发量
package com.dfsn.cloud.order.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class OrderController { @GetMapping(value = "orderHandler") public String handler() throws Exception{ int i=1/0; return "order-9100"; } }
package com.dfsn.cloud.consumer.feigns; import feign.hystrix.FallbackFactory; import org.springframework.stereotype.Component; @Component public class OrderServiceFallbackFactory implements FallbackFactory<OrderService> { @Override public OrderService create(Throwable throwable) { return new OrderService() { @Override public String handler() { try { Thread.sleep(2000); } catch (Exception e) { System.out.println(e); } throwable.printStackTrace(); return "fallbackFacotry降级"; } }; } }
server: port: 8001 spring: application: name: consumer eureka: instance: hostname: 127.0.0.1 client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka feign: hystrix: enabled: true client: config: default: connectTimeout: 2000 readTimeout: 3000 hystrix: command: default: fallback: enabled: true isolation: semaphore: maxConcurrentRequests: 2 execution: timeout: enabled: false
以上代码片段在远程调用时必然的失败,而回退方法中休眠2秒钟。fallback设置成2表示最多两个回退方法。
熔断器
当一个服务频繁出错被降级,则表示该接口是不正常的。hystrix允许我们预先设置,达到什么情况下主动熔断该接口。则以后再访问该接口都会直接降级。
hystrix.command.default.circuitBreaker.enabled 默认true开启熔断器
hystrix.command.default.circuitBreaker.requestVolumeThreshold 默认20时间窗(10秒)内最低多少请求会触发熔断
hystrix.command.default.circuitBreaker.errorThresholdPercentage 默认50 百分之50和上一个值组合使用:10秒内最低20个请求并且有50%的错误率则开启熔断
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds 默认5000毫秒 开启熔断后,多少秒恢复正常
hystrix.command.default.circuitBreaker.forceClosed 默认false 如果开启则通过所有请求,即熔断不生效感觉和enabled=false效果一样。
hystrix.command.default.circuitBreaker.forceOpen 拒绝所有请求,强行熔断还没有时间限制。
hystrix.command.default.metrics.rollingStats.timeInMilliseconds 设置时间窗的大小默认10000毫秒
hystrix.command.default.metrics.rollingStats.numBuckets 设置滚动窗口划分的桶数,例如,滚动窗口持续时间为10秒,默认配置10个桶,那么每秒钟一个桶用于存放统计数据。配置值必须符合以下条件 metrics.rollingStats.timeInMilliseconds % metrics.rollingStats.numBuckets == 0,否则会抛出异常。
hystrix:
command:
default:
circuitBreaker:
enabled: true
requestVolumeThreshold: 20
sleepWindowInMilliseconds: 60000
errorThresholdPercentage: 50
fallback:
enabled: true
isolation:
semaphore:
maxConcurrentRequests: 200
execution:
timeout:
enabled: false
isolation:
strategy: SEMAPHORE
semaphore:
maxConcurrentRequests: 200
public class Bean { private int val; public int getVal() { return val; } public void setVal(int val) { this.val = val; } }
@GetMapping(value = "toorder2") public String toOrder2(Integer val) throws Exception { Bean bean = new Bean(); bean.setVal(val); String forObject = orderService.handler(bean); return forObject; }
@GetMapping(value = "orderHandler") public String handler(Bean bean) throws Exception{ System.out.println(bean.getVal()); if (bean.getVal() == 1) { int i = 1 / 0; } return "order-9101"; }
order服务接收参数如果为1则抛出异常。再consumer设置20个请求里如果有50%发生错误,则熔断该接口,往后的一分钟内访问该接口都会直接降级。
但是不会影响order服务的其他接口。
Dashboard监控中心
hystrix允许我们通过GUI的形式查看当前服务的允许状态,主要查看被hystrix监控的接口信息。
我这里使用的是SpringBoot2.2.2 SpringCloudHoxton.SR1 特意说版本是因为各个版本的配置方式不同。
1 导入依赖
<!-- hystrix-dashboard 依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency> <!--actuator 依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
2 添加配置
management:
endpoints:
web:
exposure:
include: hystrix.stream
3 启动类添加注解 @EnableHystrixDashboard
Turbine集群监控
以上是单节点的配置,你只能查看一个节点的状态。现在我们来搭建集群监控,order集群两台,consumer两台,producer两台。consumer中远程调用order,order中远程调用producer。
以下为order,consumer集群的配置和pom。如果不需要单机查看则可以去掉依赖和@EnableHystrixDashboard。producer无需配置。
server:
port: 6300
spring:
application:
name: turbine
eureka:
instance:
hostname: 127.0.0.1
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
turbine:
app-config: CONSUMER,ORDER
aggregator:
cluster-config: default
cluster-name-expression: "'default'"
server:
port: 8002
spring:
application:
name: consumer
eureka:
instance:
hostname: 127.0.0.1
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
feign:
hystrix:
enabled: true
management:
endpoints:
web:
exposure:
include: hystrix.stream
server:
port: 9100
spring:
application:
name: order
eureka:
instance:
hostname: 127.0.0.1
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka
feign:
hystrix:
enabled: true
management:
endpoints:
web:
exposure:
include: hystrix.stream
server:
port: 9101
spring:
application:
name: order
eureka:
instance:
hostname: 127.0.0.1
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka
feign:
hystrix:
enabled: true
management:
endpoints:
web:
exposure:
include: hystrix.stream
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
新建一个项目,作为Turbine的启动项目。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-turbine</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
server:
port: 6300
spring:
application:
name: turbine
eureka:
instance:
hostname: 127.0.0.1
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
turbine:
app-config: CONSUMER,ORDER
aggregator:
cluster-config: default
cluster-name-expression: "'default'"
package com.dfsn.cloud.turbine; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard; import org.springframework.cloud.netflix.turbine.EnableTurbine; @SpringBootApplication @EnableCircuitBreaker @EnableHystrixDashboard @EnableTurbine public class Turbine_6300 { public static void main(String[] args) { SpringApplication.run(Turbine_6300.class, args); } }
Turbine分组集群监控
上边的集群监控order和consumer两个集群打到了一个控制台上,不方便查看。Turbine给我们提供了可以分开查看的方法。
给每个服务实例增加元数据,就是注册到Eureka中的数据,两个分为一组。最后再Turbine项目中设置根据组分类。
server:
port: 8001
spring:
application:
name: consumer
eureka:
instance:
hostname: 127.0.0.1
metadata-map:
dsahboardcluster: consumerdsahboard
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
feign:
hystrix:
enabled: true
management:
endpoints:
web:
exposure:
include: hystrix.stream
server:
port: 8002
spring:
application:
name: consumer
eureka:
instance:
hostname: 127.0.0.1
metadata-map:
dsahboardcluster: consumerdsahboard
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
feign:
hystrix:
enabled: true
management:
endpoints:
web:
exposure:
include: hystrix.stream
server:
port: 9100
spring:
application:
name: order
eureka:
instance:
hostname: 127.0.0.1
metadata-map:
dsahboardcluster: orderdsahboard
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka
feign:
hystrix:
enabled: true
management:
endpoints:
web:
exposure:
include: hystrix.stream
server:
port: 9101
spring:
application:
name: order
eureka:
instance:
hostname: 127.0.0.1
metadata-map:
dsahboardcluster: orderdsahboard
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka
feign:
hystrix:
enabled: true
management:
endpoints:
web:
exposure:
include: hystrix.stream
server:
port: 6300
spring:
application:
name: turbine
eureka:
instance:
hostname: 127.0.0.1
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
turbine:
app-config: CONSUMER,ORDER
aggregator:
cluster-config: consumerdsahboard,orderdsahboard
cluster-name-expression: metadata['dsahboardcluster']
Gateway
网关,思考一个问题,再服务内部使用OpenFeign按照服务名可以负载均衡(Ribbon)的访问集群服务。现在我们面对客户端该如何提供IP呢?
例如当前order服务集群中有三台服务器,用户需要访问order服务,我们不能把三个服务的IP+Port给用户,所以我们需要对外统一提供一
个入口,就是网关。网关的类型有很多比如netflix的zuul和spring cloud自己开发的Gateway。中心思想就是将网关从注册中心拉去服务注册表
用户访问网关时通过head,url等方式将自己要真正方法的服务名和controller交给网关,网关内部通过规则解析,然后匹配注册表中的服务,
最终转发。在这里负载均衡依然使用到了Ribbon,我们也可以把网关和hystrix整合使用,做服务熔断降级。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
server:
port: 6001
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka
spring:
application:
name: gateway
cloud:
gateway:
enabled: true
routes:
- id: consumerPath
uri: lb://consumer
predicates:
- Path=/gateway/c/**
- Header=X-Request-Id, \d+
filters:
- StripPrefix=2
- id: consumerPath
uri: lb://order
predicates:
- Path=/gateway/o/**
filters:
- StripPrefix=2
以上代码片段引入eureka客户端依赖和gateway依赖,yml中除了配置eureka之外,还有gateway自身的配置。
首先gateway服务不需要任何的注解,只要引入依赖就可以使用,所以如果有依赖包但是想关闭网关。设置
spring.cloud.gateway.enabled=false 表示不开启网关
spring.cloud.gateway.routes 这里的routes,routes一组route的集合,route是网关的构造块,它包含ID,
目标URL,predicates包含一组predicate是匹配器集合(官方给出了11种,但是可以互相配合使用),必须要匹配集合内的所有条件才算通过(与的关系)。
filters对过滤器再匹配规则需要转发到具体的服务,filter对转发前对请求做操作或者得到响应后对结果做操作所有的
filter都会做处理。
如上代码:id为consumerPath,其转发到lb://consumer,lb://xxx 是内部服务的格式,其实也可以直接
写IP,例如需要转发到www.baidu.com predicates里有两组匹配规则,Path规定url中需要有指定的路径,
Header规定必须有X-Request-Id的请求头,请求头的值是数字。最后filters有一个StripPrefix它修改了路径
predicates已经满足,准备转发到consumer服务,但/gateway/c/consumer1这个路径是多出来/gateway/c/这
一部分的,2就是去掉两级路径。
扩展写法
以上写法是简化写法,大部分也这样用,但是有些route或者filter参数比较多,gateway提供了完整的配置格式。
spring: cloud: gateway: routes: - id: after_route uri: https://example.org predicates: - name: Cookie args: name: mycookie regexp: mycookievalue
以上代码片段是一个 -Cookie route,再下边的代码示例中有的用的就是这种完整配置。
同时匹配到多个route
若同一个路径能匹配到多个route则第一个生效。
11个Route
route之 - Before
Before匹配规则,规定只有在某个时间之前的请求才会接收,否则404。以下代码片段规定了只有在 2020-08-01T23:20:00.000+08:00[Asia/Shanghai]
这个写法是固定的,年-月-日T时:分:秒.毫秒+8:00(东八区)[时区标记]。要根据服务器的时区规定。满足上边时间要求的会转发到博客园的官网,否则
404。看我下图两个结果,20之前的可以转发,20的就不行了。
server:
port: 6001
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka
spring:
application:
name: gateway
cloud:
gateway:
enabled: true
routes:
- id: consumerPath
uri: https://www.cnblogs.com
predicates:
- Before=2020-08-01T23:20:00.000+08:00[Asia/Shanghai]
route之 - After
这个规则和上边那个刚好相反,它规定必须在某个时间之后才可以访问,否则404。
server:
port: 6001
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka
spring:
application:
name: gateway
cloud:
gateway:
enabled: true
routes:
- id: consumerPath
uri: https://www.cnblogs.com
predicates:
- After=2020-08-01T23:30:00.000+08:00[Asia/Shanghai]
route之 - Between
这个规则接收两个时间,只有在两个时间之间的才会被转发。
server:
port: 6001
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka
spring:
application:
name: gateway
cloud:
gateway:
enabled: true
routes:
- id: consumerPath
uri: https://www.cnblogs.com
predicates:
- Between=2020-08-01T23:34:00.000+08:00[Asia/Shanghai],2020-08-01T23:36:00.000+08:00[Asia/Shanghai]
注意!以上三种转发方式如果uri是这样的 http://localhost:9100/order1 本地路径会转发失败,我在测试时只能转发到域名地址。
route之 - Cookie
请求需要带指定的cookie,否则404,需要多个就设置多个-Cookie value可以用Java正则表达式(PS:正则不太会就不做例子了)
server:
port: 6001
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka
spring:
application:
name: gateway
cloud:
gateway:
enabled: true
routes:
- id: consumerPath
uri: lb://order/
predicates:
- Path=/gateway/o/**
- Cookie=cookiekey1,cookievalue1
filters:
- StripPrefix=2
route之 - Header
请求需要带指定的header,否则404,需要多个就设置多个-Header。value可以用Java正则表达式(PS:正则不太会就不做例子了)
server:
port: 6001
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka
spring:
application:
name: gateway
cloud:
gateway:
enabled: true
routes:
- id: consumerPath
uri: lb://order/
predicates:
- Path=/gateway/o/**
- Header=headkey1, headvalue1
- Header=headkey2, headvalue2
filters:
- StripPrefix=2
route之 - Host(get不到用法)
server:
port: 6001
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka
spring:
application:
name: gateway
cloud:
gateway:
enabled: true
routes:
- id: consumerPath
uri: lb://order/
predicates:
- Path=/gateway/o/**
- Host=www.datang.com**
filters:
- StripPrefix=2
route之 - Method
这个route就比较霸道了,下面代码片段order服务有三个接口分别是GET,POST,PUT,但是gateway设置只允许
GET,POST请求可以放行,PUT请求就是404。
server:
port: 6001
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka
spring:
application:
name: gateway
cloud:
gateway:
enabled: true
routes:
- id: consumerPath
uri: lb://order/
predicates:
- Path=/gateway/o/**
- Method=GET,POST
filters:
- StripPrefix=2
package com.dfsn.cloud.order.controller; import com.dfsn.cloud.order.vo.Person; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class OrderController { @GetMapping(value = "order") public String order1(Person person) { return "order-9100"; } @PostMapping(value = "order") public String order2() { return "order-9100"; } @PutMapping(value="order") public String order3() { return "order-9100"; } }
route之 - Path
这个是最经常用到的,请求包含某个路径则转发。以下代码片段主要路径包括 o**或者p**就转发到order。
order服务中有三个接口分别是order,porder.sorder。最后一个则不会被转发。
package com.dfsn.cloud.order.controller; import com.dfsn.cloud.order.vo.Person; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class OrderController { @GetMapping(value = "order") public String order1(Person person) { return "order-9100"; } @GetMapping(value = "porder") public String order2() { return "order-9100"; } @GetMapping(value = "sorder") public String order3() { return "order-9100"; } }
server:
port: 6001
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka
spring:
application:
name: gateway
cloud:
gateway:
enabled: true
routes:
- id: consumerPath
uri: lb://order/
predicates:
- Path=/o**,/p**
route之 - Query
规定参数必须包含一些参数,也可以指定参数的值是多少,值可以用Java正则表达式。
以下代码片段指定必须有name和age参数否则404。
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka spring: application: name: gateway cloud: gateway: enabled: true routes: - id: consumerPath uri: lb://order/ predicates: - Path=/gateway/o/** - Query=name - Query=age filters: - StripPrefix=2
package com.dfsn.cloud.order.controller; import com.dfsn.cloud.order.vo.Person; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController public class OrderController { @GetMapping(value = "order") public String order1(Person person) { return person.toString(); } @PostMapping(value = "order") public String order2(@RequestBody Person person) { return person.toString(); } }
我们也可以指定参数的值范围。以下代码片段指定了name的值是张开头,age是2开头,否则404。
以下代码片段指定name必须是A开头并且后边只能有一个字符,age只能是数字(我这蹩脚的正则水平。。。)
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka spring: application: name: gateway cloud: gateway: enabled: true routes: - id: consumerPath uri: lb://order/ predicates: - Path=/gateway/o/** - Query=name,A. - Query=age,\d+ filters: - StripPrefix=2
注意参数在请求体不可以,比如JSON格式是不行的。
package com.dfsn.cloud.order.controller; import com.dfsn.cloud.order.vo.Person; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController public class OrderController { @GetMapping(value = "order") public String order1(Person person) { return person.toString(); } @PostMapping(value = "order") public String order2(@RequestBody Person person) { return person.toString(); } }
route之 - RemoteAddr
限制请求IP,这个也可以用的。如果是内部系统只开放自己局域网内的。
以下代码片段只允许我本机的真实IP访问,所以127.0.0.1就不行。
route之 - Weight
在开头我们看过,如果两个route都匹配则会使用第一个。weight设置权重,如果权重越大
越容易被转发,但是权重小的也是有机会的。以下代码片段order和consumer都有get请求
consumer的权重是2order是1,实验得出转发到consumer几率确实大很多。
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka spring: application: name: gateway cloud: gateway: enabled: true routes: - id: consumerPath1 uri: lb://order/ predicates: - Path=/gateway/g/** - Weight=group1, 1 filters: - StripPrefix=2 - id: consumerPath2 uri: lb://consumer/ predicates: - Path=/gateway/g/** - Weight=group1, 2 filters: - StripPrefix=2
package com.dfsn.cloud.order.controller; import com.dfsn.cloud.order.vo.Person; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class OrderController { @GetMapping(value = "get") public String order1(Person person) { return "order-9100"; } }
package com.dfsn.cloud.consumer.controller; import com.dfsn.cloud.consumer.vo.Person; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class ConsumerController { @GetMapping(value = "get") public String order1(Person person) { return "consumer-8001"; } }
31个Filter
这个过滤器用于给请求增加header,以下代码片段增加了两个请求头,head1,head2值是value1,value2。
server:
port: 6001
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka
spring:
application:
name: gateway
cloud:
gateway:
enabled: true
routes:
- id: consumerPath
uri: lb://order
predicates:
- Path=/gateway/o/**
filters:
- StripPrefix=2
- AddRequestHeader=head1,value1
- AddRequestHeader=head2,value2
package com.dfsn.cloud.order.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; @RestController public class OrderController { @GetMapping(value = "order1") public String order1(HttpServletRequest request) { String head1 = request.getHeader("head1"); String head2 = request.getHeader("head2"); return head1 + head2; } }
这个过滤器用于给请求增加参数,以下代码示例增加参数name和age,值为zhangsan,22。
server:
port: 6001
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka
spring:
application:
name: gateway
cloud:
gateway:
enabled: true
routes:
- id: consumerPath
uri: lb://order
predicates:
- Path=/gateway/o/**
filters:
- StripPrefix=2
- AddRequestParameter=name,zhangsan
- AddRequestParameter=age,22
package com.dfsn.cloud.order.controller; import com.dfsn.cloud.order.vo.Person; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class OrderController { @GetMapping(value = "order1") public String order1(Person person) { return person.getName() + person.getAge(); } }
这个过滤器给请求响应增加header,以下代码片段增加了两个header,header1,header2,值分别是value1,value2。
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka spring: application: name: gateway cloud: gateway: enabled: true routes: - id: consumerPath uri: lb://order predicates: - Path=/gateway/o/** filters: - StripPrefix=2 - AddResponseHeader=head1,value1 - AddResponseHeader=head2,value2
filter之 - DedupeResponseHeader
这个过滤器用于去除重复的响应header,以下代码片段再order的服务中设置了两个header,再gateway又设置两个名称相同但值不同的header。
使用这个过滤器则可以去掉重复的header。
package com.dfsn.cloud.order.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletResponse; @RestController public class OrderController { @GetMapping(value = "order1") public String order1(HttpServletResponse response) { response.setHeader("header1", "zhangsan"); response.setHeader("header2", "22"); return "order-9100"; } }
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka spring: application: name: gateway cloud: gateway: enabled: true routes: - id: consumerPath uri: lb://order predicates: - Path=/gateway/o/** filters: - StripPrefix=2 - AddResponseHeader=header1,lisi - AddResponseHeader=header2,11
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka spring: application: name: gateway cloud: gateway: enabled: true routes: - id: consumerPath uri: lb://order predicates: - Path=/gateway/o/** filters: - StripPrefix=2 - AddResponseHeader=header1,lisi - AddResponseHeader=header2,11 - DedupeResponseHeader=header1 header2
filter之 - Hystrix
这个filter可以引入hystrix的熔断可降级。以下代码片段中设置了一个HystrexCommand就是之前讲过的
hystrix.command.default 之前用default搞全局的现在换成一个具体的名字。其他的设置一样,在filter中
引用。设置超时时间为2秒,order服务休眠3秒就报错了。需要引入hystrix的依赖。经过测试只有请求超时
时才会走降级,如果是转发的服务内部异常则不会降级。
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka hystrix: command: orderCommand: execution: timeout: enabled: true isolation: thread: timeoutInMilliseconds: 2000 spring: application: name: gateway cloud: gateway: enabled: true routes: - id: consumerPath1 uri: lb://order/ predicates: - Path=/gateway/g/** filters: - StripPrefix=2 - Hystrix=orderCommand
package com.dfsn.cloud.order.controller; import com.dfsn.cloud.order.vo.Person; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class OrderController { @GetMapping(value = "get") public String order1(Person person) { try { Thread.sleep(3000); }catch(Exception e) {} return "order-9100"; } }
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
同时也可以设置对应的fallback降级方法,这需要我们在当前gateway工程内写一个可被转发的Controller,为了让所有的order接口都适用
我们用@RequestMapping注解方法,不使用具体的method。
package com.dfsn.cloud.gateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @EnableEurekaClient @RestController public class Gateway_6001 { public static void main(String[] args) { SpringApplication.run(Gateway_6001.class, args); } @RequestMapping(value = "/orderFallback") public String fallback() { return "orderback"; } }
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka hystrix: command: orderCommand: execution: timeout: enabled: true isolation: thread: timeoutInMilliseconds: 2000 spring: application: name: gateway cloud: gateway: enabled: true routes: - id: consumerPath1 uri: lb://order/ predicates: - Path=/gateway/g/** filters: - StripPrefix=2 - name: Hystrix args: name: orderCommand fallbackUri: forward:/orderFallback
fallbackUri 也可以使用 lb: 做负载均衡,需要我们在做一个route,route匹配fallbackUri的值即可。
以下代码片段最终转发到了consumer服务中。
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka hystrix: command: orderCommand: execution: timeout: enabled: true isolation: thread: timeoutInMilliseconds: 2000 spring: application: name: gateway cloud: gateway: enabled: true routes: - id: consumerPath1 uri: lb://order/ predicates: - Path=/gateway/g/** filters: - StripPrefix=2 - name: Hystrix args: name: orderCommand fallbackUri: forward:/orderFallbacklb - id: fallbackPath uri: lb://consumer/ predicates: - Path=/orderFallbacklb
package com.dfsn.cloud.consumer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.hystrix.EnableHystrix; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.Bean; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @EnableFeignClients @SpringBootApplication @EnableHystrix @RestController public class Consumer_8001 { public static void main(String[] args) { SpringApplication.run(Consumer_8001.class, args); } @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } @RequestMapping(value = "/orderFallbacklb") public String fallback() { return "orderback"; } }
filter之 - CircuitBreaker
这个过滤器对转发的服务做预熔断处理,设置格式和 - Hystrix 类似。以下代码片段设置了熔断计划,一个时间窗(10秒)
最低请求10个并且错误率100%则开启熔断,此后一分钟内的请求都会被熔断。这里有个问题是当-CircuitBreaker和-Hystrix
同时使用会出现-CircuitBreaker不生效。同样的,降级方法可以是再本项目,也可以是任意一个 lb: 这取决于你的代码设置。
server:
port: 6001
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka
hystrix:
command:
orderCircuitBreaker:
circuitBreaker:
enabled: true
requestVolumeThreshold: 10
errorThresholdPercentage: 50
sleepWindowInMilliseconds: 60000
spring:
application:
name: gateway
cloud:
gateway:
enabled: true
routes:
- id: consumerPath1
uri: lb://order/
predicates:
- Path=/gateway/g/**
filters:
- StripPrefix=2
- name: CircuitBreaker
args:
name: orderCircuitBreaker
fallbackUri: forward:/orderFallback
filter之 - FallbackHeaders
无论是熔断还是超时都可以把降级方法写道别的服务里,通过gateway转发。FallbackHeaders
会帮我生成一个header,这个header包含具体的错误信息。
server:
port: 6001
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka
hystrix:
command:
orderTimeout:
execution:
timeout:
enabled: true
isolation:
thread:
timeoutInMilliseconds: 2000
spring:
application:
name: gateway
cloud:
gateway:
enabled: true
routes:
- id: consumerPath1
uri: lb://order/
predicates:
- Path=/gateway/g/**
filters:
- StripPrefix=2
- name: Hystrix
args:
name: orderTimeout
fallbackUri: forward:/exeorderFallback
- id: fallbackpath
uri: lb://consumer/
predicates:
- Path=/exeorderFallback
filters:
- name: FallbackHeaders
args:
executionExceptionTypeHeaderName: Test-Header
package com.dfsn.cloud.consumer.controller; import com.dfsn.cloud.consumer.feigns.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; @RestController public class ConsumerController { @Autowired private OrderService orderService; @GetMapping(value = "consumer1") public String consumer1() { return orderService.order1(); } @RequestMapping(value = "/exeorderFallback") public String s(HttpServletRequest httpServletRequest) { String header = httpServletRequest.getHeader("Test-Header"); System.out.println(header); return "转发到consumer处理熔断" + header; } }
filter之 - MapRequestHeader
给请求添加header但是它和 -AddRequestHeader不同,它接收两个参数,A,B如果请求头中不包含B则会增加一个B头
B值是A头的值。若A头也不存在则什么都不做,若B头存在则增强B头,也就是一个头两个值。
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka spring: application: name: gateway cloud: gateway: enabled: true routes: - id: consumerPath1 uri: lb://order/ predicates: - Path=/gateway/o/** filters: - StripPrefix=2 - MapRequestHeader=name,nikename
package com.dfsn.cloud.order.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import java.util.Enumeration; @RestController public class OrderController { @GetMapping(value = "order1") public String order1(HttpServletRequest request) { String name = request.getHeader("name"); Enumeration<String> nikename1 = request.getHeaders("nikename"); StringBuilder sb = new StringBuilder(); while (nikename1.hasMoreElements()) { String s = nikename1.nextElement(); sb.append(s); } return name + sb.toString(); } }
filter之 - PrefixPath
向下游转发时增加一个路径。如下代码,order中的controller的path是/mypath/order1。而请求的是 http://localhost:6001/gateway/o/order1
通过 - StripPrefix 去掉两级后剩下order1,在加上PrefixPath=/mypath 刚好够。
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka spring: application: name: gateway cloud: gateway: enabled: true routes: - id: consumerPath1 uri: lb://order/ predicates: - Path=/gateway/o/** filters: - StripPrefix=2 - PrefixPath=/mypath
package com.dfsn.cloud.order.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class OrderController { @GetMapping(value = "/mypath/order1") public String order1() { return "order-9100"; } }
filter之 - PreserveHostHeader
此筛选器设置一个请求属性,路由筛选器将对该属性进行检查,以确定是否应该发送原始主机标头,而不是由HTTP客户机确定的主机标头。
翻译的结果Q_Q 我这边做测试添加这个过滤器请求头都一样,不知道是干啥的。
filter之 - RequestRateLimiter
使用这个filter对请求做限流处理。目前文档上提供了一种令牌桶限流策略,需要和Redis结合使用,给出限定请求的规则,在配置令牌策略
如下代码片段,从获取每一个请求的路径,RequestRateLimiter会根据访问的路径生成对应的令牌。当前设置每秒生成5个令牌(replenishRate),
最多存储5个(burstCapacity),每个请求消耗1个令牌(requestedTokens)也就限制了一个请求每秒最多5次访问。
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka spring: redis: host: redisIP password: redis密码 port: 6379 database: 0 application: name: gateway cloud: gateway: enabled: true routes: - id: consumerPath1 uri: lb://order/ predicates: - Path=/gateway/o/** filters: - StripPrefix=2 - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 5 redis-rate-limiter.burstCapacity: 5 redis-rate-limiter.requestedTokens: 1
package com.dfsn.cloud.gateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; import org.springframework.context.annotation.Bean; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @SpringBootApplication @RestController public class Gateway_6001 { public static void main(String[] args) { SpringApplication.run(Gateway_6001.class, args); } @Bean KeyResolver userKeyResolver() { return new KeyResolver() { @Override public Mono<String> resolve(ServerWebExchange exchange) { return Mono.just(exchange.getRequest().getPath().value()); } }; } }
filter之 - RedirectTo
对转发做重定向,但是我只测试出了怎么转发到外部网站,转发到内部,不知道怎么弄。访问http://localhost:6001/redirect 会转发到 www.cnblogs.com
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka spring: application: name: gateway cloud: gateway: enabled: true routes: - id: orderPath1 uri: lb://order predicates: - Path=/redirect filters: - RedirectTo=302,www.cnblogs.com
filter之 - RemoveRequestHeader
删除请求头
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka spring: application: name: gateway cloud: gateway: enabled: true routes: - id: orderPath1 uri: lb://order predicates: - Path=/gateway/o/* filters: - StripPrefix=2 - RemoveRequestHeader=age - RemoveRequestHeader=name
filter之 - RemoveResponseHeader
删除响应头
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka spring: application: name: gateway cloud: gateway: enabled: true routes: - id: orderPath1 uri: lb://order predicates: - Path=/gateway/o/* filters: - StripPrefix=2 - RemoveResponseHeader=a - RemoveResponseHeader=b
filter之 - RemoveRequestParameter
删除表单参数
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka spring: application: name: gateway cloud: gateway: enabled: true routes: - id: orderPath1 uri: lb://order predicates: - Path=/gateway/o/* filters: - StripPrefix=2 - RemoveRequestParameter=name - RemoveRequestParameter=age
filter之 - RewritePath
如果匹配到路径则替换匹配到的路径。以下代码示例,如果路径包含/gateway/o/,则将它替换为/gateway/o/mypath
最终成功转发到order服务的/mypath/order1
server:
port: 6001
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka
spring:
application:
name: gateway
cloud:
gateway:
enabled: true
routes:
- id: orderPath1
uri: lb://order
predicates:
- Path=/gateway/o/*
filters:
- RewritePath=/gateway/o/,/gateway/o/mypath/
- StripPrefix=2
package com.dfsn.cloud.order.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletResponse; @RestController public class OrderController { @GetMapping(value = "/mypath/order1") public String order1(HttpServletResponse response) { return "order1-9100"; } }
filter之 - RewriteLocationResponseHeader
filter之 - RewriteLocationResponseHeader
可接收参数 stripVersionMode,locationHeaderName,hostValue,protocolsRegex
stripVersionMode:可选值有
NEVER_STRIP:即使原始请求路径不包含版本,版本也不会被剥离。
AS_IN_REQUEST:只有当原始请求路径不包含版本时,版本才会被剥离。
ALWAYS_STRIP:版本总是被剥离,即使原始请求路径包含版本。
locationHeaderName:固定值 Location
hostValue: 如果提供了“hostValue”参数,则用于替换响应“Location”头部的“host:port”部分。如果没有提供,则使用“Host”请求头的值。
protocolsRegex:参数必须是一个有效的regex“字符串”,协议名必须与之匹配。如果不匹配,过滤器什么也不做。默认是“http|https|ftp|ftps”。
这么多参数我只测试除了一个有用,就是 hostValue,可能我没有彻底理解透。
以下代码示例order1在增加响应头Location,增加状态码301,浏览器接到响应后会重定向到该服务的order2。但是增加了filter后,使用hostValue
参数替换了Host:Port,最终重定向到了consumer服务。
server:
port: 6001
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka
spring:
application:
name: gateway
cloud:
gateway:
enabled: true
routes:
- id: orderPath1
uri: lb://order
predicates:
- Path=/gateway/o/*
filters:
- StripPrefix=2
- RewriteLocationResponseHeader=AS_IN_REQUEST, Location,localhost:8001,
package com.dfsn.cloud.order.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletResponse; @RestController public class OrderController { @GetMapping(value = "/order1") public String order1(HttpServletResponse response) { response.setHeader("Location", "http://localhost:6001/gateway/o/order2"); response.setStatus(301); return "order1-9100"; } @GetMapping(value = "/order2") public String order2() { return "order2-9100"; } }
package com.dfsn.cloud.consumer.controller; import com.dfsn.cloud.consumer.feigns.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class ConsumerController { @Autowired private OrderService orderService; @GetMapping(value = "consumer1") public String consumer1() { return "consumer1"; } @GetMapping(value = "/gateway/o/order2") public String consumer2() { return "我是consumer8001"; } }
filter之 - RewriteResponseHeader
替换响应头的值,三个参数 1 需要替换header,2 替换值的哪一部分(正则表达式)3 替换为
如下,替换color响应头的RRRR值为TTTT。
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka spring: application: name: gateway cloud: gateway: enabled: true routes: - id: orderPath1 uri: lb://order predicates: - Path=/gateway/o/* filters: - StripPrefix=2 - RewriteResponseHeader=color,R,T
package com.dfsn.cloud.order.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletResponse; @RestController public class OrderController { @GetMapping(value = "/order1") public String order1(HttpServletResponse response) { response.setHeader("color","RRRR"); return "order1-9100"; } }
filter之 - SaveSession
在转发请求之前,强制执行WebSession::save
操作主要用在那种像 Spring Session 延迟数据存储(数据不是立刻持久化)的,
并希望在请求转发前确保session状态保存情况。如果你将Spring Secutiry于Spring Session集成使用,并想确保安全信息都传到下游机器,
你就需要配置这个filter。
filter之 - SecureHeaders
为原始响应添加了一系列起安全作用的响应头(PS这一个和上一个不做示例,关于session和Secure)
filter之 - SetPath
它允许我们将路径的替换为指定的值。如下示例,我们的路径本是需要去掉/gateway/o 这一部分
但现在不用 -StripPrefix 而是将路径全部替换为/{path}的值。
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka spring: application: name: gateway cloud: gateway: enabled: true routes: - id: orderPath1 uri: lb://order predicates: - Path=/gateway/o/{path} filters: #- StripPrefix=2 - SetPath=/{path}
filter之 - SetRequestHeader
替换请求头header的值,如下代码片段,把用户传递的X-Request-Red请求头的值替换成了 Blue
package com.dfsn.cloud.order.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import java.util.Enumeration; @RestController public class OrderController { @GetMapping(value = "/order1") public String order1(HttpServletRequest request) { Enumeration<String> headers = request.getHeaders("X-Request-Red"); StringBuilder sb = new StringBuilder(); while (headers.hasMoreElements()){ String s = headers.nextElement(); sb.append(s); } return sb.toString(); } }
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka spring: application: name: gateway cloud: gateway: enabled: true routes: - id: orderPath1 uri: lb://order predicates: - Path=/gateway/o/{path} filters: - SetPath=/{path} - SetRequestHeader=X-Request-Red, Blue
filter之 - SetResponseHeader
修改响应头的值
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka spring: application: name: gateway cloud: gateway: enabled: true routes: - id: orderPath1 uri: lb://order predicates: - Path=/gateway/o/{path} filters: - SetPath=/{path} - SetResponseHeader=X-Response-Red, Blue
package com.dfsn.cloud.order.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletResponse; @RestController public class OrderController { @GetMapping(value = "/order1") public String order1(HttpServletResponse response) { response.setHeader("X-Response-Red","Green"); return "order1"; } }
filter之 - SetStatus
修改响应状态
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka spring: application: name: gateway cloud: gateway: enabled: true routes: - id: orderPath1 uri: lb://order predicates: - Path=/gateway/o/{path} filters: - SetPath=/{path} - SetStatus=201
filter之 - StripPrefix
如果是从头看到这里,这个过滤器不用说了吧。转发到下游之前去掉几个 /xx
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka spring: application: name: gateway cloud: gateway: enabled: true routes: - id: orderPath1 uri: lb://order predicates: - Path=/gateway/o/** filters: - StripPrefix=2
filter之 - Retry
对于请求失败的做重试。以下配置规定了只有GET请求会重试,Get请求总共发了四次,三次为重试。POST没有重试。
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka hystrix: command: orderCommand: execution: timeout: enabled: true isolation: thread: timeoutInMilliseconds: 2000 spring: application: name: gateway cloud: gateway: enabled: true routes: - id: orderid uri: lb://order/ predicates: - Path=/gateway/o/** filters: - StripPrefix=2 - name: Retry args: retries: 3 statuses: BAD_GATEWAY methods: GET exceptions: - java.lang.ArithmeticException
order1请求进来了 2020-08-09 09:18:38.989 ERROR 5268 --- [nio-9100-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ArithmeticException: / by zero] with root cause java.lang.ArithmeticException: / by zero at com.dfsn.cloud.order.controller.OrderController.order1(OrderController.java:14) ~[classes/:na] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_251] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_251] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_251] at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_251] at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:888) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:108) ~[spring-boot-actuator-2.2.2.RELEASE.jar:2.2.2.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:367) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:860) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1591) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.29.jar:9.0.29] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_251] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_251] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.29.jar:9.0.29] at java.lang.Thread.run(Thread.java:748) [na:1.8.0_251] order1请求进来了 2020-08-09 09:18:39.063 ERROR 5268 --- [nio-9100-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ArithmeticException: / by zero] with root cause java.lang.ArithmeticException: / by zero at com.dfsn.cloud.order.controller.OrderController.order1(OrderController.java:14) ~[classes/:na] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_251] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_251] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_251] at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_251] at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:888) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:108) ~[spring-boot-actuator-2.2.2.RELEASE.jar:2.2.2.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:367) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:860) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1591) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.29.jar:9.0.29] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_251] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_251] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.29.jar:9.0.29] at java.lang.Thread.run(Thread.java:748) [na:1.8.0_251] order1请求进来了 2020-08-09 09:18:39.085 ERROR 5268 --- [nio-9100-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ArithmeticException: / by zero] with root cause java.lang.ArithmeticException: / by zero at com.dfsn.cloud.order.controller.OrderController.order1(OrderController.java:14) ~[classes/:na] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_251] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_251] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_251] at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_251] at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:888) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:108) ~[spring-boot-actuator-2.2.2.RELEASE.jar:2.2.2.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:367) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:860) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1591) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.29.jar:9.0.29] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_251] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_251] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.29.jar:9.0.29] at java.lang.Thread.run(Thread.java:748) [na:1.8.0_251] order1请求进来了 2020-08-09 09:18:39.109 ERROR 5268 --- [nio-9100-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ArithmeticException: / by zero] with root cause java.lang.ArithmeticException: / by zero at com.dfsn.cloud.order.controller.OrderController.order1(OrderController.java:14) ~[classes/:na] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_251] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_251] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_251] at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_251] at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:888) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:108) ~[spring-boot-actuator-2.2.2.RELEASE.jar:2.2.2.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:367) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:860) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1591) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.29.jar:9.0.29] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_251] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_251] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.29.jar:9.0.29] at java.lang.Thread.run(Thread.java:748) [na:1.8.0_251] 2020-08-09 09:20:21.331 INFO 5268 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration
order21请求进来了 2020-08-09 09:20:46.521 ERROR 5268 --- [nio-9100-exec-9] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ArithmeticException: / by zero] with root cause java.lang.ArithmeticException: / by zero at com.dfsn.cloud.order.controller.OrderController.order2(OrderController.java:21) ~[classes/:na] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_251] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_251] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_251] at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_251] at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:888) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at javax.servlet.http.HttpServlet.service(HttpServlet.java:660) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:108) ~[spring-boot-actuator-2.2.2.RELEASE.jar:2.2.2.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:367) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:860) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1591) [tomcat-embed-core-9.0.29.jar:9.0.29] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.29.jar:9.0.29] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_251] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_251] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.29.jar:9.0.29] at java.lang.Thread.run(Thread.java:748) [na:1.8.0_251]
上面的重试过滤器,有5个配置:
retries:默认为3,用来标识重试次数
series:用来指定哪些段的状态码需要重试,默认SERVER_ERROR
,即5xx。
statuses:用于指定哪些状态需要重试,默认为空,它跟series
至少得指定一个。一般不怎么配置这个。
methods:于指定那些方法的请求需要重试,默认为GET
exceptions:用于指定哪些异常需要重试,默认为java.io.IOException
backoff:为重试机制的时间设置,默认为disabled不配置。
除了retries
、exceptions
,其他3项的对应的枚举类为:
series:org.springframework.http.HttpStatus.Series
statuses:org.springframework.http.HttpStatus
methods:org.springframework.http.HttpMethod
spring: cloud: gateway: routes: - id: retry_test uri: http://localhost:8080/flakey predicates: - Host=*.retry.com filters: - name: Retry args: retries: 3 statuses: BAD_GATEWAY methods: GET,POST backoff: firstBackoff: 10ms maxBackoff: 50ms factor: 2 basedOnPreviousValue: false
filter之 - RequestSize
限制请求大小。
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka spring: application: name: gateway cloud: gateway: enabled: true routes: - id: orderPath1 uri: lb://order predicates: - Path=/gateway/o/** filters: - StripPrefix=2 - name: RequestSize args: maxSize: 1
filter之 - SetRequestHost
写入报错,用JAVA编码方式,发现没有此方法,可能此方法已经移除,但文档未修改。
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka spring: application: name: gateway cloud: gateway: enabled: true routes: - id: orderPath1 uri: lb://order predicates: - Path=/gateway/o/** filters: - StripPrefix=2 - name: SetRequestHost args: host: example.org
filter之 - ModifyRequestBody
之前有修改请求参数的,但是那只限于表单请求,而这个filter是可以修改 requestBody请求体里的内容的。
官方文档给出的方式是通过JAVA代码形式,没有yml方式的,所以我这里也是代码形式。其实是可以用yml我在
一篇博客上看到过。另外代码形式使用的lambda表达式如果看不明白,可以先看这个章节以上yml配置都可以用JAVA编程方式实现
在这里我将lambda方式转换成了Java方式提供给你参考。
尽管如此这里我还是要大概说下,modifyRequestBody(Class<T> inClass, Class<R> outClass,String newContentType, RewriteFunction<T, R> rewriteFunction)方法
接收四个参数第一个是body体里的参数类型,无疑它是String.class,第二个是过滤器发往下游的类型,在我的Order
服务中,接口使用Person对象接收,而我的过滤器仅仅是对参数进行了一些改变,并没有直接改变类型,所以这里也是
Person.class,第三个是ContentType我也没有改变使用的还是application/json第四个则是具体处理的方法。这里是一个
Function接口,这个接口就是处理具体的逻辑的。
package com.dfsn.cloud.gateway; import java.util.function.Function; import org.reactivestreams.Publisher; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.filter.factory.rewrite.RewriteFunction; import org.springframework.cloud.gateway.route.Route; import org.springframework.cloud.gateway.route.Route.AsyncBuilder; import org.springframework.cloud.gateway.route.builder.GatewayFilterSpec; import org.springframework.cloud.gateway.route.builder.PredicateSpec; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.cloud.gateway.route.builder.UriSpec; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.context.annotation.Bean; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ServerWebExchange; import com.alibaba.fastjson.JSONObject; import com.dfsn.cloud.vo.Person; import reactor.core.publisher.Mono; @SpringBootApplication @EnableEurekaClient @RestController public class Gateway_6001 { public static void main(String[] args) { SpringApplication.run(Gateway_6001.class, args); } // 這是lambda @Bean public RouteLocator routes(RouteLocatorBuilder builder) { return builder.routes().route("orderid", p -> p.path("/gateway/o/**").filters(g -> g.stripPrefix(2) .modifyRequestBody(String.class, Person.class, MediaType.APPLICATION_JSON_VALUE, (e, s) -> { Person oldPerson = JSONObject.parseObject(s, Person.class); Person newPerson = new Person(); newPerson.setAge(oldPerson.getAge() + 100); newPerson.setName(oldPerson.getAge() + "---"); return Mono.just(newPerson); })).uri("lb://order/")).build(); } // 这是对应的JAVA编码 // @Bean public RouteLocator routes2(RouteLocatorBuilder builder) { return builder.routes().route("orderid", new Function<PredicateSpec, Route.AsyncBuilder>() { @Override public Route.AsyncBuilder apply(PredicateSpec t) { t.path("/gateway/o/**").filters(new Function<GatewayFilterSpec, UriSpec>() { @Override public UriSpec apply(GatewayFilterSpec t) { return t.stripPrefix(2).modifyRequestBody(String.class, Person.class, MediaType.APPLICATION_JSON_VALUE, new RewriteFunction<String, Person>() { @Override public Publisher<Person> apply(ServerWebExchange s, String t) { Person oldPerson = JSONObject.parseObject(t, Person.class); Person newPerson = new Person(); newPerson.setAge(oldPerson.getAge() + 100); newPerson.setName(oldPerson.getAge() + "---"); return Mono.just(newPerson); } }); } }); return t.uri("lb://order/"); } }).build(); } }
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka hystrix: command: orderCommand: execution: timeout: enabled: true isolation: thread: timeoutInMilliseconds: 2000 spring: application: name: gateway cloud: gateway: enabled: true routes: # - id: orderid # uri: lb://order/ # predicates: # - Path=/gateway/o/** # filters: # - StripPrefix=2
package com.dfsn.cloud.order.controller; import com.dfsn.cloud.order.vo.Person; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController public class OrderController { @PostMapping(value = "person") public String order1(@RequestBody Person person) { return person.getName()+"!!!!!"+person.getAge(); } }
filter之 - ModifyResponseBody
修改返回值body中的信息,这个写法就和以上类似了。
package com.dfsn.cloud.gateway; import java.util.function.Function; import org.reactivestreams.Publisher; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.filter.factory.rewrite.RewriteFunction; import org.springframework.cloud.gateway.route.Route; import org.springframework.cloud.gateway.route.Route.AsyncBuilder; import org.springframework.cloud.gateway.route.builder.GatewayFilterSpec; import org.springframework.cloud.gateway.route.builder.PredicateSpec; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.cloud.gateway.route.builder.UriSpec; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.context.annotation.Bean; import org.springframework.http.MediaType; import org.springframework.web.server.ServerWebExchange; import com.alibaba.fastjson.JSONObject; import com.dfsn.cloud.vo.Person; import reactor.core.publisher.Mono; @SpringBootApplication @EnableEurekaClient public class Gateway_6001 { public static void main(String[] args) { SpringApplication.run(Gateway_6001.class, args); } // 這是lambda @Bean public RouteLocator routes(RouteLocatorBuilder builder) { return builder.routes().route("orderid", p -> p.path("/gateway/o/**").filters(g -> g.stripPrefix(2) .modifyResponseBody(String.class, String.class, MediaType.APPLICATION_JSON_VALUE, (e, s) -> { Person oldPserson = JSONObject.parseObject(s, Person.class); Person newPerson = new Person(); newPerson.setAge(oldPserson.getAge() + 50); newPerson.setName(oldPserson.getName() + "%%%"); String jsonString = JSONObject.toJSONString(newPerson); return Mono.just(jsonString); })).uri("lb://order/")).build(); } // 这是对应的JAVA编码 //@Bean public RouteLocator routes2(RouteLocatorBuilder builder) { return builder.routes().route("orderid", new Function<PredicateSpec, Route.AsyncBuilder>() { @Override public Route.AsyncBuilder apply(PredicateSpec t) { t.path("/gateway/o/**").filters(new Function<GatewayFilterSpec, UriSpec>() { @Override public UriSpec apply(GatewayFilterSpec t) { return t.stripPrefix(2).modifyResponseBody(String.class, String.class, MediaType.APPLICATION_JSON_VALUE, new RewriteFunction<String, String>() { @Override public Publisher<String> apply(ServerWebExchange t, String u) { Person oldPserson = JSONObject.parseObject(u, Person.class); Person newPerson = new Person(); newPerson.setAge(oldPserson.getAge() + 50); newPerson.setName(oldPserson.getName() + "%%%"); String jsonString = JSONObject.toJSONString(newPerson); return Mono.just(jsonString); } }); } }); return t.uri("lb://order/"); } }).build(); } }
package com.dfsn.cloud.order.controller; import com.dfsn.cloud.order.vo.Person; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class OrderController { @GetMapping(value = "person") public Person order1() { Person person = new Person(); person.setAge(22); person.setName("小名"); return person; } }
filter之 default-filters
如果有多个route每个里边都有filter,必然会有一两种过滤器是通用的。所以可以抽成公用组件。
以下代码片段把 - StripPrefix=2 抽成默认的,所有route都可以用。
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka spring: application: name: gateway cloud: gateway: default-filters: - StripPrefix=2 enabled: true routes: - id: orderPath1 uri: lb://order predicates: - Path=/gateway/o/**
以上yml配置都可以用JAVA编程方式实现
不熟悉Lambda的可以参考我的另一篇博客 https://www.cnblogs.com/zumengjie/p/11613043.html
以下代码片段分别是yml配置和JAVA配置,JAVA配置的方式官方文档以及大部分博客都使用的是 route2() Lambda
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka a: /gateway/o/** spring: application: name: gateway cloud: gateway: enabled: true routes: - id: orderPath uri: lb://order predicates: - Path=/gateway/o/** filters: - StripPrefix=2 - id: consumerPath uri: lb://consumer predicates: - Path=/gateway/c/** filters: - StripPrefix=2
package com.dfsn.cloud.gateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.gateway.route.Route; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.builder.GatewayFilterSpec; import org.springframework.cloud.gateway.route.builder.PredicateSpec; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.cloud.gateway.route.builder.UriSpec; import org.springframework.context.annotation.Bean; import org.springframework.web.bind.annotation.RestController; import java.util.function.Function; @SpringBootApplication @RestController public class Gateway_6001 { public static void main(String[] args) { SpringApplication.run(Gateway_6001.class, args); } @Bean public RouteLocator routes1(RouteLocatorBuilder builder) { return builder.routes().route("orderPath", new Function<PredicateSpec, Route.AsyncBuilder>() { @Override public Route.AsyncBuilder apply(PredicateSpec predicateSpec) { return predicateSpec.path("/gateway/o/**").filters(new Function<GatewayFilterSpec, UriSpec>() { @Override public UriSpec apply(GatewayFilterSpec gatewayFilterSpec) { return gatewayFilterSpec.filters().stripPrefix(2); } }).uri("lb://order"); } }).route("consumerPath", new Function<PredicateSpec, Route.AsyncBuilder>() { @Override public Route.AsyncBuilder apply(PredicateSpec predicateSpec) { return predicateSpec.path("/gateway/c/**").filters(new Function<GatewayFilterSpec, UriSpec>() { @Override public UriSpec apply(GatewayFilterSpec gatewayFilterSpec) { return gatewayFilterSpec.filters().stripPrefix(2); } }).uri("lb://consumer"); } }).build(); } @Bean public RouteLocator routes2(RouteLocatorBuilder builder) { return builder.routes() .route("orderPath", p -> p.path("/gateway/o/**").filters(g -> g.stripPrefix(2)).uri("lb://order")) .route("consumerPath", p -> p.path("/gateway/c/**").filters(g -> g.stripPrefix(2)).uri("lb://consumer")) .build(); } }
全局过滤器
我们可以设定全局过滤器,给每一个route使用。多个全局过滤器指定order,值越小,越先触发。
geteway默认的也有很多过滤器,比如我们使用的 lb:// 这是一个LoadBalancerClientFilter
以下代码片段声明了两个自定义全局过滤器,这需要用@Bean配置。
server: port: 6001 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7002/eureka,http://localhost:7001/eureka hystrix: command: orderCommand: execution: timeout: enabled: true isolation: thread: timeoutInMilliseconds: 2000 spring: application: name: gateway cloud: gateway: enabled: true routes: - id: orderid uri: lb://order/ predicates: - Path=/gateway/o/** filters: - StripPrefix=2 - id: consumerid uri: lb://consumer/ predicates: - Path=/gateway/c/** filters: - StripPrefix=2
package com.dfsn.cloud.gateway; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; public class CustomGlobalFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { System.out.println("------------------------我是全局过滤器---------------------------------------"); return chain.filter(exchange); } @Override public int getOrder() { return -1; } }
package com.dfsn.cloud.gateway; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; public class CustomGlobalFilter2 implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { System.out.println("---------------------------------------我是全局过滤器222222222222222---------------------------------------"); return chain.filter(exchange); } @Override public int getOrder() { return 1; } }
package com.dfsn.cloud.gateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.context.annotation.Bean; @SpringBootApplication @EnableEurekaClient public class Gateway_6001 { public static void main(String[] args) { SpringApplication.run(Gateway_6001.class, args); } @Bean public GlobalFilter customFilter() { return new CustomGlobalFilter(); } @Bean public GlobalFilter customFilter2() { return new CustomGlobalFilter2(); } }