既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
介绍
主要目的
- 了解消息中间件背景知识、使用场景、发展
- 掌握RabbitMQ、RocketMQ、Kafka这三款主流的消息中间件的架构,模型,和使用(开发、安装、集群部署、运维、监控)
- 掌握消息的可靠性、幂等性、顺序消息、延迟消息、事务消息等进阶的知识,以及大规模生产环境中的使用经验,轻松对应各种复杂的业务场景。
- 掌握顶级开源消息中间价的源码,理解背后的架构设计思想以及在高性能存储系统、网络编程等方面的技巧(会涉及网络通信、操作系统等底层知识)
- 理解主流消息中间件的优缺点,具备技术选型能力
面试常见的题目
项目中为什么要使用消息中间件
项目中为什么使用RocketMQ而不是RabbitMQ
系统TPS有多少?引入消息中间件之后,系统一定不会被撑爆吗
消息中间件出现大量的消息堆积,会产生什么后果?
如何发现大面积的消息堆积?采取哪些应对措施?问题产生的根源是什么?如何有效的避免?
主要内容
消息中间件概述
- 分布式系统中如何进行远程通信
- 为什么使用消息中间件?市场上有哪些产品?有什么优缺点?如何选择
- JMS规范和AMQP协议
Rabbit
- RabbitMQ架构、环境准备和整合
- 高级特性如消息的可靠性保障、死信队列、延迟队列
- RabbitMQ的集群、运维
- 源码分析,解析RabbitMQ的启动过程、交换器的实现、队列的实现
消息中间价概述
分布式架构通信
分布式架构通信原理
SOA架构
根据实际业务,把系统拆分成合适的
、独立部署
的模块,模块
之间相互独立
。优点:分布式
、松耦合
、扩展灵活
、可重用
。SOA架构系统中,使用Dubbo
和Zookeeper
进行服务间的远程通信。优点:Dubbo使用自定义的TCP协议,可以让请求报文体积更小,或者使用HTTP2协议,也可以减少报文的体积,提高传输效率。
微服务
SpringCloud中使用Feign
解决服务之间远程通信的问题。
Feign:轻量级RESTful的HTTP服务客户端,广泛应用于Spring Cloud中。符合面向接口化的编程习惯。
本质:封装了HTTP调用流程,类似Dubbo的服务调用
。多用于同步远程调用。
RPC
主要基于TCP/UDP
协议,HTTP协议是应用层协议,是构建在传输层协议TCP之上的,RPC效率更高,RPC长连接:不必每次通信都像HTTP一样三次握手,减少网络开销;
HTTP服务开发迭代更快:在接口不多,系统与系统之间交互比较少的情况下,HTTP就显得更加方便;相反,在接口比较多,系统与系统之间交互比较多的情况下,HTTP就没有RPC有优势。
分布式同步通信的问题
在电商项目中,如何后台添加商品信息,该信息放到数据库。我们同时,需要更新搜索引擎的倒排索引,同时假如有商品页面的静态化处理,也需要更新该页面信息。
针对上边的问题,我们该如何解决呢?
方法一:可以在后台添加商品的方法中,如果数据插入数据库成功,就调用更新倒排索引的方法,接着调用更新静态化页面的方法
如果在此过程中更新失败怎么办?这个过程存在同步调用
处理不当的问题。在高并发的添加商品下,效率会降低。
方法二:可以先执行添加商品的方法,商品添加成功,将更新索引和更新静态页面的任务缓存到一个公共的位置,然后由相应的服务从该位置获取任务来执行
此时,由于添加商品仅仅是将数据插入数据库,然后将任务信息缓存,调用立刻返回。对于添加商品方法的调用,不会存在线程阻塞,不会存在调用栈崩溃。再考虑远一点。 由于更新倒排索引的的服务和更新静态页面的服务要从公共的缓存或者叫任务池中取出任务并执行,它们也会有执行失败的问题,也需要重试。如果一直更新失败,也需要一个方式来处理。比如如果更新失败,则每隔3秒钟重试一次,重试三次都失败则放弃执行。然后将错误结果放到另一个公共的地方,等待后续的补偿,无论是手工还是自动的。还有问题:
- 这个公共的任务池,会不会宕机?会不会服务不可用?如何解决?
- 你一定确信消息发送到任务池了吗?
- 如果在向任务池发送任务失败该如何处理?
- 如果重试的时候发送成功了,但是实际上发送了多次,更新倒排索引服务和更新静态页面服务
会不会重复执行? - 如果重复执行,最终结果会不会不一样?
- 。。。
看来真是解决了一个问题,引进来三个问题。如果上述的问题都由我们从0开始解决,开发难度可想而知。分布式服务中,由于业务拆分,应用也需要拆分,甚至数据库分库分表。但是完成一个业务处理,往往要设计到多个模块之间的协调处理。此时模块之间,服务与服务之间以及客户端与服务端之间的通信将变得非常复杂。
分布式异步通信模式
比较典型的“生产者消费者模式”,可以跨平台、支持异构系统,通常借助消息中间件来完成。
- 优点:系统间解耦,并具有一定的可恢复性,支持异构系统,下游通常可并发执行,系统具备弹性。服务解耦、流量削峰填谷等
- 缺点:消息中间件存在一些瓶颈和一致性问题,对于开发来讲不直观且不易调试,有额外成本。
使用异步消息模式需要注意的问题:
- 哪些业务需要同步处理,哪些业务可以异步处理?
- 如何保证消息的安全?消息是否会丢失,是否会重复?
- 请求的延迟如何能够减少?
- 消息接收的顺序是否会影响到业务流程的正常执行?
- 消息处理失败后是否需要重发?如果重发如何保证幂等性?
消息中间件简介
概述
面向消息的系统(消息中间件)是在分布式系统中完成消息的发送和接收的基础软件。消息中间件也可以称消息队列,是指用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的集成。通过提供消息传递和消息队列模型,可以在分布式环境下扩展进程的通信。消息中间件就是在通信的上下游之间截断:break it,Broker然后利用中间件解耦、异步的特性,构建弹性、可靠、稳定的系统。体会一下:“必有歹人从中作梗”,”定有贵人从中相助“。异步处理、流量削峰、限流、缓冲、排队、最终一致性、消息驱动等需求的场景都可以使用消息中间件。
自定义消息中间件
并发编程领域经典面试题:请使用java代码来实现“生产者消费者模式”。BlockingQueue(阻塞队列)是java中常见的容器,在多线程编程中被广泛使用。当队列容器已满时生产者线程被阻塞,直到队列未满后才可以继续put;当队列容器为空时,消费者线程被阻塞,直至队列非空时才可以继续take
kouzhao.py
package com.lagou.demo;
public class KouZhao {
private String id;
private String type;
public KouZhao(String id, String type) {
this.id = id;
this.type = type;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
@Override
public String toString() {
return "KouZhao{" +
"id='" + id + '\'' +
", type='" + type + '\'' +
'}';
}
}
Producer.java
package com.lagou.demo;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
public class Producer implements Runnable {
private final BlockingQueue<KouZhao> blockingQueue;
public Producer(BlockingQueue<KouZhao> blockingQueue) {
this.blockingQueue = blockingQueue;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(200);
if (blockingQueue.remainingCapacity() > 0) {
KouZhao kouZhao = new
KouZhao(UUID.randomUUID().toString(), "N95");
blockingQueue.add(kouZhao);
System.out.println("我在生产口罩,当前库存是:" +
blockingQueue.size());
} else {
System.out.println("我的仓库已经堆满了" +
blockingQueue.size() + "个口罩,快来买口罩啊!");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Consumer.java
package com.lagou.demo;
import java.util.concurrent.BlockingQueue;
public class Consumer implements Runnable {
private final BlockingQueue<KouZhao> blockingQueue;
public Consumer(BlockingQueue<KouZhao> blockingQueue) {
this.blockingQueue = blockingQueue;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
long startTime = System.currentTimeMillis(); // 获取开始时间
KouZhao kouZhao = blockingQueue.take();
long endTime = System.currentTimeMillis(); // 获取结束时间
System.out.println("我消费了口罩:" + kouZhao + ", 等到货时我阻
塞了" + (endTime - startTime) + "ms");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
app.java
package com.lagou.demo;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class App {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<KouZhao> queue = new ArrayBlockingQueue<>(20);
Producer producer = new Producer(queue);
Consumer consumer = new Consumer(queue);
new Thread(producer).start();
Thread.sleep(20000);
new Thread(consumer).start();
}
}
上述代码放到生产环境显然是不行的,比如没有集群,没有分布式,玩儿法太单一,不能满足企业级应用的要求。。。
比如:消息有没有持久化?怎么确定消息一定能发送成功?怎么确定消息一定能被消费成功?高并发下的性能怎么样?系统可靠吗?
有没有Pub/Sub模式?有没有考虑过限流?。。。
主流消息中间件及选型
在传统金融机构、银行、政府机构等有一些老系统还在使用IBM等厂商提供的商用MQ产品。当前业界比较流行的开源消息中间件包括:ActiveMQ、RabbitMQ、RocketMQ、Kafka、ZeroMQ等,其中应用最为广泛的要数RabbitMQ
、RocketMQ
、Kafka
这三款。Redis在某种程度上也可以是实现类似“Queue
”和“Pub/Sub
”的机制,严格意义上不算消息中间件。
选取原则
首先,产品应该是开源的。开源意味着如果队列使用中遇到bug,可以很快修改,而不用等待开发者的更新。其次,产品必须是近几年比较流行的,要有一个活跃的社区。这样遇到问题很快就可以找到解决方法。同时流行也意味着bug较少。流行的产品一般跟周边系统兼容性比较好。最后,作为消息队列,要具备以下几个特性:
- 消息传输的可靠性:保证消息不会丢失。
- 支持集群,包括横向扩展,单点故障都可以解决。
- 性能要好,要能够满足业务的性能需求。
RabbitMQ
RabbitMQ开始是用在电信业务的可靠通信的,也是少有的几款支持AMQP协议的产品之一。
优点:
- 轻量级,快速,部署使用方便
- 支持灵活的路由配置。RabbitMQ中,在生产者和队列之间有一个交换器模块。根据配置的路由规则,生产者发送的消息可以发送到不同的队列中。路由规则很灵活,还可以自己实现。
- RabbitMQ的客户端支持大多数的编程语言。
缺点:
- 如果有大量消息堆积在队列中,性能会急剧下降
- RabbitMQ的性能在Kafka和RocketMQ中是最差的,每秒处理几万到几十万的消息。如果应用要求高的性能,不要选择RabbitMQ。
- RabbitMQ是Erlang开发的,功能扩展和二次开发代价很高。
RocketMQ
RocketMQ是一个开源的消息队列,使用java实现。借鉴了Kafka的设计并做了很多改进。RocketMQ主要用于有序,事务,流计算,消息推送,日志流处理,binlog分发等场景。经过了历次的双11考验,性能,稳定性可可靠性没的说。
RocketMQ几乎具备了消息队列应该具备的所有特性和功能。
- java开发,阅读源代码、扩展、二次开发很方便。
- 对电商领域的响应延迟做了很多优化。在大多数情况下,响应在毫秒级。如果应用很关注响应时间,可以使用RocketMQ。
- 性能比RabbitMQ高一个数量级,每秒处理几十万的消息。
缺点:跟周边系统的整合和兼容不是很好。
Kafka
Kafka的可靠性,稳定性和功能特性基本满足大多数的应用场景。跟周边系统的兼容性是数一数二的,尤其是大数据和流计算领域,几乎所有相关的开源软件都支持Kafka。Kafka高效
,可伸缩
,消息持久化
。支持分区
、副本和容错
。Kafka是Scala
和Java
开发的,对批处理和异步处理做了大量的设计,因此Kafka可以得到非常高的性能。它的异步消息
的发送和接收是三个中最好的,但是跟RocketMQ拉不开数量级,每秒处理几十万
的消息。如果是异步消息
,并且开启了压缩
,Kafka最终可以达到每秒处理2000w
消息的级别。但是由于是异步的和批处理的,延迟也会高
,不适合电商
场景。
RabbitMQ | RocketMQ | Kafka | |
---|---|---|---|
单机吞吐量 | 1w量级 | 10w量级 | 10w量级 |
开发语言 | Erlang | Java | Java和Scala |
消息延迟 | 微妙 | 毫秒 | 毫秒 |
消息丢失 | 可能性很低 | 参数优化后可以0丢失 | 参数优化后可以0丢失 |
消费模式 | 推拉 | 推拉 | 拉取 |
主题数量对吞吐量影响 | \ | 几百上千个主题会对吞吐量有一个小的影响 | 几十上百个主题会极大影响吞吐量 |
可用性 | 高(主从) | 很高(主从) | 很高(分布式) |
消息中间件应用场景
消息中间件的使用场景非常广泛,比如,12306购票的排队锁座,电商秒杀,大数据实时计算等。
电商秒杀案例:
比如6.18,活动从0:00开始,仅限前 200 名,秒杀即将开始时,用户会疯狂刷新 APP或者浏览器来保证自己能够尽早的看到商品。
- 当秒杀开始前,用户在不断的刷新页面,系统应该如何应对高并发的读请求呢?
- 在秒杀开始时,大量并发用户瞬间向系统请求生成订单,扣减库存,系统应该如何应对高并发的写请求呢?
系统应该如何应对高并发的读请求
- 使用缓存策略将请求挡在上层中的缓存中
- 能静态化的数据尽量做到静态化
- 加入限流(比如对短时间之内来自某一个用户,某一个IP、某个设备的重复请求做丢弃处理)
系统应该如何应对高并发的写请求
生成订单,扣减库存,用户这些操作不经过缓存直达数据库。如果在 1s内,有 1 万个数据连接同时到达,系统的数据库会濒临崩溃。如何解决这个问题呢?我们可以使用 消息队列
。
消息队列的作用:
- 削去秒杀场景下的峰值写流量——流量削峰
- 通过异步处理简化秒杀请求中的业务流程——异步处理
- 解耦,实现秒杀系统模块之间松耦合——解耦
削去秒杀场景下的峰值写流量
将秒杀请求暂存于消息队列
,业务服务器响应用户“秒杀结果正在处理中。。。”,释放系统资源去处理其它用户的请求。
削峰填谷
,削平短暂的流量高峰,消息堆积会造成请求延迟处理,但秒杀用户对于短暂延迟有一定容忍度。
秒杀商品有 1000 件,处理一次购买请求的时间是 500ms,那么总共就需要 500s 的时间。这时你部署 10 个队列处理程序,那么秒杀请求的处理时间就是 50s,也就是说用户需要等待 50s 才可以看到秒杀的结果,这是可以接受的。这时会并发 10 个请求到达数据库,并不会对数据库造成很大的压力。
通过异步处理简化秒杀请求中的业务流程:先处理主要的业务,异步处理次要的业务。如主要流程是生成订单、扣减库存;次要流程比如购买成功之后会给用户发优惠券,增加用户的积分。此时秒杀只要处理生成订单,扣减库存的耗时,发放优惠券、增加用户积分异步去处理了。解耦,实现秒杀系统模块之间松耦合
将秒杀数据同步给数据团队,有两种思路:
- 使用 HTTP 或者 RPC 同步调用,即提供一个接口,实时将数据推送给数据服务。系统的耦合度高,如果其中一个服务有问题,可能会导致另一个服务不可用。
- 使用消息队列
将数据全部发送给消息队列,然后数据服务订阅这个消息队列,接收数据进行处理。
拉勾B端C端数据同步案例:
拉勾网站分B端和C端,B端面向企业用户,C端面向求职者。这两个模块业务处理逻辑不同,数据库表结构不同,实际上是处于解耦的状态。但是各自又需要对方的数据,需要共享:如
- 当C端求职者在更新简历之后,B端企业用户如何尽早看到该简历更新?
- 当B端企业用户发布新的职位需求后,C端用户如何尽早看到该职位信息?
无论是B端还是C端,都有各自的搜索引擎和缓存,B端需要获取C端的更新以更新搜索引擎和缓
存;C端需要获取B端的更新以更新C端的搜索引擎与缓存。
如何解决B端C端数据共享的问题?
解决方式:
- 同步方式:B端和C端通过RPC或WebService的方式发布服务,让对方来调用,以获取对方的
信息。求职者每更新一次简历,就调用一次B端的服务,进行数据的同步;B端企业用户每更
新职位需求,就调用C端的服务,进行数据的同步。 - 异步方式:使用消息队列,B端将更新的数据发布到消息队列,C端将更新的数据发布到消息
队列,B端订阅C端的消息队列,C端订阅B端的消息队列。
使用同步方式,B端和C端耦合比较紧密,如果其中一个服务有问题,可能会导致另一个服务不可用。比如C端的RPC挂掉,企业用户有可能无法发布新的职位信息,因为发布了对方也看不到;B端的RPC挂掉,求职者可能无法更新简历,因为即使简历更新了,对方也看不到。
你可能会想,可以让B端或C端在对方RPC挂掉的时候,先将该通知消息缓存起来,等对方服务恢复之后再进行同步。这正是引入异步方式,使用消息队列的目的。使用消息队列的异步方式,对B端C端进行解耦,只要消息队列可用,双方都可以将需要同步的信息发送到消息队列,对方在收到消息队列推送来的消息的时候,各自更新自己的搜索引擎,更新自己的缓存数据。
支付宝购买电影票
如上图,用户在支付宝购买了一张电影票后很快就收到消息推送和短信(电影院地址、几号厅、座位号、场次时间等),同时用户会积累一定的会员积分。这里,交易系统并不需要一直等待消息送达等动作都完成后才返回成功,允许一定延迟和瞬时不一致(最终一致性),而且后面两个动作通常可以并发执行。如果后期监控大盘想要获取实时交易数据,只需要新增个消费者程序并订阅该消息即可,交易系统对此并不感知,松耦合。
JMS规范和AMQP协议
RabbitMQ
RabbitMQ架构与实战
RabbitMQ介绍、概念、基础架构
RabbitMQ介绍
RabbitMQ,俗称“兔子MQ”(可见其轻巧,敏捷),是目前非常热门的一款开源消息中间件,不管是互联网行业还是传统行业都广泛使用(最早是为了解决电信行业系统之间的可靠通信而设计)。
- 高可靠性、易扩展、高可用、功能丰富
- 支持大多数(甚至冷门)的编程语言客户端。
- RabbitMQ遵循AMQP协议,自身采用Erlang(一种由爱立信开发的通用面向并发编程的语言)编写。
- RabbitMQ也支持MQTT等其他协议。
RabbitMQ具有很强大的插件扩展能力,官方和社区提供了非常丰富的插件可供选择:https://www.rabbitmq.com/community-plugins.html
RabbitMQ整体逻辑架构
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
124441544.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NTgxOTYx,size_16,color_FFFFFF,t_70)
[外链图片转存中…(img-VJX5l7Oq-1715644881897)]
[外链图片转存中…(img-GdGZfO3N-1715644881897)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!