前段时间跟一个朋友聊起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. react-native-image-picker用法

    1, 首先,安装下该插件. npm install react-native-image-picker@latest --save 2,自动安装(做了这一步 下面安装的平台设置大部分都自动添加好了) ...

  2. GPRS DTU的工作原理和应用场景有哪些

    GPRS DTU是属于物联网无线数据终端设备的中一种,它主要是利用公用运营商的GPRS网络(又称G网)来为用户提供无线长距离数据传输的功能.一般都是采用的高性能工业级8/16/32位通信处理器和工业级 ...

  3. 记EF的一个基本访问类

    代码: 1 using EFModel; 2 using System; 3 using System.Collections.Generic; 4 using System.Data.Entity; ...

  4. Codeforce算法题 | 你能想出解法,让你的基友少氪金吗?

    在TechFlow学长的公众号里发现一道挺有意思的CF算法题,现在利用学长的思路学习一下 题目链接:https://codeforces.com/contest/1418/problem/C 题意 这 ...

  5. 【SpringBoot】01.创建Springboot项目及启动器

    创建Springboot项目及启动器 1.创建一个简单maven项目 SpringBoot2.0以下需要使用JDK1.7 ,2.0以上使用JDK1.8 如果需要修改JDK的版本需要打开pom文件: & ...

  6. PHP博客

    创建数据库 用户表 blog_user userid int 用户id username varchar(50) 用户名 password varchar(30) 密码 type tinyint(2) ...

  7. Hadoop调优 | NameNode主备宕机引发的思考

    大家都知道在双十一这些电商大型营销活动期间,电商网站的访问量等是平时的N倍.每当这个时候到来,无论是开发还是运维人员都严阵以待生怕服务出现问题.很不幸,笔者的一个朋友在一家电商公司上班,在双十一时,恰 ...

  8. JavaSE基础语法学习

    标识符 表示类名的标识符用大写字母开始. 表示方法和变量的标识符用小写字母开始,后面的描述性词以大写开始. 注意 所有的标识符都应该以字母(A-Z 或者 a-z),美元符($).或者下划线(_)开始 ...

  9. leetcode6:binary-tree-postorder-traversal

    题目描述 求给定的二叉树的后序遍历. 例如: 给定的二叉树为{1,#,2,3}, 1↵ ↵ 2↵ /↵ 3↵ 返回[3,2,1]. 备注:用递归来解这道题太没有新意了,可以给出迭代的解法么? Give ...

  10. 天啦噜!仅仅5张图,彻底搞懂Python中的深浅拷贝

    Python中的深浅拷贝 在讲深浅拷贝之前,我们先重温一下 is 和==的区别. 在判断对象是否相等比较的时候我们可以用is 和 == is:比较两个对象的引用是否相同,即 它们的id 是否一样 == ...