SpringCloud极简入门-服务网关-spring cloud zuul

简介: 为什么要zuul试想一下如果我们有很多的微服务,他们都需要登录之后才能访问,那么我需要在每个微服务都去做一套登录检查逻辑,这样是不是会存在大量重复的代码和工作量,我们希望的是把登录检查这种公共的逻辑进行统一的抽取,只需要做一套检查逻辑即可,而zuul就可以用来干这类事情,我们可以把zuul看做是微服务的大门,所有的请求都需要通过zuul将请求分发到其他微服务,根据这一特性我们就可以在zuul做统一的登录检查,下游的微服务不再处理登录检查逻辑。

十一.服务网关-spring cloud zuul

1.理解zuul

1.1.为什么要zuul

试想一下如果我们有很多的微服务,他们都需要登录之后才能访问,那么我需要在每个微服务都去做一套登录检查逻辑,这样是不是会存在大量重复的代码和工作量,我们希望的是把登录检查这种公共的逻辑进行统一的抽取,只需要做一套检查逻辑即可,而zuul就可以用来干这类事情,我们可以把zuul看做是微服务的大门,所有的请求都需要通过zuul将请求分发到其他微服务,根据这一特性我们就可以在zuul做统一的登录检查,下游的微服务不再处理登录检查逻辑。

1.2.什么是zuul

Zuul 是netflix开源的一个API Gateway 服务器, 本质上是一个web servlet(filter)应用。Zuul 在云平台上提供动态路由(请求分发),监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门,也要注册入Eureka,用一张图来理解zuul在架构中的的角色:

需要注意的是,zuul本身是一个独立的服务,默认集成了Ribbon,zuul通过Ribbon将客户端的请求分发到下游的微服务,所以zuul需要通过Eureka做服务发行,同时zuul也集成了Hystrix。

根据上图理解 ,我们需要建立独立的工程去搭建Zuul服务,同时需要把Zuul注册到EurekaServer,因为当请求过来时,zuul需要通过EurekaServer获取下游的微服务通信地址,使用Ribbon发起调用。

2.zuul的搭建

搭建zuul工程springcloud-zuul-server-1060 ,搭建好的工程模块如下:

springcloud-parent
pom.xml
  springcloud-zuul-server-1060  //网关服务
  springcloud-hystrix-turbine-1050 //聚合监控服务
  springcloud-eureka-server-1010
  springcloud-order-server-1030
  springcloud-pay-server-1040
  springcloud-user-common
  springcloud-user-server-1020

修改 springcloud-zuul-server-1060 ,集成EurekaClient和zuul

2.1.导入依赖

因为Zuul需要通过Eureak做服务发现,所以我们导入了eureka-client基础依赖,和Zuul自身的基础依赖:netflix-zuul

<?xmlversion="1.0" encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>springcloud-parent</artifactId><groupId>cn.itsource.springboot</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>springcloud-zuul-server-1060</artifactId><name>springcloud-zuul-server-1060</name><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-zuul</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies></project>

2.2.配置开开启Zuul

配置类通过 @EnableZuulProxy 注解开启zuul服务功能。

/*** 用户的启动类* @EnableEurekaClient: 标记该应用是 Eureka客户端* @EnableZuulProxy : 开启zuul 可以看做是 @EnableZuulServer 的增强版 ,一般用这个* @EnableZuulServer : 这个标签也可以开启zuul,但是这个标签开启的Filter更少*/@SpringBootApplication@EnableEurekaClient@EnableZuulProxypublicclassZuulServerApplication1060{
publicstaticvoidmain( String[] args )
    {
SpringApplication.run(ZuulServerApplication1060.class);
    }
}

2.3.配置文件配置zuul

这里配置两个东西,一个是EurekaClien的配置,让zuul注册到EurekaServer,二个就是zuul的配置了

#注册到EurekaServereureka:  client:    serviceUrl:      defaultZone: http://peer1:1010/eureka/,http://peer2:1011/eureka/,http://peer3:1012/eureka/
#使用ip地址进行注册  instance:    prefer-ip-address: true#要指定服务的实例ID    instance-id:  zuul-server:1060server:  port: 1060spring:  application:    name: zuul-server  #服务名zuul:  prefix: "/servers"#统一访问前缀  ignoredServices: "*"#禁用掉使用浏览器通过服务名的方式访问服务  routes:    pay-server: "/pay/**"#指定pay-server这个服务使用 /pay路径来访问  - 别名    order-server: "/order/**"#指定order-server这个服务使用 /order路径来访问

提示: 我们对zuul主要做了三个配置

  • zuul.prefix : 作为统一的前缀,在浏览器访问的时候需要加上该前缀
  • zuul.ignoredServices : 忽略使用服务名方式访问服务,而是通过routes指定的路径进行访问
  • zuul.routes : 配置服务的访问路径

注意:在么有使用zuul之前我们是通过 http://localhost:1040/pay/1 来直接访问支付服务,现在需要通过zuul来访问,格式如下:http:// zuul的ip : zuul的port /zuul前缀 / 服务路径 /服务的controller路径 ,即:

http://localhost:1060/servers/pay/pay/1

特别说明:其实这里我们直接浏览器也能访问到目标服务,即可以通过: http://localhost:1040/pay/1 绕过zuul,但是这种情况不用担心,因为在产品上线的时候我们都是内网部署,只有zuul我们部署成外网,也就是说直接访问目标微服务的方式是访问不到的,所以我们只需要通过zuul访问即可。

2.4.测试zuul

浏览器访问:http://localhost:1060/servers/pay/pay/1 ,看到如下信息:

说明我们已经成功的通过zuul访问到了pay服务

3.自定义zuul的Filter

3.1.zuul的工作原理

zuul的底层是通过各种Filter来实现的,zuul中的filter按照执行顺序分为了“pre”前置(”custom”自定义一般是前置),“routing”路由,“post”后置,以及“error”异常Filter组成,当各种Filter出现了异常,请求会跳转到“error filter”,然后再经过“post filter” 最后返回结果,下面是Filter的执行流程图:

提示:

  • 正常流程:
  • 请求到达首先会经过pre类型过滤器,而后到达routing类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。
  • 异常流程:
  • 整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,再error处理完毕后,会将请求交给POST过滤器,最后返回给用户。
  • 如果是error过滤器自己出现异常,最终也会进入POST过滤器,而后返回。
  • 如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同的时,请求不会再到达POST过滤器了。

3.2.ZuulFilter

Zuul提供了一个抽象的Filter:ZuulFilter我们可以通过该抽象类来自定义Filter,该Filter有四个核心方法,如下:

publicabstractclassZuulFilterimplementsIZuulFilter{
abstractpublicStringfilterType();
abstractpublicintfilterOrder();
//下面两个方法是 IZuulFilter 提供的 booleanshouldFilter();
Objectrun() throwsZuulException;
}

提示:

  • filterType :是用来指定filter的类型的(类型见常量类:FilterConstants)
  • filterOrder :是filter的执行顺序,越小越先执行
  • shouldFilter :是其父接口IZuulFilter的方法,用来决定run方法是否要被执行
  • run :是其父接口IZuulFilter的方法,该方法是Filter的核心业务方法

3.3.自定义Filter

我们来演示一个案例,在Zuul层实现统一的登录检查:如果请求头中有“token”属性,我们就认为已经登录成功,可以继续往下游的服务执行,否则就视为请求未登录,直接返回错误信息,这一需求需要自定义Filter继承ZuulFilter类来实现,具体代码如下

packagecn.itsource.springboot.filter;
importcom.alibaba.fastjson.JSON;
importcom.netflix.zuul.ZuulFilter;
importcom.netflix.zuul.context.RequestContext;
importcom.netflix.zuul.exception.ZuulException;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
importorg.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
importorg.springframework.stereotype.Component;
importorg.springframework.util.StringUtils;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importjava.io.IOException;
importjava.util.HashMap;
importjava.util.Map;
@ComponentpublicclassLoginCheckFilterextendsZuulFilter {
privatestaticfinalLoggerlog=LoggerFactory.getLogger(LoginCheckFilter.class);
//执行顺序privatestaticfinalintORDER=1;
//filter类型 : "pre"前置@OverridepublicStringfilterType() {
returnFilterConstants.PRE_TYPE;    //pre    }
//执行顺序@OverridepublicintfilterOrder() {
returnORDER;
    }
//返回结果决定 是否要执行run方法@OverridepublicbooleanshouldFilter() {
// /static/**  ,/login , /register 不需要做登录检查,返回false//1.获取request对象 , 获取请求中的urlHttpServletRequestrequest=RequestContext.getCurrentContext().getRequest();
Stringurl=request.getRequestURI();
log.info("请求地址:"+url);
//2.判断是否包含在: static/**  ,/login , /registerif(url.endsWith("/login ") ||url.endsWith("/register ") ||url.contains("/static/") ){
returnfalse;
        }
//要做登录检查的返回truereturntrue;
    }
//核心业务方法 : 登录检查 , 如果请求头中有token,就是登录成功@OverridepublicObjectrun() {
//1.获取请求对象HttpServletRequestrequest=RequestContext.getCurrentContext().getRequest();
//响应对象HttpServletResponseresponse=RequestContext.getCurrentContext().getResponse();
//2.获取请求头中的 tokenStringtoken=request.getHeader("token");
//3.如果没有token,登录检查失败 ,if(!StringUtils.hasLength(token)){
//3.1.返回登录检查失败的错误信息 :{ success:false, message:"登录检查失败,请重新登录"}Map<String,Object>resultMap=newHashMap<>();
resultMap.put("success" , false);
resultMap.put("message" , "登录检查失败,请重新登录");
//中文编码response.setContentType("application/json;charset=utf-8");
//把map转成json字符串,写到浏览器StringresultJsonString=JSON.toJSONString(resultMap);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
try {
response.getWriter().print(resultJsonString);
            } catch (IOExceptione) {
e.printStackTrace();
            }
//3.2.阻止filter继续往后执行RequestContext.getCurrentContext().setSendZuulResponse(false);
        }
//这里的返回结果没有任何意义,不用管returnnull;
    }
}

提示:

  • 在 filterType方法中我们返回“pre”前置filter的常量,让他成为前置filter(登录检查需要在请求的最前面来做)
  • 在filterOrder方法中返回的顺序值是 1 ,执行顺序越小越先执行
  • 在shouldFilter方法中通过判断请求的url来决定是否需要做登录检查,返回true就是要做然后才会执行run方法
  • 在run方法中我们通过获取请求头中的token判断是否登录,如果没登录就返回错误信息,阻止继续执行。
  • RequestContext.getCurrentContext() 是一个Zuul提供的请求上下文对象

注意:在返回JSON格式的错误信息时我用到了fastjson,需要在zuul工程中导入依赖

<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.50</version></dependency>

3.4.测试

启动zuul,浏览器访问:http://localhost:1060/servers/pay/1 ,当没登录的情况下浏览器应该会返回如下信息代表登录检查起作用了:

使用postmain,发送post请求,在header中添加token值,如下:

4.zuul的熔断器配置

zuul作为服务网关面向的是客户端(浏览器),当服务调用链路出现异常,我们不希望直接把异常信息抛给客户端,而是希望触发降级,返回友好的提示信息,所以我们需要去配置zuul的熔断机制。

在zuul中要实现熔断功能需要实现ZuulFallbackProvider接口,该接口提供了两个方法:getRoute用来指定熔断功能应用于哪些路由的服务,fallbackResponse方法为熔断功能时执行的方法(用来返回托底数据) ,接口代码如下:

publicinterfaceFallbackProvider {
/*** The route this fallback will be used for.* @return The route the fallback will be used for.*/publicStringgetRoute();
/*** Provides a fallback response based on the cause of the failed execution.** @param route The route the fallback is for* @param cause cause of the main method failure, may be <code>null</code>* @return the fallback response*/ClientHttpResponsefallbackResponse(Stringroute, Throwablecause);
}

4.1.编码实战

我们来对对pay-server做一个熔断器配置,当zuul先pay-server发起调用,服务调用失败,触发熔断,返回一句话“抱歉,服务不可用”

importjava.io.ByteArrayInputStream;
importjava.io.IOException;
importjava.io.InputStream;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
importorg.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
importorg.springframework.http.HttpHeaders;
importorg.springframework.http.HttpStatus;
importorg.springframework.http.MediaType;
importorg.springframework.http.client.ClientHttpResponse;
importorg.springframework.stereotype.Component;
@ComponentpublicclassPayServerFallbackimplementsFallbackProvider {
privatefinalLoggerlogger=LoggerFactory.getLogger(FallbackProvider.class);
// 指定要处理的服务。@OverridepublicStringgetRoute() {
return"pay-server";  //"*"代表所有服务都有作用    }
/*** @param route :服务的路由* @param cause : 异常* @return ClientHttpResponse:熔断后的换回值*/@OverridepublicClientHttpResponsefallbackResponse(Stringroute, Throwablecause) {
returnnewClientHttpResponse() {
@OverridepublicHttpStatusgetStatusCode() throwsIOException {
returnHttpStatus.OK;
            }
@OverridepublicintgetRawStatusCode() throwsIOException {
return200;
            }
@OverridepublicStringgetStatusText() throwsIOException {
return"OK";
            }
@Overridepublicvoidclose() {
            }
@OverridepublicInputStreamgetBody() throwsIOException {
returnnewByteArrayInputStream("抱歉,服务不可用".getBytes());
            }
@OverridepublicHttpHeadersgetHeaders() {
HttpHeadersheaders=newHttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
returnheaders;
            }
        };
    }
}

提示:如果要让此熔断功能作用于所有的服务,将 getRoute 方法中的返回值修改为“*”即可。

4.2.测试

浏览器访问:http://localhost:1060/servers/pay/1 ,关闭支付服务,浏览器应该会看到 “抱歉,服务不可用” 提示信息。

5.zuul的参数配置

5.1.超时配置

Zuul集成了hystrix,如果服务的调用

链过长,或者ribbon调用事件过长,可能会触发Hystrix的熔断机制,导致请求拿不到正常的结果,我们通常会对Ribbon和Hystrix的超时时间配置。如下配置对所有消费者微服务都有用:

zuul配置文件加上如下配置:

zuul:  retryable: true #是否开启重试功能ribbon:  MaxAutoRetries: 1 #对当前服务的重试次数  MaxAutoRetriesNextServer: 1 #切换相同Server的次数  OkToRetryOnAllOperations: false # 对所有的操作请求都进行重试,如post就不能重试,如果没做幂等处理,重试多次post会造成数据的多次添加或修改  ConnectTimeout: 3000 #请求连接的超时时间  ReadTimeout: 5000 #请求处理的超时时间hystrix:  command:    default:      execution:        isolation:          thread:            timeoutInMilliseconds: 10000#如果配置ribbon的重试,hystrix的超时时间要大于ribbon的超时时间

5.2.zuul的饥饿加载

在学习Ribbon的章节我们为了让服务加快第一次调用,我们可以通过设置Ribbon的饥饿加载,zuul底层通过Ribbon实现负载均衡器,所以也需要指定饥饿加载,配置如下:

zuul:  ribbon:    eager-load.enabled: true    # 饥饿加载

需要注意的是,zuul是通过读取路由配置来实现饥饿加载的,所以如果要让eager-load.enabled: true起作用,我们一般不会使用默认的路由方式,而是单独配置路由规则,如上配置。

目录
相关文章
|
2月前
|
存储 数据可视化 Java
基于MicrometerTracing门面和Zipkin实现集成springcloud2023的服务追踪
Sleuth将会停止维护,Sleuth最新版本也只支持springboot2。作为替代可以使用MicrometerTracing在微服务中作为服务追踪的工具。
158 1
|
3天前
|
存储 安全 Java
Spring Security 入门
Spring Security 是 Spring 框架中的安全模块,提供强大的认证和授权功能,支持防止常见攻击(如 CSRF 和会话固定攻击)。它通过过滤器链拦截请求,核心概念包括认证、授权和自定义过滤器。配置方面,涉及密码加密、用户信息服务、认证提供者及过滤器链设置。示例代码展示了如何配置登录、注销、CSRF防护等。常见问题包括循环重定向、静态资源被拦截和登录失败未返回错误信息,解决方法需确保路径正确和添加错误提示逻辑。
Spring Security 入门
|
4天前
|
SpringCloudAlibaba Dubbo Java
【SpringCloud Alibaba系列】Dubbo基础入门篇
Dubbo是一款高性能、轻量级的开源Java RPC框架,提供面向接口代理的高性能RPC调用、智能负载均衡、服务自动注册和发现、运行期流量调度、可视化服务治理和运维等功能。
【SpringCloud Alibaba系列】Dubbo基础入门篇
|
27天前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
48 6
Spring Boot 入门:简化 Java Web 开发的强大工具
|
4月前
|
缓存 NoSQL Java
【Azure Redis 缓存】示例使用 redisson-spring-boot-starter 连接/使用 Azure Redis 服务
【Azure Redis 缓存】示例使用 redisson-spring-boot-starter 连接/使用 Azure Redis 服务
|
24天前
|
Java 数据库连接 数据库
从入门到精通---深入剖析Spring DAO
在Java企业级开发中,Spring框架以其强大的功能和灵活性,成为众多开发者的首选。Spring DAO(Data Access Object)作为Spring框架中处理数据访问的重要模块,对JDBC进行了抽象封装,极大地简化了数据访问异常的处理,并能统一管理JDBC事务。本文将从概述、功能点、背景、业务点、底层原理等多个方面深入剖析Spring DAO,并通过多个Java示例展示其应用实践,同时指出对应实践的优缺点。
22 1
|
28天前
|
监控 Java 数据安全/隐私保护
如何用Spring Boot实现拦截器:从入门到实践
如何用Spring Boot实现拦截器:从入门到实践
48 5
|
1月前
|
JSON Java 测试技术
SpringCloud2023实战之接口服务测试工具SpringBootTest
SpringBootTest同时集成了JUnit Jupiter、AssertJ、Hamcrest测试辅助库,使得更容易编写但愿测试代码。
65 3
|
2月前
|
Dubbo Java 应用服务中间件
Dubbo学习圣经:从入门到精通 Dubbo3.0 + SpringCloud Alibaba 微服务基础框架
尼恩团队的15大技术圣经,旨在帮助开发者系统化、体系化地掌握核心技术,提升技术实力,从而在面试和工作中脱颖而出。本文介绍了如何使用Dubbo3.0与Spring Cloud Gateway进行整合,解决传统Dubbo架构缺乏HTTP入口的问题,实现高性能的微服务网关。
|
2月前
|
Java API Spring
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
这篇文章是关于Spring Boot 2.x中拦截器的入门教程和实战项目场景实现的详细指南。
35 0
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现