所谓的流式处理其实就是对Stream的读取-处理-写入(ETL)操作,应用从Stream中读取数据,再对数据进行相应的处理分析,最后将结果写入另一个Stream中。其中仅一次语义保证了哪怕系统发生故障,每一个ETL操作也仅会被执行一次,不会产生数据的丢失或者重复。这样的可靠性保证对于一些交易、金融类的应用来说至关重要,这就需要Pravega作为流存储与流计算引擎共同努力来完成。
通常来说,对于单独的消息系统而言,语义分为如下三种:
至多一次(At most once):不管Writer在等待ACK时是否发生超时或者得到错误异常,Writer都不会重新发送Event,因此会有数据丢失的风险。在具体的实现过程中,这一种语义无需做任何额外的控制,实现起来最为简单,因此也通常有着最优的性能。在某些特定的场景中,我们只希望追求极致的性能而不关心数据的丢失,可能会选用此方案。
至少一次(At least once):如果Writer在等待ACK时发生超时或者得到错误异常,Writer将会重新发送消息,这样能保证每个Event至少被处理一次,保证了数据不会丢失,从而提高了系统的可靠性,但同时会带来数据重复的问题,例如,当Writer往Stream中成功写入一个Event,但是当系统尝试给Writer返回ACK的时候出现网络异常,Writer因没有收到ACK而判断为写入Event失败,因此Writer还是会重新发送此Event,导致数据重复。
仅一次(Exactly once):在系统发生异常时,Writer可以尝试多次重新发送Event,同时能保证最终每个Event只被写入一次。一些对数据准确性要求非常高的系统需要保证exactly-once语义,譬如支付系统,当用户在移动端付款时,很有可能会因为网络原因导致延时较长甚至超时,用户可能会手动进行刷新操作,如果没有exactly-once的语义支持,很有可能会发生两次扣费,我们绝对不希望此类错误发生。
仅一次语义是实现流处理系统正确性(correctness)的基石,因此也是流存储Pravega自从设计之初就规划好的设计目标。但是,exactly-once的实现也面临着诸多挑战,例如Kafka也直到0.11版本引入了KIP-98之后才完成了仅一次的支持。这种更强的语义不仅使编写应用程序更容易,而且使Pravega有了更为广泛的应用空间。这一篇文章我们将介绍Pravega实现这一特性的设计细节,以及和Flink社区合作开发的端到端(end-to-end)的exactly-once的实现。