高性能网关设计实践

Posted 我是程序员小贱

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了高性能网关设计实践相关的知识,希望对你有一定的参考价值。

次,受宠若惊,在此感谢大家的认可!在文末简单提了一下 OpenResty,一些读者比较感兴趣,刚好我们接入层网关也是用的 OpenResty,所以希望通过对网关设计的介绍来简单总结一下 OpenResty 的相关知识点,争取让大家对 OpenResty 这种高性能 Web 平台有一个比较全面的了解。本文会从以下几个方面来讲解。

  • 网关的作用
  • 接入层网关架构设计与实现
  • 技术选型
  • OpenResty 原理剖析
  • content_by_lua \'...\'; # 处理请求

    现在需要对其加上加密和解密的机制,只需要在 access 阶段解密, 在 body filter 阶段加密即可,原来 content 的逻辑无需做任务改动,有效实现了代码的解藕。

    # 加密协议版本
    location /request
    access_by_lua \'...\'; # 请求体解密
    content_by_lua \'...\'; # 处理请求,不需要关心通信协议
    body_filter_by_lua \'...\'; # 应答体加密

    再比如我们不是要要上文提到网关的核心功能之一不是要监控日志吗,就可以统一在 log_by_lua 上报日志,不影响其他阶段的逻辑。

    worker 间共享数据利器: shared dict

    worker 既然是互相独立的进程,就需要考虑其共享数据的问题, OpenResty 提供了一种高效的数据结构: shared dict ,可以实现在 worker 间共享数据,shared dict 对外提供了 20 多个 Lua API,都是原子操作的,避免了高并发下的竞争问题。

    路由策略插件化实现

    有了以上 OpenResty 点的铺垫,来看看上文提的网关核心功能 「路由策略插件化」,「后端集群的动态变更」如何实现

    首先针对某个请求的路由策略大概是这样的

    整个插件化的步骤大致如下

    1、每条策略由 url ,action, cluster 等组成,代表请求 url 在打到后端集群过程中最终经历了哪些路由规则,这些规则统一在我们的路由管理平台配置,存在 db 里。

    2、OpenResty 启动时,在请求的 init 阶段 worker 进程会去拉取这些规则,将这些规则编译成一个个可执行的 lua 函数,这一个个函数就对应了一条条的规则。

    需要注意的是为了避免重复去 mysql 中拉取数据,某个 worker 从 MySQL 拉取完规则(此步需要加锁,避免所有 worker 都去拉取)或者后端集群等配置信息后要将其保存在 shared dict 中,这样之后所有的 worker 请求只要从 shared dict 中获取这些规则,然后将其映射成对应模块的函数即可,如果配置规则有变动呢,配置后台通过接口通知 OpenResty 重新加载一下即可

    经过路由规则确定好每个请求对应要打的后端集群后,就需要根据 upstream 来确定最终打到哪个集群的哪台机器上,我们看看如何动态管理集群。

    后端集群的动态配置

    nginx 中配置 upstream 的格式如下

    upstream backend 
        server backend1.example.com weight=5;
        server backend2.example.com;
        server 192.0.0.1 backup;

    以上这个示例是按照权重(weight)来划分的,6 个请求进来,5个请求打到 backend1.example.com, 1 个请求打到 backend2.example.com,如果这两台机器都不可用,就打到 192.0.0.1,这种静态配置的方式 upstream 的方式确实可行,但我们知道机器的扩缩容有时候比较频繁,如果每次机器上下线都要手动去改,并且改完之后还要重新去 reload 无疑是不可行的,出错的概率很大,而且每次配置都要 reload 对性能的损耗也是挺大的,为了解决这个问题,OpenResty 提供了一个 dyups 的模块来解决此问题, 它提供了一个 dyups api,可以动态增,删,创建 upsteam,所以在 init 阶段我们会先去拉取集群信息,构建 upstream,之后如果集群信息有变动,会通过如下形式调用 dyups api 来更新 upstream


    -- 动态配置 upstream 接口站点
    server 
         listen 127.0.0.1:81;
          location / 
              dyups_interface;
         



    -- 增加 upstream:user_backend
    curl -d "server 10.53.10.191;" 127.0.0.1:81/upstream/user_backend

    -- 删除 upstream:user_backend
    curl -i -X DELETE 127.0.0.1:81/upstream/user_backend

    使用 dyups 就解决了动态配置 upstream 的问题

    网关最终架构设计图

    通过这样的设计,最终实现了网关的配置化,动态化。

    总结

    网关作为承载公司所有流量的入口,对性能有着极高的要求,所以技术选型上还是要慎重,之所以选择 OpenResty,一是因为它高性能,二是目前也有小米,阿里,腾讯等大公司在用,是久经过市场考验的,本文通过对网关的总结简要介绍了 OpenResty 的相关知识点,相信大家对其主要功能点应该有所了解了,不过 OpenResty 的知识点远不止以上这些,大家如有兴趣,可以参考文末的学习教程深入学习,相信大家会有不少启发的。

    题外话

    「码海」历史精品文章已经做成电子书了,如有需要欢迎添加我的个人微信,除了发你电子书外,也可以加入我的读者群,一起探讨问题,共同进步!里面有不少技术总监等大咖,相信不管是职场进阶也好,个人困惑也好,都能对你有所帮助哦

    巨人的肩膀

  • 谈谈微服务中的 API 网关 https://www.cnblogs.com/savorboard/p/api-gateway.html
  • Openresty动态更新(无reload)TCP Upstream的原理和实现 https://developer.aliyun.com/article/745757
  • http://www.ttlsa.com/Nginx/Nginx-modules-ngx_http_dyups_module/
  • 极客时间 OpenResty 从入门到实战

  • Leo|20页PPT剖析唯品会API网关设计与实践

    编辑:友强


    摘要:API网关做为流量入口、公共服务接入点、公共技术扩展点,是互联网公司技术架构中重要的基础组件。这次刘璟宇Leo和大家一起分享唯品会API网关的设计、性能优化、实际应用过程中的一些经验。


    Leo|20页PPT剖析唯品会API网关设计与实践

    刘璟宇Leo

    唯品会资深研发工程师,在大型高性能分布式系统设计和开发方面有丰富的经验。目前在唯品会平台与架构部负责唯品会API网关和服务安全方面的设计、开发、运营工作。


    内容解析

    Leo|20页PPT剖析唯品会API网关设计与实践


    1. 为什么引入网关

    Leo|20页PPT剖析唯品会API网关设计与实践

    唯品会是一家专门做特卖的网站,唯品会网站是一个巨大型的网站,每张页面背后,都有多个服务提供静态资源和动态数据。

    这是唯品会网站上一张商品详情页面,内容是一款女式针织衫。页面里,除去静态页面、图片之外,有些动态内容:商品价格、促销提示语、产品介绍、商品库存等。每个部分都会从后端的一个或几个服务拉取数据。

    在唯品会公司内部,已经采用服务化的方式把服务进行了拆分,内部服务之间采用基于thrift的二进制协议通讯。这些服务不能直接对外部提供服务。

    在引入API网关前,我们在外部app、浏览器和内部服务之间会做一层webapp,起到两个作用:

    一个是从外部的http协议,适配到内部的二进制协议。

    另一个是对数据进行聚合。

    另外这些webapp里面还集成了如oauth等的一些公共服务。

    Leo|20页PPT剖析唯品会API网关设计与实践

    由于唯品会网站的业务众多、业务量也非常大,这种webapp的数量有数百个,实例数量数千个。

    在数量达到这种规模后,产生了一些问题,我们设想一个场景,比如某种安全防护技术需要升级一下,那么安全开发组需要先跟业务开发团队协商开发时间,等排期开发,然后需要测试,再排期发版。这样几十个业务开发团队升级下来,几个月可能就过去了。

    再设想一个场景,例如,我可能想app支持一下二进制协议,可以提升数据交换效率。

    一般我们做webapp,都是tomcat+springmvc这种结构进行开发,支持二进制协议就很困难。

    所以,目前这种webapp的架构,对于公共服务集成升级和公共技术的升级不是很友好。

    Leo|20页PPT剖析唯品会API网关设计与实践

    我们对架构进行了优化,引入了网关。网关的主要作用有三个:

    一个是协议适配

    另一个是公共服务接入

    最后是公共接入技术优化

    在外网和内网中间有了网关,网关本身和业务程序分离,就可以独立的对这些技术进行集成和升级。

    Leo|20页PPT剖析唯品会API网关设计与实践

    http://microservices.io/ 总结的微服务模式中,网关已经成为服务化中的一种标准模式。http://microservices.io/patterns/apigateway.html

    Leo|20页PPT剖析唯品会API网关设计与实践

    网关模式,被一些大型的互联网公司采用。国内主要有唯品会、百度、阿里、京东、携程、有赞等,国外主要有Netflix, Amazon, Mashape等。


    2. 选型和设计

    Leo|20页PPT剖析唯品会API网关设计与实践

    开源网关按照平台可以分为基于nginx平台的网关和自研网关

    基于nginx平台的网关有:

    KONG

    API Umbrella

    自研的网关有:

    apigee

    StrongLoop

    Zuul

    Tyk

    按照语言分类,可以见上图,有基于lua(nginx平台), nodejs, java, go等语言的网关。

    Leo|20页PPT剖析唯品会API网关设计与实践

    基于nginx平台的网关和自研网关的优势和劣势如下:


    基于nginx

    自研

    优势

    1. nginx有完善的处理http协议的能力

    2. 全异步高性能基础处理能力

    3. http处理过程中多个扩展点可进行扩展

    4. 开箱即用,基于openresty开发相对简单

    1. 可以完全掌控对http协议的处理过程

    2. 可以完全掌控异步化业务处理过程

    3. 对内部协议支持可以较好掌控

    4. 和内部的配置中心、注册中心结合较好

    劣势

    1. nginx工作流程复杂,对大多数人来说,只能当作黑盒子用,出问题难以真正在代码级理解根本原因,扩展核心功能较为困难。

    2. 基于openresty扩展,本身有性能开销,对javaerlanggo的性能优势不明显

    3. 对内部协议和基础组件支持不方便

    1. http协议处理有较多的坑需要踩

    2. 需要大量的性能优化过程,不像nginx经过大量实践,本身有较好的性能基础

    唯品会网关是基于netty自研的API网关。

    Leo|20页PPT剖析唯品会API网关设计与实践

    唯品会网关参考各种开源网关的实现,和业内各大电商网站的成熟经验,网关逻辑上可以分为四层;

    第一层是接入层,负责接入技术的优化。

    第二层是业务层,负责实现网关本身的一些业务实现。

    第三层是网关依赖的基于netty实现的各种公共组件

    最底层是netty负责NIO、内存管理、提供各种基础库、异步化框架等。

    Leo|20页PPT剖析唯品会API网关设计与实践

    业务层前面跟大家分享过,主要包括路由、协议转换、安全、认证验签、加密解密等,大家一看估计就可以看出,这些业务逻辑已经划分的比较独立,可以按照模块进行划分。实际上我们也是这样做的。

    业务层设计需要考虑哪些方面呢?

    一方面,是流程的组织。

    另一方面,网关需要依赖外部服务,需要考虑怎样异步化的调用外部服务。

    最后,网关需要考虑高可用,高可用在程序设计方面主要是不停机发布。唯品会网关的所有业务配置,都可以通过管理界面动态管理、动态下发、动态生效,并且支持灰度。

    Leo|20页PPT剖析唯品会API网关设计与实践

    业务层实现,最重要的一点,是将逻辑和数据分离,我们的实现方式,是业务逻辑实现在模块里,数据通过context传递,context通过模块之间相互调用时,通过接口传递。在异步化调用其他服务时,context保存在Channel的AttributeMap里,在异步完成时,回调,取出context。


    有了最基本的模块设计,我们再来看唯品网关怎样设计把这些流程串在一起。

    大家看一下上面的图,在执行业务逻辑时,有些业务逻辑需要串行,比如,路由校验、参数校验、IP黑白名单、WAF等,由于性能方面考虑,一般情况下,我们会先执行黑白名单模块,因为这块是cpu消耗最小、能拦掉部分请求的模块。

    后面再执行路由、参数等的校验。这部分是内存运算,效率也比较高,也能拦掉一些非法请求,所以先执行。

    然后进入outh、风控、设备指纹等的外部服务调用,这些调用将会并发的执行。

    执行后,将进行结果合并校验,如果在认证验签或风控等校验未通过的情况下,将会直接返回,如果校验通过,再进入后续的服务调用。

    服务调用过程,又进行了多选一的流程,可能用二进制协议也可能用HTTP协议等。最终进行后处理。

    Leo|20页PPT剖析唯品会API网关设计与实践

    Leo|20页PPT剖析唯品会API网关设计与实践

    大家可能会想,这些模块看上去可以使用actor模式进行封装,为何没有使用开源异步框架呢?我们也对开源的异步框架进行了详细的调研。在将异步框架结合进网关时发现对网关的性能产生了一些影响。

    目前较为流行的异步框架,主要有akka和quasar fibers。他们的实现形式不同,但原理基本差不多。

    为什么唯品网关没有引入异步框架呢。

    一方面是引入异步框架后,网关的抖动增加。

    一方面是成熟度问题,quasar fibiers quasar fibers的模式,更加友好一些,可以以接近同步编程的模式实现异步编程。但最新的release是0.7.6,没有大规模的验证过,我们也在实际使用踩了一些坑,例如,注解的问题、代码织入冲突问题、长时间运行突然响应变慢问题,强烈建议大家如果生产使用,需要慎重再慎重。


    我们总结了一下异步化框架适用于,大量依赖其他服务,经常被block的情况。

    网关的瓶颈在cpu运算,因为有验签、加解密、协议转换等cpu密集运算,其他的调用已经是全异步的,所以,引入异步框架的收益并不明显。

     

    上面分享了业务层的设计,下面分享一下公共组件的设计。

    网关不论调用依赖的服务还是后端的服务,都会遇到大量并发调用的情况。如果对连接不加以复用和控制,将造成大量的资源消耗和性能问题。因此,唯品网关自己设计优化了连接池。


    下面就分享一下唯品网关在连接池方面的设计。

    Leo|20页PPT剖析唯品会API网关设计与实践

    Leo|20页PPT剖析唯品会API网关设计与实践

    连接复用主要是指,一个连接可以被多个使用者同时使用,且互相之间不受影响,可以并发的发送多个请求,而应答是异步的,可复用的连接一般用于私有协议的连接,因为可复用的连接,请求可以一直发送,应答也不一定是按照请求顺序进行应答,就带来了一个问题,应答怎样才能和请求对应上。私有协议就比较容易在协议包内,增加sequence id,所以能达到连接复用的要求。唯品会网关调用唯品会内部的私有协议服务时,就采用的这种连接复用模式。

    连接复用还有一种实现模式,是spymemcache的模式,memcached本身不支持sequenceid,但同一个连接上的操作会保证顺序性,所以,spymemcache通过把请求缓存在queue中的形式,顺序匹配返回结果,达到连接复用。

    Leo|20页PPT剖析唯品会API网关设计与实践

    独占的连接模式,主要是指,一个连接同一时间只能被一个使用者使用,在一个连接上,发送完一个请求后,必须等待应答后,才能发送第二个请求。一般使用HTTP协议时,比较多使用这种独占的模式。因为如果HTTP协议需要支持连接复用,需要在HTTP协议头上增加sequence id,一般的服务端都不支持这种扩展,所以,我们针对HTTP协议,使用的是独占连接模式。

    Leo|20页PPT剖析唯品会API网关设计与实践

    连接池的异步化,在连接池使用的所有阶段都应该异步化。我们在设计网关的连接池时,考虑了以下几个方面:

    获取连接的异步化。从连接池获取连接,一般情况被认为是个没有block的动作,实际上分解来看,获取连接池,可能需要锁连接池对象所在的队列,操作连接池计数器时,可能会遇到锁、超时等问题。后面我会跟大家分享我们怎样去做的优化。

    连接使用就是说实际用连接去调用其他服务,这块的异步化,大家基本都会考虑到。

    归还连接的异步化。归还连接时,也会操作连接池中的连接队列,有时连接已经异常还会执行关闭连接等动作,所以也会产生锁的问题。和获取连接时类似,我们也把操作封装为task,交由netty做cpu亲缘性路由。


    Leo|20页PPT剖析唯品会API网关设计与实践


    3. 实践经验

    上面是给大家分享了我们在连接池设计中的几个关键点,接下来跟大家分享一下我们在实践过程中实际进行的优化。


    Leo|20页PPT剖析唯品会API网关设计与实践

    jvm启动后,会在/tmp下建立一个文件,是一个内存映射文件,JVM用来导出状态数据给其它进程使用,比如jstat,jconsole等。当到达安全点时,JVM会把安全点的相关信息写入到这个文件中去。安全点是说,jvm会在这个点上,把所有其他线程都停下来,自己安全的做一些事情,GC是一种安全点,还有其他种类的安全点。而gc log和这种监控数据的写入,就是在安全点上进行写入。当IO频发且负载均重时,可能写数据动作刚好赶上操作系统将磁盘缓存刷到磁盘的过程,此时写性能数据文件的操作就会被block。最终表现为jvm暂停。解决方法,是将这些性能数据写到内存文件中,避免和其他操作抢占磁盘io。

     

    StringBuffer在写日志等处理字符串拼接的场景下经常用到,大多数情况下,我们会new一个StringBuffer,向里面追加字符串,在高并发场景,这个过程会产生大量的内存重新分配并拷贝内容的动作,造成cpu热点。我们的优化方法是,在threadlocal缓存使用过的stringbuffer,在下次使用时,直接复用。

      

    我们在初期实际使用网关时观察到,网关的OLD区使用会缓慢上升,大概两天会产生一次FGC,经过仔细的分析,发现,java NIO的server socket类由finalize最后进行释放。而GC过程是第一次GC先将没有引用的对象放入finalize队列,下次GC的时候,调用finalize,并将对象释放。而在高并发的情况下,server socket的finalize并不保证被调用,所以存活时间可能超过了升级阈值,就会有对象不断进入old区。

    即使ref queue很快被执行,也可能跨两次ygc,比如创建后接着一次ygc1,然后用完后在下一次ygc2中添加到ref queue,ref queue没有堆积的情况下,需要在ygc3中释放这些对象。

    Leo|20页PPT剖析唯品会API网关设计与实践

    由于网关会并发接受大量的请求,所以写日志的量非常大。我们实际压测的时候发现,写日志的IO操作,会周期性的被block,从而产生抖动。经过分析发现,被block的时候,操作系统在刷磁盘缓存。linux默认是脏数据超过10%,或5s刷一次缓存,而这时可能会有大量数据在缓存里等待写入磁盘,操作系统再去刷盘的时候,就会消耗比较多的时间,而这些时间内,应用无法将数据写入磁盘缓存,发生block。有两个参数可以调整,一个是脏数据占比,一个是脏数据两个取较小值生效。我们通过调小脏数据比率,让刷盘动作在数据量较小的时候就开始,减小了毛刺率。

     

    招贤纳士,联系Leo

    Leo|20页PPT剖析唯品会API网关设计与实践

    Leo|20页PPT剖析唯品会API网关设计与实践

    为庆祝中生代技术成立一周年, 由中生代技术组织的软件技术领域顶级盛会,将于2017年3月于北京、成都、上海开幕。3月11日上海站年度大会,集结了百度,阿里,华为,蘑菇街,拍拍贷,七牛,携程,盛派网,沪江网等公司大咖,共襄盛举。3.11,等你来约!6大主题,20余话题,大数据,FinTech,架构之道,移动技术,研发管理等主题,囊括国内顶尖技术专家,一起学习面基,促进技术交流!


    最后小编来送

    福利1:输入优惠码“youjun”,给你更多折扣

    福利2:评论精彩者获得特惠优惠码【单人票5折】5人; 点赞最多获得免费票1张

    福利3:欢迎各公司CEO/CTO/架构师/行业专家参与分享,参与分享者可免费获得大会门票。

    以上是关于高性能网关设计实践的主要内容,如果未能解决你的问题,请参考以下文章

    UCloud物理云网关百G级集群设计实践

    技术干货 | API网关与服务安全最佳实践

    谈京东京麦TCP网关的Netty应用实践

    网关架构设计最佳实践

    大规模 Node.js 网关架构设计与工程实践

    大规模 Node.js 网关架构设计与工程实践