前几章总结:

  • eureka实现高可用的服务注册中心,实现微服务的注册和发现
  • rubbon或feign实现服务间负载均衡的接口调用
  • 对于依赖的服务调用使用hystrix进行包装,实现线程隔离并加入熔断机制,避免在微服务架构中因为个别服务出现异常而引起级联故障蔓延

这套架构目前存在的问题:

  • 运维维护路由规则和服务实例列表难度大
  • 微服务访问各应用时的前置校验冗余, 这部分与业务无关

因此引入API网关,
API网关是一个更智能的应用服务器, 类似于面向对象设计模式中的外观模式, 作为整个微服务架构系统的门面,所有的外部客户端访问都需要经过它进行调度和过滤, 除了要实现请求路由, 负载均衡, 校验过滤等功能之外, 还需要更多能力,比如与服务治理框架结合, 请求转发时的熔断机制,服务的聚合等一系列高级功能。

spring cloud zuul

  • 服务实例维护:zuul将自己注册到eureka中拿到实例列表
  • 路由规则: 默认以服务名作为contextPath来创建路由映射
  • 签名,登录校验:zuul提供了过滤器机制,通过校验的才会路由到指定的微服务接口

总结:
image.png

快速入门

依赖

  1. <!--eureka-client-->
  2. <dependency>
  3. <groupId>org.springframework.cloud</groupId>
  4. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  5. </dependency>
  6. <!--openfeign-->
  7. <dependency>
  8. <groupId>org.springframework.cloud</groupId>
  9. <artifactId>spring-cloud-starter-openfeign</artifactId>
  10. </dependency>

启动类加注解

  1. @EnableZuulProxy
  2. @SpringCloudApplication
  3. public class ApiApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(ApiApplication.class, args);
  6. }
  7. }

配置路由映射

  1. server:
  2. port: 30005
  3. eureka:
  4. client:
  5. service-url:
  6. defaultZone: http://localhost:30000/eureka/
  7. spring:
  8. application:
  9. name: API-GATEWAY
  10. zuul:
  11. routes:
  12. hello-service:
  13. path: /hello/**
  14. serviceId: HELLO-SERVICE
  15. strip-prefix: false # 则会带上/hello, 如 http://HELLO-SERVICE/hello/xxx
  16. feign-consumer:
  17. path: /feign/**
  18. serviceId: FEIGN-CONSUMER # 默认不带/feign, 如http://FEIGN-CONSUMER/xxx

补充:
更简洁的配置, 直接 zuul.routes.=.

  1. zuul:
  2. routes:
  3. feign-consumer:
  4. path: /feign/**

测试

  1. @RestController
  2. public class HelloController {
  3. @GetMapping("/hello")
  4. public String test() {
  5. System.out.println("这里是helloService!, 当前时间为: " + LocalDateTime.now());
  6. return "hello! 这里是helloService!";
  7. }
  8. }
  9. ----------------------
  10. @RestController
  11. public class FeignController {
  12. @GetMapping
  13. public String test2() {
  14. System.out.println("这里是feignService!, 当前时间为: " + LocalDateTime.now());
  15. return "hello! 这里是feignService!";
  16. }
  17. }

请求过滤

  1. @Component
  2. public class TokenFilter extends ZuulFilter {
  3. @Override
  4. public String filterType() {
  5. return FilterConstants.PRE_TYPE;
  6. }
  7. @Override
  8. public int filterOrder() {
  9. return 0;
  10. }
  11. @Override
  12. public boolean shouldFilter() {
  13. return true;
  14. }
  15. @Override
  16. public Object run() throws ZuulException {
  17. RequestContext ctx = RequestContext.getCurrentContext();
  18. HttpServletRequest request = ctx.getRequest();
  19. if (request.getHeader("accessToken") == null) {
  20. ctx.setSendZuulResponse(false);
  21. ctx.setResponseStatusCode(401);
  22. return null;
  23. }
  24. return null;
  25. }
  26. }

路由详解

当请求到达API网关时,网关会根据请求路径找到最佳匹配的path规则,将请求路由到具体的serviceId上;由于API网关整合了eureka, 会将自身注册进注册中心并获取服务列表;因此网关根据ribbon的负载均衡策略从serviceId对应的实例清单中选择一个具体的实例进行转发即完成路由工作了。

传统路由方式

image.png

面向服务的路由方式,见示例

默认路由规则,

zuul整合的eureka会默认生成一个服务名的路由, 与配置文件中的路由同时生效
如下配置,则/feign/, /feign-consumer/都对应着feign-consumer服务

  1. zuul:
  2. routes:
  3. feign-consumer:
  4. path: /feign/**

禁用默认路由规则

  1. zuul:
  2. ignored-services: feign-consumer

自定义路由映射规则,

符合自定义规则的服务名走自定义,不符合的走配置

  1. @Configuration
  2. public class GateWayConfig {
  3. /**
  4. * 为userservice-v1自动创建/v1/userservice/**的路由匹配规则
  5. *
  6. * @return
  7. */
  8. @Bean
  9. public PatternServiceRouteMapper serviceRouteMapper() {
  10. return new PatternServiceRouteMapper("(?<name>^.+)-(?<version>v.+$)", "${version}/${name}");
  11. }
  12. }

路径匹配

  • ? 匹配任意单个字符
    • 匹配任意数量的字符
  • ** 匹配任意数量的字符, 支持多级目录

当出现一个url被两个路由规则匹配时, 由于路由匹配是通过线性遍历的方式,因此需要注意调整配置的顺序;
如下的配置才能保证/user-service/ext/生效**
image.png

忽略表达式

此表达式是针对所有的路由

  1. zuul:
  2. ignored-patterns: /**/hello/**

本地跳转

如下配置, 如果访问 /feign/hello, 会被转发到 /local/hello

  1. zuul:
  2. routes:
  3. feign-consumer:
  4. path: /feign/**
  5. url: forward:/local

cookie与头信息

过滤敏感信息

默认情况下, zuul会过滤掉HTTP请求头中的一些敏感信息, 包括Cookie, Set-cookie, Authrization三个属性
由于鉴权需要这些cookie信息,因此可以针对指定的路由设置敏感头参数:

  1. zuul:
  2. routes:
  3. feign-consumer:
  4. path: /feign/**
  5. customSensitiveHeaders: true # 方式一:开启自定义敏感头,不过滤
  6. sensitiveHeaders: # 方式二:设置敏感字段(即过滤字段)为空

重定向问题

image.png

Hystrix与Ribbon支持

zuul依赖包含了hystrix以及ribbon依赖,因此zuul天生就有线程隔离以及断路器的自我保护功能,以及对服务调用的客户端负载均衡功能。
image.png

过滤器详解

  1. @Component
  2. public class TokenFilter extends ZuulFilter {
  3. /**
  4. * FilterConstants.PRE_TYPE; 请求被路由之前调用
  5. * FilterConstants.POST_TYPE; 在route和error过滤器之后调用
  6. * FilterConstants.ERROR_TYPE; 在路由请求时被调用
  7. * FilterConstants.ROUTE_TYPE; 处理请求发生错误时被调用
  8. * 过滤器类型
  9. *
  10. * @return
  11. */
  12. @Override
  13. public String filterType() {
  14. return FilterConstants.PRE_TYPE;
  15. }
  16. /**
  17. * 过滤器质性顺序, 数字越小优先级越高
  18. *
  19. * @return
  20. */
  21. @Override
  22. public int filterOrder() {
  23. return 0;
  24. }
  25. /**
  26. * 过滤器是否执行, 用来确定过滤器的过滤范围
  27. *
  28. * @return
  29. */
  30. @Override
  31. public boolean shouldFilter() {
  32. return true;
  33. }
  34. /**
  35. * 具体逻辑
  36. *
  37. * @return
  38. * @throws ZuulException
  39. */
  40. @Override
  41. public Object run() throws ZuulException {
  42. RequestContext ctx = RequestContext.getCurrentContext();
  43. HttpServletRequest request = ctx.getRequest();
  44. if (request.getHeader("accessToken") == null) {
  45. ctx.setSendZuulResponse(false);
  46. ctx.setResponseStatusCode(401);
  47. return null;
  48. }
  49. return null;
  50. }
  51. }

请求的生命周期
image.png

核心过滤器

image.png

异常处理

这部分书上写法过时了, 且不生效, 待后续完善

FilterProcessor————-zuul过滤器的核心处理器

禁用过滤器

image.png

动态加载

动态路由

将API网关服务的配置文件通过spring Cloud Config连接的GIT仓库存储和管理,就能轻松的实现动态刷新路由规则的功能。
待完善

动态过滤器

动态加载groovy过滤器, 略