前段时间跟一个朋友聊起kafka,flint,spark这些是不是某种分布式运算框架。我自认为的分布式运算框架最基础条件是能够把多个集群节点当作一个完整的系统,然后程序好像是在同一台机器的内存里运行一样。当然,这种集成实现方式有赖于底层的一套消息系统。这套消息系统可以把消息随意在集群各节点之间自由传递。所以如果能够通过消息来驱动某段程序的运行,那么这段程序就有可能在集群中任何一个节点上运行了。好了,akka-cluster是通过对每个集群节点上的中介发送消息使之调动该节点上某段程序运行来实现分布式运算的。那么,kafka也可以实现消息在集群节点间的自由流通,是不是也是一个分布式运算框架呢?实际上,kafka设计强调的重点是消息的接收,或者叫消息消费机制。至于接收消息后怎么去应对,用什么方式处理,都是kafka用户自己的事了。与分布式运算框架像akka-cluster对比,kafka还缺了个在每个集群节点上的”运算调度中介“,所以kafka应该不算我所指的分布式运算框架,充其量是一种分布式的消息传递系统。实际上kafka是一种高吞吐量、高可用性、安全稳定、有良好口碑的分布式消息系统。

kafka的本质是一种commit-log,或者“事件记录系统”:上游产生的数据(即事件)会按发生时间顺序存入kafka,然后下游可以对任何时间段内事件按序进行读取,重演运算产生那段时间内的某种状态。这不就是妥妥的CQRS模式吗?当然kafka也可以使用在其它一些场景如:消息队列,数据存储等,不过这些都是commit-log的具体应用。

常常看到网上有朋友抱怨akka-cluster的一些处理方式太底层或太基础了。用户往往需要自己来增加一些方法来确保使用安全。我想作为一种消息驱动系统,如何保证akka消息的正确产生和安全使用应该是最基本的要求。而恰恰akka是没有提供对消息遗漏和重复消息的保障机制。我想这也是造成akka用户担心的主要原因。上面提到kafka是一种高吞吐量、高可用性、安全稳定的分布式消息系统,特别是它提供了对exactly-once,“保证一次”的消息使用支持。那么通过kafka实现一套CQRS模式的实时交易处理系统应该是可行的。这也是我使用kafka的主要目的。

上面提到,希望能充分利用kafka commit-log特性来开发一个基于CQRS的实时交易系统,比如支付系统、库存管理系统,从实践中了解kafka。kafka支持多种语言终端,怪异的是没有scala终端。kafka是用scala开发的,不提供scala终端实在是说不通啊。不过akka在alpakka社区提供了alpakka-kafka:这个东西是个基于akka-streams的kafka scala终端编程工具,稍微过了一下,感觉功能比较全面,那就是它了。不过在开始前先把kafka的原理和基本情况做个介绍:

从表面上看kafka就是一个简单的消息存储和传递工具。不过因为其特殊分布式的消息发布、存储、读取处理机制,使其成为一种高吞吐量、高可用性、安全稳定的分布式消息处理工具。从应用角度来讲,kafka应用包括三个方面,kafka本身,就叫kafka引擎吧,发布终端、订阅终端,即:kafka,writer,reader三部分,其中:所有复杂的功能实现是包嵌在kafka内部的,writer,reader应该整合到用户应用里。kafka的作业是围绕着消息的发布订阅/读写进行的。所谓消息即CQRS模式里的事件。那么kafka的工作原理直白点就是writer向kafka写事件,kafka把事件按发生时间顺序保存,reader再按顺序从kafka读取事件并进行处理以产生新的业务状态,如在某个库位的一个商品数量得到了更新。当然原理看似简单,但具体的实现才是真正复杂的地方。

首先,writer和reader是以事件关联的,即:write发布某种类型的事件,而reader则是订阅相同类型的事件。 这里的事件也就是topic,或一项业务,如:图书类当前库存。为了提高数据吞吐量,每个topic又可以细分为多个partition。每个partition分担所属topic消息类型下的一些指定的细分类消息或者事件,如"图书库房101"。如果把这些partition再分布到一个集群的节点上,就可以实现高吞吐量的分布式读写,然后通过集群partition的复本同步又可以达到数据安全及系统高可用性的目的。这些集群节点就是所谓的broker了。发布消息内容由topic,key,value所组成。其中key值指定该消息应该写入那个partition,即通过对key进行hash计算得出partition id。hash算法可以保证相同的key值永远指定同一个partition。值得注意的是kafka保证每个partition上的事件肯定按照发生时间排序,所以要保证一种事件只能写入同一个partition。当然,一个partition可以承载多种事件。要注意的是创建topic和partition都是严格的管理工作admin,不是在某些程序中任意进行增减的。一般来讲,在创建一个新topic时就要确定它下面的partition数量了。这个partition数量要按照对数据吞吐量需求设定。但一般是集群节点的倍数,这样partition可以均匀分布在各broker上。

好了,该到reader这头了:reader作业从订阅某个topic开始。上面提过:一个topic下面可能有多个partition,但每个partition都会包含topic的其中几个子业务的全部事件,而且这些事件是严格按发生时间排序的。kafka有个reader group这么个概念:针对同一个topic,容许有一组多个reader对这个topic下的partition进行读取。但每个partition只容许组内一个reader读取。至于goup内reader是如何分配partition的完全由kafka内部解决。如果发现新partition或者组内reader有增减变化,kafka会自动进行再分配rebalance。所以总的来说订阅某个topic的一个组内reader应该负责那个partition是不确定的,加上随时可能发生动态再分配的情况,比如组内某个reader出问题倒了。换言之组内所有reader都必须具备处理整个topic所有类型业务的能力,如此才能解决组内reader-partition关系不确定的难题。kafka最重要的特点就是可以容许不同的应用通过不同的reader-group对同一个partition上的事件进行任意读取,本意应该是不同的应用可以利用同一个业务事件序列进行不同的业务处理。具体实现方式应该是每个组对某个partition上事件最后读取的位置分别进行了登记,offset-commit。这样,即使发生了重新分配rebalance组内任何一个reader对分配到的partition应从那个位置开始读还是确定的。这个offset-commit方式描述了几种事件读取模式:

1、at-most-once, 最多一次:如果刚读取事件,在进行业务处理之前就登记位置commit-offset,那么commit-offset后位置已经登记,即使业务处理失败也再也不可能二次读取了。

2、at-least-once,最少一次:读取事件、完成业务处理后才commit-offset。如果处理业务中系统故障,只能从上次登记的位置重新读取了,那么就会出现重复读取的情况。

3、exactly-once, 保证只一次:控制commit-offset的时间节点是取得at-most-once, at-least-once之间安全系数的一种方式。但exactly-once不容许有模糊地带。具体做法是把业务处理和commit-offset作为一个完整事物单元来处理(atomic-transaction)。两样操作同时成功或失败。

我觉着kafka的exactly-once能力最值得推介。因为在akka或者其它消息队列工具里不容易得到保证。而在一个消息驱动的实时交易系统里,保证事件重演能正确反映当时状态是关键。任何事件遗失或重复都会造成不可逆转的误差。那么下面的一系列讨论我就会尝试用alpakka-kafka来构建一个基于CQRS模式的实时交易系统,并和大家进行交流分享。

kakafka - 为CQRS而生fka - 为CQRS而生的更多相关文章

  1. CQRS+ES项目解析-Diary.CQRS

    在<当我们在讨论CQRS时,我们在讨论些神马>中,我们讨论了当使用CQRS的过程中,需要关心的一些问题.其中与CQRS关联最为紧密的模式莫过于Event Sourcing了,CQRS与ES ...

  2. .net架构设计读书笔记--第三章 第10节 命令职责分离(CQRS)简介(Introducing CQRS)

    一.分离查询命令 Separating commands from queries     早期的面向DDD设计方法的难点是如何设计一个类,这个类要包含域的方方面面.通常来说,任务软件系统方法调用可以 ...

  3. Web API与OAuth:既生access token,何生refresh token

    在前一篇博文中,我们基于 ASP.NET Web API 与 OWIN OAuth 以 Resource Owner Password Credentials Grant 的授权方式( grant_t ...

  4. CQRS学习——最小单元的Cqrs(CommandEvent)[其一]

    [说明:博主采用边写边思考的方式完成这一系列的博客,所以代码以附件为准,文中代码仅为了说明.] 结构 在学习和实现CQRS的过程中,首要参考的项目是这个[http://www.cnblogs.com/ ...

  5. CQRS:When to use CQRS

    Now that we are at the end of our journey, we can suggest some of the criteria you should evaluate t ...

  6. 后端开发实践系列之四——简单可用的CQRS编码实践

    本文只讲了一件事情:软件模型中存在读模型和写模型之分,CQRS便为此而生. 20多年前,Bertrand Meyer在他的<Object-Oriented Software Constructi ...

  7. CQRS项目

    CQRS+ES项目解析-Diary.CQRS   在<当我们在讨论CQRS时,我们在讨论些神马>中,我们讨论了当使用CQRS的过程中,需要关心的一些问题.其中与CQRS关联最为紧密的模式莫 ...

  8. .NET 5 源代码生成器——MediatR——CQRS

    在这篇文章中,我们将探索如何使用.NET 5中的新source generator特性,使用MediatR库和CQRS模式自动为系统生成API. 中介者模式 中介模式是在应用程序中解耦模块的一种方式. ...

  9. DDD CQRS架构和传统架构的优缺点比较

    明天就是大年三十了,今天在家有空,想集中整理一下CQRS架构的特点以及相比传统架构的优缺点分析.先提前祝大家猴年新春快乐.万事如意.身体健康! 最近几年,在DDD的领域,我们经常会看到CQRS架构的概 ...

随机推荐

  1. scrapy爬取豆瓣电影信息

    最近在学python,对python爬虫框架十分着迷,因此在网上看了许多大佬们的代码,经过反复测试修改,终于大功告成! 原文地址是:https://blog.csdn.net/ljm_9615/art ...

  2. springboot配置定时任务并发执行

    @Configuration public class ScheduleConfig implements SchedulingConfigurer { @Override public void c ...

  3. python接口自动化测试--数据分离读取Excal指定单元格数据

    上一篇博客讲了怎么批量读取Excal单元格数据,现在咱们说一下怎么读取Excal指定单元格数据. 一.首先建一个Test_Main类 #!/usr/bin/python # -*- coding: U ...

  4. 通过Python提取10000份log中的产品数据

    一.背景 协助产品部门在10000份产品log信息中提取产品的SN号.IMEI号.ICCID号到Excel表格中. 1.l原始的og内容: 2.提取后的Excel表格: 二.实现 1.思路 a.for ...

  5. 【总结】spring aop

    1.aop简介 AOP的全称是Aspect Oriented Programming,面向切面编程.它的主要思想是在程序正常执行的某一个点切进去加入特定的逻辑.AOP框架中对AOP支持最完整的是Asp ...

  6. ffmpeg 部分api delphi 版

    ffmpeg 是一套强大的开源的多媒体库  一般都是用 c/c++ 调用, 抽空研究了一下该库的最新版 ,把部分api 翻译成了dephi版的 记录一下 地址   ffmpegvcl.zip

  7. 妙用 Intellij IDEA 创建临时文件,Git 跟踪不到的那种

    | 好看请赞,养成习惯 你有一个思想,我有一个思想,我们交换后,一个人就有两个思想 If you can NOT explain it simply, you do NOT understand it ...

  8. 给萌新HTML5 入门指南(二)

    本文由葡萄城技术团队原创并首发 转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 上一篇我们已经为大家介绍了HTML5新增的内容和基础页面布局,这篇会继续向大 ...

  9. 手写Koa.js源码

    用Node.js写一个web服务器,我前面已经写过两篇文章了: 第一篇是不使用任何框架也能搭建一个web服务器,主要是熟悉Node.js原生API的使用:使用Node.js原生API写一个web服务器 ...

  10. 获取List集合对象中某一列属性值

    例:获取disposeList集合中CorpusMarkPage对象中的responseId属性,生成新的List集合 List<String> responseIdList = disp ...