文档章节

随行付微服务之基于Zuul自研服务网关

马力-随行付
 马力-随行付
发布于 01/08 15:47
字数 1663
阅读 597
收藏 16

随行付微服务之服务网关

微服务是时下最流行的架构之一,作为微服务不可或缺的一部分,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<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);
        }
    }
}

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<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());
        }
    }
}

Zuul2

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

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

作者简介

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

© 著作权归作者所有

共有 人打赏支持
马力-随行付
粉丝 41
博文 12
码字总数 43774
作品 2
石景山
架构师
私信 提问
加载中

评论(3)

开源中国RIO
开源中国RIO
说tm半天,自研呢
外包项目发布平台00
程序员外包项目兼职平台,教你怎么年入500W!欢迎加入 www.hiyougo.cn
9
92年的java
http://www.jujingyun.com 北京网站建设
http://www.shjuntang.com 上海装潢
白话SpringCloud | 第九章:路由网关(Zuul)的使用

前言 介绍完分布式配置中心,结合前面的文章。我们已经有了一个微服务的框架了,可以对外提供api接口服务了。但现在试想一下,在微服务框架中,每个对外服务都是独立部署的,对外的api或者服...

oKong
2018/10/15
0
0
阿里云Kubernetes SpringCloud 实践进行时(3): API网关服务Zuul

简介 为了更好地支撑日益增长的庞大业务量,我们常常需要把服务进行整合、拆分,使我们的服务不仅能通过集群部署抵挡流量的冲击,又能根据业务在其上进行灵活的扩展。随着分布式的普及、服务...

osswangxining
2018/05/25
0
0
springCloud(13):使用Zuul构建微服务网关-简介

一、为什么要使用微服务网关 不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求。如:一个电影购票的手机APP,可能会调用多个微服务,才能完...

我爱大金子
2017/08/22
0
0
微服务网关netflix-zuul

引言:前面一个系列文章介绍了认证鉴权与API权限控制在微服务架构中的设计与实现 ,好多同学询问有没有完整的demo项目,笔者回答肯定有的。由于之前系列文章侧重讲解了权限前置,所以近期补上...

aoho
2017/11/14
0
0
Spring Cloud(四) Spring Cloud Security集成CAS (单点登录)对微服务认证

一、前言 由于leader要求在搭好的spring cloud 框架中加入对微服务的认证包括单点登录认证,来确保系统的安全,所以研究了Spring Cloud Security这个组件。在前面搭好的demo中,如何确保微服...

上善若水
2017/09/10
0
0

没有更多内容

加载失败,请刷新页面

加载更多

大数据教程(11.9)hive操作基础知识

上一篇博客分享了hive的简介和初体验,本节博主将继续分享一些hive的操作的基础知识。 DDL操作 (1)创建表 #建表语法CREATE [EXTERNAL] TABLE [IF NOT EXISTS] table_name [(col_name ...

em_aaron
今天
0
0
OSChina 周四乱弹 —— 我家猫真会后空翻

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @我没有抓狂 :#今天听这个# 我艇牛逼,百听不厌,太好听辣 分享 Led Zeppelin 的歌曲《Stairway To Heaven》 《Stairway To Heaven》- Led Z...

小小编辑
今天
2
0
node调用dll

先安装python2.7 安装node-gyp cnpm install node-gyp -g 新建一个Electron-vue项目(案例用Electron-vue) vue init simulatedgreg/electron-vue my-project 安装electron-rebuild cnpm ins......

Chason-洪
今天
3
0
scala学习(一)

学习Spark之前需要学习Scala。 参考学习的书籍:快学Scala

柠檬果过
今天
3
0
通俗易懂解释网络工程中的技术,如STP,HSRP等

导读 在面试时,比如被问到HSRP的主备切换时间时多久,STP几个状态的停留时间,自己知道有这些东西,但在工作中不会经常用到,就老是记不住,觉得可能还是自己基础不够牢固,知识掌握不够全面...

问题终结者
昨天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部