为什么要使用微服务网关
不同的微服务一般会有不同的网络地址(IP和port不一样),而外部客户端(例如手机APP)可能需要调用多个服务的接口才能完成一个业务需求。例如一个电影购票的手机APP,可能会调用多个微服务的接口,才能完成一次购票的业务流程。
如果让客户端直接与各个微服务通信,会有以下的问题:
1. 客户端会多次请求不同的微服务,增加了客户端的复杂性。
2. 存在跨域请求,在一定场景下处理相对复杂。
3. 认证复杂,每个服务都需要独立认证。
4. 难以重构,随着项目的迭代,可能需要重新划分微服务。例如,可能将多个服务合并成一个或者将一个服务拆分成多个。如果客户端直接与微服务通信,那么重构将会很难实施。
5. 某些微服务可能使用了防火墙/浏览器不友好的协议,直接访问会有一定的困难。
以上问题可借助微服务网关解决。微服务网关是介于客户端和服务器端之间的中间层, 所有的外部请求都会先经过微服务网关。
使用微服务网关后,架构可演变成下图:
如图,微服务网关封装了应用程序的内部结构,客户端只须跟网关交互,而无须直接调 用特定微服务的接口。
这样,开发就可以得到简化。不仅如此,使用微服务网关还有以下优点:
1. 易于监控。可在微服务网关收集监控数据并将其推送到外部系统进行分析。
2. 易于认证。可在微服务网关上进行认证,然后再将请求转发到后端的微服务,而无须在每个微服务中进行认证。
3. 减少了客户端与各个微服务之间的交互次数。
Zuul路由网关
Zuul包含了对请求的路由和过滤两个最主要的功能。
其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础,而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验,服务聚合等功能的基础。Zuul和Eureka进行整合,将Zuul自身注册到Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得。
注意:Zuul服务最终还是会注册到Eureka。
提供:代理+路由+过滤三大功能。
简单测试demo:
1.首先需要添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
2.在启动类上开启zuul代理
package com.maorui.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
/**
* @Author
* @Description TODO
* @Date 2021:04:21 23:16
*/
@SpringBootApplication
//开启EnableEurekaClient注解,目前版本如果配置了Eureka注册中心,默认会开启该注解
@EnableEurekaClient
@EnableZuulProxy
public class ServiceZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceZuulApplication.class);
}
}
3.将服务注册到eureka上(application.properties中进行配置)
server.port=7072
spring.application.name=service-zuul
#---------Eureka相关配置----------
#配置Eureka server注册中心
eureka.instance.hostman=service-zuul
#设置服务注册中心地址,指向另一个注册中心
#是否使用ip地址注册
eureka.instance.prefer-ip-address=true
eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}
#注册中心对外暴露的注册地址
#eureka.client.server-url.defaultZone=http://localhost:8761/eureka/,http://localhost:8762/eureka/
eureka.client.server-url.defaultZone=http://localhost:8761/eureka/
4.测试。
先测试一下不经过zuul,直接调用consumer服务的接口。
其中9090为consumer服务的端口。
使用zuul进行转发(注意看下请求url的不同)。
说明默认情况下,Zuul会代理所有注册到Eureka Server的微服务,并且Zuul的路由规则如下:http://ZUUL_HOST:ZUUL_PORT/微服务在Eureka 注册中心上的serviced/** 会被转发到 serviceld 对应的微服务。
注意:需要加上consumer实例的服务名。其中7072为zuul的端口。
zuul路由规则配置说明:
#---------zuul相关配置,使所有请求经过网关进行路由转发,无需再记一个个的端口----------
#忽略指定微服务,就可以不代理如下微服务,多个微服务之间用“,”隔开,使用'*'可忽略所有微服务
#zuul.ignored-services=service-consumer,service-consumer1
#传统路由方式,不依赖服务发现机制,通过在配置文件中具体指定每个路由表达式与服务实例的映射关系来实现API网关对外部请求的路由。
#单实例配置
#zuul.routes.service-consumer.path=/service-consumer/**
#zuul.routes.service-consumer.url=http://127.0.0.1:9090
#也可以这样配置
zuul.routes.service-consumer.path=/service-consumer/**
zuul.routes.service-consumer.serviceId=service-consumer
#路由前缀。可以全局为路由规则增加前缀信息,会在网关上的路由规则都增加/api前缀。
zuul.prefix=/api
zuul过滤器
Zuul作为网关的其中一个重要功能,就是实现请求的鉴权。而这个动作我们往往是通过Zuul提供的过滤器来实现的。
ZuulFilter是zuul中实现的过滤器的顶级父类,这里我们看一下其中定义的四个最重要的方法:
public abstract ZuulFilter implements IZuulFilter{
abstract public String filterType();
abstract public int filterOrder();
boolean shouldFilter();// 来自IZuulFilter
Object run() throws ZuulException;// IZuulFilter
}
- shouldFilter():要不要过滤,返回一个boolean值,判断过滤器是否需要执行,需要返回true,不需要返回false。
- filterType(); 过滤类型,可以使用FilterConstants调用其内的常量,也可以使用下面的字符串常量
- pre:前置,在路由之前执行
- routing:在路由请求时调用
- post:在routing和error过滤器之后调用
- error:处理请求时发生错误调用
- filterOrder():过滤顺序 通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。同样可以使用FilterConstants中的常量设置,其中定义了zuul中内置过滤器的顺序,我们可以根据这个顺序来指定自己过滤器的顺序,一般借助这些过滤器的值+1或者-1。
- run():过滤器的具体业务逻辑
- 正常流程:
- 请求到达首先会经过pre类型过滤器,而后到达routing类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。
- 异常流程:
- 整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,再error处理完毕后,会将请求交给POST过滤器,最后返回给用户。
- 如果是error过滤器自己出现异常,最终也会进入POST过滤器,而后返回。
- 如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同的时,请求不会再到达POST过滤器了。
Zuul过滤器的使用场景
- 权限鉴定:一般用在pre类型,如果发现没有访问权限就直接拦截。
- 异常处理:一般会放在error类型,亦或是post类型过滤器中结合使用。
- 服务调用时长统计:pre结合post使用。
- 流量限制:在pre和post的配合下可以判断在线人数,可以根据在线人数进行人数限制
自定义过滤器
自定义一个过滤器,模拟一个登录的校验。基本逻辑:如果请求中有access-token参数,则认为请求有效,放行。
1. 创建一个filter类,继承ZuulFilter,加上@Component注解。并进行相关配置。
package com.maorui.springcloud.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
* @author:
* @description: TODO
* @date: 2021/7/20 15:34
* @since: V1.0
*/
@Component
public class LoginFilter extends ZuulFilter {
@Override
public String filterType() {//指定该过滤器什么时候使用
//pre代表请求处理前,post代表请求处理后
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
//指定过滤器优先级别,数字越小越优先
return 0;
}
@Override
public boolean shouldFilter() {//当前过滤器是否开启
//true代表开启 false代表关闭
return true;
}
@Override
public Object run() throws ZuulException {
//上下文
RequestContext ctx = RequestContext.getCurrentContext();
//获取request对象
HttpServletRequest request = ctx.getRequest();
//获取请求参数
String token = request.getParameter("access-token");
//判断token是否存在
if(StringUtils.isBlank(token)){
//设置true放行,设置为false拦截
ctx.setSendZuulResponse(false);
//返回一个状态码,从HttpStatus中获取
ctx.setResponseStatusCode(HttpStatus.FORBIDDEN.value());
}
return null;
}
}
2.测试。
url:http://10.192.140.1:7072/api/service-consumer/order/1
url: http://10.192.140.1:7072/api/service-consumer/order/1?access-token=123
参考文档:
https://blog.csdn.net/qq_44112474/article/details/109094775