1. 首页
  2. >
  3. 架构设计
  4. >
  5. 微服务

springcloud微服务实战:服务网关,Gateway

服务网关: Spring Cloud Gateway

前面已经介绍了基于Spring Cloud搭建微服务框架所需要的必需组件,利用这些组件再配合客户端就可以构建出一个完整的系统。但在实际应用场景中,每一个微服务都会部署到内网服务器中,或者禁止外部访问这些端口,这是对应用的一种安全保护机制。因此,我们如果想通过互联网来访问这些服务,需要一个统一的入口,这就是本章将介绍的微服务的又一大组件——服务网关。

springcloud微服务实战:服务网关,Gateway

我们需要服务网关,还有一些很重要的因素,比如服务网关会对接口进行统一拦截并做合法性校验,一个服务可以启动多个端口,利用服务网关进行负载均衡处理等。

目前市面上有很多产品可以实现服务网关这一功能,如 Nginx、Apache、Zuul以及 Spring CloudGateway等。Spring Cloud集成了Zuul和Gateway,我们可以很方便地实现服务网关这一功能。

Gateway简介

关于Gateway,其官网是这样描述的:

This project provides a library for building an API Gateway on top of Spring MVC.Spring CloudGateway aims to provide a simple, yet effective way to route to APIs and provide cross cuttingconcerns to them such as: security, monitoring/metrics, and resiliency.

这个项目提供了一个在Spring MVC之上构建的API网关库,Spring Cloud Gateway致力于提供一个简单而有效的方法来由路由到API,并为它们提供跨领域的关注点,如安全、监控/度量和弹性。

Gateway是由Spring Cloud官方开发的一套基于WebFlux实现的网关组件,它的出现是为了替代Zuul。Gateway 不仅提供统一的路由方式,还基于Filter Chain供了网关的基本功能,例如安全、监控、埋点和限流等。

创建服务网关

本节中,我们将开始创建服务网关,进一步优化我们的微服务架构。

(1)创建一个子工程,命名为gateway并添加以下依赖:

<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId>< / dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</ artifactId></ dependency> <dependency> <artifactId>common</artifactId><groupId>com.lynn.blog</groupId> <version>1.e-SNAPSHOT</version> < / dependency>

前面提到,Spring Cloud Gateway基于WebFlux,因此需要添加 WebFlux依赖,注意不能引入Web依赖,否则无法正常启动gateway工程;此外,为了启用服务网关功能,还需要添加
spring-cloud-starter-gateway依赖。

(2)在Git仓库创建一个配置文件gateway.yml,并添加以下内容:

server: port: 8088spring: application: name: gatewaycloud: #Spring Cloud Gateway路由配置方式gateway: #是否与服务发现组件进行结合,通过serviceId(必须设置成大写)转发到具体的服务实例。默认为false,设为true便开启通过服务中心的自动根据serviceId创建路由的功能discovery: #路由访问方式:http://Gateway_HOST:Gateway_PORT/大写的serviceId/**,其中微服务应用名默认大写访问 locator: enabled: true logging: #配置网关日志策略level: org.springframework.cloud.gateway : trace org.springframework.http.server.reactive: debug org.springframework.web.reactive: debug reactor.ipc.netty: debug feign: hystrix: #开启熔断器 enabled: true

在上述配置中
spring.cloud.gateway.discovery.locator.enabled默认为false,设置为true,我们就可以通过注册中心的 serviceId 请求路由地址,路由访问格式为
http://Gateway_HOST:Gateway_PORT/serviceId/**,其中微服务应用名默认大写;logging.level配置的是服务网关日志策略。

(3)在gateway工程下创建bootstrap.yml配置文件,并添加以下内容:

spring: cloud : config: name: eurekaclient,gatewaylabel: master discovery : enabled: trueserviceId: configusername: admin password: admin eureka: client: serviceUrl: defaultzone: http: // admin :admin123@localhost:8101/eureka/

上述配置通过spring.cloud.config.name指定要拉取的文件,除了gateway还多了eurekaclient文件。eurekaclient 用于配置Eureka客户端。由于每个微服务都应注册到Eureka服务端中,所以每个服务都需要拉取eurekaclient配置,该配置内容如下:

spring: cloud: inetutils: preferred-networks : 127.0.e.1 eureka: instance: prefer-ip-address: trueclient: register-with-eureka: truefetch-registry: true #开启熔断器 feign: hystrix: enabled: true

这些配置前面已经讲解,这里不再重复。

(4)创建应用启动类GatewayApplication,代码和前面讲的人口程序类似,此处省略具体的代码。

这样一个最简单的服务网关组件就搭建完成了。

接下来,分别启动register、config、test、 gateway工程并访问localhost:8080/TEST/test,如果出现如图9-1所示的内容,说明服务网关搭建成功。

springcloud微服务实战:服务网关,Gateway

在以上地址中,8080为网关启动端口,TEST为服务注册名 (Spring Cloud默认为大写),test为服务的restapi3地址。

利用过滤器拦截API请求

使用服务网关还有一个很重要的原因是我们需要对外提供统一的HTTP入口,便于我们管理各个服务接口,尤其是在鉴权R方面。假如没有服务网关进行拦截,就需要在每个服务下都实现拦截代码,而微服务系统的鉴权逻辑往往是一样的,代码也是一样的,所以这样做不利于维护和扩展。因此,我们可以利用Spring Cloud Gateway统一过滤外来请求。

服务网关提供了多种过滤器( filter )供大家选择,如GatewayFilter和 GlobalFilter等,不同过滤器的作用是不一样的,GatewayFilter处理单个路由的请求,而GlobalFilter根据名字就能知道,它是一个全局过滤器,可以过滤所有路由请求。本文以全局过滤器GlobalFilter为例,讲解如何通过过滤器过滤API请求,达到鉴权的目的。

(1)创建ApiGlobalFilter类,并实现GlobalFilter:

@Component public class ApiGlobalFilter implements GlobalFilter { @Override public Mono<Void>filter(ServerwebExchange exchange,GatewayFilterChain chain){ String token = exchange.getRequest().getQueryParams().getFirst("token"); if ( StringUtils.isBlank( token)) i ServerHttpResponse response = exchange.getResponse();3SONObject message = new JSONObject(); message.put( ""status", -1); message.put( "data","鉴权失败"); byte[] bits = message.toJSONString().getBytes(StandardCharsets.UTF_8);DataBuffer buffer = response.bufferFactory( ).wrap(bits); response. setStatuscode(HttpStatus. UNAUTHORIZED); response.getHeaders().add("content-Type", "text/json;charset=UTF-8");return response.writewith(Mono.just(buffer)); } return chain.filter( exchange); } }

上述代码的意思是过滤所有请求路由,从参数中提取token并通过chain.filter方法执行目标路由,如果没有token,则提示鉴权失败,并通过writewith方法返回。

Spring Cloud Gateway依赖WebFlux,而WebFlux通过Mono对象返回数据,因此上述过滤器也返回了Mono对象。我们注意到,filter方法返回的是 Mono<Void>,读者可以将Void类理解为同Java的void关键字一样的功能,它其实就是void关键字的包装类,同int和 Integer的区别一样。该Mono并不返回任何数据,我们如果将它想象成普通的定义方法,就应该是void filter()。

(2)启动gateway 工程,访问localhost:8080/TEST/test,得到以下结果,如图9-2所示。说明全局过滤器对路由做了过滤处理。将地址加上 token参数后,将会得到如图9-1所示的结果。

springcloud微服务实战:服务网关,Gateway

请求失败处理

如果要调用的服务出现异常或者宕机了,那么Gateway请求失败,必然会返回错误。这时停止 test工程并访问网关地址,可以看到如图9-3所示的界面。

springcloud微服务实战:服务网关,Gateway

这种500 错误对用户是不友好的,需要对服务网关进行统一的异常处理并给客户端返回统一的JSON数据,让客户端具有友好的体验,具体步骤如下。

(1)创建异常处理类JsonExceptionHandler,它继承自
DefaultErrorwebExceptionHandler:

public class JsonExceptionHandler extends DefaultErrorwebExceptionHandler{ public JsonExceptionHandler(ErrorAttributes errorAttributes,ResourceProperties resourceProperties, ErrorProperties errorProperties,ApplicationContext applicationcontext) { super(errorAttributes,resourceProperties,errorProperties, applicationContext); ) @override protected Map<String, 0bject> getErrorAttributes(ServerRequest request,boolean includeStackTrace) i int code = 508; Throwable error = super.getError(request); if (error instanceof org.springframework.cloud.gateway. support.NotFoundException){ code = 404; } return response(code,this.buildMessage(request,error)); } @override protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) { return RouterFunctions.route(RequestPredicates.all(), this:: renderErrorResponse); } @override protected HttpStatus getHttpStatus(Map<String,object> errorAttributes){ int statuscode =(int) errorAttributes.get( "code" ); return Httpstatus.valueOf(statuscode); } private String buildMessage(ServerRequest request,Throwable ex){ StringBuilder message = new StringBuilder("Failed to handle request [");message.append(request.methodName()); message. append(" "); message. append(request.uri());message. append("]"); if (ex != null){ message. append(": "); message.append(ex.getMessage(); } return message.toString(); } public static Map<String,0bject> response(int status,String errorMessage){ Map<String,object> map = new HashMap<>(); map.put( "code", status); map.put( "message" , errorMessage);map.put( "data" , null); return map; } }

SpringBoot提供了默认的异常处理类
DefaultErrorwebExceptionHandler,显示效果如图9-3所示,这显然不符合我们的预期。因此,需要重写此类,并返回JSON格式。

Spring Cloud Gateway进行异常处理的原理是,当出现请求服务失败(可以是服务不可用,也可以是路由地址404等)的情况,首先会调用getRoutingFunction方法,该方法接收ErrorAttributes对象,即接收具体的错误信息,然后调用getErrorAttributes方法获得异常属性,通过该方法判断具体的错误码,最终将错误信息放到Map 并返回客户端。

(2)覆盖默认异常,具体的实现如下:

@SpringBootConfiguration @EnableConfigurationProperties({ServerProperties.class,ResourceProperties.class}) public class ErrorHandlerConfiguration { private final ServerProperties serverProperties;private final ApplicationContext applicationContext;private final ResourceProperties resourceProperties;private final List<ViewResolver> viewResolvers; private final ServerCodecConfigurer serverCodecconfigurer; public ErrorHandlerconfiguration(ServerProperties serverProperties, ResourceProperties resourceProperties,0bjectProvider<List<ViewResolver>> viewResolversProvider,ServerCodecConfigurer serverCodecConfigurer, Applicationcontext applicationcontext) { this.serverProperties = serverProperties; this.applicationContext = applicationContext;this.resourceProperties = resourceProperties; this.viewResolvers = viewResolversProvider.getIfAvailable(Collections: :emptyList);this.servercodecConfigurer = servercodecConfigurer; } @Bean @order(ordered.HIGHEST_PRECEDENCE) public ErrorwebExceptionHandler errorwebExceptionHandler(ErrorAttributes errorAttributes) { JsonExceptionHandler exceptionHandler = new 3sonExceptionHandler( errorAttributes, this.resourceProperties, this.serverProperties.getError(),this.applicationContext); exceptionHandler.setViewResolvers(this.viewResolvers); exceptionHandler.setMessagewriters(this.serverCodecConfigurer.getwriters());exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());return exceptionHandler; } }

以上代码最核心的部分是errorlwebExceptionHandler方法,因此上述类添加了@SpringBoot-Configuration注解,并且 errorWwebExceptionHandler声明了@Bean。gateway工程启动时就会执行errorwebExceptionHandler方法且需要返回ErrorWwebExceptionHandler对象,方法内可以实例化sonExceptionHandler对象并返回。这样gateway在发生异常时就会自动执行JsonExceptionHandler而不会执行其默认类了。

(3)重新启动gateway并停止 test,访问地址 localhost:8080/TEST/test就可以得到如图9-4所示的结果。

springcloud微服务实战:服务网关,Gateway

小结

本文介绍了Spring Cloud的另一大组件:服务网关,它是外部通信的唯一入口。在实际项目中,我们需要对接口进行安全性校验,而一套微服务架构可能存在成千上万个服务,不可能对每个服务都单独实现安全机制,而应通过服务网关统一拦截。Spring Cloud Gateway默认实现了负载均衡,一个服务可以部署到多台服务器,通过其负载均衡机制,.可以有效地提升系统的并发处理能力。

本文给大家讲解的内容是springcloud实战:服务网关,SpringCloudGateway