博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
随行付微服务之基于Zuul自研服务网关
阅读量:6137 次
发布时间:2019-06-21

本文共 7840 字,大约阅读时间需要 26 分钟。

hot3.png

随行付微服务之服务网关

微服务是时下最流行的架构之一,作为微服务不可或缺的一部分,API网关的作用至关重要。本文将对随行付微服务的API网关实践进行介绍。

API网关的作用

我们知道,在一个微服务系统中,整个系统被划分为许多小模块,客户端想要调用服务,可能需要维护很多ip+port信息,管理十分复杂。API网关作为整个系统的统一入口,所有请求由网关接收并路由转发给内部的微服务。对于客户端而言,系统相当于一个黑箱,客户端不需要关心其内部结构。

随着业务的发展,服务端可能需要对微服务进行重新划分等操作,由于网关将客户端和具体服务隔离,因此可以在尽量不改动客户端的情况下进行。网关可以完成权限验证、限流、安全、监控、缓存、服务路由、协议转换、服务编排、灰度发布等功能剥离出来,讲这些非业务功能统一解决、统一机制处理。

Zuul原理简介

随行付微服务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
aFiles = getFiles(); processGroovyFiles(aFiles); } catch (Exception e) { String msg = "Error updating groovy filters from disk!"; LOG.error(msg, e); throw new RuntimeException(msg, e); } }}

SpringCloud-Zuul

Spring Cloud通过集成Zuul来实现API网关模块,我们来简单介绍一下它的整合原理。

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
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 */@Configurationprotected static class ZuulFilterConfiguration {    @Autowired    private Map
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
filters; ... @PostConstruct public void contextInitialized() { ... // 设置filter for (Map.Entry
entry : this.filters.entrySet()) { filterRegistry.put(entry.getKey(), entry.getValue()); } }}

Zuul2

随着业务的不断发展,Zuul对于Netflix来说性能已经不太够用,于是Netflix又开发了Zuul2。Zuul2最大的变革是基于Netty实现了框架的异步化,从而提升其性能。根据官方的数据,Zuul2的性能比Zuul1约有20%的提升。Zuul2架构图如下:

由于框架改为了异步的模式,Zuul2在提升性能的同时,也带来了调试、运维的困难。在实际的使用当中,对于绝大多数公司来说,并发量远远没有Netflix那样庞大,选择开发调试更简单、且性能够用的Zuul1是更合适的选择。

作者简介

任金昊,随行付架构部高级开发工程师。擅长分布式、微服务架构,负责随行付微服务生态平台开发。

转载于:https://my.oschina.net/matieli/blog/2998798

你可能感兴趣的文章
Mac Tomcat 安装与配置
查看>>
自己写中文分词之(二)_用HMM模型实现分词
查看>>
java开发过程中的命名规范
查看>>
Linux系统启动过程及其修复过程简析(CentOS5、6)
查看>>
CentOS 7 防火墙设置
查看>>
RHEL java 环境变量
查看>>
yum在企业网络中应用
查看>>
03.移动页面用户行为报告
查看>>
关于embedded linux的使用、开发、学习的一点自已的体会
查看>>
找到一部不错的c语言学习教程
查看>>
openstack 虚拟机添加网卡
查看>>
Groovy学习笔记(6)-javax.script.* API
查看>>
RocketMQ服务搭建
查看>>
微信支付 - 可以下单但是无法收到通知消息Log总显示begin notify
查看>>
分享我如何活用notepad++
查看>>
Object-c的基础概念
查看>>
GNU Building System
查看>>
自我关系的建立
查看>>
RAID
查看>>
配置文件及工具包
查看>>