每年一次的互联网电商的双十一,是对一年工作的总结和洗礼,在 2017 年的双十一到来之际,我们希望通过本文梳理一下当当的一些行之有效的方法。
另外,由 InfoQ 举办的 ArchSummit 全球架构师峰会即将于 12 月 8-11 日北京举行,大会与阿里巴巴合作策划了双 11 架构专场,并邀请了顶级技术专家担任出品人,设置了“新一代 DevOps”、“人工智能与业务应用”、“架构升级与优化”等 17 个热门话题,目前大会日程已出,欢迎前来讨论交流。
面对爆发性增长的流量,首要任务是让系统在大流量冲击的情况下能够先存活下来,通过应用限流,您可以了解到当当如何在流量洪峰中保持现有系统的服务能力;在众多的系统中,系统之间的交互过于繁杂,一套全方位的链路监控系统,可以快速定位某个子系统的问题,通过 APM 系统,您将了解到当当在大量服务并存的情况下如何快速定位系统问题;MAPI 是载当当大部分入口流量的移动端网关,通过 MAPI 对服务治理以及性能的分享,您将能够以点带面地一窥当当业务系统的设计思路;搜索系统的重要程度不言而喻,而且它与业务系统的侧重点不尽相同,今年当当重点对搜索系统进行了重构,您可以通过对搜索新架构的解读一同探索技术的点点滴滴。
应用限流
电商平台的流量因促销、抢券、秒杀、热销品开卖等情况会出现大大小小的流量洪峰。流量洪峰的瞬时冲击(比如双十一零点的电商大促),可能导致系统响应缓慢,甚至整体崩溃,将对公司带来直接损失,因此大部分互联网电商都会有自己的一套解决方案,比如:流量疏导、服务降级、应用限流等。接下来介绍下当当在应用限流的设计思路和一些做法。
限流体系的整体架构图如下:
(点击放大图像)
当当的应用限流包含 3 个子系统:
- 基于 Openresty/Nginx 的限流引擎,包含限流策略和限流算法。
- 基于 Prometheus 和 Grafana 的流量监控模块
- 基于 Hadoop(计划中)和 java Web 的配置中心
下面分别介绍各子系统的一些设计经验。
限流引擎子系统
在实际业务中,绝大部分应用服务器部署在反向代理服务器(Nginx)后面。一般情况下,Nginx 的性能不会成为瓶颈,后端的应用服务器会由于各种原因(如数据库缓慢、I/O 等待时间长、代码自身的缺陷等)成为瓶颈,所以主要是根据 Nginx 后端连接的应用服务器的负载情况来决定采用何种策略对前面的 Nginx 进行限流。同时对应用服务器层无需做任何调整即可实现流量控制,开发简单,维护成本低。
反向代理服务器使用的是 Openresty( http://openresty.org ),一个基于 Nginx 的 Lua 环境封装,使用 LuaJIT 作为 Lua 编译器,由于 Just-In-Time 的方式,比传统 Lua 运行更高效。当当的限流逻辑基于它开发。
首先介绍一下限流算法,常用的拥塞控制算法有两种:
算法 描述 漏桶算法 用于控制从本节点发出请求的速率。 令牌桶算法 用于控制从外界发送到本节点的请求的速率。漏桶算法可以用在控制应用服务器发送请求到其他应用服务器的速率,确保一个稳定的速率,主要用于控制回调洪峰(其他系统的调用)的,针对用户洪峰更适合令牌桶算法。
但是如果无差别拒绝请求,会导致用户体验的严重下降,因此需要策略组的配合,按照一定的规则拒绝请求,比较合理的做法是:
-
通过防刷算法,拒绝疑似攻击的请求
通过用户识别码、IP、所在区域、上网特征和接口特征等方式拒绝频繁访问的用户,保证服务器资源能服务更多的用户。
-
尽量不影响已服务用户的使用体验
对于正在挑选商品的用户(较多的单品页、添加购物车等操作),要尽量保证不被限流挡住,否则会降低用户的使用粘性。可以做的方法是增加一个访问令牌,记录用户一些重要操作时间,在流量不够的情况下,优先放行具备访问令牌的用户。
-
尽量保证会员的使用体验
平台的会员尤其是高级会员一般是具备较大网购需求的用户,他们往往更能影响平台的口碑,所以平台要服务好这部分的会员。
其次将限流引擎中的参数进行整理,对外提供 HTTP Rest 接口,可以热更新限流策略和相关参数,对接应用限流 - 配置中心子系统。值得注意的是,openresty 提供了一种 dict 的类型是子进程内存共享的,可以跨 worker 访问,如果操作是进程不安全操作记得加锁,另外这种类型在声明时需要提供开辟内存大小,根据业务负载情况进行指定,超过内存大小会通过 LRU 算法进行内存回收。以下介绍了一些重要参数的说明:
参数 描述 限流开关 开关整个限流引擎,包含数据停止上报功能。 受限阈值 每秒低于此阈值可以任意访问,高于后将优先服务特点流量 拒绝阈值 超过后全部拒绝,保护服务器 突发阈值 允许一定量的突发请求,比如秒杀,促销开始时刻时等 访问令牌 配置秘钥和失效时间 重定向 对拒绝请求定向地址 分流代理地址 符合分流策略逻辑的流量代理地址 上报数据 配置上报数据间隔和地址最后提供了流量指标上报模块,按照配置参数进行定时数据上报,目前上报总流量、通过流量、拒绝流量、分流流量、主机 IP、应用名等实时信息。上报数据进入流量监控子系统,进行实时流量监控和离线分析,稍后会在流量监控模块详细说明。
流量监控子系统
- Prometheus 作为一种 time series 数据库,很适合做实时业务监控。因此根据上文上报数据的配置,经过 Prometheus Push Gateway 将数据初步汇总,最终通过 Prometheus 拉取数据。
- Grafana 则接入 Prometheus 数据源,提供实时流量页面和告警功能。可以通过查询各个应用集群的流量分布,通过率低于阈值或者一定时间流量为 0 则直接告警。
- Grafana Config Update Daemon 则定时比对 Prometheus 和 Grafana 的配置差异,动态调整 Grafana 配置图,能够达到上下线主机和应用时,不需要手工维护 Grafana Web,实现自动化运维。
配置中心子系统
通过 web 页面调用推荐引擎子系统提供的接口,获取当前各个系统配置信息。并通过页面进行限流策略、限流阈值等信息的实时更新。未来计划通过定时抓取 Prometheus 的数据,将数据存放在 Hadoop 中,基于 time series 预测算法训练模型数据,对未来大促进行流量预判。通过提供的 Web 页面,管理员可以查看历史流量和流量预测数据,指导系统管理员优化应用限流配置的同时,也积累了宝贵的大促流量数据。为日后分析用户行为,甚至市场运营的判断提供更广阔的空间。
APM 系统
如今的电商平台随着业务复杂化,包含的功能越来越多,内部系统间的调用关系也越来越多。针对一个用户的一次购买流程(商品展示、搜索、购买、支付)可能需要调用数十次、甚至上百次的接口。首先如此庞大的调用链梳理成为一件很棘手的事情,而更迫在眉睫的事情是针对用户偶现的慢操作问题,无法快速定位到众多调用中的哪一步导致的。因此急需一套完整的应用性能治理系统——APM(Application Performance Management)来解决这个问题。
在 APM 产品选型时,我们考虑到当当目前系统的现状,得出了以下的结论:
- APM 产品需要性能极高,嵌入线上系统的探针需要尽量少的资源来完成调用信息获取,APM 自身也需要能承载百亿量级的入库和查询操作的能力。
- APM 产品需要方便部署使用,尽量不改动或者轻微改动代码即可实现系统追踪。
- APM 系统可以提供系统依赖关系、SLA 指标和调用耗时甘特图。
- APM 产品方便定制开发,方便扩展内部不同语言的系统使用和提供各种的统计数据。
最终我们选取了 OpenSkywalking 团队开发的 skywalking 产品( http://skywalking.io )作为当当 APM 系统,并进行定制开发。Skywalking 是遵守 opentracing 规范(CNCF 基金会下,开源分布式服务追踪标准)开发的,针对 java 支持使用字节码增强技术,无需改动代码即可实现系统追踪。自动追踪支持主流的 web 容器、中间件、RPC 组件、数据库和 NOSQL 等。架构图如下:
(点击放大图像)
Skywalking 每个节点都支持集群模式,可以无限水平扩展,易用的 Java 探针自动上报数据,无需程序做任何改动。但是当当内部系统使用语言众多,除了 Java,包含 PHP、C、Python 和 Go 等语言开发的系统。对于 APM 系统来讲,追踪的调用轨迹只要一个节点没有追踪到,就会导致链路中断,达不到预期效果,因此我们在 Skywalking 上针对不同语言开发了相应的组件包 agent。agent 中为了尽量减少对业务系统的影响,使用写文件的方式,再通过 Filebeat+Kafka 的方式将追踪信息发给 collector,改造完的架构图如下:
(点击放大图像)
如上图所示,agent 只负责汇总收集数据,和 collector 保持启动时候的注册功能,数据上报功能则通过写文件的方法,交给另一个 Filebeat 进程来完成。Filebeat 通过配置一个 Kafka 的 Topic,将数据发送到 Kafka 集群,再通过 Kafka consumer 来拉取数据发送 skywalking collector。同时 agent 提供管理服务 API,可以通过管理 web 界面管理 agent 的运行情况、开关、采样率等信息,在 agent 工作影响线上服务性能时(比如大促之时),可以快速关闭探针采集和上报功能来保证系统最大性能;各个系统也可以根据自身需要调节系统的采样率,达到追踪和性能影响的一个平衡点。改造后的优缺点如下:
- 优点
a) 异步 Filebeat 进程发送数据,减少对业务进程的影响
b) 更好的支持异构语言系统
c) 探针支持热变更,更适合线上业务系统
2. 缺点
a) 部署成本和复杂度提高
b) 数据计算会有更大延迟
目前当当 APM 系统已经上线,在 MAPI、购物车、交易、促销、TMS、搜索、价格、广告等系统上嵌入,进行 7*24 小时全天候监控。在双十一前的几轮压测中,通过 web 界面提供了整个系统的 SLA 指标情况,对整体压测是否通过,提供了简单直观的数据。再通过查看慢操作的甘特图,可以快速定位具体的是哪个调用步骤比较慢,指导业务系统开发者快速发现问题所在,解决可能存在的性能瓶颈问题,也给整个电商平台性能更好的指导意见。
MAPI 服务
MAPI 是当当移动客户端所有请求的流量总入口。MAPI 需要对客户端做安全、登录等移动端校验后,才可以将客户端真实的业务请求发送相应的后端服务进一步处理;MAPI 同时负责通过缓存机制减轻后端压力;MAPI 的另一个职责是管理移动客户端的用户信息,如:Session、设备、账号等,以及移动端的定制化服务,如:移动 APP 推送消息等。
微服务统一 API 网关
为了保证对外服务的安全性,我们在服务端实现的大量服务接口均加入了权限校验机制,并且为了防止客户端在发起请求时被篡改,引入了签名校验机制。我们遵循微服务架构设计理念,将原本处于同一应用中的多个模块拆分为多个独立应用。为保证不同应用提供的接口均需保证相同的校验逻辑,我们不得不在每个应用中都实现同一套校验逻辑。
随着微服务规模的扩大,校验逻辑愈加冗余,对它们的 BUG 修复或扩展优化则必须在每个应用中分别修改。这不仅会引起开发人员的抱怨,更会加重测试人员的负担。所以,我们需要一套机制解决此类问题。
为此,我们采用了 API 网关架构的解决方案。API 网关定位为设计模式中的 Facade 模式,它作为整个微服务架构系统的门面,对所有的外部客户端访问进行调度和过滤。它不仅具有请求路由、负载均衡、校验过滤等功能,还有服务发现、熔断、服务聚合等能力。
系统架构改造前:
(点击放大图像)
系统改造后:
服务治理
服务降级
在大促期间,为了减少服务器压力,会对一些相对消耗服务器计算资源,但并非核心流程的服务进行降级。客户端请求时,会请求降级服务,根据降级的配置来判断是否需要做相应的请求,如:购物车数量、单品缓存时间、请求收藏状态等。
熔断
在微服务架构中,我们将系统拆分成了很多服务单元,单元之间通过服务注册与订阅的方式互相依赖。依赖间通过远程调用(如:RPC/Restful API 等)的方式交互,这样就有可能因为网络原因或者依赖服务自身问题出现调用延迟或异常,最后就会因等待出现的故障的依赖方响应形成请求积压,最终导致服务自身的瘫痪。
以互联网电商为例,系统会拆分为用户、订单、库存、积分、评论等一系列服务。在用户下单时,客户端将通过 MAPI 接口的调用,请求订单服务的创建订单接口,订单服务则会请求库存接口。如果库存或订单服务因网络延迟等原因造成响应超时,MAPI 接口的线程将被挂起。高并发场景中会造成挂起线程的大量堆积,导致客户经过漫长等待而下单失败。过多的客户下单请求将产生雪崩效应,导致服务不可用,甚至整个系统瘫痪。
当当采用 Netflix 开源的 Hystrix 作为熔断方案,以下对 Hystrix 进行分析讲解。
下图展示了用户请求正常访问下的一个情况:
(图片来源于 GitHub-Hystrix)
当其中的某一个系统发生延迟,并阻塞用户请求时,如下图:
(图片来源于 GitHub-Hystrix)
在高并发的请求的情况下,会造成所有的用户请求发生延迟,最终整个消费系统瘫痪而不可用,如下图:
(图片来源于 GitHub-Hystrix)
Hystrix 使用“舱壁模式”实现线程池的隔离,它会为每一个依赖服务创建一个独立的线程池,这样就算某个依赖服务出现延迟过高的情况,也只是对该依赖服务的调用产生影响,而不会拖慢其他的依赖服务。
HHVM 提升服务性能
为了满足快速的迭代开发,MAPI 中大部分采用 PHP5.6 来实现的,其中有一些使用了 Hack 的 IO 异步操作。我们经过大量的压测案例对比,发现 HHVM 更加符合 MAPI 业务,故我们采用 HHVM 虚拟机。
同时,HHVM 的作者(Facebook 的同事)主动与当当进行技术交流,更有效地提升了 HHVM 应用层性能,使得业务高效运行。
HHVM 类似于 C#的 CLR 和 Java 的 JVM。HHVM 是在 HPHPc 的基础上构建,它会将 PHP 代码转换成高级别的字节码(一种中间语言),在运行时即时(JIT)编译器会将这些字节码翻译成机器码,它比 open_cache (Zend 使用的)更稳定,更高效。
搜索新架构
今年双十一,当当网上线了搜索新架构。平均响应时间降低了一个数量级,QPS 提升了一个数量级,索引数据的实效性也有了大幅度的改善。可以更加从容的应对双十一的流量,提升用户的搜索体验。
当当网的搜索引擎是典型的电商搜索引擎,索引量远小于互联网搜索引擎,不会达到数百亿,但有很多复杂的电商搜索逻辑,可以说是术业有专功。搜索引擎全部由当当自主研发,主要使用 C++ 语言,长期以来,支撑当当搜索业务的同时,也积累了一些技术债。为了快速响应业务需求,导致业务逻辑的无序添加,由于缺乏有效的定期梳理,积重难返,搜索效果迭代的效率越来越低。
16 年底,技术部启动了搜索引擎的重构项目。重构的技术路线面临多种选择,在人力有限的情况下,是全面转向开源技术,还是自主研发,存在争议。在充分调研不同技术路线的风险和收益后,搜索引擎的不同子系统,采用了不同的方式:搜索服务对性能和稳定性有着极高的要求,现有开源框架不足以支撑,并且业务的定制能力也是一个问题,因此依然采用了全部自主研发的方式;离线系统对性能没有过高的要求,全面转向了开源技术,可以更加方便地实现业务逻辑、加快效果迭代的速度。
搜索服务重构
搜索服务在重构之前是一个单机版的检索程序,平均响应时间、索引量都存在极限,无法水平扩展。重构后,采用了分布式架构,拆分了搜索节点、query 分析节点、index 节点、摘要节点。各节点之间,没有依赖关系的运算可以并行,大幅度降低了平均响应时间;index 节点拆分后,索引数据可以分层分片,索引量不再受单机限制。
(点击放大图像)
搜索新架构没有沿用旧系统的设计和代码,重新设计编写了从底层倒排索引、索引合并、属性筛选和汇总,到最终结果合并、排序的全部代码。通过代码的重新编写全面梳理了业务逻辑,严格划分了搜索框架和业务逻辑的代码边界。实现了一个通用的搜索框架,在这个框架之上,附加了当当网的全部搜索业务逻辑。搜索框架和业务逻辑的剥离,为效果迭代打下了坚实的基础。
搜索服务重构耗费了很多时间,最终取得了明显的收益,单机检索性能有了大幅度的提升:平均响应时间降低了一个数量级、QPS 提升了一个数量级。分布式架构是一个因素,索引结构、属性筛选和汇总、排序方式的改变也是另一个重要因素。
索引实时更新
电商搜索对于实时性有着极高的要求,尤其是双十一,价格、库存的变化需要及时更新到索引中,商品的上下架、标题等促销信息的修改也需要快速生效,搜索新架构充分考虑了这些应用场景,做了专门的优化。
搜索旧系统在索引数据的更新上有 2 个问题:一个是更新需要加线程锁,更新的瞬间全部查询线程暂停;另一个是采用传统的全量 + 增量索引,增量会显著影响 term 的 doc 链合并速度,导致索引结构劣化,一个全量周期内,增量商品的数量不能太多,否则就会导致检索耗时过长。
搜索新架构采用了新设计的索引结构,更新不需要加线程锁,可以在并发查询的同时更新索引数据,大幅度提高了更新效率;同时采用全量 + 实时索引,替换了全量 + 增量索引,独立的实时索引节点使得索引结构不会劣化,增量商品的数量不会显著影响检索耗时。
新架构的实时索引常驻内存,由于需要动态增加商品,整体上是链式的,采用跳表 + 数组加速。这里有一个平衡,全链式存储效率高,没有内存空间浪费,缺点也很明显,由于它是跳跃式的,CPU 的 cache 命中率极低,term 的 doc 链合并过程耗时过长;跳表 + 数组虽然耗费内存空间,但会明显提升检索效率和 CPU 的 cache 命中率,缩短检索耗时。
搜索新架构的实时索引通过参数调整内存耗用和检索耗时,在索引量相同的情况下,对比全量索引,能够控制在 2 倍的内存空间耗用和 4 倍的检索耗时。由于实时索引的能效比没有显著下降,仍在可接受的范围内,对于时效等级特别高的商品,可以去掉全量,全部用实时索引替代,简化建库、更新流程。
离线系统重构
搜索引擎的离线系统,负责收集搜索服务需要的全部数据,当当网搜索引擎的离线系统,需要汇集商品的全部信息:标题、描述、价格、库存、销量、分类、属性、评论、店铺、促销、各种需要展示的辅助信息等。这些信息来自多个上游系统,接口方式异构,有数据库表的,也有消息队列的,还有文件形式的,各自有着独立的更新周期,之间还存在依赖、绑定关系,业务逻辑十分复杂。
搜索旧系统采用 MySQL 作为主数据库,依赖多种语言编写的同步作业对接上游系统,绝大部分数据最终汇合到主数据库。全量索引相对简单一些,直接从 MySQL 拉取全部数据;增量则麻烦一些,需要依赖一个自主研发的数据中心,通过比对检测价格、库存、标题等实时数据的变化,推送到搜索服务。
重构前,MySQL 主数据库和数据中心是整个离线系统的瓶颈:MySQL 作为主数据库,优点是关系数据库添加业务逻辑方便,但缺点也很明显,即不适合搜索场景,主要是全量数据的 dump 耗时过长;数据中心是单机版程序,受内存容量限制,能够检测的索引量存在上限,本身结构复杂,添加业务逻辑的成本过高。
(点击放大图像)
重构后,整个离线系统全面转向了分布式,用 HBase+Spark+Kafka 的大数据开源框架替换了 MySQL 主数据库和数据中心:主数据库采用 HBase 集群,明显降低了全量数据的 dump 耗时;同步作业只负责同步数据,最大限度地剥离了业务逻辑;业务逻辑全部采用 Spark 重新编写,研发效率高;增量采用 HBase 的 Coprocessor 检测数据更新,用 Kafka 做数据流转。重构后的离线系统,运行效率明显提升,全量建库时间缩短到旧系统的 1/4,绝大部分索引字段的更新缩短到了分钟级,大幅度改善了索引数据的实效性。
小结
通过以上几个系统,我们复盘了当当一年来的进展以及对双十一的准备。篇幅有限,有不少新的变化无法一一说明,目前当当基于 Mesos 的作业云平台,已经承载了上万的业务作业;容器编排也进入了最后的落地阶段;Service Mesh 的探索正在进行中;期待下一次的双十一可以分享更多的实践经验。
评论