使用mongodb、Kafka保存mqtt消息
一、引言

二、总结
优点:
1. 可靠和解耦:
Kafka的复制机制和持久化存储确保了数据在传输过程中的可靠性,即使某个节点发生故障,也不会导致数据丢失,将数据生产者和消费者解耦,各模块可以独立扩展和优化,减少了相互影响。
2. 高可用和灵活性:
MongoDB的复制集和分片机制提供了数据的高可用性和容错能力,保证了数据存储的可靠性和灵活性。
缺点:
1. 复杂度高:
包含多个组件(MQTT、Kafka、MongoDB)配置、部署和维护、各组件之间的协调和集成也增加了实现的复杂性。
2. 延迟:
数据从设备上传到最终存储在MongoDB之间经过多个处理环节,每个环节都可能增加一些延迟。
3. 一致性:
数据在Kafka和MongoDB之间传递时可能需要额外的处理机制来确保一致性。
三、实现
准备工作

version: '3.8' networks:
app-tier:
driver: bridge services:
kafka:
image: 'bitnami/kafka:latest'
networks:
- app-tier
ports:
- "9092:9092"
environment:
- KAFKA_CFG_NODE_ID=0
- KAFKA_CFG_PROCESS_ROLES=controller,broker
- KAFKA_CFG_LISTENERS=PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093
- KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://127.0.0.1:9092
- KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
- KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@kafka:9093
- KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
volumes:
- kafka-data:/bitnami/kafka mongodb:
image: 'mongo:latest'
networks:
- app-tier
container_name: mongodb
ports:
- "27017:27017"
volumes:
- mongo-data:/data/db volumes:
kafka-data:
driver: local
mongo-data:
driver: local
实现步骤

var mqttFactory = new MqttFactory();
var mqttServerOptions = new MqttServerOptionsBuilder()
.WithDefaultEndpointPort(1883)//监听的端口
.WithDefaultEndpoint()
.WithoutEncryptedEndpoint()// 不启用tls
.WithDefaultCommunicationTimeout(TimeSpan.FromSeconds(10 * 1000))//10秒超时
.WithPersistentSessions(true)//启用session
.WithConnectionBacklog(1000)//积累的最大连接请求数
.Build();
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
{
AddMqttEvents(mqttServer); await mqttServer.StartAsync();
Console.WriteLine("Press Enter Ctrl+C to exit.");
Console.ReadLine();
Console.CancelKeyPress += async (sender, e) =>
{
e.Cancel = true; // 防止进程直接终止
await mqttServer.StopAsync();
Environment.Exit(0);
};
} private static void AddMqttEvents(MqttServer mqttServer)
{
MqttServerEvents mqttEvents = new MqttServerEvents();
mqttServer.ClientConnectedAsync += mqttEvents.Server_ClientConnectedAsync;
mqttServer.StartedAsync += mqttEvents.Server_StartedAsync;
mqttServer.StoppedAsync += mqttEvents.Server_StoppedAsync;
mqttServer.ClientSubscribedTopicAsync += mqttEvents.Server_ClientSubscribedTopicAsync;
mqttServer.ClientUnsubscribedTopicAsync += mqttEvents.Server_ClientUnsubscribedTopicAsync;
mqttServer.ValidatingConnectionAsync += mqttEvents.Server_ValidatingConnectionAsync;
mqttServer.ClientDisconnectedAsync += mqttEvents.Server_ClientDisconnectedAsync;
mqttServer.InterceptingInboundPacketAsync += mqttEvents.Server_InterceptingInboundPacketAsync;
mqttServer.InterceptingOutboundPacketAsync += mqttEvents.Server_InterceptingOutboundPacketAsync;
mqttServer.InterceptingPublishAsync += mqttEvents.Server_InterceptingPublishAsync;
mqttServer.ApplicationMessageNotConsumedAsync += mqttEvents.Server_ApplicationMessageNotConsumedAsync;
mqttServer.ClientAcknowledgedPublishPacketAsync += mqttEvents.Server_ClientAcknowledgedPublishPacketAsync;
}
客户端代码

var mqttFactory = new MqttFactory();
var mqttClient = mqttFactory.CreateMqttClient(); var mqttOptions = new MqttClientOptionsBuilder()
.WithClientId("MqttServiceClient")
.WithTcpServer("127.0.0.1", 1883)
.Build();
mqttClient.ConnectedAsync+=(e =>
{
Console.WriteLine("MQTT连接成功");
return Task.CompletedTask;
}); mqttClient.DisconnectedAsync+=(e =>
{
Console.WriteLine("MQTT连接断开");
return Task.CompletedTask;
});
await mqttClient.ConnectAsync(mqttOptions, CancellationToken.None);
//发送消息
MqttApplicationMessage applicationMessage = new MqttApplicationMessage
{
Topic = "mqtttest",
PayloadSegment = new ArraySegment<byte>(System.Text.Encoding.UTF8.GetBytes(input))
}; var res = await mqttClient.PublishAsync(applicationMessage);
生产者代码

var config = new ProducerConfig
{
BootstrapServers = "localhost:9092"
};
using var producer = new ProducerBuilder<string, string>(config).Build();
try
{
var message = new Message<string, string>
{
Key = e.ClientId,
Value = JsonConvert.SerializeObject(e.Packet)
};
var deliveryResult = await producer.ProduceAsync("mqttMsg-topic", message);
Console.WriteLine($"Delivered '{deliveryResult.Value}' to '{deliveryResult.TopicPartitionOffset}'");
}
catch (ProduceException<string, string> ke)
{
Console.WriteLine($"Delivery failed: {ke.Error.Reason}");
}
消费者代码

var config = new ConsumerConfig
{
GroupId = "my-consumer-group",
BootstrapServers = "127.0.0.1:9092",
AutoOffsetReset = AutoOffsetReset.Earliest
};
using var consumer = new ConsumerBuilder<string, string>(config).Build();
consumer.Subscribe("mqttMsg-topic");
//消费消息并保存到mongodb
var client = new MongoClient("mongodb://127.0.0.1:27017");
var collection = client.GetDatabase("mqtttest").GetCollection<BsonDocument>($"history_{DateTime.UtcNow.Year}_{DateTime.UtcNow.Month}");
while (true)
{
try
{
var consumeResult = consumer.Consume(cancellationToken.Token);
Console.WriteLine($"收到Kafka消息 '{consumeResult.Message.Value}' at: '{consumeResult.TopicPartitionOffset}'.");
var document = new BsonDocument
{
{ "clientId", consumeResult.Message.Key },
{ "JsonData", MongoDB.Bson.Serialization.BsonSerializer.Deserialize<BsonDocument>(consumeResult.Message.Value) },//不同设备上报数据格式不一定一样
{ "created", DateTime.UtcNow }
};
await collection.InsertOneAsync(document);
}
catch (ConsumeException e)
{
Console.WriteLine($"处理Kafka消息异常: {e.Error.Reason}");
}
}
源码地址:https://github.com/jclown/MqttPersistence
使用mongodb、Kafka保存mqtt消息的更多相关文章
- MQTT 消息 发布 订阅
当连接向一个mqtt服务器时,clientId必须是唯一的.设置一样,导致client.setCallback总是走到 connectionLost回调.报connection reset.调查一天才 ...
- Kafka介绍与消息队列
消息队列的好处: 消息队列(Message Queue) 消息: 网络中的两台计算机或者两个通讯设备之间传递的数据.例如说:文本.音乐.视频等内容. 队列:一种特殊的线性表(数据元素首尾相接),特殊之 ...
- Kafka设计解析(十六)Kafka 0.11消息设计
转载自 huxihx,原文链接 [原创]Kafka 0.11消息设计 目录 一.Kafka消息层次设计 1. v1格式 2. v2格式 二.v1消息格式 三.v2消息格式 四.测试对比 Kafka 0 ...
- Kafka中的消息是否会丢失和重复消费(转)
在之前的基础上,基本搞清楚了Kafka的机制及如何运用.这里思考一下:Kafka中的消息会不会丢失或重复消费呢?为什么呢? 要确定Kafka的消息是否丢失或重复,从两个方面分析入手:消息发送和消息消费 ...
- Kafka作为分布式消息系统的系统解析
Kafka概述 Apache Kafka由Scala和Java编写,基于生产者和消费者模型作为开源的分布式发布订阅消息系统.它提供了类似于JMS的特性,但设计上又有很大区别,它不是JMS规范的实现,如 ...
- RabbitMQ,RocketMQ,Kafka 事务性,消息丢失和消息重复发送的处理策略
消息队列常见问题处理 分布式事务 什么是分布式事务 常见的分布式事务解决方案 基于 MQ 实现的分布式事务 本地消息表-最终一致性 MQ事务-最终一致性 RocketMQ中如何处理事务 Kafka中如 ...
- vue中使用stompjs实现mqtt消息推送通知
最近在研究vue+webAPI进行前后端分离,在一些如前端定时循环请求后台接口判断状态等应用场景用使用mqtt进行主动的消息推送能够很大程度的减小服务端接口的压力,提高系统的效率,而且可以利用mqtt ...
- 百万级开源MQTT消息服务器 搭建
下载地址:http://emqtt.com/downloads 文档地址:http://emqtt.com/docs/v2/index.html 开始使用EMQ 2.0 消息服务器简介EMQ (Erl ...
- Kafka与常见消息队列的对比
Kafka与常见消息队列的对比 RabbitMQ Erlang编写 支持很多的协议:AMQP,XMPP, SMTP, STOMP 非常重量级,更适合于企业级的开发 发送给客户端时先在中心队列排队.对路 ...
- [3] MQTT,mosquitto,Eclipse Paho---怎样使用 Eclipse Paho MQTT工具来发送订阅MQTT消息?
在上两节,笔者主要介绍了 MQTT,mosquitto,Eclipse Paho的基本概念已经怎样安装mosquitto. 在这个章节我们就来看看怎样用 Eclipse Paho MQTT工具来发送接 ...
随机推荐
- PYENV安装与使用
1.概述 pyenv 是一个python的版本管理软件,通过他,我们可以 方便的安装python 的版本,切换版本,解决版本不同带来问题. 2.安装pyenv 我们可以通过链接下载pyenv http ...
- redis 使用lua 生成流水号
在实际的业务场景中,我们会用到流水号. 之前的流水号做法是,使用redis的全局锁.然后对数据库进行更新,数据库更新 这个也会有一些问题,比如对于同一个流水号,多个线程去更新,由于事务比较长,那么就会 ...
- 使用 MOLECULE 迅速包装百度 UEditor
UEditor: UEditor - 首页http://ueditor.baidu.com/website/ 我们在对话框上放了几个 UEditor,发现第一次弹出对话框时UEditor还没有初始化 ...
- ASP.NET Core EventStream (SSE) 使用以及 WebSocket 比较
在开发环境中,对于实时数据流的需求非常常见,最常用的技术包括 Server-Sent Events (SSE) 和 WebSocket. 什么是 Server-Sent Events (SSE)? S ...
- 【C#】【报错解决】分析器错误消息: 无法识别的属性“targetFramework”。请注意属性名称区分大小写。
服务器:Windows Server 数据中心 2016 问题描述: 在本地测试正常运行,但是上传到服务器却出现该错误 报错: 分析器错误消息: 无法识别的属性"targetFramewor ...
- Ubuntu 的网络图标不见了,怎么解决
1. 问题 Ubuntu 的网络图标不见了 2. 解决 service network-manager status # 此时,你会发现状态是 active(running),不用管 service ...
- OpenLens 6.3.0 无法查案日志和进入 Pod Shell 解决方法
原因 OpenLens 6.3.0开始移除了Pod的查看日志和进入Pod Shell按钮,无法查看日志和进入Pod操作. 解决办法 OpenLens 6.3.0开始这两个功能以插件形式提供,需下载op ...
- 【转载】wget命令详解
导读: wget是Linux中的一个下载文件的工具,wget是在Linux下开发的开放源代码的软件,作者是Hrvoje Niksic,后来被移植到包括Windows在内的各个平台上. 它用在命令行下. ...
- Qt/C++离线地图的加载和交互/可以离线使用/百度和天地图离线/支持手机上运行
一.前言说明 在地图应用中,有很多时候是需要断网环境中离线使用的,一般会采用两种做法,一种是只下载好离线瓦片地图,然后根据不同的缩放和经纬度坐标绘制瓦片.这种方式优点是任何地图都支持,只需要拿到瓦片即 ...
- [转]使用Eclipse创建一个简单的servlet项目
参考链接: 1.使用Eclipse创建一个简单的servlet项目 2.如何使用eclipse创建简单的servlet