空学Kafka之二

简介: > 继续上一篇 (空学Kafka之一)[https://www.atatech.org/articles/145913] ## 构建数据通道 ### 考量点 及时性,可靠性,吞吐量,安全性(通道安全,审计等),数据格式的上线兼容,ETL or ELT,统一还是专属(比如GoldenGate是oracle私有的,有很强的耦合性),优先选择Kafka Connect ###

继续上一篇 (空学Kafka之一)[https://www.atatech.org/articles/145913]

构建数据通道

考量点

及时性,可靠性,吞吐量,安全性(通道安全,审计等),数据格式的上线兼容,ETL or ELT,统一还是专属(比如GoldenGate是oracle私有的,有很强的耦合性),优先选择Kafka Connect

深入浅出Connect

连接器插件实现了 Connector API,API 包含了两部分内容。大致上是分而治之的思想,连接器相当于分拆器splittor,任务相当于拆分后的具体执行器executer。

  1. 连接器:负责以下三件事。

    • 决定需要运行多少个任务。
    • 按照任务来拆分数据复制。
    • 从 worker 进程获取任务配置并将其传递下去。
  2. 任务:负责将数据移入或移出 Kafka。

相比较直接采用Kafka的publisher/consumer API来实现数据流入和流出逻辑,Connect做了很多基础性的工作,一方面体现在上面的Connector API的抽象定义和生态中丰富的Connector实现,另一方面体现在worker 进程,负责 REST API、配置管理、可靠性、高可用性、伸缩性和负载均衡,有经验的开发人员都知道,编写代码从 Kafka 读取数据并将其插入数据库只需要一到两天的时间,但是如果要处理好配置、异常、REST API、监控、部署、伸缩、失效等问题,可能需要几个月。如果你使用连接器来实现数据复制,连接器插件会为你处理掉一大堆复杂的问题。同时,提供的偏移量(不单单是Kafka的消费进度,也包含源数据源的进度)跟踪机制简化了连接器的开发工作,并在使用多个连接器时保证了一定程度的行为一致性。

源连接器返回的记录里包含了源系统的分区和偏移量,worker 进程将这些记录发送给 Kafka。如果 Kafka 确认记录保存成功,worker 进程就把偏移量保存下来。偏移量的存储机制是可插拔的,一般会使用 Kafka 主题来保存(又是自己吃自己的狗粮,不过,用一个topic保存进度是不是有点奢侈?)。如果连接器发生崩溃并重启,它可以从最近的偏移量继续处理数据。

跨集群数据镜像

想到了阿里由HSF构造的分布式集群其实是一个大集群,即便现在机房分布在深圳,张北的情况下仍然是统一大集群,也就是没有做到本机房内集群的收敛——这也多亏了Configserver等基础服务可以支撑百万集群,本机房调用是非默认的行为。从服务治理角度看,本人觉得同城内为一集群更合理,跨机房的服务集成调用需要特殊的治理管控。

跨集群镜像的应用场景

  • 区域集群和中心集群:跨DC的数据同步
  • 冗余Disaster Recovery:集群灾备
  • 云迁移:混合云的情形,自有数据中心与云服务的集群同步

多集群架构

现实限制包含高延迟(技术上最大的问题),宽带有限,公网宽带费用带来的高成本,考虑以上限制因素,需要给定以下架构原则。

  • 每个数据中心至少需要一个集群。
  • 每两个数据中心之间的数据复制要做到每个事件仅复制一次(除非出现错误需要重试)。
  • 如果有可能,尽量从远程数据中心读取数据,而不是向远程数据中心写入数据。

Hub和Spoke架构

这种架构适用于一个中心 Kafka 集群对应多个本地 Kafka 集群的情况,如下图所示。这种架构的好处在于,数据只会在本地的数据中心生成,而且每个数据中心的数据只会被镜像到中央数据中心一次。只处理单个数据中心数据的应用程序可以被部署在本地数据中心里,而需要处理多个数据中心数据的应用程序则需要被部署在中央数据中心里。因为数据复制是单向的,而且消费者总是从同一个集群读取数据,所以这种架构易于部署、配置和监控。不足是一个数据中心的应用程序无法访问另一个数据中心的数据。
image.png

双活(active-active)架构

image.png
这种架构的主要好处在于,它可以为就近的用户提供服务,具有性能上的优势,而且不会因为数据的可用性问题(在 Hub 和 Spoke 架构中就有这种问题)在功能方面作出牺牲。第二个好处是冗余和弹性。因为每个数据中心具备完整的功能,一旦一个数据中心发生失效,就可以把用户重定向到另一个数据中心。这种重定向完全是网络的重定向,因此是一种最简单、最透明的失效备援方案。这种架构的主要问题在于,如何在进行多个位置的数据异步读取和异步更新时避免冲突。比如镜像技术方面的问题——如何确保同一个数据不会被无止境地来回镜像?而数据一致性方面的问题则更为关键。

如果能够很好地处理在从多个位置异步读取数据和异步更新数据时发生的冲突问题,那么我们强烈建议使用这种架构。这种架构是我们所知道的最具伸缩性、弹性、灵活性和成本优势的解决方案。所以,它值得我们投入精力去寻找一些办法,用于避免循环复制、把相同用户的请求粘在同一个数据中心,以及在发生冲突时解决冲突。双活镜像(特别是当数据中心的数量超过两个)的挑战之处在于,每两个数据中心之间都需要进行镜像,而且是双向的。如果有 5 个数据中心,那么就需要维护至少 20 个镜像进程,还有可能达到 40 个,因为为了高可用,每个进程都需要冗余。

主备(active-standby)架构

image.png
简单容易实施,但浪费一个集群。

MirrorMaker

MirrorMaker完全是无状态的,收后再转发有点代理的意思:性能在于线程模型的设计。
image.png
尽量让 MirrorMaker 运行在目标数据中心里。也就是说,如果要将 NYC 的数据发送到 SF,MirrorMaker 应该运行在 SF 的数据中心里。因为长距离的外部网络比数据中心的内部网络更加不可靠,如果发生了网络分区,数据中心之间断开了连接,那么一个无法连接到集群的消费者要比一个无法连接到集群的生产者要安全得多。如果消费者无法连接到集群,最多也就是无法读取数据,数据仍然会在 Kafka 集群里保留很长的一段时间,不会有丢失的风险。相反,在发生网络分区时,如果 MirrorMaker 已经读取了数据,但无法将数据生成到目标集群上,就会造成数据丢失。所以说,远程读取比远程写入更加安全。
**如果跨数据中心流量需要加密,那么最好把 MirrorMaker 放在源数据中心,让它读取本地的非加密数据,然后通过 SSL 连接将数据生成到远程的数据中心。这个时候,使用 SSL 连接的是生产者,所以性能问题就不那么明显了。在使用这种方式时,需要确保 MirrorMaker 在收到目标 broker 副本的有效确认之前不要提交偏移量,并在重试次数超出限制或者生产者缓冲区溢出的情况下立即停止镜像。

流式处理

何谓流数据(streaming Data 或 数据流)

  • 无边界:首先,数据流是无边界数据/事件集的抽象表示。无边界意味着无限和持续增长。
  • 有序的:事件的发生总是有个先后顺序。
  • 不可变:事件一旦发生,就不能改变——时光不可倒流,每个事件都相当于一个DB事务(ACID)
  • 可重播:这是事件流非常有价值的一个属性。用户可以很容易地找出那些不可重播的流(流经套接字的 TCP 数据包就是不可重播的),但对于大多数业务来说,重播发生在几个月前(甚至几年前)的原始事件流是一个很重要的需求。可能是为了尝试使用新的分析方法纠正过去的错误,或是为了进行审计。这也就是为什么我们相信 Kafka 能够让现代业务领域的流式处理大获成功——可以借助 Kafka 来捕捉和重播事件流。如果没有这项能力,流式处理充其量只是数据科学实验室里的一个玩具而已。

这里额外提一下理念:prefer Event(已发生的确定的事实) over Command

流式处理的基本点

  • 时间:最核心最基本的概念(想起以前做蚂蚁财富的基金系统的时间也是无处不在,各种类型的时间在眼前飘。。。),包含多种类型:事件发生时间,日志追加时间,处理时间,同时注意时区问题。
  • 状态:事件与事件之间的信息被称为“状态”。这些状态一般被保存在应用程序的本地变量里。例如,使用本地散列表来保存移动计数器。不过,这不是一种可靠的方法,因为如果应用程序关闭,状态就会丢失,结果就会发生变化,而这并不是用户希望看到的。所以,要小心地持久化最近的状态,如果应用程序重启,要将其恢复。
  • 流与表的二元性:其实可以看看event sourcing和CQRS的一些架构思想和具体实践来理解这块;表-->>流,需要捕捉到在表上所发生的变更,将“insert”、“update”和“delete”事件保存到流里,即CDC(Change Data Capture);流-->>表,需要“应用”流里所包含的所有变更,这也叫作流的“物化”。首先在内存里、内部状态存储或外部数据库里创建一个表,然后从头到尾遍历流里的所有事件,逐个地改变状态。在完成这个过程之后,得到了一个表,它代表了某个时间点的状态,即materialized veiw。
  • 时间窗口:窗口大小、变化窗口、可更新的时间长度等(想起来HSF的TPS限流规则实现,TPS限流也算是时间窗口敏感的类似场景,我是觉得联想到以前类似的场景就会更加容易理解透新的场景);滚动窗口与跳跃窗口(HSF tps限流规则是滚动来刷新token数)

流处理的设计模式

  • 单事件处理:无状态,单纯计算逻辑,比如filter,map,execute等。(Event-Driven Microservice这种基于pub-sub的消息驱动其实就是最简单最常见的流处理形态;以前认为ESB这种模式的问题在于依赖一个消息中心,具有单点问题,所以相比HSF这类的SOA架构要落后,现在再看以前的认识是片面的;在延展一下想到阿里现在的中台,曾经参加过一关于星环的测试,真是复杂,高大上的无厘头的高度抽象,如果有了数据总线/数据中台,很多时候可能自建系统会更加高效且稳定,比当前的中台还要中台——中台不就是为了效率么?难道是为了利益和底盘?比如蚂蚁的paycore/tradecore就很容易调用他们的API来集成,而二级域都有自己对应的下单流水系统来控制各业务的状态流转。新加:在了解了kafka的事务性语义后,Kafka的事务和RocketMQ/Notify的事务消息还真不是一回事,差别很大,前者是Stream处理中的consumer-transform-produce或者跨分区发送消息时的原子性,后者是以业务为确定事件后消息可靠性,所以相比较而言,RocketMQ更适合交易系统类的消息集成场景,如果非要选择Kafka作为交易场景的事务性消息,就需要先落业务DB,完成业务的事务性提交,Kafka通过Connect+Kafka的事务性语义消费DB binlog事务来驱动下游系统。)
  • 使用本地状态:每一个操作都是基于组的聚合操作,例如,基于各个股票代码进行聚合,而不是基于整个股票市场。我们使用了一个 Kafka 分区器来确保具有相同股票代码的事件总是被写入相同的分区。应用程序的每个实例从分配给它们的分区上获取事件(这是 Kafka 的消费者保证)。也就是说,应用程序的每一个实例都可以维护一个股票代码子集的状态。如下图所示。需要解决下列的一些问题。

    • 内存使用:应用实例必须有可用的内存来保存本地状态。
    • 持久化:要确保在应用程序关闭时不会丢失状态,并且在应用程序重启后或者切换到另一个应用实例时可以恢复状态。Streams 可以很好地处理这些问题,它使用内嵌的 RocksDB 将本地状态保存在内存里,同时持久化到磁盘上,以便在重启后可以恢复。本地状态的变更也会被发送到 Kafka 主题上。如果 Streams 节点崩溃,本地状态并不会丢失,可以通过重新读取 Kafka 主题上的事件来重建本地状态。例如,如果本地状态包含“IBM 当前最小价格是 167.19”,并且已经保存到了 Kafka 上,那么稍后就可以通过读取这些数据来重建本地缓存。这些 Kafka 主题使用了压缩日志,以确保它们不会无限量地增长,方便重建状态。
    • 再均衡:有时候,分区会被重新分配给不同的消费者。在这种情况下,失去分区的实例必须把最后的状态保存起来,同时获得分区的实例必须知道如何恢复到正确的状态。

image.png

  • 多阶段处理和重分区:这种多阶段处理对于写过 Map-Reduce 代码的人来说应该很熟悉,因为他们经常要使用多个 reduce 步骤。如果写过 Map-Reduce 代码,就应该知道,处理每个 reduce 步骤的应用需要被隔离开来。与 Map-Reduce 不同的是,大多数流式处理框架可以将多个步骤放在同一个应用里,框架会负责调配每一步需要运行哪一个应用实例(或 worker)。 ——( 想起来了以前做基金文件的处理模式:Split-》Execute-》merge-》finalize,这一模式相比就是多了聚合。)

 image.png

  • 流与表的结合:这种方式最大的问题在于,外部查找会带来严重的延迟,一般在 5~15ms 之间。这在很多情况下是不可行的。另外,外部数据存储也无法接受这种额外的负载——流式处理系统每秒钟可以处理 10~50 万个事件,而数据库正常情况下每秒钟只能处理 1 万个事件,所以需要伸缩性更强的解决方案。为了获得更好的性能和更强的伸缩性,需要将数据库的信息缓存到流式处理应用程序里。不过,要管理好这个缓存也是一个挑战。比如,如何保证缓存里的数据是最新的?如果刷新太频繁,那么仍然会对数据库造成压力,缓存也就失去了作用。如果刷新不及时,那么流式处理中所用的数据就会过时。所以有了通过Connect的CDC来刷新缓存,如下图。

image.png

  • 基于时间窗口的流与流的连接:需要考虑时间窗口
  • 重新处理:需要考虑是否需要重置状态或者新开应用分组

Kafka Stream

  • 优势:类库形式与业务应用部署在一起,相比Spark,flink,Storm等无需额外的依赖按照和额外的调度服务。想到了蚂蚁的三层调度框架(小而美,解决了大部分的批处理问题),让复杂的事情简单了。
  • 伸缩性和面向故障容错能力是基于Kafka的分区特性,Consumer的自动负载均衡特性,这些使得Kafka天然具备数据流平台的先天基础。
  • Kafka Stream API用起来感觉就像JDK8中的Stream接口一样
目录
相关文章
|
3月前
|
消息中间件 存储 关系型数据库
Kafka 的基本概念
Kafka 的基本概念
58 0
|
4月前
|
消息中间件 存储 Kafka
kafka基础概念二
【8月更文挑战第11天】
42 6
|
7月前
|
消息中间件 缓存 关系型数据库
探究Kafka原理-1.初识Kafka
探究Kafka原理-1.初识Kafka
71 0
|
7月前
|
消息中间件 存储 分布式计算
Apache Kafka-初体验Kafka(01)-入门整体认识kafka
Apache Kafka-初体验Kafka(01)-入门整体认识kafka
82 0
|
消息中间件 缓存 大数据
Kafka学习---1、Kafka 概述、Kafka快速入门
Kafka学习---1、Kafka 概述、Kafka快速入门
Kafka学习---1、Kafka 概述、Kafka快速入门
|
消息中间件 存储 监控
【Kafka从入门到放弃系列 一】Kafka概述及基本架构
【Kafka从入门到放弃系列 一】Kafka概述及基本架构
112 0
|
消息中间件 存储 负载均衡
【Kafka从入门到放弃系列 零】Kafka看这一篇就够了(一)
【Kafka从入门到放弃系列 零】Kafka看这一篇就够了
292 0
|
消息中间件 负载均衡 监控
【Kafka从入门到放弃系列 零】Kafka看这一篇就够了(三)
【Kafka从入门到放弃系列 零】Kafka看这一篇就够了(三)
162 0
|
消息中间件 存储 缓存
【Kafka从入门到放弃系列 零】Kafka看这一篇就够了(二)
【Kafka从入门到放弃系列 零】Kafka看这一篇就够了
234 0
|
消息中间件 存储 缓存
【kafka】浅谈kafka常考特性
【kafka】浅谈kafka常考特性
313 2
【kafka】浅谈kafka常考特性