1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > 微服务网关spring cloud gateway入门详解

微服务网关spring cloud gateway入门详解

时间:2020-07-07 07:29:31

相关推荐

微服务网关spring cloud gateway入门详解

1.API网关

API 网关是一个处于应用程序或服务( REST API 接口服务)之前的系统,用来管理授权、访问控制和流量限制等,这样 REST API 接口服务就被 API 网关保护起来,对所有的调用者透明。因此,隐藏在 API 网关后面的业务系统就可以专注于创建和管理服务,而不用去处理这些策略性的基础设施。

1.1 API网关的职能

请求接入:作为所有api接口服务请求的统一接入点。

业务聚合:作为所有后端业务服务的聚合点。

中介策略:实现安全、验证、路由、过滤、限流等策略。

统一管理:对所有api服务和策略统一管理。

1.2 API网关的分类与对比

目前主流的成熟api网关如上图所示,openresty基于ngix和lua脚本,kong在openresty的基础上结合了数据库(postgreSql或Cassandra数据库)实现,这两个网关都需要lua脚本配合实现, spring cloud gateway和zuul是基于java开发,所以对于我们java开发人员相对方便,性能对比openresty>kong>spring cloud gateway≈zuul2.x>zuul1.x。

2 spring cloud gateway

spring cloud gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替代zuul1.x。(由于zuul1.x基于同步的io实现,性能有限,虽然目前zuul2.x的版本已经基于netty实现了异步非阻塞式的通信,但是spring cloud 并没有集成zuul2.x版本) ,gateway使用 Netty 实现异步 IO非阻塞式的通信,支持长连接,基于响应式编程,从而实现了一个简单、比 Zuul 1.x 更高效的、与 Spring Cloud 紧密配合的 API 网关。

2.1spring cloud gateway和zuul1.x的性能对比

通过模拟两组不同的并发量,可以看到spring cloud gateway比zuul 的性能提升了1.5–2倍,低并发的情况下两者差距不是很明显,但随着并发量的增加,spring cloud gateway的性能优势会越明显。

2.2 spring cloud gateway 底层架构实现

spring cloud gateway基于spring 5、spring boot 2和Reactor类库构建,使用Netty作为运行时环境,比较完美的支持异步非阻塞编程。Netty使用非阻塞的IO,线程处理模型建立在主从Reactor多线程模型上。

Reactor是 Spring 5 中响应式编程的基础,一个新的响应式编程库。提供了一个非阻塞的,高并发的基于健壮的Netty框架的网络运行API,响应式编程是一种关注于数据流data streams)和事件传递的异步编程方式,当你做一个带有一定延迟的才能够返回的io操作时,不会阻塞,而是立刻返回一个流,并且订阅这个流,当这个流上产生了返回数据,可以立刻得到通知并调用回调函数处理数据。

Webflux是Reactor中的一个核心概念。Webflux中有两个重要的类Flux和Mono,它们都提供了丰富的操作符。一个Flux对象代表一个包含0或者N个元素的响应式序列,元素可以是普通对象、数据库查询的结果、http响应体,甚至是异常。而一个Mono对象代表0或者1个元素的结果。下图就是一个Flux类型的数据流,Flux往流上发送了3个元素,Subscriber通过订阅这个流来接收通知。类似于java8中的Stream流。

2.3 webflux入门

从以上spring官方给出的对比图可以看出,webflux是一种区别于传统spring mvc的新型编程思想,摒弃了传统的servlet api, 基于响应式编程原理,进而提升性能,是未来的一种趋势。

2.3.1 Webflux 的三大特性

异步非阻塞

众所周知,SpringMVC是同步阻塞的IO模型,资源浪费相对来说比较严重,当我们在处理一个比较耗时的任务时,例如:上传一个比较大的文件,首先,服务器的线程一直在等待接收文件,等到接收完毕,才开始将文件写入磁盘,在写入磁盘的过程中,线程又要等待文件写完才能干其他的事情,这样就会浪费资源。

Spring WebFlux就是来解决这问题的,Spring WebFlux可以做到异步非阻塞。还是上面上传文件的例子,Spring WebFlux是这样做的:线程发现文件还没准备好,就先去做其它事情,当文件准备好之后,通知这根线程来处理,当接收完毕写入磁盘的时候(根据具体情况选择是否做异步非阻塞),写入完毕后通知这根线程再来处理。这样就会极大的避免了资源的浪费。

响应式(reactive)函数编程

支持函数式编程,得益于对于reactive-stream的支持(通过reactor框架来实现的)。

不再拘束于Servlet容器

大部分应用都运行于Servlet容器之中,例如我们大家最为熟悉的Tomcat, Jetty…等等。而现在Spring WebFlux不仅能运行于传统的Servlet容器中(前提是容器要支持Servlet3.1,因为非阻塞IO是使用了Servlet3.1的特性),还能运行在支持NIO的Netty和Undertow中。

2.3.2Webflux的原理流程

2.3.3 Webflux的编程步骤

Webflux编程大致主要有三个阶段:开始阶段的创建、中间阶段的处理和最终阶段的消费。

创建阶段:创建 Mono 和 Flux对象

中间阶段:中间阶段的 Mono 和 Flux 的方法主要有 filter、map、flatMap、then、zip、reduce 等。这些方法使用方法和java8 Stream 中的方法类似。

结束阶段:结束阶段就是 Mono 或 Flux调用 subscribe 方法。如果在 Web Flux 接口中开发,直接返回 Mono 或 Flux 即可,不用调用subscribe方法。Web Flux 框架会为我们完成最后的 Response 输出工作。

2.3.4 webflux入门程序

pom文件添加依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency>

编写简单的webflux

@Testpublic void testMono(){Mono.just("tom").subscribe(System.out::println);}@Testpublic void testFlux() {//just方法创建一个反应式的流Flux.just("joye", "jack", "allen").filter(s -> s.length() > 3).map(s -> s.concat("@")).subscribe(System.out::println);}

2.4 spring cloud gateway核心概念

2.4.1 路由Route

路由为一组断言与一组过滤器的集合,他是网关的一个基本组件。是网关转发路径规则的体现。

2.4.2 断言Predicate

在将web请求和路由进行匹配时,用到的就是断言predicate,决定请求走哪一个路由。GateWay内置了多种类型的Predicate(10种)。可以通过配置的方式直接使用,也可以组合使用多个路由predicate, 以下是gateway内置的断言。

2.4.3 过滤器Filter

用于拦截和链式处理web请求,实现横切的、与应用无关的需求。可在代理请求处理之前(pre)执行,也可以在请求之后(post)执行。pre类型过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等;post类型过滤器可以做响应内容、响应头的修改、日志输出、流量监控等。除了pre和post的区分,根据作用范围还可以分为针对单个路由的gateway filter和针对所有路由的global gateway filter。

简单的来说,gateway 的过滤器可以总结为三种,默认的过滤器(gateway内置),自定义单个路由的过滤器gateway filter(有两种方式:1、实现GatewayFilter, Ordered接口。2、继承过滤器工厂类AbstractGatewayFilterFactory),全局过滤器(实现GlobalFilter, Ordered)。

以下是gateway内置的过滤器:内置的过滤器工厂有22个实现类,根据过滤器工厂的用途来划分,可以分为以下几种:Header、Parameter、Path、Body、Status、Session、Redirect、Retry、RateLimiter和Hystrix等。

2.5 spring cloud gateway 配置路由的两种方式

2.5.1 yml文件配置(常用)

2.5.2 Java代码编写路由规则

这种方式一般不常用,配置太麻烦而且存在硬编码问题。

@Beanpublic RouteLocator routeLocator(RouteLocatorBuilder builder) {return builder.routes().route("blog", r -> r.path("/gateway/user-service/**").uri("http://ip:port/path")).build();}

2.6 spring cloud gateway过滤器的使用

Spring cloud gateway作为网关,我们在业务开发中,路由的编写,和过滤器的定义是必不可少的,路由实现了网关到子系统的桥接作用,上面已经做了简单的介绍,过滤器主要涉及到网关到子系统之间复杂的业务处理,包括统一登录,统一请求体处理,统一响应内容包装,统一异常处理等等。

Spring cloud gateway网关给我们提供了22个已经实现的过滤器工厂,参考上文spring cloudgateway重要概念过滤器介绍,这22个内置的过滤器我们可以直接使用,配置在yml文件中即可,参考上文yml文件配置。

然而,在具体的业务场景中,这22个可能都不能满足我们的业务,此时需要我们单独编写过滤器,根据具体业务,gateway提供了两种过滤器类型,全局过滤器和局部过滤器。

2.6.1 全局过滤器

所有配置的路由都会经过该过滤器处理,全局过滤器的使用比较简单,只需编写一个全局过滤器类即可,方式如下

@Component //该注解或者@Configuration将组件注入容器public class TokenGlobalFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//---todo 过滤器业务处理/** gateway基于webflux链式编程:* 区别于zuul过滤器,zuul过滤器中会有pre前置处理方法,和post后置方法* gateway的过滤器只有这一个方法,前置和后置都在此方法中处理,以调用.then()方法为界,之前的编写前置处理逻辑* 之后的编写后置处理逻辑。* 具体可以参考,cloud-gateway中ModifyRequestGlobalFilter和ModifyResponseGlobalFilter这两个全局过滤器* */return null;}@Overridepublic int getOrder() {//过滤器的执行顺序return 0;}}

2.6.2 局部过滤器

针对单个模块或者部分配置了该过滤器的路由使用。局部过滤器的实现有两种方式,以下方式仅供参考。

第一步:先编写一个过滤器,参考以下代码

public class MyGatewayFilter implements GatewayFilter, Ordered {private static final Logger LOGGER = LoggerFactory.getLogger(MyGatewayFilter.class);private static final String TIME = "Time";@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request = exchange.getRequest();exchange.getAttributes().put(TIME,System.currentTimeMillis());return chain.filter(exchange).then(Mono.fromRunnable(() -> {Long start = exchange.getAttribute(TIME);if(start !=null){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}LOGGER.info("request uri:" + exchange.getRequest().getURI() + ", Time:" + (System.currentTimeMillis() - start) + "ms");}}));}@Overridepublic int getOrder() {return Ordered.HIGHEST_PRECEDENCE;}}

第二步:编写过滤器工厂。这里类名必须严格以GatewayFilterFactory的方式结尾,下面第三步yml文件配置的时候需要用到。

/*过滤器工厂*/@Componentpublic class MyGatewayFilterFactory extends AbstractGatewayFilterFactory {@Overridepublic GatewayFilter apply(Object config) {return new MyGatewayFilter();}}

第三步:将自定义的过滤器工厂配置yml文件中的具体路由中。

spring:cloud:gateway:routes:- id: user-serviceuri: lb://user-service #lb代表从注册中心获取服务predicates:- Path=/user-service/**filters:- My #自定义过滤器 这里和过滤器工厂名称保持一致,提取前半部分

2.7 spring cloud gateway 限流

限流作为网关最基本的功能,spring cloud gateway官方已经提供了一个默认的过滤器工厂RequestRateLimiterGatewayFilterFactory这个类,直接使用即可,该方案使用Redis和lua脚本实现令牌桶限流的方式。具体实现逻辑在RequestRateLimiterGatewayFilterFactory类中。并且支持,ip限流,用户限流,接口限流三种方式,下面以ip限流为例。

首先在网关项目的pom文件中引入redis的reactive依赖,代码如下:

<dependency><groupId>org.springframework.boot</groupId><artifatId>spring-boot-starter-data-redis-reactive</artifactId></dependency>

其次,在yml配置文件中添加以下的配置:

filters:- name: RequestRateLimiter #限流args:redis-rate-limiter.replenishRate: 10 #允许用户每秒平均处理多少个请求redis-rate-limiter.burstCapacity: 20 #令牌桶的总容量,允许在一秒钟内完成的最大请求数key-resolver: "#{@ipKeyResolver}" #用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。

最后,编写网关限流配置类。

/** spring cloud 网关限流配置类* */@Configurationpublic class RateLimiterConfig {/** IP限流* */@Beanpublic KeyResolver ipKeyResolver() {return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());}/**用户限流:使用这种方式限流,请求路径中必须携带userId参数* @Beanpublic KeyResolver userKeyResolver() {return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));}* 接口限流:获取请求地址的uri作为限流key* @Beanpublic KeyResolver apiKeyResolver() {return exchange -> Mono.just(exchange.getRequest().getPath().value());}*/}

2.8 spring cloud gateway熔断

熔断主要保护的是网关服务,当下游接口负载很大,或者接口不通等其他原因导致超时,如果接口不熔断的话将会影响到下游接口得不到喘息,网关也会因为超时连接一直挂起,很可能因为一个子系统的问题导致整个系统的雪崩。所以我们的网关需要设计熔断,当熔断器打开时,网关将返回一个降级的应答。Spring Cloud Gateway的熔断可以基于Hystrix实现。

首先在pom文件中添加以下依赖

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-hystrix</artifactId></dependency>

其次,在yml配置文件中,新增熔断配置。

filters:# 熔断降级配置- name: Hystrixargs:name : defaultfallbackUri: 'forward:/defaultfallback'# hystrix 信号量隔离,3秒后自动超时hystrix:command:default:execution:isolation:strategy: SEMAPHOREthread:timeoutInMilliseconds: 3000

最后,新建一个降级默认处理方法即可,如下内容仅供参考:

/** 熔断降级默认返回数据* */@RequestMapping("/defaultfallback")public Map<String,String> defaultfallback(){System.out.println("port = " + port);System.out.println("address = " + address);System.out.println("降级操作...");Map<String,String> map = new HashMap<>();map.put("resultCode","fail");map.put("resultMessage","服务异常");map.put("resultObj","null");return map;}

2.9 spring cloud gateway 统一异常处理

spring Cloud Gateway网关的异常处理和传统的springmvc异常处理是有很大区别的,原因在于gateway使用webflux实现,两者底层的运行容器并不相同,Spring Cloud Gateway中的全局异常处理不能直接用@ControllerAdvice来处理,所以需要通过跟踪异常信息的抛出,找到对应的源码,自定义一些处理逻辑来符合业务的需求。

网关一般都是给接口做代理转发的,后端对应的都是REST API,返回数据格式都是JSON。如果不做处理,当发生异常时,Gateway默认给出的错误信息是页面,不方便前端进行异常处理。所以需要对异常信息进行处理,返回JSON格式的数据给客户端。

**示例:**修改前抛出异常

{"timestamp": "-03-12T05:34:05.356+0000","path": "/app/insertCompany","status": 500,"error": "Internal Server Error","message": "发生错误!"}

示例:配置全局异常捕获后抛出异常

{"code": 500,"message": "系统繁忙,请稍后重试。"}

统一异常的实现

自定义异常处理配置

@Configurationpublic class ExceptionConfig {/*** 自定义异常处理*/@Primary@Bean@Order(Ordered.HIGHEST_PRECEDENCE)public ErrorWebExceptionHandler errorWebExceptionHandler(ObjectProvider<List<ViewResolver>> viewResolversProvider,ServerCodecConfigurer serverCodecConfigurer) {JsonExceptionHandler jsonExceptionHandler = new JsonExceptionHandler();jsonExceptionHandler.setViewResolvers(viewResolversProvider.getIfAvailable(Collections::emptyList));jsonExceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters());jsonExceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders());return jsonExceptionHandler;}}

全局异常处理监听器

public class JsonExceptionHandler implements ErrorWebExceptionHandler {private static final Logger log = LoggerFactory.getLogger(JsonExceptionHandler.class);/*** MessageReader*/private List<HttpMessageReader<?>> messageReaders = Collections.emptyList();/*** MessageWriter*/private List<HttpMessageWriter<?>> messageWriters = Collections.emptyList();/*** ViewResolvers*/private List<ViewResolver> viewResolvers = Collections.emptyList();/*** 存储处理异常后的信息*/private ThreadLocal<Map<String, Object>> exceptionHandlerResult = new ThreadLocal<>();/*** 参考AbstractErrorWebExceptionHandler*/public void setMessageReaders(List<HttpMessageReader<?>> messageReaders) {Assert.notNull(messageReaders, "'messageReaders' must not be null");this.messageReaders = messageReaders;}/*** 参考AbstractErrorWebExceptionHandler*/public void setViewResolvers(List<ViewResolver> viewResolvers) {this.viewResolvers = viewResolvers;}/*** 参考AbstractErrorWebExceptionHandler*/public void setMessageWriters(List<HttpMessageWriter<?>> messageWriters) {Assert.notNull(messageWriters, "'messageWriters' must not be null");this.messageWriters = messageWriters;}@Overridepublic Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {// 按照异常类型进行处理HttpStatus httpStatus;String body;if (ex instanceof NotFoundException) {httpStatus = HttpStatus.NOT_FOUND;body = "很抱歉!你访问的页面不存在。";} else if (ex instanceof ResponseStatusException) {ResponseStatusException responseStatusException = (ResponseStatusException) ex;httpStatus = responseStatusException.getStatus();body = responseStatusException.getMessage();} else {httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;body = "系统异常,请稍后重试";}//封装响应体,此body可修改为自己的jsonBodyMap<String, Object> result = new HashMap<>(2, 1);result.put("httpStatus", httpStatus);String msg = "{\"code\":" + httpStatus + ",\"message\": \"" + body + "\"}";result.put("body", msg);//错误记录ServerHttpRequest request = exchange.getRequest();log.error("[全局异常处理]异常请求路径:{},记录异常信息:{}", request.getPath(), ex.getMessage());//参考AbstractErrorWebExceptionHandlerif (exchange.getResponse().isCommitted()) {return Mono.error(ex);}exceptionHandlerResult.set(result);ServerRequest newRequest = ServerRequest.create(exchange, this.messageReaders);return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse).route(newRequest).switchIfEmpty(Mono.error(ex)).flatMap((handler) -> handler.handle(newRequest)).flatMap((response) -> write(exchange, response));}/*** 参考DefaultErrorWebExceptionHandler*/protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {Map<String, Object> result = exceptionHandlerResult.get();return ServerResponse.status((HttpStatus) result.get("httpStatus")).contentType(MediaType.APPLICATION_JSON_UTF8).body(BodyInserters.fromObject(result.get("body")));}/*** 参考AbstractErrorWebExceptionHandler*/private Mono<? extends Void> write(ServerWebExchange exchange,ServerResponse response) {exchange.getResponse().getHeaders().setContentType(response.headers().getContentType());return response.writeTo(exchange, new ResponseContext());}/*** 参考AbstractErrorWebExceptionHandler*/private class ResponseContext implements ServerResponse.Context {@Overridepublic List<HttpMessageWriter<?>> messageWriters() {return JsonExceptionHandler.this.messageWriters;}@Overridepublic List<ViewResolver> viewResolvers() {return JsonExceptionHandler.this.viewResolvers;}}}

2.10 SPRING CLOUD GATEWAY整合SPRING SESSION

4.10.1 Spring session介绍

微服务项目开发中,Session会话管理是一个很重要的部分,用于存储与记录用户的状态或相关的数据;通常情况下session交由servlet容器(例如tomcat)来负责存储和管理,但是我们的微服务项目分布式部署在多台机器,则session管理存在很大的问题:比如,1、多台tomcat之间无法共享session,比如用户在tomcat A服务器上已经登录了,但当负载均衡跳转到tomcat B时,由于tomcat B服务器并没有用户的登录信息,session就失效了,用户就退出了登录;2、一旦tomcat容器关闭或重启也会导致session会话失效;因此如果项目部署在多台tomcat中,就需要解决session共享的问题。

之前在用zuul作网关的时候,我们选择了整合spring session1.x来统一管理会话。Spring Session 是Spring家族中的一个子项目,Spring Session提供了用于管理用户会话信息的API和实现。它把servlet容器实现的httpSession替换为spring session,专注于解决 session管理问题,Session信息存储在Redis中,可简单快速且无缝的集成到我们的应用中,从而完美的实现了session共享的功能。

4.10.2 Gateway网关整合spring session

Spring cloud gateway整合spring session,需要spring session2.0以上版本的支持,因为spring cloud gateway基于webflux实现,webflux中的session概念由传统的httpsession转换成websession(webflux摒弃了servlet api)。Websession需要spring session2.0版本的支持。

第一步,pom文件引入依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId></dependency>

第二步,yml文件中配置 redis的相关信息

Redis的配置有两种方式,直连和哨兵监听模式,具体可参考网关配置文件。

主要配置redis的链接地址,端口,密码,数据库(可不配置,默认为0号库)

第三步,Spring boot项目的启动类上添加@EnableRedisWebSession

注意:这里gateway是基于webflux所以添加@ EnableRedisWebSession注解 ,如果是传统的springmvc项目添加@ @EnableRedisHttpSession注解。

4.10.3 Gateway中session的简单使用

这里以gateway过滤器中获取session,以及修改session属性为例。

@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {log.info("this is a pre filter");return exchange.getSession().flatMap(webSession -> {log.info("websession: {}", webSession.getId());webSession.getAttributes().put(webSession.getId(), "aaaa");return chain.filter(exchange);}).then(Mono.fromRunnable(() -> {log.info("this is a post filter");}));}

请求经过filter的时候,会将 webSession.getAttributes().put(webSession.getId(), “aaaa”);存入redis,用PostMan发起一个请求,然后查看redis,可以看到刚才保存进去的数据。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。