快的打车架构实践王小雪发展历程5月成立,8月上线运营覆盖城市:70+收购“大黄蜂”,开启商务车业务1亿用户日订单:600w+滴滴快的合并V1阶段——基本功能可用公司没有知名度技术团队10-30人左右日订单从几百单到几万单作坊式研发系统仅满足基本的功能天气不好系统就崩溃快的一崩溃滴滴也崩溃反过来也是一样,很有默契确立了原始的系统模型MongoDBMinaV2阶段——核心链路优化2013年底打车大战爆发,很短时间内,日订单规模从几万单迅速膨胀到几百万单。系统面临很多问题,我们将问题分优先级,首先对核心链路进行优化,否则业务主流程都无法正常进行。核心链路问题及业务影响LBS瓶颈问题:暴增的LBS查询和写入给MongoDB带来巨大的压力,经常会延时影响:推单时选取的司机有时离乘客很远,或者很近的却没有推送长连接服务稳定性问题:TCPserver高峰期CPUload很高、消息被截断、连接丢失影响:司机收不到订单;司机接单了乘客却不知道;乘客取消订单了司机不知道LBS瓶颈和方案长连接服务稳定性硬件问题不支持多队列的网卡,IO中断都被分配到了一个cpu核上,大量数据包到来的情况下,单个cpu核无法全部处理,导致LVS不断丢包,连接不断中断。解决方案更换支持硬件多队列的网卡(Intel82575、82576,Boardcom的57711等,linux内核版本需要在2.6.21以上)长连接服务稳定性软件问题(Mina框架)1内存使用控制不够细粒度,垃圾回收难以有效控制2空闲连接检查效率不高,在大量连接的情况下会出现周期性CPU使用率飙高3编解码组件在高并发下会出现消息被截断的情况解决方案(快的自己的AIO框架)资源(主要是ByteBuffer)池化,减少GC造成的影响广播时,一份ByteBuffer复用到多个通道,减少内存拷贝使用TimerWheel检测空闲连接,消除空闲连接检测造成的CPU尖峰支持按优先级发送数据TimerwheelV3阶段——体系化的架构改造截止到2014年4月份,我们解决了LBS瓶颈、长连接服务的稳定性,核心业务流程的稳定性有很大提高。技术团队100多人,日订单600万左右,业务场景越来越复杂,原来很多非核心问题变得越来越严重。稳定性差经常会出现一些功能不可用。都忙着做业务需求,怎么快怎么来,后面堆积了大量的历史债伸缩性差核心业务和非核心业务混杂在一起存储瓶颈每天有几百万的订单,每天都发大量的券,单库单表已经无法支撑了效率低下所有业务都在一个系统里,每天很多的并行分支安全问题线上配置都是明文写在工程配置文件里的;XSS漏洞很多没有监控无法了解系统运行情况,故障定位效率很低协议混乱客户端和服务器的通信协议混乱,没有统一的文档和格式体系化的架构改造一切都是为了更高的可用性系统全局梳理应用分布式改造无线开放平台数据层改造日志收集与检索监控系统风控平台配置中心研发流程自动化系统全局梳理流程与规范建立研发流程、代码规范、SQL规范执行严格的codereview,普及质量意识建立故障问责机制稳定性梳理链路上的单点梳理链路上的性能瓶颈梳理每个环节的故障恢复机制,比如网络闪断后能否自动重连以及重连的影响梳理JVM配置、tomcat配置、应用参数配置定期对系统做性能压测,科学合理的应对营销活动定期review系统的机器部署建立服务降级机制业务梳理小规模的迭代重构不求一步到位,但求一直进步系统分布式改造最初只有两个系统http(系统名字就叫http):处理客户端http请求,例如乘客发单、支付等tcp(系统名字就叫tcp):客户端心跳处理;客户端消息推送系统分布式改造工程架构向分布式迈进,整体划分为三个层次:业务层、服务层、数据层。强依赖场景:A调用B,A依赖返回结果判定流程走向,用分布式RPC框架实现弱依赖场景:A调用B,A的处理逻辑不依赖返回结果,用分布式消息队列实现mysqlredismongodb文件系统订单lbs营销用户支付数据层服务层发单接单付款登录其他业务层以下是示例图,不是真实的架构图系统分布式改造RPC的选型大规模服务化之前,应用可能通过RPC工具简单暴露和引用远程服务,通过配置服务的地址进行调用,然而只是简单的RPC还不够。除了网络通信和数据序列化,大型分布式RPC还应具备的其他要素:基于快的应用场景,我们引入了开源的分布式PRC框架:dubbo。dubbo是阿里开源的框架,在阿里内部和国内大型互联网公司有广泛的应用,我们对dubbo源码足够的了解。系统分布式改造关于Dubbo踩过的一些坑链式调用的超时雪崩client-A(timeout:3s),A-B(timeout:3s),A-C(timeout:3s)。B是非关键服务,C是关键服务。B不可用时,A傻等3s最终超时,业务不可用;B变慢,高峰时期A线程耗光,业务不可用。线程优先级隔离ProviderA提供了关键服务A1,非关键服务A2,高峰时A2耗时高导致线程池满,导致A1不能提供服务。服务不停的上线下线Provider内存使用不当导致频繁fullgc,触发了Zookeeper的超时被下线,Providerfullgc完成后又向zk注册自己,过一会又fullgc服务无法注册用zookeeper做注册中心,配置的注册中心地址只写ip和端口,没有配置注册中心协议,导致用默认的Dubbo协议forbidconsumer异常调用某服务时出现该异常,原因是该服务的所有实例都下线了Zookeeper输出大量的debug日志配置的log4j不是debug级别,但应用大量出现debug日志,zookeeper3.4.3的日志系统是slf4j,应用里还依赖聊logback。StaticLoggerBinder.getSingleton()加载了logback配置,默认是debug级别,log4j没有生效。系统分布式改造消息队列的选型我们选择了RocketMQ,它在淘宝和支付宝得到了非常广泛的应用,也有很多外部用户。RocketMQ是java版的kafka。选择RocketMQ是对现实衡量后一个比较合适的方案。足够的掌控力快的所有系统都用java实现,对java最了解;RocketMQ全部是java实现;我们非常了解源码,向作者报告过几个bug,自己改进过某些实现。高性能实测千兆网卡,2K消息大小,单机TPS16000生产消费无延迟。CPU消耗不大,瓶颈在网卡。线性伸缩服务能力可以随着Broker的增加而得到线性的提升,而且扩展几乎没有限制;这方面和Kafka是一样的。系统分布式改造RocketMQ和Kafka的比较1主要相同点pull模型消费;topic分区;通过磁盘持久化消息;自己刷pagecache;mmap;sendfile;read-ahead;write-behind;运行在JVM上2主要不同点消息存储结构RocketMQ单个broker的所有消息混合存储,磁盘写性能更高;Kafka按分区存储,分区内顺序写,但全局随机,这局限了Kafka单个broker的分区数量不能太大,官方推荐不超过64,而RocektMQ单个Broker的分区可以达到10000。RocketMQ的混合存储结构导致它额外的增加了索引,每次先查询索引,再定位消息,增加了时间成本。集群管理RocketMQ用自己的NameServer,NameServer之间没有关系;Kafka用Zookeeper做集群管理广播消费RocketMQ可以将消息广播到一个ConsumerGroup内的所有机器,也可以广播给不同的ConsumerGroup,Kafka只能做到后者,这是有局限性的无线开放平台客户端与服务端通信面临的问题硬编码每新增一个业务请求,都要在服务端配置一个code和对应的逻辑处理,导致web工程经常改动发布。没有统一格式请求和响应格式没有统一规范,导致服务端很难对请求做统一处理,例如ABTest、监测不同平台和版本的流量。而且第三方合作伙伴集成的方式非常多,维护成本高。流量控制和安全来多少请求就处理多少,根本不考虑后端服务的承受能力。而某些时候需要对后端做保护,例如过多的恶意攻击流量;后端服务快撑不住了;单个IP访问频率过高;单个用户访问频率过高等等。开发效率业务逻辑比较分散,有的在web应用里,有的在dubbo服务里。提供新功能时,工程师关注的点比较多,增加了系统风险。文档维护业务频繁变化和快速发展,文档无法跟上,最后没人能说清到底有哪些协议,协议里的字段含义无线开放平台针对这些问题,我们重新设计了无线接入服务,也就是快的无线开放平台:KOP。以下是一些大的设计原则:一接入权限控制为接入的客户端分配一对appkey/appsecret,appkey是客户端唯一标识,appsecret是一个密钥由客户端保管,用来对请求做数字签名。KOP对客户端请求做签名校验,校验通过才会执行请求。二流量分配和降级同样的API,不同接入端的访问限制可以不一样。可按城市、客户端平台类型做ABTest。极端情况下,优先保证核心客户端的流量,同时也会优先保证核心API的服务能力,例如登录,下单,接单,支付这些核心的API。被访问被限制时,返回一个限流错误码,客户端根据不同场景酌情处理。无线开放平台三流量分析综合考虑客户端、API、IP、用户多个维度,实时分析当前请求是否恶意请求,恶意的IP和用户会被冻结一段时间,如果严重的则会进入黑名单永久封禁。防重放攻击,为了防止请求的URL被重放,客户端生成的URL都是有有效期的,服务器会比对请求里的时间戳和服务器当前时间,如果超过设置的阈值,将会返回一个请求过期错误。黑白名单基于请求URL的表达式拦截四实时发布上线或下线API不需要对KOP进行发布,实时生效。当然,为了安全,会有API的审核机制。五实时监控能统计每个客户端对每个API每分钟的调用总量、成功量、失败量、平均耗时能以分钟为单位查看指定时间段内的数据曲线,并且能对比历史数据当响应时间或失败数量超过阈值时,系统会自动发送报警短信。无线开放平台无线开放平台日志收集与检索日志场景快速查询分布在多台机器上的日志,方便问题快速定位用户请求跨越多个集群,一键查询出请求的日志轨迹基于日志做实时的计算分析日志收集与检索关于日志收集日志分为检索类日志和计算类日志,检索类日志通过log4j的flumeappender异步传输给flumelogserver,组后sink到elasticsearch。单个请求生命周期内的所有日志通过一个flag串联,flag随着dubbo请求传递到不同的JVM里,这样能通过一个flag搜索出请求的生命周期内的操作轨迹。日志收集的agent通过管理平台分发和部署,ageng定时上报心跳,通过管理系统能看到每个agent的运行情况,每个机器上有脚本自动监控agent是否在运行,没有运行则强制启动,当然这个行为是可以关闭的。日志收集与检索关于日志检索当一个客户端请求一个分布式的服务器端,内部的调用路径非常复杂。当发生故障或者排查问题时,根据某些关键字快速定位对应的日志轨迹显得非常重要。elasticsearch一共5台物理机,每天处理1T的日志,日志检索主要是为定位问题用,写入量大,查询量少。按照不同的业务分索引,比如出租车一个索引,代驾一个索引,每个索引10个主分片,每个分片1个副本,分片副本没有配置太多是因为查询比较少而索引更新量很大,这样可以减少主分片和副本之间数据同步的成本,加快索引执行的速度。主分片的数量需要根据实际场景权衡,分片多可以增加索引执行的并发度,但是在排序和分页时会增加更多的消耗。为充分利用内存,单台物理机上开启两个elasticsearch实例elasticsearch实例的GC策略用CMS,堆大小设置为8G,为及早进行平滑的回收,CMSInitiatingOccupancyFraction设为65Bulk方式提交数据,i