快的打车架构实践


53

快的打车架构实践

快的打车从2013年年末到2014年下半年,系统访问量迅速膨胀,不少复杂的问题要在短期内解决,且不能影响线上业务,这是比较大的挑战,本文将会阐述快的打车架构演变过程遇到的一些有表明性的问题和解决方案。前端

LBS的瓶颈和方案

先看看基本的系统模型,如图1所示。程序员

图片描述
图1 系统模型示意图算法

  1. 司机每隔几秒钟上报一次经纬度,存储在MongoDB里;
  2. 乘客发单时,经过MongoDB圈选出附近司机;
  3. 将订单经过长链接服务推送给司机;
  4. 司机接单,开始服务。

MongoDB集群是一主多从的复制集方式,读写都很密集(4w+/s写、1w+/s读)时出现如下问题:数据库

  1. 从服务器CPU负载急剧上升;
  2. 查询性能急剧下降(大量查询耗时超过800毫秒);
  3. 查询吞吐量大幅下降;
  4. 主从复制出现较大的延迟。

缘由是当时的MongoDB版本(2.6.4)是库级别的锁每次写都会锁库,还有每一次LBS查询会分解成许多单独的子查询,增大整个查询的锁等待几率。咱们最后将全国分为4个大区,部署多个独立的MongoDB集群,每一个大区的用户存储在对应的MongoDB集群里。编程

长链接服务稳定性

咱们的长链接服务经过Socket接收客户端心跳、推送消息给乘客和司机。打车大战期间,长链接服务很是不稳定。后端

先说说硬件问题,现象是CPU的第一个核常用率100%,其余的核却很是空闲,系统吞吐量上不去,对业务的影响很大。通过较长时间排查,最终发现这是由于服务器用了单队列网卡,I/O中断都被分配到了一个CPU核上,大量数据包到来时,单个CPU核没法所有处理,致使LVS不断丢包链接中断。最后解决这个问题其实很简单,换成多队列网卡就行。安全

再看软件问题,长链接服务当时用Mina实现,Mina自己存在一些问题:内存使用控制粒度不够细、垃圾回收难以有效控制、空闲链接检查效率不高、大量链接时周期性CPU使用率飙高。快的打车的长链接服务特色是:大量的广播、消息推送具备不一样的优先级、细粒度的资源监控。最后咱们用AIO重写了这个长链接服务框架,完全解决了这个问题。主要有如下特性:服务器

  1. 针对快的场景定制开发;
  2. 资源(主要是ByteBuffer)池化,减小GC形成的影响;
  3. 广播时,一份ByteBuffer复用到多个通道,减小内存拷贝;
  4. 使用TimeWheel检测空闲链接,消除空闲链接检测形成的CPU尖峰;
  5. 支持按优先级发送数据。

其实Netty已经实现了资源池化和TimeWheel方式检测空闲链接,但没法作到消息优先级区分和细粒度监控,这也算是快的自身的定制特性吧,通用的通讯框架确实很差知足。选用AIO方式仅仅是由于AIO的编程模型比较简单而已,其实底层的性能并无多大差异。微信

系统分布式改造

快的打车最初只有两个系统,一个提供HTTP服务的Web系统,一个提供TCP长链接服务的推送系统,全部业务运行在这个Web系统里,代码量很是庞大,代码下载和编译都须要花较长时间。网络

业务代码都混在一块儿,频繁的平常变动致使并行开发的分支很是多,测试和代码合并以及发布验证的效率很是低下,经常一发布就通宵。这种状况下,系统的伸缩性和扩展性很是差,关键业务和非关键业务混在一块儿,互相影响。

所以咱们Web系统作了拆分,将整个系统从上往下分为3个大的层次:业务层、服务层以及数据层。

咱们在拆分的同时,也仔细梳理了系统之间的依赖。对于强依赖场景,用Dubbo实现了RPC和服务治理。对于弱依赖场景,经过RocketMQ实现。Dubbo是阿里开源的框架,在阿里内部和国内大型互联网公司有普遍的应用,咱们对Dubbo源码比较了解。RocketMQ也是阿里开源的,在内部获得了很是普遍的应用,也有不少外部用户,可简单将RocketMQ理解为Java版的Kafka,咱们一样也对RocketMQ源码很是了解,快的打车全部的消息都是经过RocketMQ实现的,这两个中间件在线上运行得很是稳定。

借着分布式改造的机会,咱们对系统全局也作了梳理,创建研发流程、代码规范、SQL规范,梳理链路上的单点和性能瓶颈,创建服务降级机制。

无线开放平台

当时客户端与服务端通讯面临如下问题。

  1. 每新增一个业务请求,Web工程就要改动发布。
  2. 请求和响应格式没有规范,致使服务端很难对请求作统一处理,并且与第三方集成的方式很是多,维护成本高。
  3. 来多少请求就处理多少,根本不考虑后端服务的承受能力,而某些时候须要对后端作保护。
  4. 业务逻辑比较分散,有的在Web应用里,有的在Dubbo服务里。提供新功能时,工程师关注的点比较多,增长了系统风险。
  5. 业务频繁变化和快速发展,文档没法跟上,最后没人能说清到底有哪些协议,协议里的字段含义。

针对这些问题,咱们设计了快的无线开放平台KOP,如下是一些大的设计原则。

  1. 接入权限控制
    为接入的客户端分配标示和密钥,密钥由客户端保管,用来对请求作数字签名。服务端对客户端请求作签名校验,校验经过才会执行请求。

  2. 流量分配和降级
    一样的API,不一样接入端的访问限制能够不同。可按城市、客户端平台类型作ABTest。极端状况下,优先保证核心客户端的流量,同时也会优先保证核心API的服务能力,例如登陆、下单、接单、支付这些核心的API。被访问被限制时,返回一个限流错误码,客户端根据不一样场景酌情处理。

  3. 流量分析
    从客户端、API、IP、用户多个维度,实时分析当前请求是否恶意请求,恶意的IP和用户会被冻结一段时间或永久封禁。

  4. 实时发布
    上线或下线API不须要对KOP进行发布,实时生效。固然,为了安全,会有API的审核机制。

  5. 实时监控
    能统计每一个客户端对每一个API每分钟的调用总量、成功量、失败量、平均耗时,能以分钟为单位查看指定时间段内的数据曲线,而且能对比历史数据。当响应时间或失败数量超过阈值时,系统会自动发送报警短信。

实时计算与监控

咱们基于Storm和HBase设计了本身的实时监控平台,分钟级别实时展示系统运行情况和业务数据(架构如图2所示),包含如下几个主要部分。

图片描述
图2 监控系统架构图

  1. 核心计算模型
    求和、求平均、分组。

  2. 基于Storm的实时计算
    Storm的逻辑并不复杂,只有两个Bolt,一个将一条日志解析成KV对,另一个基于KV和设定的规则进行计算。每隔一分钟将数据写入RocketMQ。

  3. 基于HBase的数据存储
    只有插入没有更新,避免了HBase行锁竞争。rowkey是有序的,由于要根据维度和时间段查询,这样会造成HBase Region热点,致使写入比较集中,可是没有性能问题,由于每一个维度每隔1分钟定时插入,平均每秒的插入不多。即便前端应用的日志量忽然增长不少,HBase的插入频度仍然是稳定的。

  4. 基于RocketMQ的数据缓冲
    收集的日志和Storm计算的结果都先放入MetaQ集群,不管Storm集群仍是存储节点,发生故障时系统仍然是稳定的,不会将故障放大;即便有忽然的流量高峰,由于有消息队列作缓冲,Storm和HBase仍然能以稳定的TPS处理。这些都极大的保证了系统的稳定性。RocketMQ集群自身的健壮性足够强,都是物理机。SSD存储盘、高配内存和CPU、Broker所有是M/S结构。能够存储足够多的缓冲数据。

某个系统的实时业务指标(关键数据被隐藏),见图3。

图片描述
图3 某个业务系统大盘截图

数据层改造

随着业务发展,单数据库单表已经没法知足性能要求,特别是发券和订单,咱们选择在客户端分库分表,本身作了一个通用框架解决分库分表的问题。可是还有如下问题:

  1. 数据同步
    快的原来的数据库分为前台库和后台库,前台库给应用系统使用,后台库只供后台使用。无论前台应用有多少库,后台库只有一个,那么前台的多个库多个表如何对应到后台的单库单表?MySQL的复制没法解决这个问题。

  2. 离线计算抽取
    还有大数据的场景,大数据同事常常要dump数据作离线计算,都是经过Sqoop到后台库抽数据,有的复杂SQL常常会使数据库变得不稳定。并且,不一样业务场景下的Sqoop会形成数据重复抽取,给数据库添加了更多的负担。

咱们最终实现了一个数据同步平台,见图4。

图片描述
图4 数据同步平台架构图

  1. 数据抽取用开源的canal实现,MySQL binlog改成Row模式,将canal抽取的binlog解析为MQ消息,打包传输给MQ;
  2. 一份数据,多种消费场景,以前是每种场景都抽取一份数据;
  3. 各个消费端不须要关心MySQL,只须要关心MQ的Topic;
  4. 支持全局有序,局部有序,并发乱序;
  5. 能够指定时间点回放数据;
  6. 数据链路监控、报警;
  7. 经过管理平台自动部署节点。

分库分表解决了前台应用的性能问题,数据同步解决了多库多表归一的问题,可是随着时间推移,后台单库的问题愈来愈严重,迫切须要一种方案解决海量数据存储的问题,同时又要让现有的上层应用不会有太大改动。所以咱们基于HBase和数据同步设计了实时数据中心,如图5所示。

图片描述
图5 实时数据中心架构图

  1. 将前台MySQL多库多表经过同步平台,都同步到了HBase;
  2. 为减小后台应用层的改动,设计了一个SQL解析模块,将SQL查询转换为HBase查询;
  3. 支持二级索引。
    说说二级索引,HBase并不支持二级索引,对它而言只有一个索引,那就是Rowkey。若是要按其它字段查询,那就要为这些字段创建与Rowkey的映射关系,这就是所谓的二级索引。HBase二级索引能够经过Coprocessor在数据插入以前执行一段代码,这段代码运行在HBase服务端(Region Server),可让这段代码负责插入二级索引。实时数据中心的二级索引是在客户端负责插入的,并无使用Coprocessor,主要缘由是Coprocessor不容易实现索引的批量插入,而批量插入,实践证实,是提高HBase插入性能很是有效的手段。二级索引的应用其实还有些条件,以下:

  4. 排序
    在HBase中,只有一种排序,就是按Rowkey排序,所以,在创建索引的时候,实际上就定死了未来查询结果的排序。某个索引字段的reverse属性为true,则按这个字段倒序排序,不然正序排序。

  5. 打散
    单调变化的Rowkey读写压力很难均匀分布到多个Region上,而打散将会使读写均匀分布到多个Region,所以提高了读写性能。但打散也有局限性,主要的是,通过打散的字段将没法支持范围查询。并且,hash和reverse这两个属性是互斥的,且hash优先级高,就是说一旦设置了hash=true,则会忽略reverse这个属性。
  6. 串联
    另外须要特别强调的是,索引配置也影响到多表归一,做为“串联”的字段,必须创建惟一索引,若是串联字段上没有创建惟一索引,将没法完成多表归一。

咱们还实现了一套将SQL语句转换成HBase API的引擎,能够经过SQL语句直接操做HBase。这里须要指出的是HSQL引擎和Hive是不一样的,Hive主要用于将SQL语句转换成Hadoop的Map/Reduce任务,固然也能够转换成HBase的查询。但Hive没法利用二级索引(HBase原本就不存在二级索引这个概念),Hive主要面向的是大批量、低频度、高延迟、顺序读的访问场景,而HSQL能够有效利用二级索引,它面向的是小批量、高频度、低延迟、随机读的访问场景。

做者简介:

王小雪滴滴出行架构师,原快的打车架构师。从无到有组建了快的打车基础服务团队,主持研发、引进了众多基础框架和服务,推动快的架构升级,从稳定性、可用性、性能、安全、监控多方面体系化的建设了快的高可用架构。对高并发分布式系统架构、实时数据处理、网络通讯和Java中间件有比较深厚的经验积累。

(责编/钱曙光,关注架构和算法领域,寻求报道或者投稿请发邮件qianshg@csdn.net,交流探讨可加微信qshuguang2008,备注姓名+公司+职位)

「CSDN 高级架构师群」,内有SDCC 2015架构专场的讲师等诸多知名互联网公司的大牛架构师,架构师请加微信qshuguang2008申请入群,备注姓名+公司+职位。


本文为《程序员》原创文章,未经容许不得转载,订阅2016年《程序员》请点击 http://dingyue.programmer.com.cn

钱曙光 发布于 架构 2016-01-05 09:09
分享到:
评论

已有29条评论

  • 最新
mzty106914 23小时前

看的不是很懂,但之后也许有用

0
Geepai 23小时前

MAKR一下,说的很清晰

0
util_c 2016-01-06 21:37

mark ^_^

0
hcmarkwong 2016-01-06 12:42

markmark

0
hmw1986 2016-01-06 12:39 来自 移动客户端
mark
0
Royal_lr 2016-01-05 19:51

mark了。。然而并不懂

0
dengbin010 2016-01-05 19:46

点赞点赞点赞

0
ht3hyc 2016-01-05 18:30

mark mark

0
slade 2016-01-05 18:14

很是不错的文章。

0
kky2010_110 2016-01-05 17:18

马克
马克马克马克

0
相关文章
相关标签/搜索