返回 登录
64

快的打车架构实践

阅读42407

快的打车从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的编程模型比较简单而已,其实底层