来源:
《微服务架构实战160讲》
https://www.jianshu.com/p/d1e61f9fc13a?utm_source=oschina-app
API网关的基本功能:
Zuul网关
1 场景
Zuul实现了跨区高可用、防爬防攻击、健康检查、屏蔽坏节点、流量判断等诸多功能
- API网关
类似单体应用中的过滤器,在请求到达业务服务器之前可以做一些操作
- 红绿部署
类似配置中心Apollo可以实现的蓝绿部署(流量判断)
- 开发者测试分支
也是通过流量判断,使部分流量达到测试代码而不是全部
- 埋点测试
也是通过流量判断,使部分流量达到埋点代码而不是全部
- 压力测试
也是通过流量判断
- 调试路由
也是通过流量判断,将代码放到线上来调试
- 金丝雀测试
也是通过流量判断,部分流量测试金丝雀代码,如果没问题,再全量切换
- 粘性金丝雀
同金丝雀测试,不过固定了用户,如果第一次到达了金丝雀代码,下一次就依然会到达此处,而不是随机分配
- 失败注入测试
注入错误,看看业务服务器的容错性
- 降级测试
网关检测到错误后,能执行降级,将流量打向降级的集群,可以跟失败注入测试结合来测试降级能力
2 架构设计+源码简析(1.0)
Zuul网关的核心是一系列的过滤器,可以对请求或者响应结果做一系列过滤,在zuul中过滤器分为四种类型(Type),每一种Type中可以有多个拦截器,开发人员可自行指定执行顺序(ExecutionOrder),只要满足指定的条件(Criteria),就执行指定的动作(Action):
1 PRE Filters(前置过滤器) :当请求会路由转发到具体后端服务器前执行的过滤器,比如鉴权过滤器,日志过滤器,路由选择过滤器
2 ROUTING Filters (路由过滤器):该过滤器作用是把请求具体转发到后端服务器上,一般是通过Apache HttpClient 或者 Netflix Ribbon把请求发送到具体的后端服务器上
3 POST Filters(后置过滤器):当把请求路由到具体后端服务器后执行的过滤器,可以添加标准http 响应头、收集一些统计数据(比如请求耗时等)、写入请求结果到请求方等。
4 ERROR Filters(错误过滤器):当任何一个其他类型过滤器执行出错时候执行该过滤器
- 总体架构+原理
开发/运维人员通过管理模块来管理过滤器,可以设置不同的状态,如用于金丝雀测试的状态等,然后存储到FilterPersister中,如数据库,需要做一个表出来存储
FilterPoller会定期扫描FilterPersister(通过ZuulFilterDaoFactory,工厂模式),加载新的过滤器进FilterDirectory,不同类型的过滤器有各自的目录,FileManager组合了这几个目录,使用一个线程扫描FilterDirectory,加载到FilterLoader中,最终送到FilterRunner中
FilterRunner则完成所有请求流程的过滤,是最核心的组件
请求入口:ZuulServlet本质是一个HttpServlet,会拦截所有请求,执行四个过滤器方法,方法内部会将具体实现交给FilterRunner(委派模式),FilterRunner则交给FilterProcessor真正执行对应类型的过滤器(从FilterLoader中取出相应类型的过滤器,使用for循环依次处理,为责任链模式)
过滤器的执行顺序:ZuulServlet#service
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
...
try {
preRoute();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
route();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
postRoute();
} catch (ZuulException e) {
error(e);
return;
}
...
}
RequestContext存储整个请求的一些数据,使得过滤器之间可以共享数据,且线程安全,即每个请求有各自的局部RequestContext,由所有过滤器共享(其实就是一个请求里的数据共享)
RequestContext使用ThreadLocal
实现,其本身简单wrap了ConcurrentHashMap
,核心源码:
public class RequestContext extends ConcurrentHashMap<String, Object> {
protected static Class<? extends RequestContext> contextClass = RequestContext.class;
//使用泛型,这样可以通过继承RequestContext来定制RequestContext
protected static final ThreadLocal<? extends RequestContext> threadLocal = new ThreadLocal<RequestContext>() {
@Override
protected RequestContext initialValue() {
try {
return contextClass.newInstance();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
};
//方法内部都是通过ConcurrentHashMap的put和get
public void setRequest(HttpServletRequest request) {
put("request", request);
}
...
public static RequestContext getCurrentContext() {
if (testContext != null) return testContext;
RequestContext context = threadLocal.get();
return context;
}
}
- 请求处理生命周期
3 部署案例
- 整体架构
- 以无线网关为例
- 如何获取路由表
使用Eureka作为中介,Zuul通过集成Eureka客户端Ribbon就可以自动发现服务的ip+端口了
也可以基于域名或基于Apollo来实现,不过没有Eureka好,因为无法实现自发现,即自动化
4 Zuul2
使用了非阻塞异步的模式(Netty),增加了一些功能点,架构做了一些修改
5 Zuul1最佳实践
- 使用异步 AsyncServlet 来优化连接数
需要自实现
-
使用Apollo配置中心集成动态配置
-
Hystrix熔断限流
信号量隔离和线程池隔离两种方式建议使用信号量隔离,如果使用线程池隔离,则每个服务都要配一个线程池,会导致Zuul1上的线程更多,更容易使网关资源被耗尽
-
对连接池进行管理
-
CAT和Hystrix 监控
-
过滤器调试技巧
使用java代码调试,完成后再改成groovy去上传
-
网关不要有业务逻辑
-
自助路由 (需定制扩展 )
提供一些工具、文档,让前后端开发人员可以自助扩展网关