10月 04

“先卖10000台再说!”201189日,小米网负责人黎万强在公司内部大会上这样说道。时间回到4年多前,在小米“标配”产品发布日816日前几天,小米公司100多号人沉浸在即将到来的第一款产品诞生的亢奋中,但是,没有人能告诉我们:未来,我们将能走多远\做多大。彼时,小米网仅有三位开发工程师,在经过两个多月的紧张开发后,小米网将要第一次面对公众在线销售产品,接受大考。由于工程师资源极度紧张,我们甚至考虑过使用ECSHOP之类的开源系统搭建小米网,不过幸好我们很快放弃了这一想法。因为在3个月之后,我们就发现不得不对系统进行一轮重构了。如果使用了第三方开源系统,为适应原系统架构,我们将长期被迫“迁就”原架构而放弃很多更优的设计,并为学习这个系统而付出时间成本。

第一代的小米网架构非常简单,如图1所示。

1
第一代小米网架构

我们实现了一个最基本的电商网站基本组件:在线销售系统、订单处理系统、仓储和物流系统,其中物流只对接了兄弟公司凡客的子公司如风达(现已独立)。所有的业务系统共用一个数据库。这样运行了几个月,发现网站访问量越来越大,当有新品销售时,面对突然激增的大量访问,数据库压力陡增,造成后端业务系统几乎无法使用。

2012年上半年,在小米网运行半年多后,我们决定将业务系统进行拆分,首先将销售系统剥离,之后逐步将越多越多的子系统拆分。拆分后各业务系统相对独立,各自使用自己的数据库,这样就完美解决了不同系统抢占数据库资源的问题,也让模块更清晰,程序员也能专注于自己负责的业务系统的开发,如图2所示。

2
拆分业务系统

这种结构随着小米网子系统的增多,只运行了几个月,我们就发现灾难开始显现了:我们需要维护的接口越来越多。系统间接口调用图变成了图3这样。

3
系统间接口调用图

这张网越来越复杂,从而使系统越来越难以维护,问题层出不穷。为了让各子系统尽量解耦,我们开发了小米网异步消息服务系统(Notify),让它作为所有子系统异步通信的中间人,所有子系统只需与中间人通信,接口标准化,将网状结构变为星状结构,大大降低了系统间通信成本,提高了开发效率,如图4所示。

4 小米网异步消息服务系统(Notify

此时,我们各子系统的网络架构也进行了相应的升级,大体分三层:调度层、业务层、数据库。在调度层,我们主要使用LVSHAProxy做流量转发和故障转移;业务层则五花八门,不同语言,不同框架百花齐放;数据层主要使用MySQLNoSQL存储及缓存服务(RedisMemcache)。

经过以上改造,我们从结构上,让整个流程更清晰了。然而,流量在继续增大,特别是小米网的爆品非常多,由于供应链及硬件产业特性,导致新品上市时供应量无法满足用户需求,用户的热情又远超我们的期望。大量请求导致前端销售系统的数据库开始告急。我们急需采取一种方案,将峰值抹平。我们是一家电商网站,在交易时会有大量在线联机事务处理,对数据一致性要求极高,所以经过讨论,我们决定采用淘宝开源的数据库中间件产品Cobar来实现数据库的水平切分。一共部署了32个实例,按用户ID实现数据的均匀读写,每个实例都做了MM双主高可用实现,如图5所示。

小米网技术架构变迁实践

5 采用淘宝开源的数据库中间件产品Cobar

这种架构保证了我们日常的在线销售稳定运行无压力,但是,每逢重大产品发布时,仍然要面对数百万QPS的抢购并发压力。不光是对数据库,对前端的应用程序服务器一样造成巨大的冲击。非抢购时间和抢购时间的流量差距可达几十倍到上百倍,如果我们按抢购峰值流量部署服务器,那将是一笔巨大的硬件资产浪费。在这个背景下,我们组织专门的人员开发了小米网的大型秒杀系统:BigTap。其实道理很简单,大家都去银行办过业务,在银行窗口排队的多,但是我们几乎很少看到有人在银行的取号机面前排过队。大秒系统其实就是充当银行的取号机的角色。它的业务逻辑非常简单:判定用户是否合法,合法则给这个用户购买资格,用户抢购成功;不合法则拒绝用户请求,抢购失败,如图6所示。

小米网技术架构变迁实践

6 小米网的大型秒杀系统BigTap

由于平时不抢购,大秒系统没有任何流量,所以,我们将大秒系统整体迁移至AWS云上,抢购前一天,将系统扩容,抢购后,将服务器再下架,实现完美伸缩,大大节约了成本。

除了大秒系统以外,我们还额外开发了众多小米网特色服务以支撑自己的业务。其中之一就是基于RedisTwitter开源的Twemproxy开发的小米通用缓存服务(内部代号MCC),集群中单节点达到14QPS,支持自动分片,热加载,全Redis协议支持。由于MCC支撑了小米网全业务线的缓存服务,所以我们还将此服务设计成双机房高可用架构,如图7所示。

小米网技术架构变迁实践

7 小米双机房架构(图中M表示主Redis实例,S表示从Redis实例)

常态下,双机房同时工作,读写机房1的主和从实例。机房2Mi-Twemproxy也读写机房1的主从实例。当机房1故障时,只需修改机房2Mi-Twemproxy读写机房2的从实例,并将此实例提升为主实例。当机房2出现故障时,不需要做任何改动。不足之处是当机房1出现故障时,机房2短期内只有一个主实例工作,无冗余。

在搭建电商网站中,我们还要时刻考虑的一个业务问题是:如何尽快地将货物售出,实现最快的库存周转,同时还要有好的购物体验,在这个问题上,库存系统的设计是一个很大的挑战。我们尝试考虑过很多电商的做法:按仓库库存卖商品。这种设计的好处是:仓与仓之间不用调拨,省去物流费用。缺点是,可能某个仓库存过高卖不出,某个仓又缺货,导致用户无法下单购买。最终导致库存周转周期太长,降低了整体效率。小米网结合自身实际情况,设计了一套虚拟库存分配系统,将各个仓作为库存渠道,可以自由合并,拆分供给不同的销售渠道,且可以自由调配。这种方法的缺点是订单可能会跨仓发货,增加物流成本,但是优点也显而易见:大大提高了库存周转率,用户也获得了较好的购物体验,在以用户为中心的小米网,这是我们首先会考虑的问题,设计如图8所示。

小米网技术架构变迁实践

8 小米虚拟库存分配系统

跨仓调拨既然无法避免,那我们能做的是尽量减少跨仓调拨频次,在多个仓库之间调拨时尽量合理规划。小米网的跨仓调拨问题实际上是一个多目标线性规划求最优解的问题,我们将各仓当前需求量,未来预测需求量,调拨线路和时间均考虑了进去。

在任何一家互联网公司中,都必须要重视的一件事就是:监控。服务器、应用程序、我们的业务,都存在监控需求。然而当今好的监控方案几乎是空白,每个公司业务不同,特点不同,通用的监控方案全都收效甚微。监控的意义在于出故障时,责任人应该第一时间知道并能采取措施。这要求监控系统做到一是及时,二是准确。当监控对象是一家大型公司时,还要求监控系统做到极其重要的一点是——有效。下面我会分析何为有效监控。小米网一路走过来,做过很多监控,当业务触发异常时就开始通过短信、邮件等告警。就算监控点很少时也不足以构成威胁,一旦触发告警,马上处理。然而,业务量激增后,监控点非常多,责任人往往会在短时间内收到大量重复的告警邮件和短信,时间一长,人都会疲劳,此时极容易忽略重大告警。所以,之前监控系统设计的最大问题在于没有区分异常和告警的关系,没有设定异常的策略和告警的策略。如果一条告警送到了,但是没有引起责任人的重视,那么这就是一条失败的、无效的告警。遇到异常马上触发告警是不科学的设计。我们新设计的监控系统的目的是大大提高有效告警量,其核心思想如下。

异常判断策略:

异常判定函数

val,监控结果取值

val(key),结果若有多个字段,如Server各参数,取指定 key 的值

count,结果若有多条,取结果集条数

exist,监控结果是否存在

empty,监控结果是否为空

异常判定表达式

val>3 ,取值大于3时判定为异常

count>10,结果集条数大于10时判定为异常

exist,结果不为空即判定为异常

empty,结果为空即判定为异常

告警判断策略:

告警判定函数

times,当前连续异常次数 percent(num),最近n次监控结果中异常的百分比

limit(num),告警次数限制,num=0为不限制

snooze(minutes),不限制告警次数时,异常周期内每隔minutes分钟告警一次 告警

判定策表达式

times>3,连续异常3次则告警

percent(10)>0.8,最近10次监控结果中异常数大于80%则告警

times>3 && limit(0) && snooze(30),大于3次异常开始告警,异常周期内,每隔30分钟告警一次 
通过这两个策略组合,能够实现最大限度不打扰人的有效告警。

最后,我在此稍提一下小米网的服务化。这是目前我们正在进行的技术架构大升级,采用Thrift+ETCD+Go+PHP实现。小米SOA框架完全自行开发,框架本身由Go语言实现。非Go语言的项目,我们通过两个小插件:服务发现助手和服务注册助手帮助接入到我们的服务平台。关于服务化的意义,网上有非常多的详细的分析,在此不再赘述。对于越来越大的技术架构体系,服务化将是未来的趋势。

 

written by ocean

9月 27

余额宝总结起来包括这样几个属性,第一它是一个传统的货币基金,但它把 T + 0 做到极致,另外他管理大量的用户资产。同时他具备极简的用户体验,符合互联网精神。我们在网页、支付宝 APP 或者其他途径能快速方便的进行基金申赎,它的应用渠道也非常多和广。

可以说从余额宝开始,真正的进入一个全民理财的时代,接下来给大家分享一下几个数字。余额宝用户数可以说达到了接近于 1/4 国人数量,日交易峰值可以达到两亿笔,最大并发数可以达到每秒五千笔。截止 2016 年上一季度公开披露信息,规模已经达到六千亿以上。

深度:余额宝技术架构及演进

从余额宝的创新来说可以从两个方面去讲它,一是业务上的创新,他对 T + 0 发挥到极致,是现金管理工具,是底层帐户。还有就是嵌入式直销,把货币基金嫁接到支付宝上去。当时来讲应该是一个在行业内是具有非常大的一个开创意义的一件事情。

技术上创新是今天重点要说的事情:

1.  基金直销和 TA 清算的整合。传统的基金系统直销和清算是分开。直销系统每天要把数据以文件形式导入清算系统里去。这件事情我们做了很大的改进,这么大体量数据来说,每天导入导出这个数据不可想象,在这里做了一个直销和 TA 融合,后面我会有一个详细的介绍。

2.  交易的简化,监管大的框架下,满足监管要求的基础上,我们对交易逻辑做了很大的一个简化。

3.  余额宝是核心业务在云上运行的系统。这是余额宝技术方面的创新。

架构演进历史

一期 IOE 架构

下面介绍一下一期的架构,很明显看到就是传统的 IOE 架构。底层存储是 EMC 存储。中间层就是采用小型机,其中 KCXP 和 KCBP 是金证公司的消息中间件和业务中间件。往上前端是前置解析是用的 WebLogic,负载均衡用的硬件负载均衡。

深度:余额宝技术架构及演进

这个架构对它的定位满足需求首先是支持千万级用户,传统基金销售模式是走代销机构的方式,投资基金用户也是以理财为目的。所以每天可能处理的帐户的开户可能也就是几万到几十万的规模。由于余额宝对接是支付宝,支付宝有庞大的用户群,在用户规模上要达到千万级,这是当时对需求的定位。

第二点就是刚才提到把直销系统和 TA 清算系统做了融合,在数据库层面是共享的,避免数据再做一次导出和导入,对清算也节省了很多时间。

另外一点是传统基金的互联网化。传统基金只需要做到系统的 5 × 8 可用性,对接支付宝以后,要做 7 × 24 小时可用性。

2013 年 6 月,一期系统如期上线,业务规模远远超出我们想象。运营和运维人员反馈清算时间太长,基本上要从凌晨开始到早上八点,每天都是这样,我们感受到巨大的压力。另外当年要参加支付宝这边的双 11 活动,以当时的系统处理能力来讲,肯定是做不到的。

二期云端架构

基于这些原因,需要对一期的系统做优化,怎么优化?二期架构用一个词概括就是上云,充分利用云计算的计算能力,包括云计算对存储的处理能力。

深度:余额宝技术架构及演进

整个架构进行了水平拆分。前面一期架构实际上就是一路的处理,到了二期把它分成多路。

从数据库层面分成多个 RDS(阿里云一款基于MySQL的关系型数据库产品)。另外一个就是去Oracle,很多利用数据库存储过程计算的部分,移到计算单元完成。

第三点是把直销和 TA 再次在计算资源层面分离。余额宝系统的数据处理,包括实时处理和批量处理。过去在一期架构的时候发现清算时,数据库负荷非常高,严重影响实时请求体验。所以在上云之后,在计算资源这块再次对它进行了分离,主要目的是提升客户体验。上云之后,当然充分利用了云计算的优势,其中很主要一个优势就是可扩展性。

水平拆分

接下来详细介绍一下是怎么来做水平拆分。

第一点如何来分,以什么维度来分?最后确定以用户维度,这样最终处理时间与用户交易的均衡程度有关。确定以用户维度进行拆分之后,确定哪些点来进行拆分,同样还是从用户角度出发,帐户、交易、份额、份额明细、份额变动等等。对于历史表直接合到仓库里去了,因为每日清算完之后,当日数据直接把它归档掉。

拆分之后,涉及到这样一个问题,TA 系统因为还要与周边的系统进行交互,交互的接口同样还是文件,数据导入需要先把文件拆成多份,再把每一份导入 TA,数据导出时系统要导出多份文件,再合并为一份。

总控

拆分最大的难点是在总控节点的处理,刚才说了 worker 节点能够保持松耦合,但仍需要通过总控节点进行统一协调,保持事务一致性。

最后数据核对阶段,也是要由总控汇总节点上的数据,按照清算规则对数据进行核对。还有很重要的收益分配部分,采用两个阶段来做,第一阶段由总控节点分配到每个节点上去。,然后在节点范围分配到用户粒度。

下图是上云前后指标上的一个对比,上云前基本上核心清算工作要做八个小时,上云之后在千秒以内可以完成。所以二期上云以后,IT 终于可以喘口气。目前来讲应对春节、双11、国庆长假等场景,系统都能稳定应对这些。

深度:余额宝技术架构及演进

(点击图片查看大图)

这是上云前后投入产出对比情况,传统的 IOE 架构特点成本很高,硬件成本给企业带来的压力非常大,云计算的好处就是在成本上是可以做到很细的,并且方便按需增加,这是一个非常大的成本上的优势。过去投入四百万只能支持一千万的帐户的量级,现在在投入上可能只是增长一倍,支持用户数已经远远不止一倍了。

深度:余额宝技术架构及演进

数据架构

二期架构可以满足核心交易之后,还要考虑余额宝目前这么大的数据量,怎么把这个数据用好。

近一年来很多工作都是考虑数据后处理这块。其中数据来源于业务数据、日志数据和其他数据。我们推进数据仓库的建设和数据的产出。工具方面我们有很多自主开发的,同时也采用了阿里采云间,以及其他外采工具,具体支撑业务包括生产数据分析、资金预测、数据监控、运营支持,合规风控支持等等。开篇也提到了金融系统数据安全是重中之重,所以这块我们也会有相关的数据安全方面的管理。

深度:余额宝技术架构及演进

二期架构的问题

二期架构解决很多问题,但并不是尽善尽美,总结一下还是有几个可以提高的点:

·       耦合。首先计算和数据的耦合还是存在的。这实际上是对系统的扩展是不利的。另外,单个计算节点上,在业务上还是存在耦合,我们很多业务上的东西还是存在拆分的可能。

·       数据流转,我们现在数据库层面也是分布式,所以数据的抽取、同步和流转会遇到很多现实的问题。

·       运维。在运维方面除了遇到的传统分布式系统的运维遇到的一些难题之外,我们还在业务层面的运维也会遇到一些现实问题。

未来演进思考

对系统未来演进思考,主要分这么几个方面。

1.  从大的方面来讲是全局通盘考虑。我们要把核心和辅助系统通盘考虑,降低数据的冗余,降低数据维护成本。

2.  数据方面要用多不同的存储来解决不同场景的需求,还有刚才提到计算和存储的彻底解耦,做到计算和存储的独立可扩展。

3.  计算方面尽量做到业务上的拆分和轻量化,化繁为简,拆分之后把应用服务化。

数据驱动

我们系统的演进,数据量由单一小量向大量多类转变,同时应用种类从以交易为主到交易、分析和挖掘多种类并存。另外实时性要求也有变化,新的业务模式有时候要求实时或者准实时给用户呈现结果。

深度:余额宝技术架构及演进

对业务来说对不同数据应用采用不同的存储。

·       比如对于在线交易,可以采用经过阿里支付宝验证过的 OB,专门用于解决金融级的分布式关系数据库的解决方案;

·       对于批量结算,可以继续沿用多年来在余额宝已经用的很娴熟的 RDS 集群。

·       对于 2T 到 PB 级的小数仓可以用 PetaData,解决以年度为单位的数据存储。

·       对于大规模的批量计算,数据仓库这块,我们直接就用 ODPS。

·       对大表存储可采用 OTS。

·       对于分析型、挖掘类需求可采用列存数据库。

服务化

关于拆分和服务化治理,后面考虑做的事情是充分利用阿里云的 PaaS 平台技术,把我们大应用拆分为简单的可横向扩展的小应用。

深度:余额宝技术架构及演进

在服务的调用上,每个服务同时是服务提供方也是服务调用方,由 PaaS 平台的中间件统一管理服务。对我们来说是更多考虑如何基于中间件把业务来做好。服务化改造之后肯定会涉及到服务之间的调用。同步调用,可以直接走服务化的接口。

深度:余额宝技术架构及演进

异步调用

异步调用主要靠消息中间件。金融系统对消息中间件的可靠性要求非常高,这块我们还是沿用传统思路,并不想采用开源解决方案去填那些坑,更多考虑采用成熟金融级消息中间件来做这件事情。

深度:余额宝技术架构及演进

下面是一个总图,中间 EDAS 是统一企业级服务化解决方案,然后通过 DTS 解决数据实时同步的问题,采用 CDP 解决离线数据同步的问题。在数据应用上可以满足很多的需求,比如采集系统或者报表展示或者是用户短信的推送等等,这就是我们对整个未来的架构演进的思考。

深度:余额宝技术架构及演进

Q&A

提问:都切到云上,数据安全上怎么考虑?

陈雨:之前讲到金融要求是私有云,我们是在阿里金融云上,并不是在公有云上,可理解为物理上是隔离的。

提问:接口交互的技术是文件,文件的完整性和一致性如何保证的?你们自己要处理它吗?为什么要用文件的方式?

陈雨:我们对接是支付宝,文件的正确性和准确性由支付宝保证。我们需要对大文件按节点数拆分成小文件,然后并行处理。接口必须用文件方式,金融行业很多系统对接最后要走文件接口,文件是用来对帐的准确性保障,实时不是那么可靠。

提问:说到计算和数据耦合,输入输出解开,具体大体上是怎么实施它?

陈雨: RDS 来是单机数据库产品,通过分布式中间件 DRDS 或其他解决方案,以实现计算节点像使用单机数据库一样使用数据库集群。

提问:咱们有基于用户纬度拆分,主要是什么原因导致我们要这么拆,基于用户纬度拆分,有没有比较坑的地方或者我们怎么避免它?

陈雨:基于用户的拆分,一方面签约协议号是跟支付宝的接口,还有一个考虑是以用户为维度的查询需求相对多。当然其他非用户纬度查询就费点事了。

提问:我是互联网金融从业者,刚才您提到我们余额宝系统,有清算系统是吧。不知道清算是有内部清算和外部清算,我们这边清算是怎么做的?比如说内部清算是指交易明细和你的帐户余额之间的比对。你外部清算可能是你本地的数据和银行数据之间的比对。

陈雨:我所说的清算是你所说的第一种。每天做一次内部比对,计算用户的份额和收益。

提问:之前也用过其他的消息中间件,你刚才提到成熟的消息中间件不是开源,我们其他从业者不能用到是吧?

陈雨:这涉及到一个生态圈的问题,如果进入阿里云的生态圈,可充分享用云计算资源。如果确实是在生态圈之外,可选择它的对应开源版本。开源版本在版本更替上或者服务方面,跟阿里云上存在一定的差别。

 

written by ocean

6月 03
每天万亿级调用的重量级系统,每次申请序列号平时调用耗时1ms,99.9%的调用耗时小于3ms,服务部署于数百台4核CPU服务器上!
老司机介绍
曾钦松,微信高级工程师,目前负责微信后台基础服务、朋友圈后台等开发优化,致力于高可用高性能后台系统的设计与研发。2011年毕业于西安电子科技大学,早先曾在腾讯搜搜从事检索架构、分布式数据库方面的工作。

 

 

微信在立项之初,就已确立了利用数据版本号实现终端与后台的数据增量同步机制,确保发消息时消息可靠送达对方手机,避免了大量潜在的家庭纠纷。时至今日,微信已经走过第五个年头,这套同步机制仍然在消息收发、朋友圈通知、好友数据更新等需要数据同步的地方发挥着核心的作用。

而在这同步机制的背后,需要一个高可用、高可靠的序列号生成器来产生同步数据用的版本号。这个序列号生成器我们称之为seqsvr,目前已经发展为一个每天万亿级调用的重量级系统,其中每次申请序列号平时调用耗时1ms,99.9%的调用耗时小于3ms,服务部署于数百台4核CPU服务器上。本文会重点介绍seqsvr的架构核心思想,以及seqsvr随着业务量快速上涨所做的架构演变。

背景

 

 

微信服务器端为每一份需要与客户端同步的数据(例如消息)都会赋予一个唯一的、递增的序列号(后文称为sequence),作为这份数据的版本号。在客户端与服务器端同步的时候,客户端会带上已经同步下去数据的最大版本号,后台会根据客户端最大版本号与服务器端的最大版本号,计算出需要同步的增量数据,返回给客户端。这样不仅保证了客户端与服务器端的数据同步的可靠性,同时也大幅减少了同步时的冗余数据。

这里不用乐观锁机制来生成版本号,而是使用了一个独立的seqsvr来处理序列号操作,一方面因为业务有大量的sequence查询需求——查询已经分配出去的最后一个sequence,而基于seqsvr的查询操作可以做到非常轻量级,避免对存储层的大量IO查询操作;另一方面微信用户的不同种类的数据存在不同的Key-Value系统中,使用统一的序列号有助于避免重复开发,同时业务逻辑可以很方便地判断一个用户的各类数据是否有更新。

从seqsvr申请的、用作数据版本号的sequence,具有两种基本的性质:

  1. 递增的64位整型变量

  2. 每个用户都有自己独立的64位sequence空间

举个例子,小明当前申请的sequence为100,那么他下一次申请的sequence,可能为101,也可能是110,总之一定大于之前申请的100。而小红呢,她的sequence与小明的sequence是独立开的,假如她当前申请到的sequence为50,然后期间不管小明申请多少次sequence怎么折腾,都不会影响到她下一次申请到的值(很可能是51)。

这里用了每个用户独立的64位sequence的体系,而不是用一个全局的64位(或更高位)sequence,很大原因是全局唯一的sequence会有非常严重的申请互斥问题,不容易去实现一个高性能高可靠的架构。对微信业务来说,每个用户独立的64位sequence空间已经满足业务要求。

目前sequence用在终端与后台的数据同步外,同时也广泛用于微信后台逻辑层的基础数据一致性cache中,大幅减少逻辑层对存储层的访问。虽然一个用于终端——后台数据同步,一个用于后台cache的一致性保证,场景大不相同。

但我们仔细分析就会发现,两个场景都是利用sequence可靠递增的性质来实现数据的一致性保证,这就要求我们的seqsvr保证分配出去的sequence是稳定递增的,一旦出现回退必然导致各种数据错乱、消息消失;另外,这两个场景都非常普遍,我们在使用微信的时候会不知不觉地对应到这两个场景:小明给小红发消息、小红拉黑小明、小明发一条失恋状态的朋友圈,一次简单的分手背后可能申请了无数次sequence。

微信目前拥有数亿的活跃用户,每时每刻都会有海量sequence申请,这对seqsvr的设计也是个极大的挑战。那么,既要sequence可靠递增,又要能顶住海量的访问,要如何设计seqsvr的架构?我们先从seqsvr的架构原型说起。

架构原型

 

 

不考虑seqsvr的具体架构的话,它应该是一个巨大的64位数组,而我们每一个微信用户,都在这个大数组里独占一格8bytes的空间,这个格子就放着用户已经分配出去的最后一个sequence:cur_seq。每个用户来申请sequence的时候,只需要将用户的cur_seq+=1,保存回数组,并返回给用户。

图1. 小明申请了一个sequence,返回101

预分配中间层

任何一件看起来很简单的事,在海量的访问量下都会变得不简单。前文提到,seqsvr需要保证分配出去的sequence递增(数据可靠),还需要满足海量的访问量(每天接近万亿级别的访问)。满足数据可靠的话,我们很容易想到把数据持久化到硬盘,但是按照目前每秒千万级的访问量(~10^7 QPS),基本没有任何硬盘系统能扛住。

后台架构设计很多时候是一门关于权衡的哲学,针对不同的场景去考虑能不能降低某方面的要求,以换取其它方面的提升。仔细考虑我们的需求,我们只要求递增,并没有要求连续,也就是说出现一大段跳跃是允许的(例如分配出的sequence序列:1,2,3,10,100,101)。于是我们实现了一个简单优雅的策略:

  1. 内存中储存最近一个分配出去的sequence:cur_seq,以及分配上限:max_seq

  2. 分配sequence时,将cur_seq++,同时与分配上限max_seq比较:如果cur_seq > max_seq,将分配上限提升一个步长max_seq += step,并持久化max_seq

  3. 重启时,读出持久化的max_seq,赋值给cur_seq

图2. 小明、小红、小白都各自申请了一个sequence,但只有小白的max_seq增加了步长100

这样通过增加一个预分配sequence的中间层,在保证sequence不回退的前提下,大幅地提升了分配sequence的性能。实际应用中每次提升的步长为10000,那么持久化的硬盘IO次数从之前~10^7 QPS降低到~10^3 QPS,处于可接受范围。在正常运作时分配出去的sequence是顺序递增的,只有在机器重启后,第一次分配的sequence会产生一个比较大的跳跃,跳跃大小取决于步长大小。

分号段共享存储

请求带来的硬盘IO问题解决了,可以支持服务平稳运行,但该模型还是存在一个问题:重启时要读取大量的max_seq数据加载到内存中。

我们可以简单计算下,以目前uid(用户唯一ID)上限2^32个、一个max_seq 8bytes的空间,数据大小一共为32GB,从硬盘加载需要不少时间。另一方面,出于数据可靠性的考虑,必然需要一个可靠存储系统来保存max_seq数据,重启时通过网络从该可靠存储系统加载数据。如果max_seq数据过大的话,会导致重启时在数据传输花费大量时间,造成一段时间不可服务。

为了解决这个问题,我们引入号段Section的概念,uid相邻的一段用户属于一个号段,而同个号段内的用户共享一个max_seq,这样大幅减少了max_seq数据的大小,同时也降低了IO次数。

图3. 小明、小红、小白属于同个Section,他们共用一个max_seq。在每个人都申请一个sequence的时候,只有小白突破了max_seq上限,需要更新max_seq并持久化

目前seqsvr一个Section包含10万个uid,max_seq数据只有300+KB,为我们实现从可靠存储系统读取max_seq数据重启打下基础。

工程实现

工程实现在上面两个策略上做了一些调整,主要是出于数据可靠性及灾难隔离考虑

  1. 把存储层和缓存中间层分成两个模块StoreSvr及AllocSvr。StoreSvr为存储层,利用了多机NRW策略来保证数据持久化后不丢失;AllocSvr则是缓存中间层,部署于多台机器,每台AllocSvr负责若干号段的sequence分配,分摊海量的sequence申请请求。

  2. 整个系统又按uid范围进行分Set,每个Set都是一个完整的、独立的StoreSvr+AllocSvr子系统。分Set设计目的是为了做灾难隔离,一个Set出现故障只会影响该Set内的用户,而不会影响到其它用户。

图4. 原型架构图

容灾设计

 

 

接下来我们会介绍seqsvr的容灾架构。我们知道,后台系统绝大部分情况下并没有一种唯一的、完美的解决方案,同样的需求在不同的环境背景下甚至有可能演化出两种截然不同的架构。既然架构是多变的,那纯粹讲架构的意义并不是特别大,期间也会讲下seqsvr容灾设计时的一些思考和权衡,希望对大家有所帮助。

seqsvr的容灾模型在五年中进行过一次比较大的重构,提升了可用性、机器利用率等方面。其中不管是重构前还是重构后的架构,seqsvr一直遵循着两条架构设计原则:

  1. 保持自身架构简单

  2. 避免对外部模块的强依赖

这两点都是基于seqsvr可靠性考虑的,毕竟seqsvr是一个与整个微信服务端正常运行息息相关的模块。按照我们对这个世界的认识,系统的复杂度往往是跟可靠性成反比的,想得到一个可靠的系统一个关键点就是要把它做简单。相信大家身边都有一些这样的例子,设计方案里有很多高大上、复杂的东西,同时也总能看到他们在默默地填一些高大上的坑。当然简单的系统不意味着粗制滥造,我们要做的是理出最核心的点,然后在满足这些核心点的基础上,针对性地提出一个足够简单的解决方案。

那么,seqsvr最核心的点是什么呢?每个uid的sequence申请要递增不回退。这里我们发现,如果seqsvr满足这么一个约束:任意时刻任意uid有且仅有一台AllocSvr提供服务,就可以比较容易地实现sequence递增不回退的要求。

图5. 两台AllocSvr服务同个uid造成sequence回退。Client读取到的sequence序列为101、201、102

但也由于这个约束,多台AllocSvr同时服务同一个号段的多主机模型在这里就不适用了。我们只能采用单点服务的模式,当某台AllocSvr发生服务不可用时,将该机服务的uid段切换到其它机器来实现容灾。这里需要引入一个仲裁服务,探测AllocSvr的服务状态,决定每个uid段由哪台AllocSvr加载。出于可靠性的考虑,仲裁模块并不直接操作AllocSvr,而是将加载配置写到StoreSvr持久化,然后AllocSvr定期访问StoreSvr读取最新的加载配置,决定自己的加载状态。

图6. 号段迁移示意。通过更新加载配置把0~2号段从AllocSvrA迁移到AllocSvrB

同时,为了避免失联AllocSvr提供错误的服务,返回脏数据,AllocSvr需要跟StoreSvr保持租约。这个租约机制由以下两个条件组成:

  1. 租约失效:AllocSvr N秒内无法从StoreSvr读取加载配置时,AllocSvr停止服务

  2. 租约生效:AllocSvr读取到新的加载配置后,立即卸载需要卸载的号段,需要加载的新号段等待N秒后提供服务

图7. 租约机制。AllocSvrB严格保证在AllocSvrA停止服务后提供服务

这两个条件保证了切换时,新AllocSvr肯定在旧AllocSvr下线后才开始提供服务。但这种租约机制也会造成切换的号段存在小段时间的不可服务,不过由于微信后台逻辑层存在重试机制及异步重试队列,小段时间的不可服务是用户无感知的,而且出现租约失效、切换是小概率事件,整体上是可以接受的。

到此讲了AllocSvr容灾切换的基本原理,接下来会介绍整个seqsvr架构容灾架构的演变

容灾1.0架构:主备容灾

 

 

最初版本的seqsvr采用了主机+冷备机容灾模式:全量的uid空间均匀分成N个Section,连续的若干个Section组成了一个Set,每个Set都有一主一备两台AllocSvr。正常情况下只有主机提供服务;在主机出故障时,仲裁服务切换主备,原来的主机下线变成备机,原备机变成主机后加载uid号段提供服务。

图8. 容灾1.0架构:主备容灾

可能看到前文的叙述,有些同学已经想到这种容灾架构。一主机一备机的模型设计简单,并且具有不错的可用性——毕竟主备两台机器同时不可用的概率极低,相信很多后台系统也采用了类似的容灾策略。

设计权衡

主备容灾存在一些明显的缺陷,比如备机闲置导致有一半的空闲机器;比如主备切换的时候,备机在瞬间要接受主机所有的请求,容易导致备机过载。既然一主一备容灾存在这样的问题,为什么一开始还要采用这种容灾模型?事实上,架构的选择往往跟当时的背景有关,seqsvr诞生于微信发展初期,也正是微信快速扩张的时候,选择一主一备容灾模型是出于以下的考虑:

  1. 架构简单,可以快速开发

  2. 机器数少,机器冗余不是主要问题

  3. Client端更新AllocSvr的路由状态很容易实现

前两点好懂,人力、机器都不如时间宝贵。而第三点比较有意思,下面展开讲下

微信后台绝大部分模块使用了一个自研的RPC框架,seqsvr也不例外。在这个RPC框架里,调用端读取本地机器的client配置文件,决定去哪台服务端调用。这种模型对于无状态的服务端,是很好用的,也很方便实现容灾。我们可以在client配置文件里面写“对于号段x,可以去SvrA、SvrB、SvrC三台机器的任意一台访问”,实现三主机容灾。

但在seqsvr里,AllocSvr是预分配中间层,并不是无状态的。而前面我们提到,AllocSvr加载哪些uid号段,是由保存在StoreSvr的加载配置决定的。那么这时候就尴尬了,业务想要申请某个uid的sequence,Client端其实并不清楚具体去哪台AllocSvr访问,client配置文件只会跟它说“AllocSvrA、AllocSvrB…这堆机器的某一台会有你想要的sequence”。换句话讲,原来负责提供服务的AllocSvrA故障,仲裁服务决定由AllocSvrC来替代AllocSvrA提供服务,Client要如何获知这个路由信息的变更?

这时候假如我们的AllocSvr采用了主备容灾模型的话,事情就变得简单多了。我们可以在client配置文件里写:对于某个uid号段,要么是AllocSvrA加载,要么是AllocSvrB加载。Client端发起请求时,尽管Client端并不清楚AllocSvrA和AllocSvrB哪一台真正加载了目标uid号段,但是Client端可以先尝试给其中任意一台AllocSvr发请求,就算这次请求了错误的AllocSvr,那么就知道另外一台是正确的AllocSvr,再发起一次请求即可。

也就是说,对于主备容灾模型,最多也只会浪费一次的试探请求来确定AllocSvr的服务状态,额外消耗少,编码也简单。可是,如果Svr端采用了其它复杂的容灾策略,那么基于静态配置的框架就很难去确定Svr端的服务状态:Svr发生状态变更,Client端无法确定应该向哪台Svr发起请求。这也是为什么一开始选择了主备容灾的原因之一。

主备容灾的缺陷

在我们的实际运营中,容灾1.0架构存在两个重大的不足:

  1. 扩容、缩容非常麻烦

  2. 一个Set的主备机都过载,无法使用其他Set的机器进行容灾

在主备容灾中,Client和AllocSvr需要使用完全一致的配置文件。变更这个配置文件的时候,由于无法实现在同一时间更新给所有的Client和AllocSvr,因此需要非常复杂的人工操作来保证变更的正确性(包括需要使用iptables来做请求转发,具体的详情这里不做展开)。

对于第二个问题,常见的方法是用一致性Hash算法替代主备,一个Set有多台机器,过载机器的请求被分摊到多台机器,容灾效果会更好。在seqsvr中使用类似一致性Hash的容灾策略也是可行的,只要Client端与仲裁服务都使用完全一样的一致性Hash算法,这样Client端可以启发式地去尝试,直到找到正确的AllocSvr。

例如对于某个uid,仲裁服务会优先把它分配到AllocSvrA,如果AllocSvrA挂掉则分配到AllocSvrB,再不行分配到AllocSvrC。那么Client在访问AllocSvr时,按照AllocSvrA -> AllocSvrB -> AllocSvrC的顺序去访问,也能实现容灾的目的。但这种方法仍然没有克服前面主备容灾面临的配置文件变更的问题,运营起来也很麻烦。

容灾2.0架构:嵌入式路由表容灾

 

 

最后我们另辟蹊径,采用了一种不同的思路:既然Client端与AllocSvr存在路由状态不一致的问题,那么让AllocSvr把当前的路由状态传递给Client端,打破之前只能根据本地Client配置文件做路由决策的限制,从根本上解决这个问题。

所以在2.0架构中,我们把AllocSvr的路由状态嵌入到Client请求sequence的响应包中,在不带来额外的资源消耗的情况下,实现了Client端与AllocSvr之间的路由状态一致。具体实现方案如下:

seqsvr所有模块使用了统一的路由表,描述了uid号段到AllocSvr的全映射。这份路由表由仲裁服务根据AllocSvr的服务状态生成,写到StoreSvr中,由AllocSvr当作租约读出,最后在业务返回包里旁路给Client端。

图9. 容灾2.0架构:动态号段迁移容灾

把路由表嵌入到请求响应包看似很简单的架构变动,却是整个seqsvr容灾架构的技术奇点。利用它解决了路由状态不一致的问题后,可以实现一些以前不容易实现的特性。例如灵活的容灾策略,让所有机器都互为备机,在机器故障时,把故障机上的号段均匀地迁移到其它可用的AllocSvr上;还可以根据AllocSvr的负载情况,进行负载均衡,有效缓解AllocSvr请求不均的问题,大幅提升机器使用率。

另外在运营上也得到了大幅简化。之前对机器进行运维操作有着繁杂的操作步骤,而新架构只需要更新路由即可轻松实现上线、下线、替换机器,不需要关心配置文件不一致的问题,避免了一些由于人工误操作引发的故障。

图10. 机器故障号段迁移

路由同步优化

把路由表嵌入到取sequence的请求响应包中,那么会引入一个类似“先有鸡还是先有蛋”的哲学命题:没有路由表,怎么知道去哪台AllocSvr取路由表?另外,取sequence是一个超高频的请求,如何避免嵌入路由表带来的带宽消耗?

这里通过在Client端内存缓存路由表以及路由版本号来解决,请求步骤如下:

  1. Client根据本地共享内存缓存的路由表,选择对应的AllocSvr;如果路由表不存在,随机选择一台AllocSvr

  2. 对选中的AllocSvr发起请求,请求带上本地路由表的版本号

  3. AllocSvr收到请求,除了处理sequence逻辑外,判断Client带上版本号是否最新,如果是旧版则在响应包中附上最新的路由表

  4. Client收到响应包,除了处理sequence逻辑外,判断响应包是否带有新路由表。如果有,更新本地路由表,并决策是否返回第1步重试

基于以上的请求步骤,在本地路由表失效的时候,使用少量的重试便可以拉到正确的路由,正常提供服务。

总结

 

 

到此把seqsvr的架构设计和演变基本讲完了,正是如此简单优雅的模型,为微信的其它模块提供了一种简单可靠的一致性解决方案,支撑着微信五年来的高速发展,相信在可预见的未来仍然会发挥着重要的作用。

 

written by ocean \\ tags: ,

11月 24

首先,非常感谢 OneAPM 技术公开课举办的这次活动。本场演讲我主要阐述一下,58 同城从小流量、中等规模流量、大流量,到更大的流量过程中,架构是怎么演进的?遇到了哪些问题?以及如何解决这些问题?

好的架构不是设计出来的而是演进出来的

对很多创业公司而言,在初期的时候,我们很难在初期就预估到流量十倍以后、百倍以后、一千倍以后网站的架构会变成什么样。当然,如果在最初的时期,就设计一个千万级并发的流量架构,那样的话,成本是也是非常之高的,估计很难有公司会这样做。

所以,我们主要来讲架构是如何进行演化的。我们在每个阶段,找到对应该阶段网站架构所面临的问题,然后在不断解决这些问题的过程中,整个战略的架构就是在不断的演进了。

其实,在 58 同城建立之初,站点的流量非常小,可能也就是是十万级别,这也就意味着,平均每秒钟也就是几次的访问。此时网站架构的特点:请求量是比较低,数据量比较小,代码量也比较小。可能找几个工程师,很容易就做一个这样的站点,根本没什么「架构」可言。

其实,这也是很多创业公司初期面临的问题,最开始58同城的站点架构用一个词概括就是「ALL  IN  ONE」,如下图所示:

好的架构是进化来的,不是设计来的

就像一个单机系统,所有的东西都部署在一台机器上,包括站点、数据库、文件等等。而工程师每天的核心工作就是 CURD,前端传过来一些数据,然后业务逻辑层拼装成一些 CURD 访问数据库,数据库返回数据,数据拼装成页面,最终返回到浏览器。相信很多创业团队,初期做的工作也是类似,每天写代码,写 SQL、接口参数、访问数据等等。

这里需要说明一个问题,大家都知道目前 58 同城使用的是 Windows、iis、SQL-Sever、C# 这条路。现在很多创业公司可能就不会这么做。58 同城为什么当时选择了这条路?原因是公司招聘的第一个工程师和第二个工程师只会这个,所以只能走这条路。

如果可以重来?那么会选择LAMP

很多创业的同学可能会想,如果我们初期希望做一个产品的话,我们应该使用什么架构? 如果让我们重来,可能我们现在会选 LAMP,为什么?首先是无须编译,而且快速发布功能强大,从前端到后端、数据库访问、业务逻辑处理等等全部可以搞定,最重要的是因为开源产品,是完全免费的。如果使用 LAMP 搭建一个论坛,两天的时间就很足够了。所以,如果在创业初期,就尽量不要再使用 Windows 的技术体系了。

好的架构是进化来的,不是设计来的

在这个阶段 58 同城面临的主要问题是什么?其实就是招人。很多工程师可能都是再培训学校里培训了3月就过来上班,所以他们写 CURD 的话很容易出错。当时,我们引进了 DAO 和 ORM。虽然那些培训了几个月的工程师可能写CURD不是特别的擅长,但是他们写面向对象的一些程序引入了 DAO 和 ORM,让他们不再直接面对 CURD 语句,这样就会相对容易一些。因为工程师比较擅长的是面向对象的数据,不是 CURD,所以我们当时引入了 ORM,总的来说,如果大家现在的项目处于一个初期孵化的阶段,DAO 和 ORM 能够极大的提高效率,而且可以降低出错的概率。

中等规模:流量跨过十万的阶段,数据库成为瓶颈

随着 58 同城的高速增长,我们很快跨越了十万流量的阶段。主要需求是什么?网站能够正常访问,当然速度更快点就好了。而此时系统面临问题包括:在流量的高峰期容易宕机,因为大量的请求会压到数据库上,所以数据库成为新的瓶颈,而且人多的时候,访问速度会很慢。这时,我们的机器数量也从一台变成了多台。现在的架构就采用了分布式,如下图所示:

好的架构是进化来的,不是设计来的

首先,我们使用了一些非常常见的技术,一方面是动静分离,动态的页面通过 Web-Servre 访问,静态的像图片等就单独放到了一些服务器上。另外一点就是读写分离。其实,对 58 同城或者说绝大部分的站点而言,一般来说都是读多写少。对 58 同城来说,绝大部分用户是访问信息,只有很少的用户过来发贴。那么如何扩展整个站点架构的读请求呢?常用的是主从同步,读写分离。我们原来只有一个数据库,现在使用多个不同的数据库提供服务,这样的话,就扩展了读写,很快就解决了中等规模下数据访问的问题。

在这个阶段,系统的主要矛盾就是「站点耦合+读写延时」,58 同城是如何进行解耦,如何缓解延时呢?

对 58 同城而言,典型业务场景是主页,发布信息有发布页,信息聚合、标题聚合有列表页,点开一个标题有详细页,而这些站点都是耦合在一个程序中的,或者说耦合在一个站点中的,当我们有一个站点出现问题的时候,整个站点就会因为耦合一起出问题。

好的架构是进化来的,不是设计来的

第二个问题,大家都知道做数据库读请求和写请求,分布在不同的数据库上,这个时候如果再读取可能读到的是旧数据,因为读写有一个延时。如果有用户发帖子,马上去找的话肯定找不到,很可能带来的后果就是陆续在发布两条信息,这就是一个很大的问题。尤其是在请求量越来越大的时候,这个问题就更加突出。

在解决这些问题是,最先想到的是针对原来站点的核心业务做切分,然后工程师根据自己的站点和业务场景进行细分。首先,业务拆分是 58 同城最先尝试的优化。我们将业务垂直拆分成了首页和发布页。另外,在数据库层面,我们也随之进行了拆分,将大数据量拆分成一个个小的数据量。这样,读写延时就马上得到了缓解。尤其是在代码拆分成了不同的层面之后,站点耦合也得到了缓解,数据量加载速度也提升了很多。

好的架构是进化来的,不是设计来的

当时,还使用了一些技术,前面也提到了对动态资源和静态资源进行拆分。其中,我们对静态资源使用了 CDN 服务,便于数据缓存和就近访问,访问速度得到很明显的提升。除此之外,我们还使用了 MVC 模式,擅长前端的去做展示层,擅长协作逻辑的工程师就做 Contorller,擅长数据的人就负责数据,效率就会逐步的提高,最后就是负载均衡技术。

大流量:将整个 Windows 技术体系转向了 Java 体系

流量越来越大,当流量超过一千多万时,58 同城面对最大的问题就是性能和成本。此前,我提到58同城最初的技术选型是 Windows,应该是在 2006 年的时候,整个网站的性能变得非常之低。即使进行了业务拆分和一些优化,但是依然解决不了这个问题,所以我们当时做了一个非常艰难的决定,就是转型:将整个 Windows 技术体系转向了 Java 体系,这涵盖了操作系统、数据库等多个维度。

好的架构是进化来的,不是设计来的

其实,现在很多大的互联网公司在流量从小到大的过程中都经历过转型,包括京东、淘宝等等。对技术的要求越来越高,任何一个站点都不能挂,对站点的可用性要求也是越来越高。

就在这个时候,58 同城业务量也出现一个爆发期。于是我们招聘了很多的工程师,大家一起写越来越多的站点,但是发现效率很低,经常做一些重复性的工作比如参数解析等等。同时,业务之间相互依赖,无论是分类的子系统还是信息的子系统,二手车业务、房产业务都要访问用户和信息等一些底层数据,代码之间频繁的沟通,效率也不可能很高。

问题随之而来,站点数越来越多,数据量越来越大,机器数从最开始的几台上升到几百台的级别。那么如何提供整个架构的可用性呢?首先,在上层我们进行了一些改进和优化,再做进一步的垂直拆分,同时我们引入了 Cache,如下图所示:

好的架构是进化来的,不是设计来的

在架构的改进上,我们构建了一个相对独立的服务层,这个服务层做的每个业务线都会写对应的代码。如果用户发出请求,就由这个服务层统一来管理,所有的上游业务线就像调用本地函数一样,通过 IDC 的框架来调用这个服务。整个用户登录先访问 Cache,如果 Cache 变动了就直接返回,如果 Cache 不变动,就会访问数据库,这样把数据库的数据拿到本地再放回 Cache,再打回上一轮。如此一来,业务逻辑全部封装在这个服务的上游管理,该业务逻辑只有服务层能够编写代码,然后由这个服务层集中管理、集中优化,这样就提高了效率。

好的架构是进化来的,不是设计来的

除此之外,为了保证站点的高可用,我们主要使用了反向代理技术。因为用户而言,他主要为了使用 58 同城的服务,他不关注访问是58同城或者有十台首页的服务器。58 同城通过反向代理技术,通过 DNS 群,通过 LVS 技术,来保证接入层的高可用性,同时还保证了服务层、站点层、数据层的高可用。另外,为了保证高可用我们经常使用冗余的方法,无论是站点服务和数据服务都可以使用这种方式进行解决,一个站点不可用,我们就换一个站点,一个数据库不够用,我们就多加几个。当然,数据冗余也会带来一些副作用,如果数据量更新的话,那就需要将所有的“冗余”都要进行更新。

58同城也做了一个图片存储系统,开始都是存储在操作系统之上,随着新增站点、新增服务,压力就变得越来越大。于是,58 同城就自建了站点框架和服务框架,现在这两个框架也已经开源(如何降低站点开发成本?https://github.com/58code/Argo  如何降低服务开发成本? https://github.com/58code/Gaea )只需要修改一些基本的配置就可以使用了。

当架构变成「蜘蛛网」,人肉已很难搞定!

随着用户量、数据量并发量进一步的增长,58同城也拓展了很多的新业务,那么对产品迭代速度要求就非常高,整体的架构对自动化的要求越来越高。

好的架构是进化来的,不是设计来的

为了支撑业务的发展,技术团队对架构做了进一步的解耦,另外就是引入了配置中心,如果要访问任何一个服务,不会直接在本地的配置中留下一个服务,配置中心告诉这个服务的特点,如果扩展的话,配置中心自动下达消息,如果有机器要下线的话,配置中心会反向通过发邮件的方式进行通知。

而柔性服务是指当流量增加的时候,自动的新增服务。可以看到进一步解耦之后,有垂直业务、无线业务、集成业务等等,这些子系统之间都是通过配置中心相应之间发生关系的。

另一点就是关于数据库,当某一点成为一个业务线重点的时候,我们就会集中解决这个点的问题。最初期的时候每个业务线都要访问数据库,访问缓存,访问用户数据,于是我们把代码集中的放到了服务层。现在数据量越来越大,大家都要做数据切分,每个业务线都做切分,这个时候58同城的每个页面都面对这样的痛点,于是把这个痛点拿到集中的层面来解决。

最后一点就是效率矛盾,此时很多问题,靠「人肉」已经很难进行搞定了。这就需要自动化,包括回归、测试、运维、监控等等都要回归到自动化。

这里需要补充一点,就是在产品层面,我们引入了智能化,比如说智能推荐,主动推荐一些相关的话题;智能广告,通过一些智能的策略,让用户对广告的点击更多,增加对 58 同城的收录;智能搜索,在搜索的过程中加入一些搜索的策略,可以提高搜索的权重,也可以增加 58 同城的 PV。当然,所有的自动化的产品背后都是由技术在驱动。

未来的挑战

现在,58同城的流量已经突破的 10 亿的量级,那么架构上未来面临哪些挑战呢?一方面是无线化、移动化。另一方面就是需求的变化,我们必须加快迭代一些东西。如果拥有10亿的流量,却跑在一亿的架构上肯定是不行的。未来,我们会使用更多的并行计算、实时计算,如果能做到实时推荐,效果肯定非常好,这也是我们的挑战。最后一点,58同城现在的服务器大概在3000台左右,未来将拓展到 10000 万,这就是运维的挑战了。

好的架构是进化来的,不是设计来的

总结:

最后做一个小的总结,网站在不同的阶段遇到的问题不一样,而解决这些问题使用的技术也不一样,流量小的时候,我们主要目的是提高开发效率,在早期要引入 ORM,DAO 这些技术。随着流量变大,使用动静分离、读写分离、主从同步、垂直拆分、CDN、MVC 等方式不断提升网站的稳定性。面对更大的流量时,通过垂直拆分、服务化、反向代理、开发框架(站点/服务)等等,不断提升高可用。在面对上亿级的更大流量时,通过中心化、柔性服务、消息总线、自动化(回归,测试,运维,监控)来迎接新的挑战。未来的就是继续实现 移动化,大数据实时计算,平台化…

原文:http://news.oneapm.com/shenjian-oneapm-course/

written by ocean

11月 23

移动互联网、云计算和大数据的成熟和发展,让更多的好想法得以在很短的时间内实现为产品。此时,如果用户需求抓得准,用户数量将很可能获得爆发式增长,而不需要像以往一样需要精心运营几年的时间。然而用户数量的快速增长(尤其是短时间内的爆发式增长),通常会让应用开发者有些吃不消,不得不面临一些严峻的技术挑战:如何避免因为单台机器当机导致服务不可用;如何避免在服务容量不足时,用户体验下降,等等。在系统构建之初就采用高可用和可伸缩架构,将能有效避免这些问题。

如何构建高可用和可伸缩架构呢?七牛云存储首席架构师李道兵在3月22的「开发者最佳实践日」第十期沙龙活动上给出了自己的想法。他结合自己多年的实践经验,针对一些不太复杂的业务场景,从入口层、业务层、缓存层和数据库层四个层面细致讲述了如何构建高可用和可伸缩系统。希望大家读完这篇文章,能觉得高可用和可伸缩不是一个高不可攀的东西,投入不高的成本就能在项目早期把高可用和可伸缩纳入架构设计之中。

如何实现高可用

入口层

入口层,通常指Nginx和Apache等层面的东西,负责应用(不管是Web应用还是移动应用)的服务入口。我们通常会将服务定位在一个IP,如果这个IP对应的服务器当机了,那么用户的访问肯定会中断。此时,可以用keepalived来实现入口层的高可用。例如,机器A 的IP是 1.2.3.4,机器 B 的 IP 是 1.2.3.5, 那么再申请一个 IP 1.2.3.6(称为?跳IP), 平时绑定在机器A上,如果A当机,IP会自动绑定在机器B上;如果B当机,IP会自动绑定在机器A上。对于这种形式,我们将DNS绑定到心跳IP上,即可实现入口层的高可用。

但这个方案有一点小问题。第一,它的切换可能会有一到两秒的中断,也就是说,如果不是要求到非常严格的毫秒级就不会有问题。第二,对入口的机器会有些浪费,因为买了两台机器的入口,可能就只有一台机器用上。对一些长连接的应用可能会导致服务中断,这时候就需要客户端做配合做一些重新创建连接的工作。简单来说,对于比较普通的业务来说,这个方案就能解决一部分问题。

这里要注意,keepalived在使用上会有一些限制。

  • 两台机器必须在同一个网段,不是在同一个网段,没有办法实现互相抢IP。

  • 内网服务也可以做心跳,但需要注意的是,以前为了安全我们会把内网服务绑定在内网IP上,避免出现安全问题。但为了使用keepalived,必须监听在所有IP上(如果监听在心跳IP上,那么机器没有持有该IP时,服务无法启动),简单的方案是启用 iptables, 避免内网服务被外网访问。

  • 服务器利用率下降,这时可以考虑做混合部署来改善这一点。

比较常见的一个错误是,如果有两台机器,两个公网IP,DNS上把域名同时定位到两个IP,就觉得已经做了高可用了。这完全不是高可用,因为如果一台机器当机,那么就有一半左右的用户无法访问。

除了keepalive,lvs也能用来解决入口层的高可用问题。不过,与keepalived相比,lvs会更复杂一些,门槛也会高一些。

业务层

业务层通常是由PHP、Java、Python、Go等写的逻辑代码构成的,需要依赖于后台数据库及一些缓存层面的东西。如何实现业务层的高可用呢?最核心的就是,业务层不要有状态,将状态分散到缓存层和数据库。目前大家通常喜欢将以下几种数据放入业务层。

第一个是session,即用户登录相关的数据,但好的做法是将session放在数据库里,或者一个比较稳定的缓存系统中。

第二个是缓存,在访问数据库时,如果一个查询很慢,就希望将这些结果暂时放到进程里,下次再做查询时就不用再访问数据库了。这种做法带来的问题是,当业务层服务器不只一台时,数据很难做到一致,从缓存拿到的数据就可能是错误的。。

一个简单的原则就是业务层不要有状态。在业务层没有状态时,一台业务层服务器当掉了之后,Nginx/Apache会自动将所有的请求打到另外一台业务层的服务器上。由于没有状态,两台服务器没有任何差异,所以用户完全感受不到。如果把session放在业务层里面的话,那么面临的问题是,这个用户以前是登录在一台机器上的,这个进程死掉后,用户就会被登出了。

友情提醒:有一段时间比较流行cookie session,就是将session中的数据加密之后放在客户的cookie里,然后下发到客户端,这样也能做到与服务端完全无状态。但这里面有很多坑,如果能绕过这些坑就可以这样使用。第一个坑是怎么保证加密的密钥不泄露,一旦泄露就意味着攻击者可以伪造任何人的身份。第二个坑是重放攻击,如何避免别人通过保存 cookie 去不停地尝试的验证码,当然也还有其他一些攻击手段。如果没有好办法解决这两方面的问题,那么cookie session尽量慎用。最好是将session放在一个性能比较好的数据库中。如果数据库性能不行,那么将session放在缓存中也比放在cookie里要好一点。

缓存层

非常简单的架构里是没有缓存这个概念的。但在访问量上来之后,MySQL之类的数据库扛不住了,比如在SATA盘里跑MySQL,QPS到达200、300甚至500时,MySQL的性能会大幅下降,这时就可以考虑用缓存层来挡住绝大部分服务请求,提升系统整体的容量。

缓存层做高可用一个简单的方法就是,将缓存层分得细一点儿。比如说,缓存层就一台机器的话,那么这台机器当了以后,所有应用层的压力就会往数据库里压,数据库扛不住的话,整个网站(或应用)就会随之当掉。而如果缓存层分在四台机器上的话,每台只有四分之一,这台机器当掉了以后,也只有总访问量的四分之一会压在数据库上面,数据库能扛住的话,网站就能很稳定地等到缓存层重新起来。在实践中,四分之一显然是不够的,我们会将它分得更细,以保证单台缓存当机后数据库还能撑得住即可。在中小规模下,缓存层和业务层可以混合部署,这样可以节省机器。

数据库层

在数据库层面实现高可用,通常是在软件层面来做。例如,MySQL有主从模式(Master-Slave),还有主主模式(Master-Master)都能满足需求。MongoDB也有ReplicaSet的概念,基本都能满足大家的需求。

总之,要想实现高可用,需要做到这几点:入口层做心跳,业务层服务器无状态,缓存层减小粒度,数据库做一个主从模式。对于这种模式来讲,我们做的高可用不需要太多服务器,这些东西都可以同时部署在两台服务器上。这时,两台服务器就能满足早期的高可用需求了。任何一台服务器当机用户完全无感知。

如何实现可伸缩

入口层

在入口层实现伸缩性,可以通过直接水平扩机器,然后DNS加IP来实现。但需要注意,尽管一个域名解析到几十个IP没有问题,但是很多浏览器客户端只会使用前几个IP,部分域名供应商对此有优化(如每次返回的IP顺序随机),但这个优化效果不稳定。

推荐的做法是使用少量的Nginx机器作为入口,业务服务器隐藏在内网(HTTP类型的业务这种方式居多)。另外,也可以把所有IP下发到客户端,然后在客户端做一些调度(特别是非HTTP型的业务,如游戏、直播)。

业务层

业务层的伸缩性如何实现?与做高可用时的解决方案一样,要实现业务层的伸缩性,保证无状态是很好的手段。此外,加机器继续水平部署即可。

缓存层

比较麻烦的是缓存层的伸缩性,最简单粗暴的方式是什么呢?趁着半夜量比较低的时候,把整个缓存层全部下线,然后上线新的缓存层。新的缓存层启动起来之后,再等这些缓存慢慢预热。当然这里一个要求,你的数据库能抗住低估期的请求量。如果扛不住呢?取决于缓存类型,下面我们先可以将缓存的类型区分一下。

  • 强一致性缓存:无法接受从缓存拿到错误的数据 (比如用户余额,或者会被下游继续缓存这种情形)

  • 弱一致性缓存:能接受在一段时间内从缓存拿到错误的数据 (比如微博的转发数)。

  • 不变型缓存:缓存key对应的value不会变更 (比如从SHA1推出来的密码, 或者其他复杂公式的计算结果)。

那什么缓存类型伸缩性比较好呢?弱一致性和不变型缓存的扩容很方便,用一致性Hash即可;强一致性情况稍微复杂一些,稍后再讲。使用一致性Hash,而不用简单Hash的原因是缓存的失效率。如果缓存从9台扩容到10台,简单Hash 情况下90%的缓存会马上失效,而如果使用一致性Hash情况,只有10%的缓存会失效。

那么,强一致性缓存会有什么问题?第一个问题是,缓存客户端的配置更新时间会有微小的差异,在这个时间窗内有可能会拿到过期的数据。第二个问题是,如果扩容之后再裁撤节点,会拿到脏数据。比如 a 这个key之前在机器1,扩容后在机器2,数据更新了,但裁撤节点后key回到机器1,这时候就会拿到脏数据。

要解决问题2比较简单,要么保持永不减少节点,要么节点调整间隔大于数据的有效时间。问题1可以用如下的步骤来解决:

  1. 两套hash配置都更新到客户端,但仍然使用旧配置;

  2. 逐个客户端改为只有两套hash结果一致的情况下会使用缓存,其余情况从数据库读,但写入缓存;

  3. 逐个客户端通知使用新配置。

Memcache 设计得比较早,导致在伸缩性高可用方面的考虑得不太周到。Redis 在这方面有不少改进,特别是 @ngaut 团队基于 redis 开发了 codis 这个软件,一次性地解决了缓存层的绝大部分问题。推荐大家考察一下。

数据库

在数据库层面实现伸缩,方法很多,文档也很多,此处不做过多赘述。大致方法为:水平拆分、垂直拆分和定期滚动。

总之,我们可以在入口层、业务层面、缓存层和数据库层四个层面,使用刚才介绍的方法和技术实现系统高可用和可伸缩性。具体为:在入口层用心跳来做到高可用,用平行部署来伸缩;在业务层做到服务无状态;在缓存层,可以减小一些粒度,以方便实现高可用,使用一致性Hash将有助于实现缓存层的伸缩性;数据库层的主从模式能解决高可用问题,拆分和滚动能解决可伸缩问题。

本文中分享的这些技巧和方法,主要想帮助不太复杂的业务场景或者中小型应用快速搭建起高可用可伸缩的系统。关于如何构建高可用和可伸缩系统还有很多更为细节的点和实践经验值得探讨,望以后能与大家做更充分的交流。

from 

http://www.infoq.com/cn/articles/high-availability-scalable-architecture-practical-experience

written by ocean

11月 21

前言

    一个成熟的大型网站(如淘宝、京东等)的系统架构并不是开始设计就具备完整的高性能、高可用、安全等特性,它总是随着用户量的增加,业务功能的扩展逐渐演变完善的,在这个过程中,开发模式、技术架构、设计思想也发生了很大的变化,就连技术人员也从几个人发展到一个部门甚至一条产品线。所以成熟的系统架构是随业务扩展而完善出来的,并不是一蹴而就;不同业务特征的系统,会有各自的侧重点,例如淘宝,要解决海量的商品信息的搜索、下单、支付,例如腾讯,要解决数亿的用户实时消息传输,百度它要处理海量的搜索请求,他们都有各自的业务特性,系统架构也有所不同。尽管如此我们也可以从这些不同的网站背景下,找出其中共用的技术,这些技术和手段可以广泛运行在大型网站系统的架构中,下面就通过介绍大型网站系统的演化过程,来认识这些技术和手段。

一、最开始的网站架构

    最初的架构,应用程序、数据库、文件都部署在一台服务器上,如图:

image

二、应用、数据、文件分离

    随着业务的扩展,一台服务器已经不能满足性能需求,故将应用程序、数据库、文件各自部署在独立的服务器上,并且根据服务器的用途配置不同的硬件,达到最佳的性能效果。

image

三、利用缓存改善网站性能

    在硬件优化性能的同时,同时也通过软件进行性能优化,在大部分的网站系统中,都会利用缓存技术改善系统的性能,使用缓存主要源于热点数据的存在,大部分网站访问都遵循28原则(即80%的访问请求,最终落在20%的数据上),所以我们可以对热点数据进行缓存,减少这些数据的访问路径,提高用户体验。

251844453265971

    缓存实现常见的方式是本地缓存、分布式缓存。当然还有CDN、反向代理等,这个后面再讲。本地缓存,顾名思义是将数据缓存在应用服务器本地,可以存在内存中,也可以存在文件,OSCache就是常用的本地缓存组件。本地缓存的特点是速度快,但因为本地空间有限所以缓存数据量也有限。分布式缓存的特点是,可以缓存海量的数据,并且扩展非常容易,在门户类网站中常常被使用,速度按理没有本地缓存快,常用的分布式缓存是Memcached、Redis。

四、使用集群改善应用服务器性能

    应用服务器作为网站的入口,会承担大量的请求,我们往往通过应用服务器集群来分担请求数。应用服务器前面部署负载均衡服务器调度用户请求,根据分发策略将请求分发到多个应用服务器节点。

251844471702801

    常用的负载均衡技术硬件的有F5,价格比较贵,软件的有LVS、Nginx、HAProxy。LVS是四层负载均衡,根据目标地址和端口选择内部服务器,Nginx是七层负载均衡和HAProxy支持四层、七层负载均衡,可以根据报文内容选择内部服务器,因此LVS分发路径优于Nginx和HAProxy,性能要高些,而Nginx和HAProxy则更具配置性,如可以用来做动静分离(根据请求报文特征,选择静态资源服务器还是应用服务器)。

五、数据库读写分离和分库分表

    随着用户量的增加,数据库成为最大的瓶颈,改善数据库性能常用的手段是进行读写分离以及分表,读写分离顾名思义就是将数据库分为读库和写库,通过主备功能实现数据同步。分库分表则分为水平切分和垂直切分,水平切换则是对一个数据库特大的表进行拆分,例如用户表。垂直切分则是根据业务不同来切换,如用户业务、商品业务相关的表放在不同的数据库中。

260851219209749

六、使用CDN和反向代理提高网站性能

  假如我们的服务器都部署在成都的机房,对于四川的用户来说访问是较快的,而对于北京的用户访问是较慢的,这是由于四川和北京分别属于电信和联通的不同发达地区,北京用户访问需要通过互联路由器经过较长的路径才能访问到成都的服务器,返回路径也一样,所以数据传输时间比较长。对于这种情况,常常使用CDN解决,CDN将数据内容缓存到运营商的机房,用户访问时先从最近的运营商获取数据,这样大大减少了网络访问的路径。比较专业的CDN运营商有蓝汛、网宿。

  而反向代理,则是部署在网站的机房,当用户请求达到时首先访问反向代理服务器,反向代理服务器将缓存的数据返回给用户,如果没有没有缓存数据才会继续走应用服务器获取,也减少了获取数据的成本。反向代理有Squid,Nginx。

260851254513595

七、使用分布式文件系统

    用户一天天增加,业务量越来越大,产生的文件越来越多,单台的文件服务器已经不能满足需求。需要分布式的文件系统支撑。常用的分布式文件系统有NFS。

260851282647353

八、使用NoSql和搜索引擎

    对于海量数据的查询,我们使用nosql数据库加上搜索引擎可以达到更好的性能。并不是所有的数据都要放在关系型数据中。常用的NOSQL有mongodb和redis,搜索引擎有lucene。

260851321075527

九、将应用服务器进行业务拆分

    随着业务进一步扩展,应用程序变得非常臃肿,这时我们需要将应用程序进行业务拆分,如百度分为新闻、网页、图片等业务。每个业务应用负责相对独立的业务运作。业务之间通过消息进行通信或者同享数据库来实现。

260851352481788

 

十、搭建分布式服务

    这时我们发现各个业务应用都会使用到一些基本的业务服务,例如用户服务、订单服务、支付服务、安全服务,这些服务是支撑各业务应用的基本要素。我们将这些服务抽取出来利用分部式服务框架搭建分布式服务。淘宝的Dubbo是一个不错的选择。

260851397174320

小结

    大型网站的架构是根据业务需求不断完善的,根据不同的业务特征会做特定的设计和考虑,本文只是讲述一个常规大型网站会涉及的一些技术和手段。

 

 

参考资料:

《大型网站技术架构》 ——李智慧

《海量运维运营规划》 ——唐文

http://www.cnblogs.com/leefreeman/p/3993449.html

written by ocean