1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > Spring Cloud Alibaba微服务架构实战教程—15最详细的Gateway统一网关

Spring Cloud Alibaba微服务架构实战教程—15最详细的Gateway统一网关

时间:2021-08-13 01:38:07

相关推荐

Spring Cloud Alibaba微服务架构实战教程—15最详细的Gateway统一网关

前言

在分布式微服务架构中,会将服务进行拆分,不同的服务负责各自的业务功能,拆分后也有一个问题?每一个服务都有自己的服务名、ip、端口等,服务越多数量越多时,这样怎么记忆这么多的URL呢? 此外,一些公共性的功能(如认证、鉴权、服务流控等)需要重复在各子模块中自身实现,造成的代码冗余怎么办??

如图,诞生了一个统一网关,它将所有子服务封装起来,外部请求服务时,由网关统一调配URL和转发各个请求到不同的微服务去,并且可以在网关层针对所有公共性的功能作统一的处理,避免冗余。

一、网关简介

网关介绍:

网关:流量请求的入口功能:服务网关 = 路由转发+过滤器。路由转发:接受一切外界的请求,转发到后端的微服务上去。过滤器:在网关中可以完成一系列的横切功能,如权限校验,限流,及监控等。(其实路由转发也是个过滤器)

本案例项目中,网关的作用:

Smartcat-project 项目中,后台请求会先访问到API网关,然后网关通过注册中心实时感知各个微服务的状态及路由地址,最后准确地将请求路由到具体的服务中去处理。

如图:

既然网关作用这么大,我们该怎么实现它呢?就可以借用一下开源组件了。

第一代网关Zuul,阻塞式,不支持websockets长连接,是netflix公司项目。(底层是servlet,Zuul处理的是http请求)第二代网关Gateway 是基于Netty,由Cloud官方提供,使用的是异步IO,非阻塞式,性能较Zuul提升1.6倍不止。

二、Gateway网关组件

注意:GateWay跟 Servlet 不兼容,不能打成war包,工程中不能出现Servlet的组件。

根据 官方的原理图:

可知,网关原理大致如下:

请求到达网关后,先经过断言Predicate(主要用来判断一个参数是否符合要求);

判断是否符合某个路由的规则?如果符合,则按规则路由到指定地址,如不符合,则退回请求;

请求和响应可以通过过滤器Filter进行配置,过滤I一些不正常的请求和异常返回。

使用gateway之后,客户端只需要记住一个gateway的地址即可,类似于java变量的作用,用来标识内存地址。不用记录地址,只需要记录变量名称,也好比学校的传达室,从此由他来传达请求。

三、项目集成gateway网关

由于geateway是一个相对独立的个体,而且作用和其安全性不言而喻,因此在我们当前的项目中,集成gateway网关,我们采用再建立一个单独的子模块,实现网关功能。

3.1.创建Gateway子模块

创建Gateway 微服务,取名smartcar-gateway,如下所示:

3.2.引入Gateway依赖

在其pom.xml中,引入Gateway依赖,与父类关联。

<parent><groupId>com.smart.car.root</groupId><artifactId>smartcar-project</artifactId><version>0.0.1-SNAPSHOT</version></parent><!--引入gateway--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency>

注意:记得如果自动生成的项目中,存在web依赖,请删除掉。因为Gateway跟 Servlet 不兼容。(你知道为什么吗?)

3.3.在父类中聚合Gateway模块

在父类的pom.xml中,增加聚会服务的标识。

这样打包的时候,会起到聚合作用。

3.4.添加配置文件

Gateway网关的路由过滤器有两种配置方式:

1.在配置文件yml中配置2.代码中注入RouteLocator的Bean(用编码的方式实现路由映射)

这种用编码方式实现gateway进行路由映射的配置方法,自行百度即可,具体不表述。 但是有一点需要记住,这两种方式都是不支持动态配置的。(在本案例中,采用第一种配置方案)

将application.properties改为application.yml。

server:port: 8008spring:application:name: samrtcar-getewaycloud:#gateway configgateway:discovery:locator:enabled: true #开启网关映射lower-case-service-id: true #将请求路径上的服务名配置为小写filters: #去掉path的前缀- StripPrefix=1#配置路由数组routes:#member子服务- id: member_service#自定义唯一名称-限流时用到uri: http://localhost:8003 #映射提供服务的uripredicates:- Path= /member/** #断言,映射路径#filters: #去掉前缀,视自身URL而定# - StripPrefix=1#路由实例- id: route_baiduuri: predicates:- Query= url,baidu

注意,写的是URI不是URL,你知道两者区别么?我们在配置中,指定当Path中含有member/路径时候,就会路由到localhost:8003的微服务去,也就是member项目的接口去。这样就实现了路由的转发。

参数说明:

StripPrefix=1,是去掉URL的前缀,如果你发现你访问地址老是404,请检查是否需要开启去前缀。

Predicates(断言):一个Java 8的定义。 作用:符合 Predicate 条件的,就使用该路由配置,否则就不管。(用来判断是否符合规范的)。

3.5.启动项目测试

启动member项目和gateway项目,访问URL。

目前有两种方式可以访问接口,一种是直接访问原服务的URL,一种是访问网关,网关再路由到对应服务URL去。

访问效果:

其次,只要当请求中包含 url=baidu的就会进行匹配和路由到baidu网站了。

测试效果:

上面这种做法是直接在配置文件中配置了路由地址, 但是有个问题?到此,依然可以直接访问其目标服务的接口,那以后别人绕开网关直接访问URL怎么办? 所以就不让直接请求具体的服务,请求都走网关,就是只开放网关的端口,其它微服务的端口都不对外开放-需要直接隐藏起来。

微服务开发,各种服务都是用nacos注册中心来统一管理的,nacos中通过服务名也有映射,那现在我们应该让 gateway 直接去 nacos 中发现服务 ,然后再自动转发到对应的服务去。

四、Gateway结合Nacos

4.1.引入Nacos组件

因common模块引入了nacos注册中心组件,所以我们可以直接引用common模块。

<!--引入公共工具微服务--><dependency><groupId>com.mon</groupId><artifactId>smartcar-common</artifactId><version>0.0.1-SNAPSHOT</version><exclusions><!--排出servlet容器--><exclusion><groupId>org.apache.tomcat.embed</groupId><artifactId>tomcat-embed-core</artifactId></exclusion></exclusions></dependency>

注意:Gateway跟 Servlet 不兼容,所以网关服务中不能出现spring-web类的依赖。(你知道为什么吗?)

4.2.添加开启nacos注解

启动类上添加开启nacos的@EnableDiscoveryClient注解

@RefreshScope@EnableDiscoveryClient@SpringBootApplicationpublic class SmartGatewayApplication {public static void main(String[] args) {SpringApplication.run(SmartGatewayApplication.class, args);}}

4.3.配置路由方式

在yml中配置nacos地址,还需要配置新的路由方式。

在gateway中配置uri配置有三种方式,包括

第一种:websocket方式: uri: ws://localhost:9000第二种:http方式: uri: http://localhost:8130/第三种:lb(注册中心中服务名字)方式: uri: lb://brilliance-consumer

其中ws和http方式不容易出错,因为http格式比较固定,但是lb方式比较灵活自由,且使用 uri: lb:// 方式配置时,服务名有特殊要求(见文末)。

lb: //后面的是其他服务注册在nacos上的名称,也就是spring.applicaiton.name属性。

我们采用lb的方式配置,如下:

server:port: 8008spring:application:name: samrtcar-getewaycloud:#nacos confignacos:discovery:server-addr: 127.0.0.1:8848register-enabled: true#gateway configgateway:discovery:locator:enabled: true #开启网关映射lower-case-service-id: true #将请求路径上的服务名配置为小写filters: #去掉path的前缀- StripPrefix=1#配置路由数组routes:#member子服务- id: member_route#自定义唯一名称uri: lb://smartcar-member # 映射提供服务的uripredicates:- Path= /member/** #断言,映射路径#filters: #去掉前缀,视URL而定# - StripPrefix=1

当选择了nacos做服务的映射,则不需要考虑前缀问题,会自动处理。

4.4.测试lb路由方式

启动member8003和80013两个服务和gateway8008一个服务,如图:

然后查看naocs注册列表:

通过网关访问接口:http://localhost:8008/member//list ,

你会发现在,接口在轮询请求8003和8004端口的member服务,不光证明了可以通过网关调用服务,还证明了相同服务的负载均衡成功。

这样以后,前端直接访问网关,不必再关心子服务的服务名、服务端口,路由地址等情况,即使是我们修改了其他服务的端口号,也不影响前端的调用。

五、网关服务熔断

网关虽然仅仅是转发路由到对应服务,但是也是请求的入口处,如果对应的服务宕机了,请求就会一致堆积在网关处,为了不引起网关的阻塞,我们需要在网关处,配置熔断机制。

本实例降级方案,就采用hystrix来快速熔断吧。在前文中我说过,真正的业务开发上,很多都是多组件相互协助,并不是仅仅靠谁就可以独立完成。

5.1.采用hystrix快速熔断

因此,网关的熔断,我们用hystrix来快速完成工作。

1.引入hystrix依赖。

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

2.设置熔断超时时间设置

#hystrix超时时间,默认时间为1000mshystrix:command:default:execution:isolation:thread:timeoutInMilliseconds: 2000

3.新增降级兜底方法

熔断后,会由gateway层提供一个快速失败的方法返回给请求的调用方,我们也可称为降级过滤器

@Slf4j@RestController@RequestMapping("error")public class FallbackController {@RequestMapping("/fallback")public ResponseResult<String> fallback() {ResponseResult<String> result = new ResponseResult<>();log.error("Invoke service failed...");result.setCode(429);result.setMsg("对应的服务调用失败,快速熔断,进入fallback降级方法。");return result;}}

4.配置指定的过滤器

并在yml配置文件中指向该降级过滤器

#member子服务- id: member-service#自定义唯一名称-限流时用到uri: lb://smartcar-member # 映射提供服务的uripredicates:- Path= /member/** #断言,映射路径filters:#结合Hystrix对服务宕机做fallback机制(降级过滤器)- name: Hystrixargs:name: fallbackfallbackUri: forward:/error/fallback

还有一种情况,假如服务是暂时的不可用,发起重试之后又能调用正常返回结果,则可以设置重试次数,来确保服务的可用性,我们称为重试过滤器

配置如下:

fallbackUri: forward:/error/fallback#重试次数(重试过滤器)(若两者都配置了,降级过滤器需要配在重试过滤器之前)- name: Retryargs:#重试3次,加上初次访问,正确执行应当是4次访问retries: 3statuses:- OKmethods:- GET- POST

这就需要你在对应的调用方法上,人为增加一个异常,才会触发。本案例不详说。

最终给出完整的yml内容:

server:port: 8008servlet:context-path: /gateway#logback配置logging:level:root: info#关闭nacos心跳日志com.alibaba.nacos.client.*: WARN#org.springframework:cloud.gateway: debugfile:name: ./tmp/gateway.logspring:application:name: samrtcar-getewaycloud:#nacos confignacos:discovery:server-addr: 127.0.0.1:8848register-enabled: true#gateway configgateway:#开启网关映射discovery:locator:enabled: truelower-case-service-id: true #将请求路径上的服务名配置为小写filters: #去掉path的前缀,视自身URL而定- StripPrefix=1#配置路由数组routes:#member子服务- id: member-service#自定义唯一名称-限流时用到uri: lb://smartcar-member # 映射提供服务的uripredicates:- Path= /member/** #断言,映射路径filters:#结合Hystrix对服务宕机做fallback机制(降级过滤器)- name: Hystrixargs:name: fallbackfallbackUri: forward:/error/fallback#重试次数(重试过滤器)(若两者都配置了,位置不可变)- name: Retryargs:#重试3次,加上初次访问,正确执行应当是4次访问retries: 3statuses:- OKmethods:- GET- POST#points子服务- id: points-service#自定义唯一名称-限流时用到uri: lb://smartcar-points # 映射提供服务的uripredicates:- Path= /points/** #断言,映射路径#hystrix超时时间,默认时间为1000mshystrix:command:default:execution:isolation:thread:timeoutInMilliseconds: 2000

5.启动gateway项目,停掉member项目,让其宕机不可用,如果调用返回错误,或者超时的话,进入降级处理,如果调用成功,则返回结果。

这样,如果遇到服务宕机或者超时情况,gateway网关就不会再去请求该服务,会直接调用对应的fallback方法,实现快速响应。

六、网关服务限流

sentinel可以作为各微服务的限流,也可以作为gateway网关的限流组件。

虽然spring cloud gateway自带了限流功能,但此处用sentinel来作为替待,可以更好的管控。

SpringCloud Gateway原生限流,主要基于过滤器实现,我们可以直接使用内置的过滤器RequestRateLimiterGatewayFilterFactory。这里就不说了,因为大部分情况下,是不采用它的。

采用Sentinel服务限流

本段落介绍Spring Cloud Gateway集成Sentinel和Nacos实现网关的动态限流。

Sentinel从 1.6.0 版本之后,提供两种资源维度的限流:

Route 维度:即在 Spring 配置文件中配置的路由条目,资源名为对应的 routeId自定义API 维度:用户可以利用 Sentinel 提供的 API 来自定义一些 API 分组。

1.引入依赖如下:

<!--集成sentinel持久化到nacos--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency><!--集成sentinel网关层限流start--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId></dependency><!--sentinel限流规则持久化 --><dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-datasource-nacos</artifactId></dependency><!--集成sentinel网关层限流end-->

由于需要使用 nacos作为sentinel的配置中心,所以也引入了sentinel-datasource-nacos 引入nacos分布式配置后,必须在bootstrap.yml文件中声明,否则无法获取相应的数据。

2.新建bootstrap.yml:

#nacos配置中心spring:cloud:nacos:config:server-addr: 127.0.0.1:8848

3.在application.yml文件中添加对应的sentinel与集成的功能

spring:cloud:sentinel:transport:dashboard: 10.0.10.48:8858eager: truedatasource:ds:nacos:server-addr: 10.0.10.48:8848data-id: gateway-sentinel-flowgroup-id: DEFAULT_GROUPrule-type: gw-flow

这里主要是sentinel的相关配置,从nacos配置中心获取 gateway-sentinel-flow 配置文件,限流类型是网关类型gw-flow或者是flow。

参数说明如下:

data-id:可以使用了${spring.application.name}变量,方便区分不同应用的配置。data-type:指配置项的内容格式,提供了 JSON 和 XML 两种格式,rule-type:表示数据源中规则属于哪种类型,如 flow、degrade、param-flow、gw-flow 等。

4.编写简单的测试类

@Slf4j@RestController@RequestMapping("sentinel")public class SentinelTestController {@PostMapping("flowTest")public String test() {String dString = UUID.randomUUID().toString();log.info("产生的结果是:{}",dString);return dString;}}

5.限流配置

前一篇,写了可以在nacos中配置流控规则,然后会自动同步到sentinel中去,因此我们也在试试效果。在nacos控制台中建立对应的配置文件smartcar-gateway-sentinel-nacos,格式为json格式,如下内容:

[{"resource": "member-service", //服务路由的名称"limitApp": "default","grade": 1,"count": 2,"strategy": 0,"controlBehavior": 0,"clusterMode": false},{"resource": "flowTest", //网关本地接口的名称"limitApp": "default","grade": 1,"count": 3,"strategy": 0,"controlBehavior": 0,"clusterMode": false}]

网关限流规则,如图:

即使服务进行重启,nacos中的限流规则也不会再丢失。

6.配置完成以后启动网关项目,登录sentinel控制台,查看限流规则:

对应关系:

7.运行项目,测试限流结果,发起get请求:http://localhost:8008/member/list

当触发限流后页面显示的是Blocked by Sentinel: FlowException。

(这个原理是DefaultBlockRequestHandler,实现了BlockRequesthandler接口)

100个并发中,只要前两个成功,其它都被拦截,也可以直接在sentinel控制台中看到直观的监测结果:

关于在 Gateway 用 Sentinel还是 Hystrix 的问题?

在 Sentinel 控制台对 网关模块 进行具体的限流可视化配置,方式比较友好。直接用 Gateway 内置的 RequestRateLimiter 跟 Hystrix 进行熔断配置比较方便快速。

其实两种可以搭配使用的,一个做限流的,一个做快速失败的,没有什么工具是全面的,懂得配合就好。

关于网关的限流或熔断更多的方式配置,可参考 sentinel下网关规则配置。

小总结:

网关的熔断降级:在分布式系统中,网关作为流量的入口,大量请求进入网关,向后端远程系统或服务发起调用,后端服务不可避免的会产生调用失败(超时或异常),失败时不能让请求堆积在网关上,需要快速失败并返回回去,这就需要在网关上做熔断、降级操作。

网关的限流:网关上有大量请求,对指定服务进行限流,可以很大程度上提高服务的可用性与稳定性,限流的目的是通过对并发访问/请求进行限速,或对一个时间窗口内的请求进行限速来保护系统。一旦达到限制速率则可以拒绝服务、排队或等待、降级操作。

七、自定义网关的响应异常

当触发限流后,接口显示的是Blocked by Sentinel: FlowException。 为了展示更加友好的限流提示, Sentinel支持自定义异常处理。同样,也有两种方案可以使用。

7.1.方案一:配置文件

properties、yml配置文件类

spring.cloud.sentinel.scg.fallback.mode = responsespring.cloud.sentinel.scg.fallback.response-body = '{"code":429,"mes":"限流了"}'

或:

spring:cloud:sentinel:scg:fallback:mode: responseresponse-body: '{"code":403,"msg":"不好意思,请稍后再试(您被限流了)"}'

7.2.方案二:配置类注入Bean

import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpStatus;import org.springframework.http.MediaType;import org.springframework.web.reactive.function.BodyInserters;import org.springframework.web.reactive.function.server.ServerResponse;import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Mono;import javax.annotation.PostConstruct;import java.util.HashMap;@Configurationpublic class GatewayConfiguration {//自定义限流异常页面@PostConstructpublic void initBlockHandlers(){BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {@Overridepublic Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {Map map = new HashMap();map.put("code",0);map.put("msg","被限流了");return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromObject(map));}};GatewayCallbackManager.setBlockHandler(blockRequestHandler);}}

图示:

当触发流控规则后,就会达到自定义异常的效果了。(图片是后补的,没有user)

其实,有两种方式来实现网关这层的限流。

第一种:基于路由的限流 ,就是上述说写的。第二种:基于 API 分组限流(基于路由配置)。这个留给读者去探索把

八、网关Cors跨域支持

系统架构基本是前、后端独立部署,到时候会直接引发一个问题:跨域请求

前端通过网关入口,去调用其它微服务时,通常会出现如下错误:

Access to XMLHttpRequest at 'xxx' from origin 'xxx' has been been blocked by CORS policy

所以,必须要在网关层配置支持跨域,不然无法将请求路由到正确的处理节点,常见的有两种解决方式。

8.1第一种,代码方式

新增一个配置类配置

@Configurationpublic class CORSConfiguration {@Beanpublic CorsWebFilter corsWebFilter() {CorsConfiguration config = new CorsConfiguration();config.setAllowCredentials(Boolean.TRUE);//config.addAllowedMethod("*");config.addAllowedOrigin("*");config.addAllowedHeader("*");config.addExposedHeader("setToken");UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());source.registerCorsConfiguration("/**", config);return new CorsWebFilter(source);}}

8.2.第二种,注解方式

只需要在 application.yml 配置文件中添加:

spring:cloud:gateway:globalcors:cors-configurations:'[/**]':allowedHeaders: "*"allowedOrigins: "*"allowedMethods:- GET- POST- PUT- DELETE- OPTION

(最简单的方式。 本文也是采用的这种。)

就注意几点:

既在网关里边来解决跨域问题,就不能在后面的其他服务里边,再重复引入解决跨域的配置,否则会导致跨域失效,出现错误。

若要求跨域请求必须携带cookie时,则请求头中需要设置"Access-Control-Allow-Credentials:true" 这个属性。

allowedOrigins(“*”) 中的 *号,不能和allowCredentials(true) 同时存在。

好了,到这里,本章节就讲完了,关于Gateway网关,还是有很多值得单独去学习的地方,希望读者朋友们,可以自行多学习和大胆尝试。

小课堂

1.启动网关报错:Failed to configure a DataSource: ‘url’ attribute is not specified and no embedded datasource could be configured.

那是因为我们的common包中包含了mysql依赖,但是你又没在yml指定mysql的连接信息。 因为我们网关不需要配置,只需要在启动类上声明一下即可:

@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})

2.uri为什么前面要加lb:呢?

spring:cloud:gateway:routes: #路由配置uri: lb://provider #目标路径

因为在org.springframework.cloud.gateway.discovery.DiscoveryLocatorProperties类中限制了前缀。

注意,它有2个特殊要求:

1.能被gateway的lb方式识别到的命名规则为:

"[a-zA-Z]([a-zA-Z]|\\d|\\+|\\.|-)*:.*"

这也意味着,java命名规范中可以使用的英文下划线(“_”),在这里会不能识别。

你会见到这样的报错异常信息:

java.lang.IllegalStateException: Invalid host: lb://brilliance_consumer

2.lb方式用的是注册中心,只要你的服务能注册到同一个注册中心,就没问题,不在同一个注册中心,无法调通。要求gateway和其他微服务在一个注册中心中

3.在nacos + gateway的基础上,如何实现负载平衡?关掉其中一个provider,立即通过gateway进行轮询,发现gateway仍然会call已经down掉的provider,导致查询失败。但在大约5秒后,轮询恢复正常,不再call已经挂掉的prodiver,这个现象是为什么?

因为gateway call provider流程是:请求会先经过网关,网关根据nacos注册中心获取服务地址,但nacos心跳机制默认每5秒钟检查一次provider是否正常?当服务挂掉时,nacos还未更新,导致gateway仍然去轮询挂掉的服务。因此就会出现上面现象。

4.什么是Api分组?作用是什么?

API 分组:比如我们可以定义一个 API 叫 my_api,请求 path 模式为 /A/ 和 /B/ 的都归到 my_api 这个 API 分组下面,对这个api进行限流,就对A和B接口路径都限流了。 (如果想让两个路由共用一个限流规则,就可以用这个方式)

ps: 最后说两点小常识:

1.网关的核心概念就是路由配置和路由规则,且作为所有请求流量的入口,在实际生产环境中要保证高可靠和高可用,尽量要避免重启,所以实现动态路由是非常有必要的;

2.Api网关结合ribbon完成负载均衡和动态路由,还是比较复杂的,我不建议刚入门的朋友学习,所以不写在本教程中了。将会单独写成博客,感兴趣的朋友单独搜索即可。

(内容看来有点多,后期我将考虑拆分多章节来讲吧,如果效果不好的话,有什么意见请评论区踊跃提出~)

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