本文为分布式系列文章的集锦汇总,长期保持置顶及更新,读者可以在本文中更好的学习到某个具体的系列。

我说分布式事务系列

文章链接
我说分布式事务之TCC
我说分布式事务之最大努力通知型事务
我说分布式事务之可靠消息最终一致性事务1-原理及实现
我说分布式事务之消息一致性事务2-rocketmq的实现
【汇总】我说分布式事务系列
分布式事务之聊聊TCC
分布式事务最终一致性常用方案

跟我学RocketMQ

文章链接
[1-1]安装RocketMQ
[1-2]安装RocketMQ-Console管理平台
[1-3]发送普通消息及封装DefaultMQProducer支持spring
[1-4]消费消息及封装DefaultMQPushConsumer支持spring
[1-5]发送事务消息实现分布式事务及封装TransactionMQProducer支持spring
[1-6]跟我学RocketMQ之消息重试
[1-7]跟我学RocketMQ之消息幂等

Read More

本文主要介绍Kafka的消息持久化策略及副本机制。

Kafka消息持久化策略

首先简要说明一下Kafka持久化消息的优点:

  1. Kafka通过消息持久化解耦了消息的生产者和消费者,这也是采用队列的优势,使得生产者和消费者均只需要关心自己的逻辑,而不需要直接感知到彼此的存在。
  2. Kafka支持对消费过的消息进行“消息重演(Message Replay)”,而重演的基础就是实现消息持久化。

Kafka为了保证消息落盘的实时,确保消息不丢失,没有采用直接写内存,等写满后一次性刷盘的策略,而是将数据立即写入文件系统的日志中,写入成功后才将结果返回给客户端告知客户端–消息已经成功写入。这么做的目的是,一方面实时地保存了数据,另一方面又减少了对内存的消耗,将内存空间尽量留给页缓存使用,从而提升了整体的性能。

Read More

提升Kafka对消息处理的吞吐量及低延时主要通过磁盘顺序写、零拷贝(zero copy)以及利用页缓存(Page Cache)实现。下面我们进行具体说明。

消息顺序写

Kafka的消息存储在每次写入时,只是将数据写入到操作系统的页缓存(PageCache)中,最终是由操作系统决定何时将页缓存中的数据落盘的。这样做的好处如下:

  1. 由于页缓存是OS在内存中分配的,因此消息写入速度很快;
  2. 由于Kafka将消息写入页缓存中,因此避免了直接与底层文件系统打交道时候的繁琐流程,所有的I/O操作均交给了操作系统进行处理;
  3. Kafka写操作采用了append方式(即:追加写入),这种顺序写盘的方式速度很快,避免了因随机写而导致的写入效率低下。

Read More

本文是新系列“研磨”系列的开篇,该系列主要立足源码分析、核心技术点分析、常见问题整理等方面。一篇文章围绕一个中间件,立足于讲清、讲透、讲明白。

系列的第一部分主题为–“研磨Kafka”,本篇中,我们先从宏观的视角窥探Kafka的核心角色,并对后文中要讲到的主题做一个宏观的概述。

Kafka的设计原理及结构概述

首先对Kafka重要的角色进行总结。

要点 解释
Kafka概述 Kafka是一款分布式消息中间件,但它不符合JMS规范。
消费特点 Kafka消费消息成功,不会马上删除消息。消息存储一段时间后,会被批量删除。
Broker Broker是Kafka的服务端,主要作用为接收消息并对消息进行持久化存储。
Broker可以有多个,每一个Broker节点可存储多个Topic。但是Broker本身并不会存储消费的Offset(即消费者消费消息的位置),Offset数据由Consumer(消费者)保存,保存位置为Zookeeper。
Topic Topic为消息主题,它是生产者与消费者之间进行消息传递的依据。单个Topic下包括多个Partition,它的所有元数据存储在zookeeper中。
Partition Partition即分区,Kafka为提高性能及扩展性,将一个Topic分为多个分区,及Partition,每个Partition均可独立存放在一个Broker上,这保证了Kafka的可用性及稳定性。
Producer Producer为消息生产者,它一般是集成了Kafka客户端的业务应用。
Producer负责发送消息到Broker。Producer直连Broker,具有往Topic下发布消息的能力。
它会与Topic下的所有PartitionLeader保持Socket长连接。Producer具有同步、异步两种消息发送能力,且Producer支持消息的批量发送,即将多条消息缓存在客户端,在达到指定的时间延迟或者消息数量后,批量地提交给Broker。
Consumer Consumer为消息消费者,它一般是集成了Kafka客户端的业务应用。消费者向Broker订阅Topic,并从Topic中接收消息。
每一个消费者均属于某一个消费者组,且同消费者组中的消费者订阅同一个Topic,同一个Topic下的不同消费者分别订阅该Topic下不同Partition的数据。一个消费者可以订阅多个Partition,但同一个Partition只能被一个消费者订阅,该策略的目的是一定程度上避免消息被重复消费。
可以通过增加Partition的方式对消费能力进行横向扩展。
当出现某个消费者down机,消费过程会进行 Rebalance

研磨Kafka主题

该系列后续会涉及到的要点,在此处进行整理

要点
如何提升Kafka的吞吐量及低延时
Kafka的消息持久化方式
Kafka如何实现负载均衡及故障转移
如何理解Kafka的“伸缩性”特点
概览主题(Topic)与分区(Partition)
解释什么是消息位移offset
解释下Kafka的副本机制如何实现
Kafka如何利用“ISR机制”保证消息不丢失?
kafka是否存在消息丢失的情况?
概览Kafka的使用场景

首先了解下何为SPI,这里引用Dubbo官方对SPI的解说,比较全面。SPI简介

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。

Dubbo本身实现了对JDK的SPI的扩展,为了能够更好的理解Dubbo的SPI机制,我们需要理解JDK的原生SPI原理。

从概念可以看出,SPI机制是一种服务发现机制,能够在运行时动态的增加、获取服务实现。个人感觉很像设计原则中的里氏替换原则。

我将通过一个实例来讲解如何对JDK的SPI进行运用。

Read More

考虑到部分业务场景下没有使用RocketMQ,因此我在之前的基础上添加了对单机模式调度的封装。

本文就单机模式下,不依赖RocketMQ如何使用shield-job进行定时任务调度展开讲解。

目前有两种主要的调用方式,客户端实现抽象调度方法 execute ,或者直接使用内置的 executeOneway 方法进行调用,接下来分别介绍两种方式。

Read More

自己写分布式调度组件继续更新,目前已经完成了一个里程碑版本。

千呼万唤始出来,却不是当初想的模样。之前立的flag太大,最终决定暂时放弃开发分布式版本,把目标改为基于消息队列RocketMQ的任务分发框架,具体的调度逻辑由调用方自行开发。

客户端接口

首先介绍客户端需要关注的接口以及实体

客户端实体–任务实体BaseJob

任务实体BaseJob为shield-job的调度核心实体,调用方的业务实体需要继承该作业抽象类,实现其中的encode() 以及 decode(String msg) 抽象方法。

Read More

在上篇中,我们了解了RocketMQ中的消息重试机制以及如何在Producer、Consumer两端对重试消息进行处理。

RocketMQ会在消息消费时,按照一定规则推送消息到消费者端进行消息重试。这里涉及到了消息幂等的概念。

首先我们了解一下什么是幂等,以及何为消息幂等。

什么是幂等

百度对 “幂等” 解释如下

设f为一由X映射至X的一元运算,则f为幂等的,当对于所有在X内的x,
f(f(x)) = f(x).
特别的是,恒等函数一定是幂等的,且任一常数函数也都是幂等的。

这里的关键是 f(f(x)) = f(x), 翻译成通俗的解释就是:

如果有一个操作,多次执行与一次执行所产生的影响是相同的,我们就称这个操作是幂等的。

关于消息幂等

基于上述的概念,结合消息消费的场景,我们能够很容易的总结出消息幂等的概念:

即:

如果消息重试多次,消费者端对该重复消息消费多次与消费一次的结果是相同的,并且多次消费没有对系统产生副作用,那么我们就称这个过程是消息幂等的。

例如:

支付场景下,消费者消费扣款消息,对一笔订单进行扣款操作,该扣款操作需要扣除10元。

这个扣款操作重复多次与执行一次的效果相同,只进行一次真实扣款,用户的扣款记录中对应该笔订单的只有一条扣款流水。不会多扣。那么我们就说这个扣款操作是符合要求的,这个消费过程是消息幂等的。

Read More

本文中,我将讲解RocketMQ使用过程中,如何进行消息重试。

首先,我们需要明确,只有当消费模式为 MessageModel.CLUSTERING(集群模式) 时,Broker才会自动进行重试,对于广播消息是不会重试的。

集群消费模式下,当消息消费失败,RocketMQ会通过消息重试机制重新投递消息,努力使该消息消费成功。

当消费者消费该重试消息后,需要返回结果给broker,告知broker消费成功(ConsumeConcurrentlyStatus.CONSUME_SUCCESS)或者需要重新消费(ConsumeConcurrentlyStatus.RECONSUME_LATER)。

这里有个问题,如果消费者业务本身故障导致某条消息一直无法消费成功,难道要一直重试下去吗?

答案是显而易见的,并不会一直重试。

事实上,对于一直无法消费成功的消息,RocketMQ会在达到最大重试次数之后,将该消息投递至死信队列。然后我们需要关注死信队列,并对该死信消息业务做人工的补偿操作。

Read More

Fork me on GitHub