2018年,Apache Kafka以一种特殊的设计和方法实现了强语义的exactly-once和事务性。

这篇文章将讲解kafka中exactly-once和事务操作的原理,具体为

(1)exactly-once在kafka中的定义。
(2)数据生产者“幂等操作”,kafka的事务性以及exactly-once实现原理。
(3)exactly-once的流处理。
1. 什么是恰好一次exactly-once
exactly-once定义为: 不管在处理的时候是否有错误发生,计算的结果(包括所有所改变的状态)都一样。

所以,在计算的时候如果发生了一个错误,系统重新计算,重新计算的结果和没有错误发生所得到的结果是一样的,因为这些计算操作是“恰好一次的”。这有另外一个专业术语:“幂等操作”。

为什么exactly-once那么重要呢?(1)在流处理操作中,很多应用场景必须需要“恰好一次”的支持。比如生活着有一个很重要的使用场景:在转账给朋友的时候,用户只希望一次转账,如果不支持“恰好一次”,那么就无法保障在违背用户本意的情况下重复转账。(2)对于kafka而言,其是流处理平台的核心部件,因为kafka通常作为公司内部的消息系统中间件,是其他系统的消息传输的桥梁。(3)支持exactly-once操作可以解锁更过的应用,比如金融行业应用。

使用Kafka进行流处理通常主要包含三个步骤:根据topic读取数据 - 流操作 - 将结果保存到指定的topic下。Kafka的流处理支持无状态的流操作(stateless)和有状态的流操作(stateful),无状态的意思是流处理的时候只需要针对某一条消息进行处理,结果只受到这条消息的影响,比如在每一条消息后面追加字符“a”;有状态指的是在消息处理的时候需要保存前后多条消息的相关信息,结果受到多条消息的影响,比如count,average操作。所以有状态操作更加强大但是实现起来更加困难,特别是当它也支持“恰好一次”的时候。

如果不支持exactly-once操作,那么可能出现下面的错误:
(1)重复写入。下图所示左边为输入数据,中间为数据处理,右边是结果写入。现在计算出了结果并且成功写入,但是由于某些原因,系统没有正确识别成功写入结果这个信号,所以系统重试了,这样就导致了下面第二张图所示的结果:也就是计算结果重复写入。

(2)计算状态被多次更新。
如下图所示,箭头所指的是一个有状态的操作(前面已经讲到无状态操作和有状态操作),第一次计算的时候更新了该处的状态。那么如果因为某些原因第一次的计算有问题需要重新计算,箭头所指的状态会被再次更新,从而导致最终的计算结果不正确。因为正确的计算是状态只被更新一次。这里所说的状态似乎有一点抽象,举一个例子,在统计操作中,count++,可以表示成一个状态,每次来一个数据,就增加1个量。

(3)重复读入
第一个数据已经顺利读取,处理和结果写入,但是由于数据读取的原因,系统没有正确识别到第一次数据的读取,所以再次读取了相同的数据,再次计算并输出结果。此时如下面第二张图所示。这样同样的输入数据就产生了两个结果写入,而且如果中间的流操作是有状态的,这两个结果很可能是不一样的。

上面所阐述的问题,进一步说明了exactly-once的重要性。kafka提供了自己的exactly-once保证。

2. 要么都做,要么都不做
要么都做,要么都不做。做什么呢?体现在:(1)写出所有的计算结果 (计算结果写入到kafka指定的topic中).(2)所有状态的更新。(3)把输入的消息标记为已消费(这里的输入数据理解为kafka的消费者从broker中pull数据)。对上面这三个,kafka使用另一种具有相同语义的方式表示,分别为:(1)将计算结果写入输出topic中(2)把更新操作写入“更新日志changelog”中(注意,操作的状态能够根据“更新日志”进行回滚,类似于MySQL的更新日志,这个有别于普通的系统操作日志)(3)把消费的消息偏移量写入相应的topic中。这也就是Apache Kafka实现“要么都做,要么都不做”和exactly-once的总体设计思路。

具体地,上面阐述关系到三个操作,分别为:

消息生产者提交数据到broker
broker进行消息处理,
消费者消费数据
对于第一点,需要实现幂等操作以及多分区地原子写入。这里的“写入”指的是消息的producer向broker传入消息。这里不多讲“幂等”操作,可以简单理解为同一个消息,producer一次或者多次重复向broker传输,对broker的影响是一样的。多分区原子写入指的是,producer将多条消息一次向broker中的多个partition传输,原子性体现在要么这些消息都成功传入了,要么都没有传入。
在下面第一张图片中展示了kafka实现消息传输的幂等操作的原理。每一条消息除了消息的key和消息的值,还增加了两个字段,分别是producer的ID和一个全局唯一的序列号。这个序列号由broker生成,类似于流水号。在图片中,闪电表示消息的ack失败,消息重传,kafka根据消息的pid和seq来判断这条消息是否已经传过。因为pid和seq也同消息一样存在kafka的patition中的,所以不需要当心丢失问题。要启动kafka的幂等性,无需修改代码,默认为关闭,需要修改配置文件:enable.idempotence=true 同时要求 ack=all 且 retries>1。

对于第二点,需要实现:将模式“消息读入->消息处理->结果写出”作为事务操作,并且整个操作满足exactly-once。所谓的事务操作,也就是这个操作需要满足原子性,完整性,一致性和持久性。kafka在支持事务性的同时也保证了系统性能,这体现在它简单但是高效的设计和实现上面。下面分析kafka实现事务的原理。在下面第一张图片中,左下角表示事务日志,系统存在一个事务锁,在某一个事务开始之前需获取这个锁。左上角的T1,T2表示两个topic,P1和P2表示两个partition,也就是这个事务往两个不同的topic和两个不同的partition上面存储数据。看图片的右上角,第一行代码,首先告诉系统要开始一个事务,接着发送消息到broker相应topic和partition中,所有都正常且完成之后,提交这个事务。所有的这个过程都另外有相应的log。只有成功完成了这个事务之后,消费者才能消费这个事务所提交的消息。

实现事务的回滚需要借助changelogs的帮助,如下面第一张图片所示。changelogs是存储在相应的topic中。

对于第三点,需要实现,kafka消费者只读取已经标记为“成功提交”的数据,这句话隐含了另外一层意思,消息提交的状态有多种,而成功提交只是其中之一。这里的“提交”指的是producer向broker提交的消息。那么什么才能算是成功提交了呢?消息被partition的leader和其所有的follower成功记录了,才能算是成功提交了。成功提交所带来的好处就是不怕断电不怕机器故障,也就是高容错性。下面图片展示了kafka如何解决消费者重复读的问题。(1)消息的消费,(2)消息的处理,(3)把消息的处理结果发送到某一个topic中和(4)把偏移量的发送某一个topic中,它们被放到一个事务中,当所有这些成功之后,才能算是成功。注意到,消费者的偏移量是使用一个producer发送的,也就是把偏移量当成了一种消息在kafka集群中保存起来。这样的话,只要这个事务完成了,那么偏移量也成功保存了。

所以,对应下面第一张图片,不仅仅有changelogs,还有__consumer_offsets

默认情况下kafka的事务是关闭的,通过配置文件开启,需要
transactional.id=“unique-id”, 要求enable.idempotence=true.

启动exactly-once需要配置:processing.guarantee="exactly-once ", 默认是最少一次。

3. 脏数据
脏数据指的是producer把消息数据提交到了broker中,但是它们没有成功,此时这些数据依然存在broker中。为了避免让消费者消费这些脏数据,kafka设置了消息的隔离等级,可以通过配置文件,指定只有成功提交的数据才能被消费。配置为isolation.level=“read_committed”。默认是read_uncommitted

kafka exactly-once的更多相关文章

  1. Spark踩坑记——Spark Streaming+Kafka

    [TOC] 前言 在WeTest舆情项目中,需要对每天千万级的游戏评论信息进行词频统计,在生产者一端,我们将数据按照每天的拉取时间存入了Kafka当中,而在消费者一端,我们利用了spark strea ...

  2. 消息队列 Kafka 的基本知识及 .NET Core 客户端

    前言 最新项目中要用到消息队列来做消息的传输,之所以选着 Kafka 是因为要配合其他 java 项目中,所以就对 Kafka 了解了一下,也算是做个笔记吧. 本篇不谈论 Kafka 和其他的一些消息 ...

  3. kafka学习笔记:知识点整理

    一.为什么需要消息系统 1.解耦: 允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束. 2.冗余: 消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险. ...

  4. .net windows Kafka 安装与使用入门(入门笔记)

    完整解决方案请参考: Setting Up and Running Apache Kafka on Windows OS   在环境搭建过程中遇到两个问题,在这里先列出来,以方便查询: 1. \Jav ...

  5. kafka配置与使用实例

    kafka作为消息队列,在与netty.多线程配合使用时,可以达到高效的消息队列

  6. kafka源码分析之一server启动分析

    0. 关键概念 关键概念 Concepts Function Topic 用于划分Message的逻辑概念,一个Topic可以分布在多个Broker上. Partition 是Kafka中横向扩展和一 ...

  7. Kafka副本管理—— 为何去掉replica.lag.max.messages参数

    今天查看Kafka 0.10.0的官方文档,发现了这样一句话:Configuration parameter replica.lag.max.messages was removed. Partiti ...

  8. Kafka:主要参数详解(转)

    原文地址:http://kafka.apache.org/documentation.html ############################# System ############### ...

  9. kafka

    2016-11-13  20:48:43 简单说明什么是kafka? Apache kafka是消息中间件的一种,我发现很多人不知道消息中间件是什么,在开始学习之前,我这边就先简单的解释一下什么是消息 ...

  10. Spark Streaming+Kafka

    Spark Streaming+Kafka 前言 在WeTest舆情项目中,需要对每天千万级的游戏评论信息进行词频统计,在生产者一端,我们将数据按照每天的拉取时间存入了Kafka当中,而在消费者一端, ...

随机推荐

  1. Ukulele 天空之城

  2. iOS使用技巧---高效使用你的xcode

    推荐一遍好文章:绝对可以学到关于xcode的很多哟 转载自cocoachina: http://www.cocoachina.com/ios/20140731/9284.html

  3. 【数学 裴蜀定理】bzoj2257: [Jsoi2009]瓶子和燃料

    使gcd最大的trick Description jyy就一直想着尽快回地球,可惜他飞船的燃料不够了. 有一天他又去向火星人要燃料,这次火星人答应了,要jyy用飞船上的瓶子来换.jyy的飞船上共有 N ...

  4. activiti工作流使用一般步骤

    activiti工作流使用的一般步骤 一.在eclipse或Myeclipse中安装activiti插件: 二.通过activiti连接数据库,有以下两种连接数据库的形式: 1.通过java代码链接数 ...

  5. grub加密。

    一.介绍 安全无小事  linux系统的安全分为很多方面,什么端口啊,什么网络啊,听着都特么烦,今天谈谈最简单明显的密码安全. 二.单用户模式 单用户模式个人觉得相当有用,可以用来修复系统,修改密码… ...

  6. css实现盒尺寸重置、均匀分布的子元素、截断文本

    盒尺寸重置 重置盒子模型,以便width s和height s并没有受到border 还是padding他们的影响 . CSS文字折断 css实现盒尺寸重置.均匀分布的子元素.截断文本 如何对多行文本 ...

  7. URL链接后面的参数解析,与decode编码解码;页面刷新回到顶部jquery

    function request() { var urlStr = location.search; ) { theRequest = []; return; } urlStr = urlStr.su ...

  8. 剑指Offer(书):二叉树的镜像

    题目:操作给定的二叉树,将其变换为源二叉树的镜像. public void Mirror(TreeNode root) { if (root == null) { return ; } if (roo ...

  9. python基础学习笔记——collections模块

    在内置数据类型(dict.list.set.tuple)的基础上,collections模块还提供了几个额外的数据类型:Counter.deque.defaultdict.namedtuple和Ord ...

  10. python基础学习笔记——深浅拷贝

    2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 lst1 = ["⾦⽑狮王", "紫衫⻰王&qu ...