目前,滴滴打车业务量激增,系统访问量迅速膨胀,很多复杂的问题要在短时间内解决,且不能影响线上业务,这是比较大的挑战,本文将会阐述滴滴打车架构演变过程遇到的一些有代表性的问题和解决方案。
LBS的瓶颈和方案
先看看基本的系统模型,如图1所示。
图1 系统模型示意图
司机每隔几秒钟上报一次经纬度,存储在MongoDB里;
乘客发单时,通过MongoDB圈选出附近司机;
将订单通过长连接服务推送给司机;
司机接单,开始服务。
MongoDB集群是一主多从的复制集方式,读写都很密集(4w+/s写、1w+/s读)时出现以下问题:
从服务器CPU负载急剧上升;
查询性能急剧降低(大量查询耗时超过800毫秒);
查询吞吐量大幅降低;
主从复制出现较大的延迟。
原因是当时的MongoDB版本(2.6.4)是库级别的锁每次写都会锁库,还有每一次LBS查询会分解成许多单独的子查询,增大整个查询的锁等待概率。我们最后将全国分为4个大区,部署多个独立的MongoDB集群,每个大区的用户存储在对应的MongoDB集群里。
长连接服务稳定性
我们的长连接服务通过Socket接收客户端心跳、推送消息给乘客和司机。打车大战期间,长连接服务非常不稳定。
先说说硬件问题,现象是CPU的第一个核经常使用率100%,其他的核却非常空闲,系统吞吐量上不去,对业务的影响很大。经过较长时间排查,最终发现这是因为服务器用了单队列网卡,I/O中断都被分配到了一个CPU核上,大量数据包到来时,单个CPU核无法全部处理,导致LVS不断丢包连接中断。最后解决这个问题其实很简单,换成多队列网卡就行。
再看软件问题,长连接服务当时用Mina实现,Mina本身存在一些问题:内存使用控制粒度不够细、垃圾回收难以有效控制、空闲连接检查效率不高、大量连接时周期性CPU使用率飙高。打车的长连接服务特点是:大量的广播、消息推送具有不同的优先级、细粒度的资源监控。最后我们用AIO重写了这个长连接服务框架,彻底解决了这个问题。主要有以下特性:
针对场景定制开发;
资源(主要是ByteBuffer)池化,减少GC造成的影响;
广播时,一份ByteBuffer复用到多个通道,减少内存拷贝;
使用TimeWheel检测空闲连接,消除空闲连接检测造成的CPU尖峰;
支持按优先级发送数据。
其实Netty已经实现了资源池化和TimeWheel方式检测空闲连接,但无法做到消息优先级区分和细粒度监控,这也算是自身的定制特性吧,通用的通信框架确实不好满足。选用AIO方式仅仅是因为AIO的编程模型比较简单而已,其实底层的性能并没有多大差别。
系统分布式改造
打车最初只有两个系统,一个提供HTTP服务的Web系统,一个提供TCP长连接服务的推送系统,所有业务运行在这个Web系统里,代码量非常庞大,代码下载和编译都需要花较长时间。
业务代码都混在一起,频繁的日常变更导致并行开发的分支非常多,测试和代码合并以及发布验证的效率非常低下,常常一发布就通宵。这种情况下,系统的伸缩性和扩展性非常差,关键业务和非关键业务混在一起,互相影响。
因此我们Web系统做了拆分,将整个系统从上往下分为3个大的层次:业务层、服务层以及数据层。
我们在拆分的同时,也仔细梳理了系统之间的依赖。对于强依赖场景,用Dubbo实现了RPC和服务治理。对于弱依赖场景,通过RocketMQ实现。Dubbo是阿里开源的框架,在阿里内部和国内大型互联网公司有广泛的应用,我们对Dubbo源码比较了解。RocketMQ也是阿里开源的,在内部得到了非常广泛的应用,也有很多外部用户,可简单将RocketMQ理解为Java版的Kafka,我们同...
点击查看剩余70%
网友评论0