前言
在分布式微服务架构中,会将服务进行拆分,不同的服务负责各自的业务功能,拆分后也有一个问题?每一个服务都有自己的服务名、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: 8008 spring: application: name: samrtcar-geteway cloud: #gateway config gateway: discovery: locator: enabled: true #开启网关映射 lower-case-service-id: true #将请求路径上的服务名配置为小写 filters: #去掉path的前缀 - StripPrefix=1 #配置路由数组 routes: #member子服务 - id: member_service #自定义唯一名称-限流时用到 uri: http://localhost:8003 #映射提供服务的uri predicates: - Path= /member/** #断言,映射路径 # filters: #去掉前缀,视自身URL而定 # - StripPrefix=1 #路由实例 - id: route_baidu uri: http://www.baidu.com 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.smart.car.common</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 @SpringBootApplication public 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: 8008 spring: application: name: samrtcar-geteway cloud: #nacos config nacos: discovery: server-addr: 127.0.0.1:8848 register-enabled: true #gateway config gateway: discovery: locator: enabled: true #开启网关映射 lower-case-service-id: true #将请求路径上的服务名配置为小写 filters: #去掉path的前缀 - StripPrefix=1 #配置路由数组 routes: #member子服务 - id: member_route #自定义唯一名称 uri: lb://smartcar-member # 映射提供服务的uri predicates: - 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超时时间,默认时间为1000ms hystrix: 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 # 映射提供服务的uri predicates: - Path= /member/** #断言,映射路径 filters: #结合Hystrix对服务宕机做fallback机制(降级过滤器) - name: Hystrix args: name: fallback fallbackUri: forward:/error/fallback
还有一种情况,假如服务是暂时的不可用,发起重试之后又能调用正常返回结果,则可以设置重试次数,来确保服务的可用性,我们称为重试过滤器。
配置如下:
fallbackUri: forward:/error/fallback #重试次数(重试过滤器)(若两者都配置了,降级过滤器需要配在重试过滤器之前) - name: Retry args: #重试3次,加上初次访问,正确执行应当是4次访问 retries: 3 statuses: - OK methods: - GET - POST
这就需要你在对应的调用方法上,人为增加一个异常,才会触发。本案例不详说。
最终给出完整的yml内容:
server: port: 8008 servlet: context-path: /gateway #logback配置 logging: level: root: info #关闭nacos心跳日志 com.alibaba.nacos.client.*: WARN #org.springframework:cloud.gateway: debug file: name: ./tmp/gateway.log spring: application: name: samrtcar-geteway cloud: #nacos config nacos: discovery: server-addr: 127.0.0.1:8848 register-enabled: true #gateway config gateway: #开启网关映射 discovery: locator: enabled: true lower-case-service-id: true #将请求路径上的服务名配置为小写 filters: #去掉path的前缀,视自身URL而定 - StripPrefix=1 #配置路由数组 routes: #member子服务 - id: member-service #自定义唯一名称-限流时用到 uri: lb://smartcar-member # 映射提供服务的uri predicates: - Path= /member/** #断言,映射路径 filters: #结合Hystrix对服务宕机做fallback机制(降级过滤器) - name: Hystrix args: name: fallback fallbackUri: forward:/error/fallback #重试次数(重试过滤器)(若两者都配置了,位置不可变) - name: Retry args: #重试3次,加上初次访问,正确执行应当是4次访问 retries: 3 statuses: - OK methods: - GET - POST #points子服务 - id: points-service #自定义唯一名称-限流时用到 uri: lb://smartcar-points # 映射提供服务的uri predicates: - Path= /points/** #断言,映射路径 #hystrix超时时间,默认时间为1000ms hystrix: 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:8858 eager: true datasource: ds: nacos: server-addr: 10.0.10.48:8848 data-id: gateway-sentinel-flow group-id: DEFAULT_GROUP rule-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 = response spring.cloud.sentinel.scg.fallback.response-body = '{"code":429,"mes":"限流了"}'
或:
spring: cloud: sentinel: scg: fallback: mode: response response-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; @Configuration public class GatewayConfiguration {
//自定义限流异常页面 @PostConstruct public void initBlockHandlers(){
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override public 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第一种,代码方式
新增一个配置类配置
@Configuration public class CORSConfiguration {
@Bean public 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完成负载均衡和动态路由,还是比较复杂的,我不建议刚入门的朋友学习,所以不写在本教程中了。将会单独写成博客,感兴趣的朋友单独搜索即可。
(内容看来有点多,后期我将考虑拆分多章节来讲吧,如果效果不好的话,有什么意见请评论区踊跃提出~)
今天的文章
springcloudalibaba微服务架构图_Spring cloud分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/61056.html