Spring Cloud Gateway为Spring生态系统上的一个API网关组件,主要提供一种简单而有效的方式路由映射到指定的API,并为他们提供安全性、监控和限流等等。
创建一个gmaya-gateway 项目。
<!--gateway网关,内置webflux 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--eureka客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--hystrix容错降级-->
<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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
@SpringBootApplication
@EnableDiscoveryClient
server:
port: 9200
spring:
application:
name: gmaya-geteway
cloud:
gateway:
discovery:
locator:
# 开启服务注册和发现
# 如果为true,访问路径有两个:
# 1.ip:9200/gmaya-service-admin/user/test
# 2.ip:9200/admin/user/test (这个是Path自己定义的/admin/**)
# 如果为false,访问路径有一个ip:9200/admin/user/test
enabled: false
# 服务名配置为小写
lower-case-service-id: true
routes:
# 系统服务
- id: gmaya-service-admin # 不重复即可
uri: lb://gmaya-service-admin # 需要转发到的服务名称
predicates:
# 以 /admin/开头的路径全部转发到lb://gmaya-service-admin的服务上,此时gmaya-service-admin可以负载均衡
- Path=/admin/**
filters:
# 去掉前面1个前缀,也就是真正转发访问的时候不带/admin/
- StripPrefix=1
# 注册服务中心
eureka:
instance:
# 心跳时间,即服务续约间隔时间(缺省为30s)
lease-renewal-interval-in-seconds: 5
# 发呆时间,即服务续约到期时间(缺省为90s)
lease-expiration-duration-in-seconds: 10
client:
service-url:
defaultZone: http://localhost:8000/eureka/
healthcheck:
enabled: true # 开启健康检查
# 表示eureka client间隔多久去拉取服务注册信息,默认为30秒
registry-fetch-interval-seconds: 5
# 监控
management:
endpoints:
web:
exposure:
# 通过HTTP公开所有的端点, 默认是info,health
include: '*'
endpoint:
health:
# 显示完整信息,#默认是never(简要信息)
show-details: always
注册中心:gmaya-service-center :8000
系统服务:gmaya-service-admin :9001
网关服务:gmaya-gateway :9200
启动三个项目,访问:
http://localhost:9200/admin/user/test
没问题!
测一下gateway 的负载均衡,现在再把系统服务启动一个9011端口。
-Dserver.port=9011
此时已经有了一个服务名一样,但是端口不一样的两个服务了。
再次访问接口。
查看后端打印情况,确实是轮询方式实现了负载均衡。
在分布式系统中,网关作为流量的入口,因此会有大量的请求进入网关,向其他服务发起调用,其他服务不可避免的会出现调用失败(超时、异常),失败时不能让请求堆积在网关上,需要快速失败并返回给客户端,想要实现这个要求,就必须在网关上做熔断、降级操作。
有两种方式进行熔断、降级
package top.gmaya.gmayagateway.handler;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR;
/**
* 熔断、降级类
* @author GMaya
* @dateTime 2020/5/13 16:19
*/
@Component
@Slf4j
public class HystrixFallbackHandler implements HandlerFunction<ServerResponse>
{
@Override
public Mono<ServerResponse> handle(ServerRequest serverRequest)
{
Optional<Object> originalUris = serverRequest.attribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
originalUris.ifPresent(originalUri -> log.error("网关执行请求:{}失败,hystrix服务降级处理", originalUri));
// 可以根据自己的工具类返回统一格式
Map<String,String> map = new HashMap();
map.put("code","500");
map.put("msg","服务出现异常,降级操作。");
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR.value()).contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(JSON.toJSONString(map)));
}
}
package top.gmaya.gmayagateway.config;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import top.gmaya.gmayagateway.handler.HystrixFallbackHandler;
/**
* 路由配置信息
* @author GMaya
* @dateTime 2020/5/13 16:19
*/
@Configuration
// 全参构造方法
@AllArgsConstructor
public class GatewayRoutesConfig {
private final HystrixFallbackHandler hystrixFallbackHandler;
@Bean
public RouterFunction<?> routerFunction(){
return RouterFunctions.route(RequestPredicates.path("/defaultFallback").and(RequestPredicates.accept(
MediaType.TEXT_PLAIN)),hystrixFallbackHandler);
}
}
大部分都省略了,和上面的一样。
spring:
cloud:
gateway:
routes:
# 系统服务
- id: gmaya-service-admin # 不重复即可
filters:
# 去掉前面1个前缀,也就是真正转发访问的时候不带/admin/
- StripPrefix=1
# 降级配置
- name: Hystrix
args:
name: default
fallbackUri: 'forward:/defaultFallback'
hystrix:
command:
default: #default全局有效
execution:
timeout:
enabled: true
isolation:
thread:
timeoutInMilliseconds: 3000 #断路器超时时间,默认1000ms
系统服务接口中添加延迟,模拟超时
此时访问,三秒后返回设置好的熔断信息
或者将系统服务直接关闭。
这种方式和正常controller中方法一样
package top.gmaya.gmayagateway.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* web方式进行熔断降级
* @author GMaya
* @dateTime 2020/5/13 17:05
*/
@RestController
@Slf4j
public class DefaultFallbackController {
@GetMapping("defaultFallback")
public Map<String,String> defaultFallback(){
// 可以根据自己的工具类返回统一格式
Map<String,String> map = new HashMap();
map.put("code","500");
map.put("msg","服务出现异常,降级操作。2");
log.error("网关执行请求失败,web方式记录");
return map;
}
}
将第一种方式配置先去掉,重启项目
浏览器访问
注意: 如果两个都开启了, 就会默认使用第一种方式。
在高并发的系统中,往往需要在系统中做限流,一方面是为了防止大量的请求使服务器过载,导致服务不可用,另一方面是为了防止网络攻击。
常见的限流纬度有比如通过Ip来限流、通过uri来限流、通过用户访问频次来限流。
采用springcloud gateway 为我们提供了限流过滤器RequestRateLimiterGatewayFilterFactory。使用Redis和lua脚本实现了令牌桶的方式限流。
引入
<!-- redis配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!--使用redis的lettuce连接池使用到,如果不用可不加-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
创建KeyResolverConfig类
package top.gmaya.gmayagateway.config;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;
/**
* 路由限流配置
* 配置文件中使用的那种限流 就是真正的限流方式,三种任选其一,不能同时存在
* @author GMaya
* @dateTime 2020/5/14 9:19
*/
@Configuration
public class KeyResolverConfig {
/**
* 根据ip限流
* @return
*/
@Bean(value = "hostAddrKeyResolver")
public KeyResolver hostAddrKeyResolver(){
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
}
/**
* 根据路径限流
* @return
*/
/* @Bean(value = "uriKeyResolver")
public KeyResolver uriKeyResolver(){
return exchange -> Mono.just(exchange.getRequest().getURI().getPath());
}*/
/**
* 根据用户限流,参数中必须有user字段
* @return
*/
/*@Bean(value = "userKeyResolver")
public KeyResolver userKeyResolver(){
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
}*/
}
spring:
cloud:
gateway:
routes:
# 系统服务
- id: gmaya-service-admin # 不重复即可
uri: lb://gmaya-service-admin # 需要转发到的服务名称
predicates:
# 以 /admin/开头的路径全部转发到lb://gmaya-service-admin的服务上,此时gmaya-service-admin可以负载均衡
- Path=/admin/**
filters:
# 去掉前面1个前缀,也就是真正转发访问的时候不带/admin/
- StripPrefix=1
# 降级配置
- name: Hystrix
args:
name: default
fallbackUri: 'forward:/defaultFallback'
# 限流配置
- name: RequestRateLimiter
args:
# 用于限流的键的解析器的 Bean 对象的名字
key-resolver: '#{@hostAddrKeyResolver}'
# 令牌桶每秒填充平均速率(实际情况适当加大即可:10)
redis-rate-limiter.replenishRate: 1
# 令牌桶总容量(实际情况适当加大即可:20)
redis-rate-limiter.burstCapacity: 3
使用测试工具测试,每秒两次访问。
查看redis中的值
将令牌桶总容量中的3个值消耗完毕,再多次访问即页面就是一片空白。