前几章总结:
- eureka实现高可用的服务注册中心,实现微服务的注册和发现
- rubbon或feign实现服务间负载均衡的接口调用
- 对于依赖的服务调用使用hystrix进行包装,实现线程隔离并加入熔断机制,避免在微服务架构中因为个别服务出现异常而引起级联故障蔓延
这套架构目前存在的问题:
- 运维维护路由规则和服务实例列表难度大
- 微服务访问各应用时的前置校验冗余, 这部分与业务无关
因此引入API网关,
API网关是一个更智能的应用服务器, 类似于面向对象设计模式中的外观模式, 作为整个微服务架构系统的门面,所有的外部客户端访问都需要经过它进行调度和过滤, 除了要实现请求路由, 负载均衡, 校验过滤等功能之外, 还需要更多能力,比如与服务治理框架结合, 请求转发时的熔断机制,服务的聚合等一系列高级功能。
spring cloud zuul
- 服务实例维护:zuul将自己注册到eureka中拿到实例列表
- 路由规则: 默认以服务名作为contextPath来创建路由映射
- 签名,登录校验:zuul提供了过滤器机制,通过校验的才会路由到指定的微服务接口
总结:
快速入门
依赖
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
启动类加注解
@EnableZuulProxy
@SpringCloudApplication
public class ApiApplication {
public static void main(String[] args) {
SpringApplication.run(ApiApplication.class, args);
}
}
配置路由映射
server:
port: 30005
eureka:
client:
service-url:
defaultZone: http://localhost:30000/eureka/
spring:
application:
name: API-GATEWAY
zuul:
routes:
hello-service:
path: /hello/**
serviceId: HELLO-SERVICE
strip-prefix: false # 则会带上/hello, 如 http://HELLO-SERVICE/hello/xxx
feign-consumer:
path: /feign/**
serviceId: FEIGN-CONSUMER # 默认不带/feign, 如http://FEIGN-CONSUMER/xxx
补充:
更简洁的配置, 直接 zuul.routes.
zuul:
routes:
feign-consumer:
path: /feign/**
测试
@RestController
public class HelloController {
@GetMapping("/hello")
public String test() {
System.out.println("这里是helloService!, 当前时间为: " + LocalDateTime.now());
return "hello! 这里是helloService!";
}
}
----------------------
@RestController
public class FeignController {
@GetMapping
public String test2() {
System.out.println("这里是feignService!, 当前时间为: " + LocalDateTime.now());
return "hello! 这里是feignService!";
}
}
请求过滤
@Component
public class TokenFilter extends ZuulFilter {
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
if (request.getHeader("accessToken") == null) {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
return null;
}
return null;
}
}
路由详解
当请求到达API网关时,网关会根据请求路径找到最佳匹配的path规则,将请求路由到具体的serviceId上;由于API网关整合了eureka, 会将自身注册进注册中心并获取服务列表;因此网关根据ribbon的负载均衡策略从serviceId对应的实例清单中选择一个具体的实例进行转发即完成路由工作了。
传统路由方式
面向服务的路由方式,见示例
默认路由规则,
zuul整合的eureka会默认生成一个服务名的路由, 与配置文件中的路由同时生效
如下配置,则/feign/, /feign-consumer/都对应着feign-consumer服务
zuul:
routes:
feign-consumer:
path: /feign/**
禁用默认路由规则
zuul:
ignored-services: feign-consumer
自定义路由映射规则,
符合自定义规则的服务名走自定义,不符合的走配置
@Configuration
public class GateWayConfig {
/**
* 为userservice-v1自动创建/v1/userservice/**的路由匹配规则
*
* @return
*/
@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
return new PatternServiceRouteMapper("(?<name>^.+)-(?<version>v.+$)", "${version}/${name}");
}
}
路径匹配
- ? 匹配任意单个字符
- 匹配任意数量的字符
- ** 匹配任意数量的字符, 支持多级目录
当出现一个url被两个路由规则匹配时, 由于路由匹配是通过线性遍历的方式,因此需要注意调整配置的顺序;
如下的配置才能保证/user-service/ext/生效**
忽略表达式
此表达式是针对所有的路由
zuul:
ignored-patterns: /**/hello/**
本地跳转
如下配置, 如果访问 /feign/hello, 会被转发到 /local/hello
zuul:
routes:
feign-consumer:
path: /feign/**
url: forward:/local
cookie与头信息
过滤敏感信息
默认情况下, zuul会过滤掉HTTP请求头中的一些敏感信息, 包括Cookie, Set-cookie, Authrization三个属性
由于鉴权需要这些cookie信息,因此可以针对指定的路由设置敏感头参数:
zuul:
routes:
feign-consumer:
path: /feign/**
customSensitiveHeaders: true # 方式一:开启自定义敏感头,不过滤
sensitiveHeaders: # 方式二:设置敏感字段(即过滤字段)为空
重定向问题
Hystrix与Ribbon支持
zuul依赖包含了hystrix以及ribbon依赖,因此zuul天生就有线程隔离以及断路器的自我保护功能,以及对服务调用的客户端负载均衡功能。
过滤器详解
@Component
public class TokenFilter extends ZuulFilter {
/**
* FilterConstants.PRE_TYPE; 请求被路由之前调用
* FilterConstants.POST_TYPE; 在route和error过滤器之后调用
* FilterConstants.ERROR_TYPE; 在路由请求时被调用
* FilterConstants.ROUTE_TYPE; 处理请求发生错误时被调用
* 过滤器类型
*
* @return
*/
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
/**
* 过滤器质性顺序, 数字越小优先级越高
*
* @return
*/
@Override
public int filterOrder() {
return 0;
}
/**
* 过滤器是否执行, 用来确定过滤器的过滤范围
*
* @return
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 具体逻辑
*
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
if (request.getHeader("accessToken") == null) {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
return null;
}
return null;
}
}
核心过滤器
异常处理
这部分书上写法过时了, 且不生效, 待后续完善
FilterProcessor————-zuul过滤器的核心处理器
禁用过滤器
动态加载
动态路由
将API网关服务的配置文件通过spring Cloud Config连接的GIT仓库存储和管理,就能轻松的实现动态刷新路由规则的功能。
待完善
动态过滤器
动态加载groovy过滤器, 略