实现微服务网关的技术有很多,
- SLB概念
负载均衡(Server Load Balancer)是将访问流量根据转发策略分发到后端多台云服务器(Elastic Compute Service,简称 ECS)的流量分发控制服务, 分给多个可用区
- nginx Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务
- zuul ,Zuul 是 Netflix 出品的一个基于 JVM 路由和服务端的负载均衡器。
- spring-cloud-gateway, 是spring 出品的 基于spring 的网关项目,集成断路器,路径重写,性能比Zuul好。
网关主要用途
(1)统一入口
为全部微服务提供唯一入口点,网关起到内部和外部隔离,保障了后台服务的安全性。
(2)鉴权校验
识别每个请求的 权限,拒绝不符合要求的请求。
(3)动态路由转发
动态的将请求 路由 到不同的后端集群中。
(4)降低耦合度
减少客户端与服务的 耦合 ,服务可以独立发展。通过网关层来做映射。
防止每个服务都写一遍权限校验
官网: https://springcloud.cc/spring-cloud-dalston.html#_router_and_filter_zuul
springcloud版本: 【Finchley 版】
网关比较总结
停止维护 版本1.X
基于servlet,来一个请求一个线程,使用filter过滤分发(适合实时请求)
停止维护 版本2.X
,通过netty Server接受请求,保存请求,通过另一种filter处理,通过netty Client调用服务(适合IO)
gateway基于zuul2.X
代理、路由和过滤—-Zuul
- Zuul是Netflix的基于JVM的 路由器和过滤器
- 将外部请求转发到具体的微服务实例上,实现外部统一入口
- 最终注册到eureka
Spring Cloud Zuul和Spring Cloud Eureka进行整合,将自身注册为Eureka服务治理下的应用,同时从Eureka中获得了所有微服务的实例信息。这就使得维护实例的工作交给服务治理框架来完成,不再需要人工介入。对于路由规则的维护,Zuul默认会将通过以服务名作为ContextPath的方式来创建路由映射,大部分情况下这么的默认设置已经可以实现我们大部分的路由需求,除了一些特殊情况(比如兼容老的URL)还需要做一些特别的配置,但是已经大大减少了运维的工作量。
Zuul很容易实现 负载均衡、智能路由 和 熔断器
可以做身份认证和权限认证
可以实现监控,在高流量状态下,对服务进行降级。
zuul 2.x 应用
zuul 1.x 依赖spring-cloud-starter-zuul
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
启动类
@EnableDiscoveryClient
@EnableZuulProxy // 开启路由网关
@SpringBootApplication
@EnableCircuitBreaker // Hystrix
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
yml(要注册到eureka)
server:
port: 9101
spring:
application:
name: eureka-client-gateway-zuul
eureka:
instance:
instanceId: ${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}
lease-expiration-duration-in-seconds: 1
lease-renewal-interval-in-seconds: 2
prefer-ip-address: true
client:
service-url:
defaultZone: http://localhost:8760/eureka,http://localhost:8761/eureka
info:
app.name: gateway-zuul
配置路由
统一前缀
zuul: prefix: /zuul
正常配置
zuul:
routes:
api-a:
path: /user/**
serviceId: eureka-client-user
api-b:
path: /movie/**
serviceId: eureka-client-movie
- 屏蔽微服务名称
zuul:
#屏蔽所有 "*"
ignored-services: eureka-client-user,eureka-client-movie
prefix: /cloud # 设置前缀
routes:
api-a:
path: /user/**
serviceId: eureka-client-user
api-b:
path: /movie/**
serviceId: eureka-client-movie
- 屏蔽路径
通过该方式可以限制用户的权限
zuul:
ignore-patterns:**/auto/**
配置过滤器
实现 限流 ,灰度发布 ,权限控制 等等
过滤器类型:Pre、Routing、Post
前置Pre就是在请求之前进行过滤,Routing路由过滤器就是我们上面所讲的路由策略,而Post后置过滤器就是在 Response
之前进行过滤的过滤器。
// 返回过滤器类型 这里是前置过滤器
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
// 指定过滤顺序 越小越先执行,这里第一个执行
// 当然不是只真正第一个 在Zuul内置中有其他过滤器会先执行
// 那是写死的 比如 SERVLET_DETECTION_FILTER_ORDER = -3
@Override
public int filterOrder() {
return 0;
}
// 什么时候该进行过滤
// 这里我们可以进行一些判断,这样我们就可以过滤掉一些不符合规定的请求等等
@Override
public boolean shouldFilter() {
RequestContext context = RequestContext.getCurrentContext();
if(!RATE_LIMITER.tryAcquire()) {
log.warn(“访问量超载”);
// 指定当前请求未通过过滤
context.setSendZuulResponse(false);
// 向客户端返回响应码429,请求数量过多
context.setResponseStatusCode(429);
return false;
}
return true;
}
// 如果过滤器允许通过则怎么进行处理
@Override
public Object run() throws ZuulException {
// 这里我设置了全局的RequestContext并记录了请求开始时间
RequestContext ctx = RequestContext.getCurrentContext();
ctx.set(“startTime”, System.currentTimeMillis());
return null;
}
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
// 从RequestContext获取原先的开始时间 并通过它计算整个时间间隔
Long startTime = (Long) context.get(“startTime”);
// 这里我可以获取HttpServletRequest来获取URI并且打印出来
String uri = request.getRequestURI();
long duration = System.currentTimeMillis() - startTime;
log.info(“uri: “ + uri + “, duration: “ + duration / 100 + “ms”);
return null;
@Slf4j
@Component
public class PreRequestFilter extends ZuulFilter {
@Override
public String filterType() {
// pre filter
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
//获取当前请求的请求上下文
RequestContext requestContext = RequestContext.getCurrentContext();
//记录请求进入时间
requestContext.set("api_request_time", System.currentTimeMillis());
return null;
}
}
/**
* AccessLogFilter for 记录服务请求结束时间,配合{@link PreRequestFilter}计算整个调用请求链路消耗时间
*
* @author
* @since 2019/6/13
*/
@Slf4j
@Component
public class AccessLogFilter extends ZuulFilter {
@Override
public String filterType() {
return FilterConstants.POST_TYPE;
}
@Override
public int filterOrder() {
//需要最后一个执行的filter
return FilterConstants.SEND_RESPONSE_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
log.info("Request \"{}\" spent : {} ms.", request.getRequestURI(),
(System.currentTimeMillis() - Long.valueOf(requestContext.get("api_request_time").toString())));
return null;
}
}
token校验
/**
* ValidateTokenFilter for 服务token校验
*
* @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang</a>
* @see
* @since 2019/6/13
*/
@Slf4j
@Component
public class ValidateTokenFilter extends ZuulFilter {
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
log.info(String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString()));
Object accessToken = request.getHeader("accessToken"); //.getParameter("accessToken");
if (accessToken == null) {
log.warn("access token is empty");
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
// ctx.setResponseBody(body)对返回body内容进行编辑
return null;
}
log.info("access token ok");
return null;
}
}
Gateway应用
Gateway网关的使用链接