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

简介:

刘璟宇Leo

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


内容解析

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1


1. 为什么引入网关

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

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

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

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

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

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

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

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

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

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

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

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

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

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

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

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

一个是协议适配

另一个是公共服务接入

最后是公共接入技术优化

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

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

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

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

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


2. 选型和设计

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

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

基于nginx平台的网关有:

KONG

API Umbrella

自研的网关有:

apigee

StrongLoop

Zuul

Tyk

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

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

基于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网关。

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

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

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

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

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

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

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

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

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

一方面,是流程的组织。

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

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

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

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


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

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

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

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

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

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

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

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

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

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

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

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


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

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

 

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

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


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

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

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

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

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

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

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

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

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

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

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


640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1


3. 实践经验

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


640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

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中释放这些对象。

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

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


来源:中生代技术

原文链接


相关文章
|
11天前
|
监控 负载均衡 应用服务中间件
探索微服务架构下的API网关设计与实践
在数字化浪潮中,微服务架构以其灵活性和可扩展性成为企业IT架构的宠儿。本文将深入浅出地介绍微服务架构下API网关的关键作用,探讨其设计原则与实践要点,旨在帮助读者更好地理解和应用API网关,优化微服务间的通信效率和安全性,实现服务的高可用性和伸缩性。
30 3
|
14天前
|
前端开发 API 数据安全/隐私保护
打造高效后端API:RESTful设计原则与实践
【9月更文挑战第4天】在数字化时代的浪潮中,后端API作为连接数据和前端的桥梁,其设计质量直接影响着应用的性能和扩展性。本文将深入探讨RESTful API的设计哲学、核心原则以及如何在实际开发中应用这些原则来构建一个高效、易于维护的后端系统。我们将通过代码示例,揭示如何将理论转化为实践,从而为开发者提供一条清晰的道路,去创造那些能够在不断变化的技术环境中茁壮成长的API。
|
20天前
|
测试技术 API 数据库
构建高效的RESTful API 实践与思考
【8月更文挑战第31天】本文将深入探讨在现代Web开发中,如何设计并实现一个高效且易于维护的RESTful API。通过实际案例分析,我们将了解API设计的基本原则、最佳实践和常见陷阱。文章旨在为开发者提供一套清晰的指导方针,帮助他们在创建API时做出明智的决策。
|
19天前
|
存储 JSON API
构建高效后端API:实践与思考
【8月更文挑战第31天】本文深入探讨如何打造一个高效、可靠的后端API。我们将通过实际案例,揭示设计原则、开发流程及性能优化的关键步骤。文章不仅提供理论指导,还附带代码示例,旨在帮助开发者构建更优的后端服务。
|
20天前
|
JavaScript 前端开发 API
深入浅出:使用Node.js搭建RESTful API的实践之旅
【8月更文挑战第31天】本文将带你踏上一次Node.js的探险之旅,通过实际动手构建一个RESTful API,我们将探索Node.js的强大功能和灵活性。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供宝贵的实践经验和深刻的技术洞见。
|
26天前
|
API 开发者 网络架构
探索RESTful API设计的艺术与实践
【8月更文挑战第24天】在数字化时代的浪潮中,RESTful API已成为现代Web应用的脊梁。本文将带你深入理解RESTful原则,并通过实际代码示例,展示如何将这些原则应用于设计高效、可维护的API接口。我们将从基础理论出发,逐步过渡到高级技巧,让你在API设计的旅程上一帆风顺。
|
19天前
|
XML JSON API
构建高效后端API:RESTful设计原则与实践
【8月更文挑战第31天】在数字化浪潮中,后端API成为连接世界的桥梁。本文将引导你探索RESTful API的设计哲学,通过实例展示如何构建一个高效、易于维护且具有扩展性的后端服务。从资源定义到HTTP方法的应用,再到状态码的精准使用,我们将一步步揭开高效后端API的秘密。
|
19天前
|
缓存 前端开发 API
打造高效后端API:RESTful设计原则与实践
【8月更文挑战第31天】 在数字化时代,后端API的设计至关重要。本文将深入探讨如何根据RESTful设计原则构建高效、易于维护和扩展的后端API。我们将从基础概念出发,逐步引导至高级实践,确保读者能够理解并应用这些原则,以提升API性能和开发效率。
|
19天前
|
UED 存储 自然语言处理
【语言无界·体验无疆】解锁Vaadin应用全球化秘籍:从代码到文化,让你的应用畅游世界每一个角落!
【8月更文挑战第31天】《国际化与本地化实战:构建多语言支持的Vaadin应用》详细介绍了如何使用Vaadin框架实现应用的国际化和本地化,提升用户体验和市场竞争力。文章涵盖资源文件的创建与管理、消息绑定与动态加载、日期和数字格式化及文化敏感性处理等方面,通过具体示例代码和最佳实践,帮助开发者构建适应不同语言和地区设置的Vaadin应用。通过这些步骤,您的应用将更加灵活,满足全球用户需求。
30 0
|
19天前
|
API 数据库 UED
全面解析构建高性能API的秘诀:运用Entity Framework Core与异步编程提升Web应用响应速度及并发处理能力的详细指南与实践案例
【8月更文挑战第31天】本文详细介绍了如何利用 Entity Framework Core (EF Core)的异步编程特性构建高性能 API。通过创建基于 EF Core 的 .NET Core Web API 项目,配置数据库上下文,并定义领域模型,文章展示了如何使用异步方法进行数据查询、加载相关实体及事务处理。具体代码示例涵盖了 GET、POST、PUT 和 DELETE 操作,全面展示了 EF Core 异步编程的优势,有助于提升 API 的响应速度和处理能力。
25 0