ArchSummit 全球架构师峰会北京站将于 2015 年 12 月 18 日~19 日在北京国际会议中心召开,大会设置了《揭秘双十一背后的技术较量》专题来深入解读双十一背后的技术故事,欢迎关注。
区别于其他网购品牌唯品会定位是“一家专门做特卖的网站”, 商业模式为“名牌折扣 + 限时抢购 + 正品保险”,即“闪购”(flash sales)模式。每天上新品,以低至 1 折的深度折扣及充满乐趣的限时抢购模式,为消费者提供一站式优质购物体验,
这种闪购限时特卖业务特点决定了网站随时都需要处理高并发、大流量的用户请求。大量买家在每次新的品牌档期上线后,大量涌入,抢购商品,造成网站承担大量流量。尤其碰到热门商品,网站并发访问剧增,会造成整个网站负载过重,响应延迟,严重时甚至会出现服务宕机的情况。
另外唯品会有众多的业务销售模式,如自营销售模式、JIT、直发、海淘、O2O 等等,这些业务销售模式导致系统非常复杂。系统外部需要通过开放平台对接供应商系统和第三方物流系统。内部系统包括诸多系统,如供应商管理、商品选品、商品交易、支付系统、物流仓储、客服系统、商品配送等等。这些系统功能模块之间关联性非常强,逻辑扩展非常复杂,如何快速满足业务发展的需要,是一个非常迫切的问题。
为了保证系统在高并发、大流量访问下工作,并且使系统有较强的扩展性,我们的设计主要从以下几个方面展开:
- 系统模块有效切分
- 服务化解耦,集中服务治理
- 增加异步访问
- 多阶段缓存,降低后端压力
- 优化数据库访问
- 加强系统监控
系统模块有效切分
唯品会整个业务系统虽然已经拆分成几个相对独立的子系统如交易平台(B2C)、VIS、WMS、TMS、ODS、CS、EBS 等,但是这些业务系统在实际运作中业务耦合严重。碰到新业务逻辑加入,就需要每个模块做大量修改,各个开发团队之间为了业务逻辑放在那里争论不休,浪费了大量的时间,导致开发效率比较低。这主要由于模块划分不合理,导致模块之间边界不清楚。所以我们架构团队从整个系统角度出发,梳理整个流程,重新做系统定位,将不同业务子系统做物理分离,减少彼此之间的依赖,使各个子系统独立部署,出现问题后能快速采取措施,隔离出问题模块,将故障影响降到最低。
服务化解耦,集中服务治理
服务化设计已经被主流电商系统证明是一个切实可行的方向。通过 SOA 服务化改造,实现了服务使用者和服务提供者的分离,使系统间的服务解耦和系统内高内聚,大大简化了系统复杂性,具有更强的伸缩性和扩展性,满足了业务快速发展的需要。
我们怎么有效管理这些服务呢?
Venus 是唯品会自开发的一款基于 Spring 的 Java 开发框架, 以降低开发的复杂度, 提高开发人员的开发效率, 提升代码质量, 规范开发流程。
Venus 框架涵盖了以下内容:
- 数据库访问层封装,支持分库、分表,连接池优化
- 缓存(Redis/Memcached)接口封装,连接池优化
- CRUD 服务代码自动生成(包含数据库操作)
- OSP/REST 服务调用接口封装及异步支持
- ValidateInternals
- 单元/集成测试模版代码自动生成
- 配置中心
- 中央文档中心
Venus 生态体系
其中开放服务平台(OSP)的主要目标是提供服务化的核心远程调用机制。契约化的服务接口保证系统间的解耦清晰、干净;基于 Thrift 的通信和协议层确保系统的高性能;服务可以自动注册并被发现,易于部署;配合配置中心,服务配置可以动态更新;客户端与治理逻辑的分离使服务接入得到极大简化;除此之外,OSP 提供了丰富的服务治理能力,如路由、负载均衡、服务保护和优雅降级等,通过 OSP,有效的实现了流量控制。
服务分流
首先 OSP Proxy 具有软负载的作用,系统不需要硬件负载均衡,可以将服务请求均衡地分配到不同服务主机上。另外 OSP 可以配置服务的路由,服务请求可以被分配到不同版本的服务中处理,这样很容易实现灰度发布。
服务限流
在系统流量达到极限时的情况,有自动熔断机制。熔断器是在服务或者周边环境(如网络)出现了异常后主动断开客户端后续的使用,从而避免服务崩溃无法恢复。但是在后续时间熔断将使用小量请求尝试侦测服务是否已经恢复,如果恢复则将服务再次提供给客户端调用。熔断器的机制即保护了服务也减少了人工干预。相关的阀值都在是在配置中心中配置,并支持动态修改生效。限流一定要谨慎使用,要使用恰当的限流策略,区分正常访问和恶意请求,不能将正常的用户请求抹杀掉。如果无法区分是否是恶意请求,需要将应用分级,确保优先级最高的应用能被访问到,比如所有上线的商品信息。而对于下线的商品信息,可以根据请求容量作适当的限流。
Nginx Rate Limiter 是一个自主开发的防刷工具,通过 Nginx 上的 LUA 脚本插件,实现在 Nginx 上对本机的 HTTP 访问进行限流控制的工具,以提高在促销等高业务量环境下保障系统稳定运行的能力。
Nginx Rate Limiter 通过 RESTful API 接口进行配置以及信息查看,可以对全局进行开关等配置,也可以针对指定 URL 分别添加多个限流配置,包括全局的限流。限流配置可以选择以下一种方式:
- 最大访问请求速率,超出则丢弃请求
- 按比例丢弃请求。
服务降级
对于电商系统,为了保证用户体验,在资源有限的条件下,我们必须保证关键系统的稳定性。通过对不同业务级别定义不同的降级策略,对除核心主流程以外的功能,根据系统压力情况进行有策略的关闭,从而达到服务降级的目的,例如在线商品信息,我们必须保证优先访问,而对于下线的商品信息,我们可以容许在访问容量受限情况下,容许关闭下线商品详情页面的访问等。
增加异步访问
对于系统间实时性要求不高的操作,如果执行时比较耗时,可通过异步处理提高调用者性能,提高响应能力,尤其通过异步调用通知非主要流程,加快了系统主要业务流程的反应速度和性能,异步处理机制可起到缓冲的作用,被通知的下游系统可依据自身能力控制处理数据量,避免遭受超负荷的冲击,保证系统稳定运行,增加了系统可用性。
分布式异步消息队列服务器可在宕机后确保消息不丢失,异步系统有重试机制,从而提高系统可用性、健壮性和扩展性。
在用户下单后,其他系统如物流、供应商系统、配送、财务等系统需要获取订单详情、订单状态,订单系统通过异步消息方式将订单的变化通知其它系统,异步调用实现系统间隔离解耦,上下游系统业务逻辑分离,下游系统只需要解析异步消息进行处理,不需要依赖上游系统的业务逻辑,从而降低了系统之间的依赖。即使下游系统出现异常,订单系统仍然能正常处理数据。
多阶段缓存,降低后端压力
1、动静分离,静态化
静态化可降低后端压力,一方面通过用户浏览器缓存静态资源,失效时间通过 cache-control 来控制。另外一方面通过 CDN 缓存,例如商品详情页面,为了提高缓存效率,可将商品详情页面伪静态化,将 URL 后缀显示为 HTML,商品描述信息等静态信息在有用户访问情况下,缓存到靠近用户的 CDN 节点,另外为了提高 CDN 效率,提前将商品图片推送到 CDN。其它商品动态数据可动态加载,如商品运营信息、商品库存、尺码表等,从而降低了避免不必要的后台访问。
2、分布式缓存
引入分布式缓存,对缓存数据服务节点做统一集中管理,可支持缓存集群弹性扩展,通过动态增加或减少节点应对变化的数据访问负载,通过冗余机制实现高可用性,无单点失效,不会因服务器故障而导致缓存服务中断或数据丢失。应用端使用统一的 API 接口访问缓存服务器。
通过分布式缓存,可做应用对象缓存、数据库缓存、会话状态及应用横向扩展时的状态数据缓存。
3、巧用应用服务器本地缓存
分布式缓存虽然有效解决了访问压力,但由于缓存服务器分布在不同网络端、不同数据中心中部署,随着访问量增大将导致 I/O 和带宽瓶颈。为此可将那些基本不修改的配置数据、全局数据可以在应用服务器本地缓存,减少对后端缓存服务器实例的峰值冲击。本地缓存需要谨慎使用,如果大量使用本地缓存,可能会导致相同的数据被不同的节点存储多份,对内存资源造成较大的浪费。
使用缓存对提高系统性能有很多好处,但是不合理使用缓存非但不能提高系统的性能,反而成为系统的累赘,甚至影响系统运作,产生很大的风险。对于频繁修改的数据、没有热点的访问数据、数据一致性要求非常高的数据,不建议使用缓存。
优化数据库访问
在高并发大数据量的访问情况下,数据库存取瓶颈一直是个令人头疼的问题。如果数据库访问出现性能问题,整个系统将受到影响。
为此需要优化数据库访问,从以下几个方面解决高并发问题:
- 优化复杂查询,提高数据库查询效率,找出关键模块的数据库慢查询进行优化。例如减少数据库表之间的 Join、重构数据库表相关索引、对 where 子句进行优化等。
- 保证在实现功能的基础上,尽量减少对数据库的访问次数;通过查询参数,尽量减少对表的访问行数,最小化结果集,从而减轻网络负担;能够分开的操作尽量分开处理,提高每次的响应速度,查询时用到几列就选择几列,降低数据库访问 IO 负载压力。
- 基于电商系统读写比很大的特性,采用读写分离技术,通过一主多从,写操作只发生在主表,多操作发生在从表上,可以大大缓解对主数据库的访问压力。
- 对业务和系统的细分,对数据库表进行垂直拆分。将数据库想象成由很多个"数据块"(表)组成,垂直地将这些"数据块"切开,然后把它们分散到多台数据库主机上面,从而可以分散单位时间内数据库访问压力。垂直切分的依据原则是:将业务紧密,表间关联密切的表划分在一起。
- 垂直切分后,需要对分区后表的数据量和增速进一步分析,以确定是否需要进行水平切分。对于核心数据如订单,采取水平分区的方式,通过一致性哈希算法,利用用户 Id 把订单数据均匀的分配在各个数据库分区上,在应用系统查询时,以用户 ID 调用哈希算法找到对应数据库分区,从而将高峰时期的数据库访问压力分散到不同数据库分区上,从而实现数据库的线性伸缩,极大提升数据库的承载能力。
- 借助于分布式缓存,缓存提供了远大于数据库访问的性能。当某一应用要读取数据时,会首先从缓存中查找需要的数据,如果找到了则直接执行,找不到的话则从数据库中找。在设计时,需要防止缓存失效带来的缓存穿透压力。
- 容许一定程度的数据冗余,对于关键模块,为了防止对其它模块的依赖而影响当前模块的性能和可靠性,可适度保存其它模块的关键数据,减少由于访问其它模块服务带来的系统损耗和可靠性压力。
- 使用 NoSQL 数据库对海量大数据进行存储和处理。
加强系统监控
业务系统通常由众多分布式组件构成,这些组件由 web 类型组件,RPC 服务化类型组件,缓存组件,消息组件和数据库组件。一个通过浏览器或移动客户端的前端请求到达后台系统后,会经过很多个业务组件和系统组件,并且留下足迹和相关日志信息。但这些分散在每个业务组件和主机下的日志信息不利于问题排查和定位问题的根本原因。这种监控场景正是应用性能监控系统的用武之地,应用性能监控系统收集,汇总并分析日志信息达到有效监控系统性能和问题的效果。通过监控信息,可以清晰地定位问题发生原因,让开发人员及时修复问题。
唯品会有三级监控,系统 / 网络层面监控、应用层面监控和业务层面监控。
系统 / 网络层面监控,主要是对下列指标进行监控:服务器指标,如 CPU、内存、磁盘、流量、TCP 连接数等;数据库指标,如 QPS、主从复制延时、进程、慢查询等。
业务层面监控,通过两种方法,第一种在指定页面做埋点,第二种方法从业务系统的数据库中,将需要监控的数据抽取出来,做必要的分析处理,存入运维自己维护的数据库中;然后通过浏览器页面,展示监控数据,页面同时提供各种时间维度上的筛选、汇总。对一些业务的关键指标如 PV、UV、商品展示、登录 / 注册、转化率、购物车、订单数量、支付量、发货量和各仓订单数据。可自定义告警范围,通知相关人以便做出响应。
应用层面监控系统 Mercury,是唯品会独立研发的应用性能监控平台。通过在应用程序中植入探针逻辑来实现对应用代码、关系数据库、缓存系统的实时监控。Mercury 通过收集日志、上报日志的方式即时获取相关性能指标并进行智能分析,及时发现分布式应用系统的性能问题以及异常和错误,为系统解决性能和程序问题提供方便可靠的依据。同时通过 Mercury 数据展现平台,用户可轻松便捷的获取应用 360 度监控信息。
在唯品会体系中,Mercury 提供的主要功能包括:
- 定位慢调用:包括慢 Web 服务(包括 Restful Web 服务),慢 OSP 服务,慢 SQL
- 定位错误:包括 4XX,5XX,OSP Error
- 定位异常:包括 Error Exception,Fatal Exception
- 展现依赖和拓扑:域拓扑,服务拓扑,trace 拓扑
- Trace 调用链:将端到端的调用,以及附加在这次调用的上下文信息,异常日志信息,每一个调用点的耗时都呈现给用户
- 应用告警:根据运维设定的告警规则,扫描指标数据,如违反告警规则,则将告警信息上报到唯品会中央告警平台
Mercury 架构主要分以下几大模块:
日志由客户端传输到服务端后,分二条路径。第一条路径,裸日志(Trace log / Exception log )通过 kafka,再通过 flume 直接落地到 HBase。这些裸日志用来查询 trace 调用链信息和异常日志。另一条路径,日志信息通过 kafka 直接送到 spark stream,通过 spark 分析后计算后,产生 data points 性能指标数据,再通过 flume 写入 OpenTSDB。整个传输过程最重要的就是保证数据消费不要丢失和积压。
一旦满足通过运维人员事先配置的告警规则,告警模块可触发告警动作。告警信息可在第一时间将故障上报给中央告警平台。
结束语
以上几点是我们对高并发系统的一些体会,我们在不断改进系统,为将唯品会做大做强持续努力,也希望通过本次分享给大家带来一定收获。
感谢郭蕾对本文的策划和审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入 InfoQ 读者交流群)。
评论