实现微服务网关的技术有很多,

  • 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

微服务网关 - 图1
微服务网关 - 图2

微服务网关 - 图3

代理、路由和过滤—-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

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
  4. </dependency>

启动类

  1. @EnableDiscoveryClient
  2. @EnableZuulProxy // 开启路由网关
  3. @SpringBootApplication
  4. @EnableCircuitBreaker // Hystrix
  5. public class ZuulApplication {
  6. public static void main(String[] args) {
  7. SpringApplication.run(ZuulApplication.class, args);
  8. }
  9. }

yml(要注册到eureka)

  1. server:
  2. port: 9101
  3. spring:
  4. application:
  5. name: eureka-client-gateway-zuul
  6. eureka:
  7. instance:
  8. instanceId: ${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}
  9. lease-expiration-duration-in-seconds: 1
  10. lease-renewal-interval-in-seconds: 2
  11. prefer-ip-address: true
  12. client:
  13. service-url:
  14. defaultZone: http://localhost:8760/eureka,http://localhost:8761/eureka
  15. info:
  16. app.name: gateway-zuul

配置路由

  1. 统一前缀

    zuul:
    prefix: /zuul
    
  2. 正常配置

zuul:
  routes:
    api-a:
      path: /user/**
      serviceId: eureka-client-user
    api-b:
      path: /movie/**
      serviceId: eureka-client-movie
  1. 屏蔽微服务名称
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
  1. 屏蔽路径

通过该方式可以限制用户的权限

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网关的使用链接