能力目标:
- 清楚SpringCloud技术栈分类
- 能够说出SpringCloud Gateway的工作流程
- 至少掌握Gateway动态路由配置中基于Path的路由方式
- 能实现全局过滤器和局部过滤器的创建和使用
- 能写出SpringCloud Gateway跨域配置
- 理解限流漏桶算法
- 能够实现基于漏桶算法的限流操作
前言
开发分布式系统可能具有挑战性,复杂性已从应用程序层转移到网络层,并要求服务之间进行更多的交互。将代码设为“cloud-native”就需要解决12-factor,例如外部配置,服务无状态,日志记录以及连接到备份服务之类的问题,Spring Cloud项目套件包含使您的应用程序在云中运行所需的许多服务。
清楚SpringCloud技术栈分类![](https://i-blog.csdnimg.cn/blog_migrate/1d77afd051921b03d81811ecf411d729.png)
![](https://i-blog.csdnimg.cn/blog_migrate/561fcad292cd79e3d425f27be4bfb529.png)
SpringCloud技术栈非常丰富,这也是SpringCloud为什么在微服务领域中如此受欢迎的原因之一,技术栈如上图,在服务注册与配置、服务调用、微服务网关、消息组件、链路追踪、配置中心、安全控制、将极限流等诸多方面技术栈都比较完善,而且阿里巴巴也出了一套SpringCloud Alibaba版本,主要集成了Alibaba中主流的技术栈。
能够说出SpringCloud Gateway的工作流程
Gateway的执行流程如下:
1:Gateway的客户端回向Spring Cloud Gateway发起请求,请求首先会被HttpWebHandlerAdapter进行提取组装成网关的上下文,然后网关的上下文会传递到DispatcherHandler。 2:DispatcherHandler是所有请求的分发处理器,DispatcherHandler主要负责分发请求对应的处理器,比如将请求分发到对应RoutePredicateHandlerMapping(路由断言处理器映射器)。 3:路由断言处理映射器主要用于路由的查找,以及找到路由后返回对应的FilteringWebHandler。4:FilteringWebHandler主要负责组装Filter链表并调用Filter执行一系列Filter处理,然后把请求转到后端对应的代理服务处理,处理完毕后,将Response返回到Gateway客户端。 在Filter链中,通过虚线分割Filter的原因是,过滤器可以在转发请求之前处理或者接收到被代理服务的返回结果之后处理。所有的Pre类型的Filter执行完毕之后,才会转发请求到被代理的服务处理。被代理的服务把所有请求完毕之后,才会执行Post类型的过滤器。
至少掌握Gateway动态路由配置中基于Path的路由方式
Gateway静态路由配置参数说明:
spring:
cloud:
gateway:
#路由配置
routes:
#唯一标识符
- id: hailtaxi-driver
uri: lb://hailtaxi-driver
#路由断言
predicates:
- Path=/driver/**
#唯一标识符
- id: hailtaxi-order
uri: lb://hailtaxi-order
#路由断言
predicates:
- Path=/order/**
#唯一标识符
- id: hailtaxi-pay
uri: lb://hailtaxi-pay
#路由断言
predicates:
- Path=/pay/**
基于代码路由配置
我们同样实现上面的功能,但这里基于代码方式实现。所有路由规则我们可以从数据库中读取并加载到程序中。基于代码的路由配置我们只需要创建RouteLocator
并添加路由配置即可,代码如下:
/***
* 路由配置
* @param builder
* @return
*/
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("hailtaxi-driver", r -> r.path("/driver/**").uri("lb://hailtaxi-driver"))
.route("hailtaxi-order", r -> r.path("/order/**").uri("lb://hailtaxi-order"))
.route("hailtaxi-pay", r -> r.path("/pay/**").uri("lb://hailtaxi-pay"))
.build();
}
能实现全局过滤器和局部过滤器的创建和使用
-
GlobalFilter : 全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器,它为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路由上。
-
过滤器分类
默认过滤器:出厂自带,实现好了拿来就用,不需要实现
全局默认过滤器
局部默认过滤器
自定义过滤器:根据需求自己实现,实现后需配置,然后才能用哦。
全局过滤器:作用在所有路由上。
局部过滤器:配置在具体路由下,只作用在当前路由上。
自定义GatewayFilter,局部过滤器
-
1、实现GatewayFilter接口
-
GatewayFilter 一般作用在某一个路由上,需要实例化创建才能使用,局部过滤器需要实现接口
GatewayFilter、Ordered
。创建
com.itheima.filter.PayFilter
代码如下:public class PayFilter implements GatewayFilter,Ordered { /*** * 过滤器执行拦截 * @param exchange * @param chain * @return */ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { System.out.println("GatewayFilter拦截器执行---pre-----PayFilter"); return chain.filter(exchange).then(Mono.fromRunnable(()->{ System.out.println("GatewayFilter拦截器执行---post-----PayFilter"); })); } @Override public int getOrder() { return 0; } }
使用局部过滤器:(使用下面RouteLocator的时候,配置文件中的路由记得注释或删除)
/*** * 路由配置 * @param builder * @return */ @Bean public RouteLocator routeLocator(RouteLocatorBuilder builder) { return builder.routes() .route("hailtaxi-driver", r -> r.path("/api/driver/**") .and().cookie("username","itheima") .and().header("token","123456") .filters(f->f.filter(new PayFilter()).addResponseHeader("X-Response-Default-MyName", "itheima") .addRequestHeader("myheader", "1234567") .stripPrefix(1) ) // .and().method(HttpMethod.POST) .uri("lb://hailtaxi-driver") //.filter(new PayFilter()) ) .route("hailtaxi-order", r -> r.path("/order/**").uri("lb://hailtaxi-order")) .route("hailtaxi-pay", r -> r.path("/pay/**").uri("lb://hailtaxi-pay")) .build(); }
为了更好看到效果,我们在
RouterFilter
添加System.out.println("GlobalFilter拦截器执行");
再访问测试。访问:http://localhost:8001/api/driver/info/1,注意使用postman发送请求时添加请求头,添加cookie。
- 2、继承GatewayFilterFactory
- 如果定义局部过滤器,想在配置文件中进行配置来使用,可以继承
AbstractGatewayFilterFactory<T>
抽象类或者AbstractNameValueGatewayFilterFactory
这两个抽象类的区别就是前者接收一个参数(像StripPrefix和我们创建的这种),后者接收两个参数(像AddResponseHeader)
代码的编写可以参考:
StripPrefixGatewayFilterFactory
和AddRequestHeaderGatewayFilterFactory
过滤器工厂默认命名规则必须按照"名称"+GatewayFilterFactory`,如上StripPrefixGatewayFilterFactory的过滤器名称为StripPrefix
继承
AbstractGatewayFilterFactory
需求:
在网关中统一支付方式,编写一个过滤器:
PayMethodGatewayFilterFactory
,1、编写过滤器
@Slf4j @Component //一定要将其交给spring容器管理 public class PayMethodGatewayFilterFactory extends AbstractGatewayFilterFactory<PayMethodGatewayFilterFactory.Config> { public PayMethodGatewayFilterFactory() { super(Config.class); } @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { String paymethod = config.getPayMethod(); String msg = config.getMsg(); log.info("PayMethodGatewayFilterFactory 加载到的配置信息为:{}---{}",paymethod,msg); //将paymethod添加到请求头中 exchange.getRequest().mutate().header("paymethod",paymethod); return chain.filter(exchange); }; } @Override public List<String> shortcutFieldOrder() { return Arrays.asList("payMethod","msg");//指定从yml中提前出来的配置信息填充到配置类中哪个属性,按规则配置 } @Override public ShortcutType shortcutType() { return ShortcutType.DEFAULT;//默认规则 } /** * 加载从yml中提取出来的配置信息 */ @Data public static class Config { private String payMethod; private String msg; } }
配置文件中使用如下:
gateway: #路由配置 routes: #唯一标识符 - id: hailtaxi-driver uri: lb://hailtaxi-driver #路由断言 predicates: - Path=/driver/** - Cookie=username,itheima - Header=token,^(?!\d+$)[\da-zA-Z]+$ - Method=GET,POST - Token=Authorization filters: - PayMethod=alipay,业务整合
再次测试,查看hailtaxi-driver 服务接收到请求后是否多了
paymethod
请求头信息 -
自定义GlobalFilter
-
定义全局过滤器需要实现GlobalFilter,Ordered接口:
GlobalFilter:过滤器拦截处理方法 Ordered:过滤器也有多个,这里主要定义过滤器执行顺序,里面有个方法getOrder()会返回过滤器执行顺序,返回值越小,越靠前执行 需求: 我们创建全局过滤器并完成常见业务用户权限校验,如果请求中有带有一个名字为token 的请求参数,则认为请求有效放行,如果没有则拦截提示授权无效。 创建全局过滤器:com.itheima.filter.RouterFilter,代码如下:
@Component public class RouterFilter implements GlobalFilter,Ordered { /*** * 路由拦截 * @param exchange * @param chain * @return */ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.info("RouterFilter----------------"); //获取请求参数 String token = exchange.getRequest().getQueryParams().getFirst("token"); //如果token为空,则表示没有登录 if(StringUtils.isEmpty(token)){ //没登录,状态设置403 exchange.getResponse().setStatusCode(HttpStatus.PAYLOAD_TOO_LARGE); //结束请求 return exchange.getResponse().setComplete(); } //放行 return chain.filter(exchange); } /*** * 拦截器顺序 * @return */ @Override public int getOrder() { return 0; } }
能写出SpringCloud Gateway跨域配置
-
出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)。
在Spring Cloud Gateway中配置跨域是非常简单的,如下面
application.yml
所示:gateway: globalcors: corsConfigurations: '[/**]': allowedOrigins: "*" allowedMethods: - GET - POST - PUT
另外一种写法就需要创建
CorsWebFilter
过滤器,代码如下:/** * 配置跨域 * @return */ @Bean public CorsWebFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); // cookie跨域 config.setAllowCredentials(Boolean.TRUE); config.addAllowedMethod("*"); config.addAllowedOrigin("*"); config.addAllowedHeader("*"); // 配置前端js允许访问的自定义响应头 config.addExposedHeader("Authorization"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser()); source.registerCorsConfiguration("/**", config); return new CorsWebFilter(source); }
理解限流漏桶算法
令牌桶算法是常见的限流算法之一,我们讲解一下漏桶算法:
1)所有的请求在处理之前都需要拿到一个可用的令牌才会被处理;
2)根据限流大小,设置按照一定的速率往桶里添加令牌;
3)桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝;
4)请求达到后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完业务逻辑之后,将令牌直接删除;
5)令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以此保证足够的限流
能够实现基于漏桶算法的限流操作
spring cloud gateway 默认使用redis的RateLimter限流算法来实现,外面来简要实现一下:
1、引入依赖
首先需要引入redis的依赖:
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
同时不要忘记Redis配置:
redis:
host: 127.0.0.1
port: 6379
2、定义KeyResolver
在Application引导类中添加如下代码,KeyResolver用于计算某一个类型的限流的KEY也就是说,可以通过KeyResolver来指定限流的Key。
我们可以根据IP来限流,比如每个IP每秒钟只能请求一次,在GatewayApplication定义key的获取,获取客户端IP,将IP作为key,如下代码:
/***
* IP限流
* @return
*/
@Bean(name="ipKeyResolver")
public KeyResolver userKeyResolver() {
return new KeyResolver() {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
//获取远程客户端IP
String hostName = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
System.out.println("hostName:"+hostName);
return Mono.just(hostName);
}
};
}
在路由中配置如下:
gateway:
#路由配置
routes:
#唯一标识符
- id: hailtaxi-driver
uri: lb://hailtaxi-driver
#路由断言
predicates:
- Path=/driver/**
- Cookie=username,itheima
- Header=token,^(?!\d+$)[\da-zA-Z]+$
- Method=GET,POST
- Token=Authorization
filters:
- PayMethod=alipay,业务整合
- name: RequestRateLimiter #请求数限流 名字不能随便写 ,使用默认的facatory
args:
key-resolver: "#{@ipKeyResolver}"
redis-rate-limiter.replenishRate: 1
redis-rate-limiter.burstCapacity: 1
参数说明:
redis-rate-limiter.replenishRate是您希望允许用户每秒执行多少请求,而不会丢弃任何请求。这是令牌桶填充的速率
redis-rate-limiter.burstCapacity是指令牌桶的容量,允许在一秒钟内完成的最大请求数,将此值设置为零将阻止所有请求。
key-resolver: “#{@ipKeyResolver}” 用于通过SPEL表达式来指定使用哪一个KeyResolver.
如上配置: 表示 一秒内,允许 一个请求通过,令牌桶的填充速率也是一秒钟添加一个令牌。 最大突发状况 也只允许 一秒内有一次请求,可以根据业务来调整 。