程序员人生 网站导航

[置顶] 如何创建一条可靠的实时数据流

栏目:框架设计时间:2016-06-15 14:32:58



数据的生命周期1般包括“生成、传输、消费”3个阶段。在有些场景下,我们需要将数据的变化快速地反馈到在线服务中,因此出现了实时数据流的概念。如何衡量数据流是不是“可靠”,不同的业务之间关注的指标差别很大。根据对大量业务场景的视察,我们发现对数据流要求最严格的业务场景常常和钱有关


在广告平台业务中,广告的预算和消费数据

  1. 广告主修改广告预算,投放系统首先将新的预算更新到数据库,然后需要将其同步到检索端。检索端将广告的预算和已消费金额作对照,重新决定广告是不是有效。如果没有及时更新,会致使广告超预算消费或没有及时上线,不管哪一种情况,损失都将由广告平台承当。
  2. 广告的消费数据1般在广告产生特定事件的时候产生(展现,点击,转化等)。当产生消费数据时,需要将其同步到检索端,检索端更新广告的已消费金额,并和广告的预算做对照,重新决定广告是不是有效。如果没有及时更新,会致使广告超预算消费,损失将由广告平台承当。

在电商业务中,商品的库存和价格当商品库存变少时,如果没有及时同步到服务端,会使用户在结算时才发现商品已无货,伤害用户体验,乃至致使用户流失。1个极真个业务场景是秒杀。一样,如果商品的价格变高时没有及时同步到服务端,会致使用户在付款的时候发现价格变高,伤害用户体验,乃至致使用户流失。另外,如果库存增加或价格下降时,没有及时同步到服务端,会流失可能带来的交易,带来的损失也只能是电商平台承当。


在电商业务中,用户的消费数据有些电商业务中允许用户预充值,用户的账户可能会有余额。如果用户产生了消费,但定单和余额并没有及时更新,可想而知会致使用户产生很大的疑惑。


因此,本文重点讨论1下这些业务场景下对实时数据流的要求。相信在这些场景下都可以认为是可靠的实时数据流,可以很容易适应其他业务。在这些场景下的实时数据流中,常常最关心3个指标:可用性,准确性,实时性。


  • 可用性

最基本的要求,可靠的实时数据流必须要高可用的。


  • 准确性

准确性表示数据流的消费端接收的数据,和数据流发送端发送的数据保持严格1致。也就是常说的“不重不漏”。在有些场景下,如果消费真个操作满足“幂等性”,那末对“不重”的要求可以放宽。但是对“不漏”的要求常常1般是不能让步的。


  • 实时性

实时性表示数据的传输要满足低延时。延时1般定义为,1条数据从被发送端发送到被消费端接收之间的时间。不同的场景对实时性的要求不同,1般分为秒级和分钟级。




为了方便讨论,我们以1个最简单的实时数据流系统为例,其包括3个模块:生产者,传输模块,消费者。复杂的实时数据流系统可以认为是这3个模块的屡次组合。1般来讲,我们会使用 Message Queue 作为数据的传输模块,因此在下文中使用MQ来代替传输模块。接下来我们从3个方面讨论如何保证实时数据流的可靠。


  • 可用性

成熟的 MQ 系统(例如kafka)都用保障高可用性的方案。生产者和消费者我们1般是使用集群来提高可用性。


在生产者端,对可用性的定义包括两重含义:

  1. 数据总是能被生成的。
  2. 数据总是能被发送到 MQ 的。

具体分两种情况。1种情况是数据量非常大,但是能容忍在极端情况,有很小1部份的丢失(例如广告的消费数据)。另1种情况是数据量不是特别大,但对准确性要求非常高,数据是严格不能丢失的(例如用户充值数据)。两种情况的处理方案有所不同。


第1种情况,在消息生成的时候,生产者1般都会先落地到本地磁盘,再由1个单独的程序从磁盘读取数据并发送到 MQ。这样有几个好处:

  1. 当生产者产生宕机的时候,其实不影响数据的继续发送。生产者重启或迁移到备用机器后,数据可以继续生成并发送。
  2. 避免由于网络抖动或 MQ 性能出现问题时,影响生产者的对外服务质量。1般来讲,数据是生产者在对外提供服务的进程中产生的。如果由生产者直接将数据写入 MQ,为了保证数据和对外响应结果的1致性,不能使用异步写的方式,需要同步写。因此在出现网络抖动或 MQ 写延迟太长的时候,会致使生产者没法对外提供服务。
  3. 下降生产者代码复杂性。
  4. 这个将磁盘数据发送到 MQ 的程序1般仅仅是调用 MQ 的library,可以非常简单,减少了出错致使崩溃的可能性,其开源方案是 Flume。实战中,Flume + Kafka 已有非常成熟的方案,而且非常稳定。

第2种情况,生产者1般是将数据直接写入1个可靠的存储系统中(例如数据库),再由1个单独的程序将数据从存储系统中读出并写入到 MQ。


一样,在消费者端,也是先使用 Flume 将数据落地到本地磁盘。如果消费者产生了宕机,也其实不影响 Flume 继续从 MQ 读取数据。在消费者重启或迁移到备用机器以后,可以继续之前的工作。


这样,生产者和消费者都可以认为是在使用磁盘作为介质和 MQ 在通讯,而不是网络,而磁盘的可靠性常常比网络更高。另外一方面,生产者和消费者可以更专注于其本职工作,使用 Flume -> Kafka -> Flume 的开源方案,也避免重复开发。


虽然 Flume 在使用进程中非常稳定,但如果是对可用性要求非常高的系统,我们依然要斟酌在 Flume 程序崩溃乃至磁盘破坏时的恢复方案。特别在磁盘产生破坏时,我们常常没法准肯定位生产者哪些已生产的数据没有被发送到 MQ。1个典型的方案是重做,行将我们没法肯定是不是已发送到 MQ 的数据全部重发1次。因此,在消费者端,保证操作的幂等性是非常重要的。


  • 准确性

准确性可以简单表述为“不重不漏”。“不重”的保证比较困难,在上文已讨论,在数据流产生异常的某些情况下,我们是没法或相当麻烦才能定位哪些数据已发送到 MQ 中,因此需要批量重做,这就会致使 MQ 中有重复的数据。因此,1般的方案常常都是将消费者设计成操作幂等性的,这样就可以够容忍数据重复的情况。


“不漏”在设计到财务的系统中常常不能让步,可以延迟,但不能遗漏。

基于防御性编程的思想,我们做好任何上下游交互的模块都可能出错的准备,并提供更高层次的协议保证业务的正确性。例如,在向 MQ 写入数据时,我们要假定及时 MQ API 返回成功状态,数据在 MQ 中依然是可能被丢失的,message id 机制也不是100%可靠的。那末,我们如何验证生产者发送的数据,经过 MQ 以后1定能够到达消费者?我们需要在生产者和消费者之间建立新的协议。


协议的第1步是为每条数据做1个唯1的标示,即 GUID。更进1步,我们希望 GUID 能够可读,并且能表示数据的前后顺序。为了满足这个需求,我们需要1个高可用的能够产生严格递增的 ID 的服务。这是另外一个很大的话题,在这里不展开。有了这个服务,在生产者发送数据前,先向这个服务要求1个 ID,附加到1条数据上,然后再传输。


在消费者端,有两个方案进行验证。1个方案是生产者要保证发送到数据 ID 是严格递增的消费者验证前后两条数据的 ID 是不是连续。如果不连续,即认为是数据有丢失,停止继续消费,通知生产者进行到毛病恢复流程。待生产者恢复后,通知消费者继续消费。


另外一个方案生产者在发送数据时,除给每条数据附加上自己的 ID,还要附加上其前1条数据的 ID。这样消费者通过两个 ID 可以验证数据是不是有丢失。这个方案的好处是,不需要生产者保证数据的 ID 是连续的。

在实战中,生产者常常将数据写入到1个 Topic 的多个 Partition,每一个消费者只消费指定 Partition 的数据,因此同1个 Partition 内的数据 ID 常常是不连续的。因此,这类方案更多的被采用。


  • 实时性

实时性对系统带来的影响常常弱于准确性。从文章开头的案例讨论可以看出,实时性不好常常也致使业务上的经济损失,特别在1些流水很大的系统中,可能致使非常可观的经济损失。


为了提高实时性,我们1般通过几个手段:

  • 减少网络通讯

  • 上下游服务尽可能同机房乃至同机架部署

  • 如果1定出现跨机房(特别是异地机房)的通讯,在机房间使用专线

尽量少地拆分服务是最有效的方法。这需要在系统的扩大性、伸缩性和本钱之间做好权衡,根据业务需要设计方案,避免过度优化。


实时性的另外一个问题是我们如何监控数据的延迟,并在延迟太高的能及时发现并处理。1个常见的方案是使用“哨兵数据”。生产者定期(例如每5分钟)向 MQ 写入1条固定格式的数据,这条数据必须包括的字段是数据生成的时间,这条数据成为哨兵数据。消费者需要监控,是不是在经过1个指定的时间间隔能接收到“哨兵数据”。如果接收不到,说明生产者或 MQ 出现了问题。如果接收到了,将数据的生成时间和接收时间做对照,如果时间间隔超越阈值,说明延迟过大。不管哪一种情况,都应当触发报警,乃至有些依赖于数据流实时性的服务都要同时停止服务。




对绝大多数实时数据流系统来讲,可用性、准确性、实时性,3个指标斟酌的是优先级顺次下降,实现的代价也是顺次增长。在不同的业务场景中,对“可靠”的定义也有所不同。可能有些系统数据丢失1%对业务的影响不大,如果要保证100%准确带来的本钱会大幅增加;也可能有些系统分钟级实时和秒级实时对业务的影响不大,但如果从分钟级提高到秒级本钱会大幅增加。因此,在架构设计中,1定要结合具体业务场景,综合斟酌和权衡服务质量、用户体验、系统本钱等多方面因素。

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐