使用消息中间件实现系统间的异步通信和解耦
✨✨谢谢大家捧场,祝屏幕前的小伙伴们每天都有好运相伴左右,一定要天天开心哦!✨✨
🎈🎈作者主页: 喔的嘛呀🎈🎈
✨✨ 帅哥美女们,我们共同加油!一起进步!✨✨
目录
引言
一. 选择合适的消息中间件
二. 定义消息格式和通信协议
1. 定义消息格式
消息头
消息体
2. 定义通信协议
发送消息
接收消息
消息处理
3. 示例代码
定义消息格式
发送消息
接收消息
三、发布-订阅模式
1. 定义发布-订阅模式
2. 示例代码
发布消息
订阅消息
3. 运行示例
4. 异步处理消息
5. 解耦系统
6. 实现步骤
7. 实例场景
实例场景:电商系统订单处理
场景描述
实现步骤
示例代码
订单服务发送消息
库存服务接收消息
物流服务接收消息
引言
在现代分布式系统中,异步通信和解耦是非常重要的设计原则。通过使用消息中间件,可以实现系统间的异步通信和解耦,提高系统的可扩展性和可靠性。本文将介绍如何使用消息中间件来实现系统间的异步通信和解耦,并通过一个实际场景来演示。
一. 选择合适的消息中间件
选择合适的消息中间件需要考虑多个因素,包括项目需求、性能要求、可靠性、社区支持等。常见的消息中间件包括 RabbitMQ、Kafka、ActiveMQ、Redis 等,下面针对不同的需求给出一些选择建议:
-
消息传递模式:
- 点对点:适合使用 RabbitMQ、ActiveMQ 等传统消息中间件。
- 发布-订阅:适合使用 RabbitMQ、Kafka 等支持广播消息的中间件。
-
可靠性:
- 如果对消息的可靠性要求较高,需要确保消息不会丢失,可以考虑使用 RabbitMQ、Kafka 等提供消息持久化和高可靠性的中间件。
-
性能:
- 如果需要处理大量的消息并且需要低延迟,可以考虑使用 Kafka,它是一个高吞吐量的消息中间件,适合大数据场景。
- 如果对延迟要求较低,可以选择 RabbitMQ、ActiveMQ 等传统消息中间件。
-
社区支持和生态系统:
- 考虑选择一个有活跃社区支持和完善生态系统的消息中间件,这样可以更容易地解决问题和扩展功能。
-
技术栈兼容性:
- 考虑选择一个与你的技术栈兼容的消息中间件,避免出现集成上的问题。
综合考虑以上因素,可以选择最适合项目需求的消息中间件。
二. 定义消息格式和通信协议
定义消息格式和通信协议是使用消息中间件的关键步骤之一,它涉及到消息的结构、内容和交互方式。下面以 RabbitMQ 为例,演示如何定义消息格式和通信协议。
1. 定义消息格式
在 RabbitMQ 中,消息通常由两部分组成:消息头和消息体。消息头包含一些元数据信息,如消息的类型、路由键等;消息体包含实际的业务数据。
消息头
Content-Type:消息体的类型,如application/json、text/plain等。DeliveryMode:消息持久性标志,标识消息是否需要持久化存储,可选值为1(持久化)和2(非持久化)。CorrelationId:消息关联标识,用于关联一组相关消息。- 其他自定义的消息头字段,根据业务需求定义。
消息体
- 消息体可以是任意格式的数据,如 JSON、XML、文本等,根据业务需求定义。
2. 定义通信协议
通信协议定义了消息的交互方式,包括消息的发送、接收和处理流程。通信协议可以包括以下几个方面:
发送消息
- 客户端向消息队列发送消息,包括指定交换机(Exchange)、路由键(Routing Key)和消息体。
接收消息
- 服务端从消息队列接收消息,根据消息的交换机和路由键接收对应的消息。
消息处理
- 客户端接收到消息后,根据消息的内容执行相应的业务逻辑。
3. 示例代码
定义消息格式
public class Message {private String content;private String contentType;private int deliveryMode;private String correlationId;// 省略getter和setter方法
}
发送消息
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;public class SendMessage {private final static String QUEUE_NAME = "hello";public static void main(String[] argv) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setHost("localhost");try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) {channel.queueDeclare(QUEUE_NAME, false, false, false, null);Message message = new Message();message.setContent("Hello, RabbitMQ!");message.setContentType("text/plain");message.setDeliveryMode(1); // 持久化message.setCorrelationId("123456");String messageJson = toJson(message);channel.basicPublish("", QUEUE_NAME, null, messageJson.getBytes());System.out.println(" [x] Sent '" + messageJson + "'");}}private static String toJson(Message message) {// 将 message 对象转换成 JSON 格式的字符串return "{ \"content\": \"" + message.getContent() + "\", \"contentType\": \"" + message.getContentType() + "\", \"deliveryMode\": " + message.getDeliveryMode() + ", \"correlationId\": \"" + message.getCorrelationId() + "\" }";}
}
接收消息
import com.rabbitmq.client.*;public class ReceiveMessage {private final static String QUEUE_NAME = "hello";public static void main(String[] argv) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setHost("localhost");try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) {channel.queueDeclare(QUEUE_NAME, false, false, false, null);System.out.println(" [*] Waiting for messages. To exit press Ctrl+C");DeliverCallback deliverCallback = (consumerTag, delivery) -> {String messageJson = new String(delivery.getBody(), "UTF-8");Message message = fromJson(messageJson, Message.class);System.out.println(" [x] Received '" + messageJson + "'");};channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {});}}private static <T> T fromJson(String json, Class<T> clazz) {// 将 JSON 格式的字符串转换成指定类型的对象// 这里可以使用 JSON 框架(如 Jackson、Gson)来实现return null;}
}
通过以上步骤,可以定义消息格式和通信协议,并使用 RabbitMQ 实现消息的发送和接收。
三、发布-订阅模式
发布-订阅模式是一种常见的消息传递模式,用于实现消息的广播和订阅。在发布-订阅模式中,消息发布者将消息发布到一个主题(Topic),而消息订阅者可以订阅感兴趣的主题,从而接收到相关消息。下面以 RabbitMQ 为例,演示如何使用发布-订阅模式。
1. 定义发布-订阅模式
在发布-订阅模式中,有一个交换机(Exchange)用来接收发布者发布的消息,并根据订阅者的绑定关系将消息路由到对应的队列。订阅者可以创建自己的队列,并将队列绑定到交换机上,从而接收到发布者发布的消息。
2. 示例代码
发布消息
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;public class Publisher {private final static String EXCHANGE_NAME = "logs";public static void main(String[] argv) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setHost("localhost");try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) {channel.exchangeDeclare(EXCHANGE_NAME, "fanout");String message = "Hello, subscribers!";channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());System.out.println(" [x] Sent '" + message + "'");}}
}
订阅消息
import com.rabbitmq.client.*;public class Subscriber {private final static String EXCHANGE_NAME = "logs";public static void main(String[] argv) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setHost("localhost");try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) {channel.exchangeDeclare(EXCHANGE_NAME, "fanout");String queueName = channel.queueDeclare().getQueue();channel.queueBind(queueName, EXCHANGE_NAME, "");System.out.println(" [*] Waiting for messages. To exit press Ctrl+C");DeliverCallback deliverCallback = (consumerTag, delivery) -> {String message = new String(delivery.getBody(), "UTF-8");System.out.println(" [x] Received '" + message + "'");};channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {});}}
}
3. 运行示例
- 先运行订阅者
Subscriber,它会创建一个队列并绑定到交换机上,开始监听消息。 - 然后运行发布者
Publisher,它会向交换机发布一条消息。 - 订阅者会接收到发布者发布的消息,并输出到控制台。
通过以上步骤,可以实现基于 RabbitMQ 的发布-订阅模式。
4. 异步处理消息
通过消息中间件实现异步处理消息,即发送消息后不需要立即等待结果,而是继续执行其他任务。这样可以提高系统的响应速度和吞吐量。
5. 解耦系统
通过消息中间件,系统之间的通信变成了基于消息的方式,系统不再直接依赖于对方的接口和实现细节,从而实现了系统之间的解耦。
6. 实现步骤
- 定义消息格式和通信协议:确定消息的格式和通信协议,包括消息的内容结构、消息的生命周期等。
- 配置消息中间件:在系统中配置和启动消息中间件,确保消息中间件正常运行。
- 消息的发布和订阅:编写代码实现消息的发布和订阅逻辑,将消息发布到指定的主题,并订阅感兴趣的主题。
- 处理接收到的消息:编写代码处理接收到的消息,根据消息的内容执行相应的业务逻辑。
- 测试和验证:对系统进行测试和验证,确保消息的发布、订阅和处理功能正常运行。
7. 实例场景
实例场景:电商系统订单处理
场景描述
假设有一个电商系统,包含订单服务、库存服务和物流服务。当用户下单时,订单服务需要通知库存服务减少库存,通知物流服务发货。为了提高系统的可扩展性和可靠性,我们可以使用消息中间件来实现订单处理的异步通信和解耦。
实现步骤
-
定义消息格式和通信协议:定义订单消息的格式,包括订单号、商品信息等,并确定消息的交换机和队列名称。
-
配置消息中间件:在消息中间件中配置交换机和队列,并确保消息的持久化。
-
订单服务发送消息:订单服务在用户下单后,将订单消息发送到消息队列中。
-
库存服务订阅消息:库存服务订阅订单消息队列,接收并处理订单消息,减少库存。
-
物流服务订阅消息:物流服务也订阅订单消息队列,接收并处理订单消息,进行发货。
示例代码
订单服务发送消息
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;public class OrderService {private static final String EXCHANGE_NAME = "orders";private static final String QUEUE_NAME = "order_queue";public static void main(String[] argv) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setHost("localhost");try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) {channel.exchangeDeclare(EXCHANGE_NAME, "fanout");channel.queueDeclare(QUEUE_NAME, false, false, false, null);String message = "New order placed";channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());System.out.println(" [x] Sent '" + message + "'");}}
}
库存服务接收消息
import com.rabbitmq.client.*;public class InventoryService {private static final String EXCHANGE_NAME = "orders";private static final String QUEUE_NAME = "order_queue";public static void main(String[] argv) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setHost("localhost");try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) {channel.exchangeDeclare(EXCHANGE_NAME, "fanout");channel.queueDeclare(QUEUE_NAME, false, false, false, null);channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");System.out.println(" [*] Waiting for orders. To exit press Ctrl+C");DeliverCallback deliverCallback = (consumerTag, delivery) -> {String message = new String(delivery.getBody(), "UTF-8");System.out.println(" [x] Received '" + message + "'");// 处理订单消息,减少库存};channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });}}
}
物流服务接收消息
import com.rabbitmq.client.*;public class LogisticsService {private static final String EXCHANGE_NAME = "orders";private static final String QUEUE_NAME = "order_queue";public static void main(String[] argv) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setHost("localhost");try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) {channel.exchangeDeclare(EXCHANGE_NAME, "fanout");channel.queueDeclare(QUEUE_NAME, false, false, false, null);channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");System.out.println(" [*] Waiting for orders. To exit press Ctrl+C");DeliverCallback deliverCallback = (consumerTag, delivery) -> {String message = new String(delivery.getBody(), "UTF-8");System.out.println(" [x] Received '" + message + "'");// 处理订单消息,发货};channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });}}
}
通过以上步骤的简单演示,订单服务可以异步发送订单消息,库存服务和物流服务可以订阅订单消息并处理,实现了订单处理的异步通信和解耦。
通过以上步骤,可以使用消息中间件实现系统间的异步通信和解耦,提高系统的可扩展性和可维护性。
相关文章:
使用消息中间件实现系统间的异步通信和解耦
✨✨谢谢大家捧场,祝屏幕前的小伙伴们每天都有好运相伴左右,一定要天天开心哦!✨✨ 🎈🎈作者主页: 喔的嘛呀🎈🎈 ✨✨ 帅哥美女们,我们共同加油!一起进步&am…...
【QML】用 Image(QQuickPaintedItem) 显示图片
大体功能: 频繁地往界面推送图片,帧率达到视频效果。捕获画布上的鼠标事件和键盘事件。 代码如下: // DrawImageInQQuickPaintedItem.pro 代码如下: QT quick# You can make your code fail to compile if it uses deprecated…...
C++抽象类
C中抽象类 1.抽象类概念1.1、抽象类如何使用1.2、抽象类规定 2.抽象类代码示例2.1、抽象类2.2、子类12.3、子类22.4、程序入口 1.抽象类概念 C是一门面向对象的编程语言,而所有的对象都是通过类来描述的,如果一个类没有足够的信息来描述一个具体的对象&…...
计算机网络 —— 应用层(DHCP)
计算机网络 —— 应用层(DHCP) 什么是DHCPDHCP工作过程DHCP DISCOVERDHCP OFFERDHCP RQUESTDHCP ACK DHCP租约机制中继代理工作原理功能与优势 我们今天来计网的DHCP: 什么是DHCP DHCP(Dynamic Host Configuration Protocol&…...
Linux ComfyUI安装使用;Stable Diffusion 3使用
1、Linux ComfyUI安装使用 参考: https://zhuanlan.zhihu.com/p/689021495 安装步骤: ## 1、下载ComfyUI git clone https://github.com/comfyanonymous/ComfyUI ## 2、进入ComfyUI,安装依赖包 cd ComfyUI pip install -r requirements.txt ## 3\安装插件 cd custom_nodes…...
JavaScripts数组里的对象排序的24个方法
1. 使用 Array.prototype.sort() 这是最基本、也是最常用的方法。sort() 方法会原地修改数组,并返回排序后的数组。你需要传入一个比较函数来定义排序逻辑。 const array [{ name: Alice, age: 25 },{ name: Bob, age: 22 },{ name: Charlie, age: 30 } ];// 按照…...
Mongodb介绍及window环境安装
本文主要内容为nosql数据库-MongoDB介绍及window环境安装。 目录 什么是MongoDB? 主要特点 MongoDB 与Mysql对应 安装MongoDB 下载MongoDB 自定义安装 创建目录 配置环境变量 配置MongoDB服务 服务改为手动 启动与关闭 安装MongoDB Shell 下载安装包 …...
Spring响应式编程之Reactor核心组件
Reactor核心组件 Flux和Mono组件(1)Flux组件(2)Mono组件 Flux和Mono组件 Reactor 框架提供了两个核心组件来发布数据,分别是 Flux 和 Mono 组件。两者都是实现Publisher接口的高级抽象,可以说是应用程序开…...
动手学深度学习(Pytorch版)代码实践 -计算机视觉-37微调
37微调 import os import torch import torchvision from torch import nn import liliPytorch as lp import matplotlib.pyplot as plt from d2l import torch as d2l# 获取数据集 d2l.DATA_HUB[hotdog] (d2l.DATA_URL hotdog.zip,fba480ffa8aa7e0febbb511d181409f899b9baa5…...
视频监控平台:支持交通部行业标准JT/T905协议(即:出租汽车服务管理信息系统)的源代码的函数和功能介绍及分享
目录 一、视频监控平台介绍 (一)概述 (二)视频接入能力介绍 (三)功能介绍 二、JT/T905协议介绍 (一)概述 (二)主要内容 1、设备要求 2、业务功能要求…...
【jenkins1】gitlab与jenkins集成
文章目录 1.Jenkins-docker配置:运行在8080端口上,机器只要安装docker就能装载image并运行容器2.Jenkins与GitLab配置:docker ps查看正在运行,浏览器访问http://10....:8080/2.1 GitLab与Jenkins的Access Token配置:不…...
边缘计算设备有哪些
边缘设备是指那些位于数据源附近,能够执行数据处理、分析和决策的计算设备。这些设备通常具有一定的计算能力、存储能力和网络连接能力,能够减少数据传输到云端的需要,从而降低延迟、节省带宽并提高数据处理的效率。以下是一些常见的边缘设备…...
C++初学者指南第一步---7.控制流(基础)
C初学者指南第一步—7.控制流(基础) 文章目录 C初学者指南第一步---7.控制流(基础)1.术语:表达式/语句Expressions表达式Statements语句 2.条件分支3.Switching(切换):基于值的分支4.三元条件运算符5.循环迭代基于范围的循环 C…...
MFC学习--CListCtrl复选框以及选择
如何展示复选框 //LVS_EX_CHECKBOXES每一行的最前面带个复选框//LVS_EX_FULLROWSELECT整行选中//LVS_EX_GRIDLINES网格线//LVS_EX_HEADERDRAGDROP列表头可以拖动m_listctl.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_CHECKBOXES | LVS_EX_GRIDLINES); 全选,全…...
如何与PM探讨项目
我曾在2020年撰写过一篇名为对产品经理的一些思考的文章,紧接着在2021年,我又写了一篇对如何分析项目的思考。在这两篇文章中,我提出了一个核心观点:“船长需要把控所有事情,但最核心的是:需要知道目标是什…...
今年618各云厂商的香港服务器优惠活动汇总
又到了一年618年中钜惠活动时间,2024年各大云服务器厂商都有哪些活动呢?有哪些活动包括香港服务器呢?带着这些问题,小编给大家一一讲解各大知名厂商的618活动有哪些值得关注的地方,如果对你有帮助,欢迎点赞…...
Android平台下VR头显如何低延迟播放4K以上超高分辨率RTSP|RTMP流
技术背景 VR头显需要更高的分辨率以提供更清晰的视觉体验、满足沉浸感的要求、适应透镜放大效应以及适应更广泛的可视角度,超高分辨率的优势如下: 提供更清晰的视觉体验:VR头显的分辨率直接决定了用户所看到的图像的清晰度。更高的分辨率意…...
WHAT - NextJS 系列之 Rendering - Server Components
目录 一、Server Components1.1 Server Components特点使用 1.2 Client Components特点使用 1.3 综合使用示例1.4 小结 二、Server Components 优势三、Streaming 特性3.1 基本介绍和使用Streaming的理解工作原理使用示例服务器端组件客户端组件页面流程解释 3.2 HTTP/1.1和HTT…...
Web项目部署后浏览器刷新返回Nginx的404错误对应解决方案
data: 2024/6/22 16:05:34 周六 limou3434 叠甲:以下文章主要是依靠我的实际编码学习中总结出来的经验之谈,求逻辑自洽,不能百分百保证正确,有错误、未定义、不合适的内容请尽情指出! 文章目录 1.源头2.排错3.原因4.解…...
视频与音频的交响:探索达摩院VideoLLaMA 2的技术创新
一、简介 文章:https://arxiv.org/abs/2406.07476 代码:https://github.com/DAMO-NLP-SG/VideoLLaMA2 VideoLLaMA 2是由阿里巴巴集团的DAMO Academy团队开发的视频大型语言模型(Video-LLM),旨在通过增强空间-时间建模…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...
盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...
Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...
vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...
MinIO Docker 部署:仅开放一个端口
MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...
Vite中定义@软链接
在webpack中可以直接通过符号表示src路径,但是vite中默认不可以。 如何实现: vite中提供了resolve.alias:通过别名在指向一个具体的路径 在vite.config.js中 import { join } from pathexport default defineConfig({plugins: [vue()],//…...
