微服务是时下最流行的架构之一,做为微服务不可或缺的一部分,API网关的做用相当重要。本文将对随行付微服务的API网关实践进行介绍。java
咱们知道,在一个微服务系统中,整个系统被划分为许多小模块,客户端想要调用服务,可能须要维护不少ip+port信息,管理十分复杂。API网关做为整个系统的统一入口,全部请求由网关接收并路由转发给内部的微服务。对于客户端而言,系统至关于一个黑箱,客户端不须要关心其内部结构。web
随着业务的发展,服务端可能须要对微服务进行从新划分等操做,因为网关将客户端和具体服务隔离,所以能够在尽可能不改动客户端的状况下进行。网关能够完成权限验证、限流、安全、监控、缓存、服务路由、协议转换、服务编排、灰度发布等功能剥离出来,讲这些非业务功能统一解决、统一机制处理。spring
随行付微服务API网关基于Netflix的Zuul实现。Netflix是实践微服务最成功的公司之一,他们建立并开源了一系列微服务相关的框架,Zuul即是用来实现网关功能的框架。Zuul的总体架构图以下:缓存
Zuul基于Servlet开发,ZuulServlet是整个框架的入口。Zuul的核心组件是Filter,Filter分为四类,分别是pre、route、post、error。pre-filter用来实现前置逻辑,route-filter用来实现对目标服务的调用逻辑,post-filter用来实现收尾逻辑,error-filter则在任意位置发生异常时作异常处理(此处应该注意,若是pre或route发生异常,执行error后,仍然会执行post),其示意图以下:安全
在Filter中能够定义某些条件下是否执行过滤器逻辑,以及同种类Filter的优先级。Filter的各个方法中并不存在入参,其参数传递是经过一个基于ThreadLocal实现的RequestContext,虽然RequestContext中定义了不少参数的读写方法,但初始的可用参数仅有req和res,对应HttpSerlvetRequest和HttpServletResponse。Filter代码范例以下:架构
public class TestFilter extends ZuulFilter {
@Override /** 是否拦截 */
public boolean shouldFilter() {
return false;
}
@Override /** filter逻辑 */
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();// 获取当前线程的
HttpServletRequest req = context.getRequest();// 获取请求信息
return null; // 从源码来看,这个返回值没什么用
}
@Override /** filter类型 */
public String filterType() {
return "pre";// pre/route/post/error
}
@Override /** filter优先级,仅在同类型filter中生效 */
public int filterOrder() {
return 0;
}
}
复制代码
Filter一般使用groovy编写,以便于动态加载。当咱们编写好一个Filter类后,将其放在指定的磁盘路径下,FilterFileManager会启动一个守护线程去按期读取并加载。经过动态加载,咱们能够在不停机的状况下添加、修改功能模块。FilterFileManager源码摘要以下:并发
public class FilterFileManager {
...
/** * Initialized the GroovyFileManager. * * @throws Exception */
@PostConstruct
public void init() throws Exception {
long startTime = System.currentTimeMillis();
filterLoader.putFiltersForClasses(config.getClassNames());
manageFiles();
startPoller();
LOG.warn("Finished loading all zuul filters. Duration = " + (System.currentTimeMillis() - startTime) + " ms.");
}
...
/** 启动线程定时读取文件 */
void startPoller() {
poller = new Thread("GroovyFilterFileManagerPoller") {
public void run() {
while (bRunning) {
try {
sleep(config.getPollingIntervalSeconds() * 1000);
manageFiles();
}
catch (Exception e) {
LOG.error("Error checking and/or loading filter files from Poller thread.", e);
}
}
}
};
poller.start();
}
...
/** 读取文件并加载 */
void manageFiles() {
try {
List<File> aFiles = getFiles();
processGroovyFiles(aFiles);
}
catch (Exception e) {
String msg = "Error updating groovy filters from disk!";
LOG.error(msg, e);
throw new RuntimeException(msg, e);
}
}
}
复制代码
Spring Cloud经过集成Zuul来实现API网关模块,咱们来简单介绍一下它的整合原理。app
SpringCloud-Zuul的核心配置类是ZuulServerAutoConfiguration以及ZuulProxyAutoConfiguration。Spring首先使用ZuulController来封装ZuulServlet,而后定义一个ZuulHandlerMapping,使得除一些特殊请求之外(如/error)的大部分请求被转发到ZuulController进行处理。源码摘要以下:框架
@Configuration
@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass(ZuulServlet.class)
@ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)
// Make sure to get the ServerProperties from the same place as a normal web app would
@Import(ServerPropertiesAutoConfiguration.class)
public class ZuulServerAutoConfiguration {
...
@Bean
public ZuulController zuulController() {
return new ZuulController();
}
@Bean
public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
mapping.setErrorController(this.errorController);
return mapping;
}
}
public class ZuulController extends ServletWrappingController {
public ZuulController() {
setServletClass(ZuulServlet.class);
setServletName("zuul");
setSupportedMethods((String[]) null); // Allow all
}
...
}
public class ZuulHandlerMapping extends AbstractUrlHandlerMapping {
...
private final ZuulController zuul;
...
@Override
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
if (this.errorController != null && urlPath.equals(this.errorController.getErrorPath())) {
return null;
}
if (isIgnoredPath(urlPath, this.routeLocator.getIgnoredPaths())) return null;
RequestContext ctx = RequestContext.getCurrentContext();
if (ctx.containsKey("forward.to")) {
return null;
}
if (this.dirty) {
synchronized (this) {
if (this.dirty) {
registerHandlers();
this.dirty = false;
}
}
}
return super.lookupHandler(urlPath, request);
}
...
private void registerHandlers() {
Collection<Route> routes = this.routeLocator.getRoutes();
if (routes.isEmpty()) {
this.logger.warn("No routes found from RouteLocator");
}
else {
for (Route route : routes) {
registerHandler(route.getFullPath(), this.zuul);
}
}
}
}
复制代码
SpringCloud默认定义了一些Filter来实现网关逻辑,其中最核心的Filter——RibbonRoutingFilter是负责实际转发操做的,在它的过滤逻辑里又集成了hystrix、ribbon等其余重要框架。源码摘要以下:运维
public class RibbonRoutingFilter extends ZuulFilter {
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
this.helper.addIgnoredHeaders();
try {
RibbonCommandContext commandContext = buildCommandContext(context);//构建请求数据
ClientHttpResponse response = forward(commandContext);//执行请求
setResponse(response);//设置应答信息
return response;
}
catch (ZuulException ex) {
throw new ZuulRuntimeException(ex);
}
catch (Exception ex) {
throw new ZuulRuntimeException(ex);
}
}
}
复制代码
加载Filter的方式经过ZuulFilterInitializer扩展为能够从ApplicationContext中获取。源码摘要:
/** 代码出自ZuulServerAutoConfiguration */
@Configuration
protected static class ZuulFilterConfiguration {
@Autowired
private Map<String, ZuulFilter> filters;//从spring上下文中获取Filter bean
@Bean
public ZuulFilterInitializer zuulFilterInitializer( CounterFactory counterFactory, TracerFactory tracerFactory) {
FilterLoader filterLoader = FilterLoader.getInstance();
FilterRegistry filterRegistry = FilterRegistry.instance();
return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory, filterLoader, filterRegistry);
}
}
public class ZuulFilterInitializer {
private final Map<String, ZuulFilter> filters;
...
@PostConstruct
public void contextInitialized() {
...
// 设置filter
for (Map.Entry<String, ZuulFilter> entry : this.filters.entrySet()) {
filterRegistry.put(entry.getKey(), entry.getValue());
}
}
}
复制代码
随着业务的不断发展,Zuul对于Netflix来讲性能已经不太够用,因而Netflix又开发了Zuul2。Zuul2最大的变革是基于Netty实现了框架的异步化,从而提高其性能。根据官方的数据,Zuul2的性能比Zuul1约有20%的提高。Zuul2架构图以下:
因为框架改成了异步的模式,Zuul2在提高性能的同时,也带来了调试、运维的困难。在实际的使用当中,对于绝大多数公司来讲,并发量远远没有Netflix那样庞大,选择开发调试更简单、且性可以用的Zuul1是更合适的选择。
任金昊,随行付架构部高级开发工程师。擅长分布式、微服务架构,负责随行付微服务生态平台开发。