提到应用网关系统,我们脑海中或多或少都会闪过一些关键词,比如统一入口、高并发、大流量、限流、防刷、实时监控等等。 Youzan Application Gateway Center(公司内部称之为Carmen系统),它就是目前有赞的应用层网关系统。每天承载着亿级别的请求量,持续的为开放平台和多个有赞App应用提供着稳定的服务。
今天我们来一起剖析下整个有赞的应用层网关,聊聊网关的当前的概况、整个网关系统的构成、遇到的一些问题的思考和解决方案。当然在进入正题之前,我们不妨先对这个网关系统提出几个问题:
网关在有赞技术生态系统中的使用场景是什么?
一个请求通过网关,它会有多少额外的性能消耗?
网关系统能给我们带来哪些显而易见的好处?
网关系统流量不断上涨的过程中,有遇到过哪些棘手问题?
如果我要设计一个网关系统,有哪些值得推荐的设计思路?
在网关这样一个高并发系统中,有没有遇到一些有意思的并发问题?
网关系统如何保障服务的可靠性?
以上这些问题在您耐心读完整篇文章后,将会一一得到答案。
首先,网关作为一个系统入口,目前它在有赞技术生态圈中主要面向开放平台和移动app两种应用场景。开放平台属于外部调用,移动app属于内部调用。内部与外部调用使用不同的域名,以便合理分配资源、有效进行权限控制、流量控制等。
一、应用网关系统概况
整个网关系统拆分为3个子系统,都由java实现:
控制台,负责API的创建与管理。
核心系统,负责接收、分发请求并且返回结果。
监控系统,负责呈现API的各项监控指标。
小结:目前的使用经验看来G1回收算法并没有比CMS用得舒服,反而带来了不触发FGC的问题以及增加了OOM后分析这么大的堆文件的时间成本。 当然,在尝试新技术的过程中,我们会不断去探索G1这种增量回收算法的最佳使用姿势。
2) 内存泄漏 内存泄漏跟临界区也类似,让人防不胜防。 集合实现类是内存泄漏的重灾区,比如对于一个线程池配上无限大队列时一定要注意这个无限队列在某个时刻会让系统发生OOM。 我们会仔细检查系统中是否存在线程池配无限大队列的情况,同时在容易造成内存泄漏的集合类指定一个大小,比如本地缓存,我们通常会设置一个上界。 我们网关线上实际碰到过线程池的无界队列引发的OOM,一台服务后端阻塞导致一个线程的队列撑爆了堆内存,一直FGC,CPU暂用率100%,当前服务器无法提供正常服务,系统dump了一个超过6GB大小的堆文件。最后只有把它拷贝到一台大内存服务器用命令行分析后,再把结果文件拷贝回本机使用HeapAnalyzer分析。它能清晰展示对象层次关系,直接定位问题,尤其适合分析较大的dump文件。 遇到OOM并不可怕,使用一款优秀的分析工具很容易定位到具体的问题代码。
网络优化: 网络方面的优化我们主要做了几方面的事:
短连接改用长连接。一开始我们使用短连接,流量稍微一上来,大量time-wait状态的链接耗尽文件句柄资源,让系统无法正常提供服务。
运维层面在服务器限制time-wait状态的上限,比如10000,这样无论如何time-wait都不会超过这个上限。
容器版本的选择,之前在使用Jetty9.3.0时,偶尔会遇到大量time-close的情况,后来同时升级了操作系统内核版本以及Jetty版本之后再没出现这种问题。
服务部署:
服务多机集群部署,我们一般按照3~5倍当前流量来评估机器数量,而且运维都会配置上完善的系统监控,一旦某个指标,磁盘、CPU,内存等超标立马报警。
应用配置健康检查,当一台机器服务宕机,应该自动被下线。
容量不够时,运维会快速扩容,或者应用手动启动服务降级。
网关上面一层的Nginx会把请求均匀地分发给后端服务器,使得每台服务器压力保持一致。
服务对稀缺资源都是弱依赖,比如对redis和mysql是弱依赖。这主要得益于本地缓存的使用。
对外部的依赖系统设置合理超时时间,比如redis设置1s,db设置2s,这会在一定程度上保护我们的系统。
3.2 高性能:
网关接受到请求到发起后端请求之间的平均时耗约2ms,即网关本身平均每次请求消耗了约2ms。 当今一个主频3GHz的CPU每秒能处理30亿条指令,所以耗在执行程序指令上的时间相比网络IO和磁盘IO可以忽略不计。根据这个思路,我们考虑使用本地缓存来降低网络IO的消耗。
充分利用缓存技术: 前期仅使用redis,压测的时候QPS并不是很理想。每次访问一个接口会组装参数,这个过程会多次读redis(当时的压测环境读redis的速度跟我们线上还是有一定差距)。 根据API不怎么修改的特点,再添加一层本地缓存,仅做这个改动之后QPS直接翻了好几倍。 当然本地缓存固然是好,不仅提升了性能,还增加了系统的健壮性,但是同样要增加缓存维护的复杂度。 总的来说,两级缓存的使用是网关性能的保障。
3.3 扩展性:
网关核心逻辑采用类责任链模式(Filter Chain),每个filter处理一件事情,这样无论增加处理逻辑还是增加不同协议的服务,仅需新增一个Filter到调度逻辑;想要禁用某个Filter,也能静态或者动态排除它,即可插拔性。
3.4 安全性:
完善的限流防刷机制,既有默认的限流方案,也允许API责任人随时配置定制化限流方案。
可以通过ACL模块随时禁用或者启用某个来访者。
服务降级,利用限流机制,可以从不同维度对异常的服务或者接口降低访问频次。
鉴权,接口的访问通过签名和免签两种模式来鉴别用户身份。免签使用业界通用的OAuth2.0。
3.5 可靠性:
线上系统环境错综复杂,问题在所难免。 所以保障可靠性除了服务自身需要高质量以外,主要还是依赖强大的监控系统来作为我们的千里眼、顺风耳。监控系统将会在第四节介绍。
三、控制台系统
控制台的主要功能点:
API的创建、功能测试、发布、编辑、自动生成文档
API流控、ACL的配置
本地缓存的管理
用户权限管理
鉴权相关的配置与管理
在以上常规功能点之上,我只想重点介绍下API的设计思路,这个设计成功解耦了外部调用者和内部开发者之间的耦合,不仅让网关后期的功能扩展工作事半功倍,而且为API开发者在配置的时候提供了灵活性。
我们使用API名字和版本号来唯一标识一个API,比如kdt.item.add.1.0.0。
外部API名字与内部API名字分离;外部参数与内部参数也完全分离。中间通过一层映射关系联系起来。 分离API名字是为了修改内部API名字不影响外部API,而且多个外部API名字可以映射到同一个内部API名字。
分离内外参数为了可以在修改内部参数时不会影响外部参数,同时可以在不同参数之间映射
四、监控系统
网关自身有个针对API层面的监控系统,主要功能如下: 统计每个请求经过每个Filter的时间,这可以让我们监控到每个阶段的性能,帮助我们进行优化和排查问题。
统计API的调用量、成功次数、异常次数。
实时统计调用量、错误量,异常量的top10。
系统监控 主要针对内存、磁盘、CPU等指标,它们超出阈值的之后会自动触发告警,这时相关负责人会及时处理异常。
印象中已经有好几次是监控系统帮助我们网关系统及时发现问题,避免了故障的发生。
五、结语:
有赞应用层网关系统的概况以及遇到的一些网络、并发、GC问题的处理思路大致如上所述。重点交流一下思路,不再续说一些模块实现细节,相信对具体的实现方案每个人都有自己的一套解决方案。
随着业务不断地发展,有赞应用层网关系统将会面向更多的应用场景,同时也会面临诸多新的挑战,当然网关的未来也值得期待。
最后谢谢您对有赞的关注,同时如果双方合适,期待在往后的日子里并肩作战。强势插入招人内推邮箱 dingdongsheng@youzan.com
网友评论