重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
本文由阿里闲鱼技术团队逸昂分享,原题“消息链路优化之弱感知链路优化”,有修订和改动,感谢作者的分享。
创新互联服务项目包括交口网站建设、交口网站制作、交口网页制作以及交口网络营销策划等。多年来,我们专注于互联网行业,利用自身积累的技术优势、行业经验、深度合作伙伴关系等,向广大中小型企业、政府机构等提供互联网行业的解决方案,交口网站推广取得了明显的社会效益与经济效益。目前,我们服务的客户以成都为中心已经辐射到交口省份的部分城市,未来相信会继续扩大服务区域并继续获得客户的支持与信任!
闲鱼的IM消息系统作为买家与卖家的沟通工具,增进理解、促进信任,对闲鱼的商品成交有重要的价值,是提升用户体验最关键的环节。
然而,随着业务体量的快速增长,当前这套消息系统正面临着诸多急待解决的问题。
以下几个问题典型最为典型:
1) 在线消息的体验提升;
2) 离线推送的到达率;
3) 消息玩法与消息底层系统的耦合过强。
经过评估,我们认为现阶段离线推送的到达率问题最为关键,对用户体验影响较大。
本文将要分享的是闲鱼IM消息在解决离线推送的到达率方面的技术实践,内容包括问题分析和技术优化思路等 ,希望能带给你启发。
(本文已同步发布于: )
本文是系列文章的第6篇,总目录如下:
《 阿里IM技术分享(一):企业级IM王者——钉钉在后端架构上的过人之处 》
《 阿里IM技术分享(二):闲鱼IM基于Flutter的移动端跨端改造实践 》
《 阿里IM技术分享(三):闲鱼亿级IM消息系统的架构演进之路 》
《 阿里IM技术分享(四):闲鱼亿级IM消息系统的可靠投递优化实践 》
《 阿里IM技术分享(五):闲鱼亿级IM消息系统的及时性优化实践 》
《 阿里IM技术分享(六):闲鱼亿级IM消息系统的离线推送到达率优化 》(* 本文)
从数据通信链接的技术角度,我们根据闲鱼客户端是否在线,将整体消息链路大致分为强感知链路和弱感知链路。
强感知链路由以下子系统或模块:
1) 发送方客户端;
2) idleapi-message(闲鱼的消息网关);
3) heracles(闲鱼的消息底层服务);
4) accs(阿里自研的长连接通道);
5) 接收方客户端组成。
整条链路的核心指标在于端到端延迟和消息到达率。
强感知链路中的双方都是在线的,消息到达客户端就可以保证接收方感知到。强感知链路的主要痛点在消息的端到端延迟。
弱感知链路与强感知链路的主要不同在于: 弱感知链路的接收方是离线的,需要依赖离线推送这样的方式送达。
因此弱感知链路的用户感知度不强,其核心指标在于消息的到达率,而非延迟。
所以当前阶段,优化弱感知链路的重点也就是提升离线消息的到达率。换句话说, 提升离线消息到达率问题,也就是优化弱感知链路本身 。
下图一张整个IM消息系统的架构图,感受下整体链路:
如上图所示,各主要组件和子系统分工如下:
1) HSF是一个远程服务框架,是dubbo的内部版本;
2) tair是阿里自研的分布式缓存框架,支持 memcached、Redis、LevelDB 等不同存储引擎;
3) agoo是阿里的离线推送中台,负责整合不同厂商的离线推送通道,向集团用户提供一个统一的离线推送服务;
4) accs是阿里自研的长连接通道,为客户端、服务端的实时双向交互提供便利;
5) lindorm是阿里自研的NoSQL产品,与HBase有异曲同工之妙;
6) 域环是闲鱼消息优化性能的核心结构,用来存储用户最新的若干条消息。
强感知链路和弱感知链路在通道选择上是不同的:
1) 强感知链路使用accs这个在线通道;
2) 弱感知链路使用agoo这个离线通道。
通俗了说,弱感知链路指的就是离线消息推送系统。
相比较于在线消息和端内推送(也就是上面说的强感知链路),离线推送难以确保被用户感知到。
典型的情况包括:
1) 未发送到用户设备:即推送未送达用户设备,这种情况可以从通道的返回分析;
2) 发送到用户设备但没有展示到系统通知栏:闲鱼曾遇到通道返回成功,但是用户未看到推送的案例;
3) 展示到通知栏,并被系统折叠:不同安卓厂商对推送的折叠策略不同,被折叠后,需用户主动展开才能看到内容,触达效果明显变差;
4) 展示到通知栏,并被用户忽略:离线推送的点击率相比于在线推送更低。
针对“1)未发送到用户设备”,原因有:
1) 离线通道的token失效;
2) 参数错误;
3) 用户关闭应用通知;
4) 用户已卸载等。
针对“3)展示到通知栏,并被系统折叠”,原因有:
1) 通知的点击率;
2) 应用在厂商处的权重;
3) 推送的数量等。
针对“4)展示到通知栏,并被用户忽略”,原因有:
1) 用户不愿意查看推送;
2) 用户看到了推送,但是对内容不感兴趣;
3) 用户在忙别的事,无暇处理。
总之: 以上这些离线消息推送场景,对于用户来说感知度不高,我们也便称之为弱感知链路。
我们的弱感知链路分为3部分,即:
1) 系统;
2) 通道;
3) 用户。
共包含了Hermes、agoo、厂商、设备、用户、承接页这几个环节。具体如下图所示。
从推送的产生到用户最终进入APP,共分为如下几个步骤:
步骤1 :Hermes是闲鱼的用户触达系统,负责人群管理、内容管理、时机把控,是整个弱感知链路的起点。;
步骤2 :agoo是阿里内部承接离线推送的中台,是闲鱼离线推送能力的基础;
步骤3 :agoo实现离线推送依靠的是厂商的推送通道(如:苹果的 apns通道 、Google的fcm通道、及 国内各厂商的自建通道 。;
步骤4 :通过厂商的通道,推送最终出现在用户的设备上,这是用户能感知到推送的前提条件;
步骤5 :如果用户刚巧看到这条推送,推送的内容也很有趣,在用户的主动点击下会唤起APP,打开承接页,进而给用户展示个性化的商品。
经过以上5个步骤,至此弱感知链路就完成了使命。
弱感知链路的核心问题在于:
1) 推送的消息是否投递给了用户;
2) 已投递到的消息用户是否有感知。
这对应推送的两个阶段:
1) 推送消息是否已到达设备;
2) 用户是否查看推送并点击。
其中: 到达设备这个阶段是最基础的,也是本次优化的核心。
我们可以将每一步的消息处理量依次平铺,展开为一张漏斗图,从而直观的查看链路的瓶颈。
漏斗图斜率最大的地方是优化的重点,差异小的地方不需要优化:
通过分析以上漏斗图,弱感知链路的优化重点在三个方面:
1) agoo受理率:是指我们发送推送请到的数量到可以通过agoo(阿里承接离线推送的中台)转发到厂商通道的数量之间的漏斗;
2) 厂商受理率:是指agoo中台受理的量到厂商返回成功的量之间的漏斗;
3) Push点击率:也就通过以上通道最终已送到到用户终端的消息,是否最终转化为用户的主动“点击”。
有了优化方向,我们来看看优化手段吧。
跟随推送的视角,顺着链路看一下我们是如何进行优化的。
用户的推送,从 Hermes 站点搭乘“班车”,驶向下一站: agoo 。
这是推送经历的第一站。到站一看,傻眼了,只有不到一半的推送到站下车了。这是咋回事嘞?
这就要先说说 agoo 了,调用 agoo 有两种方式:
1) 指定设备和客户端,agoo直接将推送投递到相应的设备;
2) 指定用户和客户端,agoo根据内部的转换表,找到用户对应的设备,再进行投递。
我们的系统不保存用户的设备信息。因此,是按照用户来调用agoo的。
同时: 由于没有用户的设备信息,并不知道用户是 iOS 客户端还是 Android 客户端。工程侧不得不向 iOS 和 Android 都发送一遍推送。虽然保证了到达,但是,一半的调用都是无效的。
为了解这个问题: 我们使用了agoo的设备信息。将用户转换设备这一阶段提前到了调用 agoo 之前,先明确用户对应的设备,再指定设备调用 agoo,从而避免无效调用。
agoo调用方式优化后,立刻剔除了无效调用,agoo受理率有了明显提升。
至此: 我们总算能对 agoo 受理失败的真正原因做一个高大上的分析了。
根据统计: 推送被 agoo 拒绝的主要原因是——用户关闭了通知权限。同时,我们对 agoo 调用数据的进一步分析发现——有部分用户找不到对应的设备。 优化到此,我们猛然发现多了两个问题。
那就继续优化呗:
1) 通知体验优化,引导打开通知权限;
2) 与agoo共建设备库,解决设备转换失败的问题。
这两个优化方向又是一片新天地,我们择日再聊。
推送到达 agoo ,分机型搭乘厂商“专列”,驶向下一站:用户设备。
这是推送经历的第二站。出站查票,发现竟然超员了。
于是乎: 我们每天有大量推送因为超过厂商设定的限额被拦截。
为什么会这样呢?
实际上: 提供推送通道的厂商(没错, 各手机厂商的自家推送通道良莠不齐 ),为了保证用户体验,会对每个应用能够推送的消息总量进行限制。
对于厂商而言,这个限制会根据推送的类型和应用的用户规模设定——推送主要分为产品类的推送和营销类的推送。
厂商推送通道对于不同类型消息的限制是:
1) 对于产品类推送,厂商会保证到达;
2) 对于营销类推送,厂商会进行额度限制;
3) 未标记的推送,默认作为营销类推送对待。
我们刚好没有对推送进行标记,因此触发了厂商的推送限制。
这对我们的用户来说,会带来困扰。闲鱼的交易,很依赖买卖家之间的消息互动。这部分消息是需要确保到达的。
同样: 订单类的消息、用户的关注,也需要保证推送给用户。
根据主流厂商的接口协议,我们将推送的消息分为以下几类,并进行相应标记:
1) 即时通讯消息;
2) 订单状态变化;
3) 用户关注内容;
4) 营销消息这几类。
同时,在业务上,我们也进行了推送的治理——将用户关注度不高的消息,取消推送,避免打扰。
经过这些优化,因为超过厂商限额而被拦截的推送实现了清零。
通过优化agoo受理率、厂商受理率,我们解决了推送到达量的瓶颈。但即使消息被最终送达,用户到底点击了没有?这才是消息推送的根本意义所在。
于是,在日常的开发测试过程中,我们发现了推送的两个体验问题:
1) 用户点击Push有开屏广告;
2) 营销Push也有权限校验,更换用户登陆后无法点击。
对于开屏广告功能,我们增加了Push点击跳过广告的能力。
针对Push的权限校验功能,闲鱼根据场景做了细分:
1) 涉及个人隐私的推送,保持权限校验不变;
2) 营销类的推送,放开权限校验。
以上是点击体验的优化,我们还需要考虑用户的点击意愿。
用户点击量与推送的曝光量、推送素材的有趣程度相关。推送的曝光量又和推送的到达量、推送的到达时机有关。
具体的优化手段是:
1) 在推送内容上:我们需要优化的是推送的时机和相应的素材;
2) 在推送时机上:算法会根据用户的偏好和个性化行为数据,计算每个用户的个性化推送时间,在用户空闲的时间推送(避免在不合适的时间打扰用户,同时也能提升用户看到推送的可能性)。
3) 在推送素材上:算法会根据素材的实时点击反馈,对素材做实时赛马。只发用户感兴趣的素材,提高用户点击意愿。
通过以上我们的分析和技术优化手段,整体弱推送链路链路有了不错的提升,离线消息的到达率相对提升了两位数。
本篇主要和大家聊的是只是IM消息系统链路中的一环——弱感知链路的优化,落地到到具体的业务也就是离线消息送达率问题。
整体IM消息系统,还是一个比较复杂的领域。
我们在消息系统的发展过程中,面临着如下问题:
1) 如何进行消息的链路追踪;
2) 如何保证IM消息的快速到达(见《 闲鱼亿级IM消息系统的及时性优化实践 》);
3) 如何将消息的玩法和底层能力分离;
4) 离线推送中如何通过用户找到对应的设备。
这些问题,我们在以前的文章中有所分享,以后也会陆续分享更多,敬请期待。
[1] Android P正式版即将到来:后台应用保活、消息推送的真正噩梦
[2] 一套高可用、易伸缩、高并发的IM群聊、单聊架构方案设计实践
[3] 一套亿级用户的IM架构技术干货(上篇):整体架构、服务拆分等
[4] 一套亿级用户的IM架构技术干货(下篇):可靠性、有序性、弱网优化等
[5] 从新手到专家:如何设计一套亿级消息量的分布式IM系统
[6] 企业微信的IM架构设计揭秘:消息模型、万人群、已读回执、消息撤回等
[7] 融云技术分享:全面揭秘亿级IM消息的可靠投递机制
[8] 移动端IM中大规模群消息的推送如何保证效率、实时性?
[9] 现代IM系统中聊天消息的同步和存储方案探讨
[10] 新手入门一篇就够:从零开发移动端IM
[11] 移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”
[12] 移动端IM开发者必读(二):史上最全移动弱网络优化方法总结
[13] IM消息送达保证机制实现(一):保证在线实时消息的可靠投递
[14] IM消息送达保证机制实现(二):保证离线消息的可靠投递
[15] 零基础IM开发入门(一):什么是IM系统?
[16] 零基础IM开发入门(二):什么是IM系统的实时性?
[17] 零基础IM开发入门(三):什么是IM系统的可靠性?
[18] 零基础IM开发入门(四):什么是IM系统的消息时序一致性?
(本文已同步发布于: )
一、消息中间件相关知识
1、概述
消息队列已经逐渐成为企业IT系统内部通信的核心手段。它具有低耦合、可靠投递、广播、流量控制、最终一致性等一系列功能,成为异步RPC的主要手段之一。当今市面上有很多主流的消息中间件,如老牌的ActiveMQ、RabbitMQ,炙手可热的Kafka,阿里巴巴自主开发RocketMQ等。
2、消息中间件的组成
2.1 Broker
消息服务器,作为server提供消息核心服务
2.2 Producer
消息生产者,业务的发起方,负责生产消息传输给broker,
2.3 Consumer
消息消费者,业务的处理方,负责从broker获取消息并进行业务逻辑处理
2.4 Topic
2.5 Queue
2.6 Message
消息体,根据不同通信协议定义的固定格式进行编码的数据包,来封装业务数据,实现消息的传输
3 消息中间件模式分类
3.1 点对点
PTP点对点:使用queue作为通信载体
说明:
消息生产者生产消息发送到queue中,然后消息消费者从queue中取出并且消费消息。
消息被消费以后,queue中不再存储,所以消息消费者不可能消费到已经被消费的消息。 Queue支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费。
说明:
queue实现了负载均衡,将producer生产的消息发送到消息队列中,由多个消费者消费。但一个消息只能被一个消费者接受,当没有消费者可用时,这个消息会被保存直到有一个可用的消费者。
4 消息中间件的优势
4.1 系统解耦
交互系统之间没有直接的调用关系,只是通过消息传输,故系统侵入性不强,耦合度低。
4.2 提高系统响应时间
例如原来的一套逻辑,完成支付可能涉及先修改订单状态、计算会员积分、通知物流配送几个逻辑才能完成;通过MQ架构设计,就可将紧急重要(需要立刻响应)的业务放到该调用方法中,响应要求不高的使用消息队列,放到MQ队列中,供消费者处理。
4.3 为大数据处理架构提供服务
通过消息作为整合,大数据的背景下,消息队列还与实时处理架构整合,为数据处理提供性能支持。
4.4 Java消息服务——JMS
Java消息服务(Java Message Service,JMS)应用程序接口是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。
5 消息中间件应用场景
5.1 异步通信
有些业务不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。
5.2 解耦
降低工程间的强依赖程度,针对异构系统进行适配。在项目启动之初来预测将来项目会碰到什么需求,是极其困难的。通过消息系统在处理过程中间插入了一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口,当应用发生变化时,可以独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。
5.3 冗余
有些情况下,处理数据的过程会失败。除非数据被持久化,否则将造成丢失。消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的”插入-获取-删除”范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。
5.4 扩展性
因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。不需要改变代码、不需要调节参数。便于分布式扩容。
5.5 过载保护
在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量无法提取预知;如果以为了能处理这类瞬间峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。
5.6 可恢复性
系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。
5.7 顺序保证
在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。
5.8 缓冲
在任何重要的系统中,都会有需要不同的处理时间的元素。消息队列通过一个缓冲层来帮助任务最高效率的执行,该缓冲有助于控制和优化数据流经过系统的速度。以调节系统响应时间。
5.9 数据流处理
分布式系统产生的海量数据流,如:业务日志、监控数据、用户行为等,针对这些数据流进行实时或批量采集汇总,然后进行大数据分析是当前互联网的必备技术,通过消息队列完成此类数据收集是最好的选择。
6 消息中间件常用协议
6.1 AMQP协议
AMQP即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同开发语言等条件的限制。
优点:可靠、通用
6.2 MQTT协议
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时通讯协议,有可能成为物联网的重要组成部分。该协议支持所有平台,几乎可以把所有联网物品和外部连接起来,被用来当做传感器和致动器(比如通过Twitter让房屋联网)的通信协议。
优点:格式简洁、占用带宽小、移动端通信、PUSH、嵌入式系统
6.3 STOMP协议
STOMP(Streaming Text Orientated Message Protocol)是流文本定向消息协议,是一种为MOM(Message Oriented Middleware,面向消息的中间件)设计的简单文本协议。STOMP提供一个可互操作的连接格式,允许客户端与任意STOMP消息代理(Broker)进行交互。
优点:命令模式(非topic\queue模式)
6.4 XMPP协议
XMPP(可扩展消息处理现场协议,Extensible Messaging and Presence Protocol)是基于可扩展标记语言(XML)的协议,多用于即时消息(IM)以及在线现场探测。适用于服务器之间的准即时操作。核心是基于XML流传输,这个协议可能最终允许因特网用户向因特网上的其他任何人发送即时消息,即使其操作系统和浏览器不同。
优点:通用公开、兼容性强、可扩展、安全性高,但XML编码格式占用带宽大
6.5 其他基于TCP/IP自定义的协议
有些特殊框架(如:redis、kafka、zeroMq等)根据自身需要未严格遵循MQ规范,而是基于TCP\IP自行封装了一套协议,通过网络socket接口进行传输,实现了MQ的功能。
7 常见消息中间件MQ介绍
7.1 RocketMQ
阿里系下开源的一款分布式、队列模型的消息中间件,原名Metaq,3.0版本名称改为RocketMQ,是阿里参照kafka设计思想使用java实现的一套mq。同时将阿里系内部多款mq产品(Notify、metaq)进行整合,只维护核心功能,去除了所有其他运行时依赖,保证核心功能最简化,在此基础上配合阿里上述其他开源产品实现不同场景下mq的架构,目前主要多用于订单交易系统。
具有以下特点:
官方提供了一些不同于kafka的对比差异:
7.2 RabbitMQ
使用Erlang编写的一个开源的消息队列,本身支持很多的协议:AMQP,XMPP, SMTP,STOMP,也正是如此,使的它变的非常重量级,更适合于企业级的开发。同时实现了Broker架构,核心思想是生产者不会将消息直接发送给队列,消息在发送给客户端时先在中心队列排队。对路由(Routing),负载均衡(Load balance)、数据持久化都有很好的支持。多用于进行企业级的ESB整合。
7.3 ActiveMQ
Apache下的一个子项目。使用Java完全支持JMS1.1和J2EE 1.4规范的 JMS Provider实现,少量代码就可以高效地实现高级应用场景。可插拔的传输协议支持,比如:in-VM, TCP, SSL, NIO, UDP, multicast, JGroups and JXTA transports。RabbitMQ、ZeroMQ、ActiveMQ均支持常用的多种语言客户端 C++、Java、.Net,、Python、 Php、 Ruby等。
7.4 Redis
使用C语言开发的一个Key-Value的NoSQL数据库,开发维护很活跃,虽然它是一个Key-Value数据库存储系统,但它本身支持MQ功能,所以完全可以当做一个轻量级的队列服务来使用。对于RabbitMQ和Redis的入队和出队操作,各执行100万次,每10万次记录一次执行时间。测试数据分为128Bytes、512Bytes、1K和10K四个不同大小的数据。实验表明:入队时,当数据比较小时Redis的性能要高于RabbitMQ,而如果数据大小超过了10K,Redis则慢的无法忍受;出队时,无论数据大小,Redis都表现出非常好的性能,而RabbitMQ的出队性能则远低于Redis。
7.5 Kafka
Apache下的一个子项目,使用scala实现的一个高性能分布式Publish/Subscribe消息队列系统,具有以下特性:
7.6 ZeroMQ
号称最快的消息队列系统,专门为高吞吐量/低延迟的场景开发,在金融界的应用中经常使用,偏重于实时数据通信场景。ZMQ能够实现RabbitMQ不擅长的高级/复杂的队列,但是开发人员需要自己组合多种技术框架,开发成本高。因此ZeroMQ具有一个独特的非中间件的模式,更像一个socket library,你不需要安装和运行一个消息服务器或中间件,因为你的应用程序本身就是使用ZeroMQ API完成逻辑服务的角色。但是ZeroMQ仅提供非持久性的队列,如果down机,数据将会丢失。如:Twitter的Storm中使用ZeroMQ作为数据流的传输。
ZeroMQ套接字是与传输层无关的:ZeroMQ套接字对所有传输层协议定义了统一的API接口。默认支持 进程内(inproc) ,进程间(IPC) ,多播,TCP协议,在不同的协议之间切换只要简单的改变连接字符串的前缀。可以在任何时候以最小的代价从进程间的本地通信切换到分布式下的TCP通信。ZeroMQ在背后处理连接建立,断开和重连逻辑。
特性:
二、主要消息中间件的比较
个人不认为nosql在少量数据存储上有啥优势。nosql主要解决的是auto sharding的问题,你不需要sharding,搞啥nosql. 作者:方圆 链接:
肯定是Oracle,因为从简单查询性能角度来比较:Oracle MySQL NoSQL,NoSQL 产品不支持 Join,MySQL 的查询优化器由于所基于的统计信息相对少很多,当Query 复杂度很高的时候容易出现执行计划不是最优选择的问题,而 Oracle 由于大量的统计信息支持,使得其查询优化器更为智能,对复杂查询有更优的表现。
淘宝开源的TDDL和cobar的结合,放到了阿里云上就是DRDS,是商品,服务,可以购买使用的。可以在阿里云官网上注册免费试用。
=====================================================
随着互联网时代的到来,计算机要管理的数据量呈指数级别地飞速上涨,而我们却完全无法对用户数做出准确预估。我们的系统所需要支持的用户数,很可能在短短的一个月内突然爆发式地增长几千倍,数据也很可能快速地从原来的几百GB飞速上涨到了几百个TB。如果在这爆发的关键时刻,系统不稳定或无法访问,那么对于业务将会是毁灭性的打击。
伴随着这种对于系统性能、成本以及扩展性的新需要,以HBase、MongoDB为代表的NoSQL数据库和以阿里DRDS、VoltDB、ScaleBase为代表的分布式NewSQL数据库如雨后春笋般不断涌现出来。
本文将会介绍阿里DRDS的技术理念、发展历程、技术特性等内容。
DRDS设计理念
从20世纪70年代关系数据库创立开始,其实大家在数据库上的追求就从未发生过变化:更快的存取数据,可以按需扩缩以承载更大的访问量和更大的数据量,开发容易,硬件成本低,我们可以把这叫做数据库领域的圣杯。
为了支撑更大的访问量和数据量,我们必然需要分布式数据库系统,然而分布式系统又必然会面对强一致性所带来的延迟提高的问题,因为网络通信本身比单机内通信代价高很多,这种通信的代价就会直接增加系统单次提交的延迟。延迟提高会导致数据库锁持有时间变长,使得高冲突条件下分布式事务的性能不升反降(这个具体可以了解一下Amdahl定律),甚至性能距离单机数据库都还有明显的差距。
从上面的说明,我们可以发现,问题的关键并不是分布式事务做不出来,而是做出来了却因为性能太差而没有什么卵用。数据库领域的高手们努力了40年,但至今仍然没有人能够很好地解决这个问题,Google Spanner的开发负责人就经常在他的Blog上谈论延迟的问题,相信也是饱受这个问题的困扰。
面对这个难题,传统的关系数据库选择了放弃分布式的方案,因为在20世纪70~80年代,我们的数据库主要被用来处理企业内的各类数据,面对的用户不过几千人,而数据量最多也就是TB级别。用单台机器来处理事务,用个磁盘阵列处理一下磁盘容量不够的问题,基本上就能解决一切问题了。
然而,信息化和互联网的浪潮改变了这一切,我们突然发现,我们服务的对象发生了根本性变化,从原来的几千人,变成了现在的几亿人,数据量也从TB级别到了PB级别甚至更多。存在单点的单机系统无论如何努力,都会面对系统处理能力的天花板。原来的这条路,看起来是走不下去了,我们必须想办法换一条路来走。
可是,分布式数据库所面对的强一致性难题却像一座高山,人们努力了无数个日日夜夜,但能翻越这座山的日子看来仍然遥遥无期。
于是,有一群人认为,强一致性这件事看来不怎么靠谱,那彻底绕开这个问题是不是个更好的选择?他们发现确实有那么一些场景是不需要强一致事务的,甚至连SQL都可以不要,最典型的就是日志流水的记录与分析这类场景。而去掉了事务和SQL,接口简单了,性能就更容易得到提升,扩展性也更容易实现,这就是NoSQL系统的起源。
虽然NoSQL解决了性能和扩展性问题,但这种绕开问题的方法给用户带来了很多困扰,系统的开发成本也大大提升。这时候就有另外一群人,他们觉得用户需要SQL,觉得用户也需要事务,问题的关键在于我们要努力地往圣杯的方向不断前进。在保持系统的扩展性和性能的前提下,付出尽可能小的代价来满足业务对数据库的需要。这就是NewSQL这个理念的由来。
DRDS也是一个NewSQL的系统,它与ScaleBase、VoltDB等系统类似,都希望能够找到一条既能保持系统的高扩展性和高性能,又能尽可能保持传统数据库的ACID事务和SQL特性的分布式数据库系统。
DRDS发展历程
在一开始,TDDL的主要功能就是做数据库切分,一个或一组SQL请求提交到TDDL,TDDL进行规则运算后得知SQL应该被分发到哪个机器,直接将SQL转发到对应机器即可(如图1)。
图1 TDDL数据库切分
开始的时候,这种简单的路由策略能够满足用户的需要,我们开始的那些应用,就是通过这样非常简单的方式完成了他所有的应用请求。我们也认为,这种方案简单可靠,已经足够好用了。
然而,当我们服务的应用从十几个增长到几百个的时候,大量的中小应用加入,大家纷纷表示,原来的方案限制太大,很多应用其实只是希望做个读写分离,希望能有更好的SQL兼容性。
于是,我们做了第一次重大升级,在这次升级里,我们提出了一个重要的概念就是三层架构,Matrix对应数据库切分场景,对SQL有一定限制,Group对应读写分离和高可用场景,对SQL几乎没有限制。如图2所示。
图2 数据库升级为三层架构
这种做法立刻得到了大家的认可,TDDL所提供的读写分离、分库分表等核心功能,也成为了阿里集团内数据库领域的标配组件,在阿里的几乎所有应用上都有应用。最为难得的是,这些功能从上线后,到现在已经经历了多年双11的严酷考验,从未出现过严重故障(p0、p1级别故障属于严重故障)。数据库体系作为整个应用系统的重中之重,能做到这件事,真是非常不容易。
随着核心功能的稳定,自2010年开始,我们集中全部精力开始关注TDDL后端运维系统的完善与改进性工作。在DBA团队的给力配合下,围绕着TDDL,我们成功做到了在线数据动态扩缩、异步索引等关键特征,同时也比较成功地构建了一整套分布式数据库服务管控体系,用户基本上可以完全自助地完成整套数据库环境的搭建与初始化工作。
大概是2012年,我们在阿里云团队的支持下,开始尝试将TDDL这套体系输出到阿里云上,也有了个新的名字:阿里分布式数据库服务(DRDS),希望能够用我们的技术服务好更多的人。
不过当我们满怀自信地把自己的软件拿到云上的时候,却发现我们的软件距离用户的要求差距很大。在内部因为有DBA的同学们帮助进行SQL review,所以SQL的复杂度都是可控的。然而到了云上,看了各种渠道提过来的兼容性需求,我们经常是不自觉地发出这样的感叹:“啊?原来这种语法MySQL也是可以支持的?”
于是,我们又进行了架构升级,这次是以兼容性为核心目标的系统升级工作,希望能够在分布式场景下支持各类复杂的SQL,同时也将阿里这么多年来在分布式事务上的积累都带到了DRDS里面。
这次架构升级,我们的投入史无前例,用了三年多才将整个系统落地完成。我们先在内部以我们自己的业务作为首批用户上线,经过了内部几百个应用的严酷考验以后,我们才敢拿到云上,给到我们的最终用户使用。
目前,我们正在将TDDL中更多的积累输出到云上,同时也努力优化我们的用户界面。PS:其实用户界面优化对我们这种专注于高性能后端技术的团队来说,才是最大的技术挑战,连我也去学了AngularJS,参与了用户UI编。
DRDS主要功能介绍
发展历史看完了,下面就由我来介绍一下目前我们已经输出到云上的主要功能。
【分布式SQL执行引擎】
分布式SQL引擎主要的目的,就是实现与单机数据库SQL引擎的完全兼容。目前我们的SQL引擎能够做到与MySQL的SQL引擎全兼容,包括各类join和各类复杂函数等。他主要包含SQL解析、优化、执行和合并四个流程,如图3中绿色部分。
图3 SQL引擎实现的主要流程
虽然SQL是兼容的,但是分布式SQL执行算法与单机SQL的执行算法却完全不同,原因也很简单,网络通信的延迟比单机内通信的延迟大得多。举个例子说明一下,我们有份文件要从一张纸A上誊写到另外一张纸B上,单机系统就好比两张纸都在同一个办公室里,而分布式数据库则就像是一张纸在北京,一张纸在杭州。
自然地,如果两张纸在同一个办公室,因为传输距离近,逐行誊写的效率是可以接受的。而如果距离是北京到杭州,用逐行誊写的方式,就立刻显得代价太高了,我们总不能看一行,就打个“飞的”去杭州写下来吧。在这种情况下,还是把纸A上的信息拍个照片,【一整批的】带到杭州去处理,明显更简单一些。这就是分布式数据库特别强调吞吐调优的原因,只要是涉及到跨机的所有查询,都必须尽可能的积攒一批后一起发送,以减少系统延迟提高带来的不良影响。
【按需数据库集群平滑扩缩】
DRDS允许应用按需将新的单机存储加入或移出集群,DRDS则能够保证应用在迁移流程中实现不停机扩容缩容。
图4 DRDS按需进行平滑扩缩
在内部的数据库使用实践中,这个功能的一个最重要应用场景就是双11了。在双11之前,我们会将大批的机器加入到我们的数据库集群中,抗过了双11,这批机器就会下线。
当DRDS来到云上,我们发现双11其实不仅仅只影响阿里内部的系统。在下游的各类电商辅助性系统其实也面对巨大压力。在双11前5天,网聚宝的熊总就找到我说,担心撑不过双11的流量,怕系统挂。于是我们就给他介绍了这个自动扩容的功能怎么用,他买了一个月的数据库,挂接在DRDS上。数据库能力立刻翻倍,轻松抗过了双11,也算是我印象比较深刻的一个案例了。
因为我们完全无法预测在什么时间点系统会有爆发性的增长,而如果在这时候系统因为技术原因不能使用,就会给整个业务带来毁灭性的影响,风口一旦错过,就追悔莫及了。我想这就是云计算特别强调可扩展能力的原因吧。
【小表广播】
小表广播也是我们在分布式数据库领域内最常用的工具之一,他的核心目的其实都是一个——尽可能让查询只发生在单机。
让我们用一个例子来说明,小表广播的一般使用场景。
图5 小表广播场景
图5中,如果我想知道买家id等于0的用户在商城里面买了哪些商品,我们一般会先将这两个表join起来,然后再用where平台名=”商城” and buyerID = 0找到符合要求的数据。然而这种join的方式,会导致大量的针对左表的网络I/O。如果要取出的数据量比较大,系统延迟会明显上升。
这时候,为了提升性能,我们就必须要减少跨机join的网络代价。我们比较推荐应用做如下处理,将左表复制到右表的每一个库上。这样,join操作就由分布式join一下变回到本地join,系统的性能就有很大的提升了,如图6所示。
图6
【分布式事务套件】
在阿里巴巴的业务体系中存在非常多需要事务类的场景,下单减库存,账务,都是事务场景最集中的部分。
而我们处理事务的方法却和传统应用处理事务的方案不大一样,我们非常强调事务的最终一致性和异步化。利用这种方式,能够极大地降低分布式系统中锁持有的时间,从而极大地提升系统性能。
图7 DRDS分布式事务解决套件
这种处理机制,是我们分布式事务能够以极低成本大量运行的最核心法门。在DRDS平台内,我们将这些方案产品化,为了DRDS的分布式事务解决套件。
利用他们,能够让你以比较低的成本,实现低延迟,高吞吐的分布式事务场景。
DRDS的未来
阿里分布式数据库服务DRDS上线至今,大家对这款产品的热情超出了我们的预期,短短半年内已经有几千个申请。
尽管还在公测期,但是大家就已经把关系到身家性命的宝贵在线数据业务放到了DRDS上,我能够感受到这份沉甸甸的信赖,也不想辜负这份信赖。
经过阿里内部几千个应用的不断历练,DRDS已经积累出一套强大的分布式SQL执行引擎和和一整套分布式事务套件。
我也相信,这些积累能够让用户在基本保持单机数据库的使用习惯的前提下,享受到分布式数据库高性能可扩展的好处。
在平时的DRDS支持过程中,我面对最多的问题就是,DRDS能不能够在不改变任何原有业务逻辑和代码的前提下,实现可自由伸缩和扩展呢?十分可惜的是,关系数据库发展至今,还没有找到既能保留传统数据库一切特性,又能实现高性能可扩展数据库的方法。
然而,虽不能至,吾心向往之!我们会以“可扩展,高性能”为产品核心,坚定地走在追寻圣杯的路上,并坚信最终我们一定能够找寻到它神圣的所在。
作者简介:王晶昱,花名沈询,阿里巴巴资深技术专家。目前主要负责阿里的分布式数据库DRDS(TDDL)和阿里的分布式消息服务ONS(RocketMQ/Notify)两个系统。