网关在微服务架构里相当于是客户端请求的一个总入口,有解耦和隐藏后端服务的作用。除此之外,网关还有多种功能,本篇文章我们用Spring Cloud Gateway来模拟实现网关的各种应用场景。
1. 流控(金丝雀发布,蓝绿发布)
目前很多公司都实现了生产不停机发布版本,在生产中同时存在新老版本代码,很多是通过ngnix来做流量的分流。发版时让流量都往老版本的服务走,等新版本发布完后再把流量慢慢切到新版本去验证,这就是所谓的金丝雀发布,也叫做蓝绿发布。下面我们用Spring Cloud Gateway来模拟实现这种分流
@Bean
public RouteLocator blueGreenRoute(RouteLocatorBuilder builder){
return builder.routes()
// 80%流量到老版本服务
.route(p -> p.weight("group1",8).and().path("/**")
.uri("http://httpbin.org:80"))
// 20%流量到新版本服务
.route(p -> p.weight("group1",2).and().path("/**")
.filters(f -> f.stripPrefix(1))
.uri("https://www.baidu.com"))
.build();
}
命令行测试:curl http://localhost:8080/get
2.熔断
熔断发生在请求报错时返回友好的提示信息给到用户,而不是异常堆栈信息。下面测试代码,是让头部带有name的请求路由到错误的地址h
ttp://httpbin.org33:80,异常后重定向到/fallback,输出提示信息
// fallback,返回提示:请稍后再试
@Bean
public RouterFunction<ServerResponse> testFunRouterFunction() {
RouterFunction<ServerResponse> route = RouterFunctions.route(
RequestPredicates.path("/fallback"),
request -> ServerResponse.ok().body(BodyInserters.fromValue("请稍后再试")));
return route;
}
@Bean
public RouteLocator hystrixRoute(RouteLocatorBuilder builder){
return builder.routes()
.route(p -> p.header("name")
.filters(f -> f.hystrix((config -> config
.setName("mytest")
.setFallbackUri("forward:/fallback")))) // 熔断,如果访问报错则跳到/fallback
.uri("http://httpbin.org33:80"))
.build();
}
命令行测试:curl -H ‘name:dd’ http://localhost:8080/get
3.黑名单控制
首先我们自定义一个过滤器,检测请求头里的id是否为黑名单,如果为黑名单,则直接返回,不做路由转发。
public class BlackFilter implements GatewayFilter, Ordered {
// 黑名单列表,也可以放在缓存数据库里
List<String> blackList = Arrays.asList("123","456");
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 用户id
String id = exchange.getRequest().getHeaders().getFirst("id");
if(blackList.contains(id)){
// 如果是黑名单就直接返回,不再往目标服务器转发
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
@Bean
public RouteLocator blackRoute(RouteLocatorBuilder builder){
return builder.routes()
.route(p -> p.path("/**")
.uri("http://httpbin.org:80").filters(new BlackFilter()))
.build();
}
命令测试:curl -H ‘id:123’ http://localhost:8080/get
4.日志记录
请求的路径、入参和耗时日志是排查问题的重要信息,可以在网关上做统一的处理。
public class LogFilter implements GatewayFilter, Ordered {
private static final String START_TIME = "startTime";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 下面3行代码在前过滤器pre filter执行
String url = exchange.getRequest().getURI().getRawPath();
System.out.println("请求地址:" + url + ",入参:" + exchange.getRequest().getQueryParams().toString());
exchange.getAttributes().put(START_TIME, System.currentTimeMillis());
// chain.filter里面的逻辑相当于后过滤器post filter
return chain.filter(exchange).then(
Mono.fromRunnable(() -> {
Long startTime = exchange.getAttribute(START_TIME);
if(startTime != null){
System.out.println(url+ "耗时:" +
(System.currentTimeMillis() - startTime) + "ms");
}
})
);
}
@Override
public int getOrder() {
return 0;
}
}
@Bean
public RouteLocator logRoute(RouteLocatorBuilder builder) {
return builder.routes()
.route(p -> p.path("/**")
.uri("http://httpbin.org:80").filters(new LogFilter()))
.build();
}
命令测试:curl -H ‘id:123’ http://localhost:8080/get
5.用户鉴权
用户统一鉴权控制,是网关常见的应用场景,下面模拟的情况是从Cookie中获取token,然后用token验证用户是否已经登录。如果用户没有登录,则重定向到登录页面。
public class MyAuthFilter implements GatewayFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = null;
HttpCookie tokenCookie = exchange.getRequest().getCookies().getFirst("token");
if (tokenCookie != null) {
token = tokenCookie.getValue();
}
// 拿到token判断是否已经登录
if(!isLogin(token)){
// 如果没有登录则重定向都登录页面
exchange.getResponse().setStatusCode(HttpStatus.FOUND);
exchange.getResponse().getHeaders().set("Location", "/login");
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
private boolean isLogin(String token){
return false;
}
@Override
public int getOrder() {
return 0;
}
@Bean
public RouterFunction<ServerResponse> testFunRouterFunction() {
RouterFunction<ServerResponse> route = RouterFunctions.route(
RequestPredicates.path("/login"),
request -> ServerResponse.ok().body(BodyInserters.fromValue("请登录系统")));
return route;
}
@Bean
public RouteLocator authRoute(RouteLocatorBuilder builder) {
return builder.routes()
.route(p -> p.path("/get")
.filters(f -> f.filter(new MyAuthFilter()))
.uri("http://httpbin.org:80"))
.build();
}
浏览器访问:http://localhost:8080/get
6.总结
Spring Cloud Gateway的基本使用方法暂时就写到这里了,还有很多功能后面做研究。下一步进入源码,学习整体的架构设计以及运作原理。