一、引言

随着物联网技术的迅猛发展,大量的设备和传感器产生了海量的数据。本文利用了 MQTT、Kafka 和 MongoDB 各自的优点,满足实时数据处理和大规模数据存储的需求。
如图:

二、总结

优点:

1. 可靠和解耦:

Kafka的复制机制和持久化存储确保了数据在传输过程中的可靠性,即使某个节点发生故障,也不会导致数据丢失,将数据生产者和消费者解耦,各模块可以独立扩展和优化,减少了相互影响。
2. 高可用和灵活性:

MongoDB的复制集和分片机制提供了数据的高可用性和容错能力,保证了数据存储的可靠性和灵活性。

缺点:

1. 复杂度高:

包含多个组件(MQTT、Kafka、MongoDB)配置、部署和维护、各组件之间的协调和集成也增加了实现的复杂性。
2. 延迟:

数据从设备上传到最终存储在MongoDB之间经过多个处理环节,每个环节都可能增加一些延迟。
3. 一致性:

数据在Kafka和MongoDB之间传递时可能需要额外的处理机制来确保一致性。

三、实现

准备工作

使用docker-compose.yml创建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

实现步骤

1. 设备数据上传:
服务端代码

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);
2. Kafka消息处理:

生产者代码

        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消息的更多相关文章

  1. MQTT 消息 发布 订阅

    当连接向一个mqtt服务器时,clientId必须是唯一的.设置一样,导致client.setCallback总是走到 connectionLost回调.报connection reset.调查一天才 ...

  2. Kafka介绍与消息队列

    消息队列的好处: 消息队列(Message Queue) 消息: 网络中的两台计算机或者两个通讯设备之间传递的数据.例如说:文本.音乐.视频等内容. 队列:一种特殊的线性表(数据元素首尾相接),特殊之 ...

  3. Kafka设计解析(十六)Kafka 0.11消息设计

    转载自 huxihx,原文链接 [原创]Kafka 0.11消息设计 目录 一.Kafka消息层次设计 1. v1格式 2. v2格式 二.v1消息格式 三.v2消息格式 四.测试对比 Kafka 0 ...

  4. Kafka中的消息是否会丢失和重复消费(转)

    在之前的基础上,基本搞清楚了Kafka的机制及如何运用.这里思考一下:Kafka中的消息会不会丢失或重复消费呢?为什么呢? 要确定Kafka的消息是否丢失或重复,从两个方面分析入手:消息发送和消息消费 ...

  5. Kafka作为分布式消息系统的系统解析

    Kafka概述 Apache Kafka由Scala和Java编写,基于生产者和消费者模型作为开源的分布式发布订阅消息系统.它提供了类似于JMS的特性,但设计上又有很大区别,它不是JMS规范的实现,如 ...

  6. RabbitMQ,RocketMQ,Kafka 事务性,消息丢失和消息重复发送的处理策略

    消息队列常见问题处理 分布式事务 什么是分布式事务 常见的分布式事务解决方案 基于 MQ 实现的分布式事务 本地消息表-最终一致性 MQ事务-最终一致性 RocketMQ中如何处理事务 Kafka中如 ...

  7. vue中使用stompjs实现mqtt消息推送通知

    最近在研究vue+webAPI进行前后端分离,在一些如前端定时循环请求后台接口判断状态等应用场景用使用mqtt进行主动的消息推送能够很大程度的减小服务端接口的压力,提高系统的效率,而且可以利用mqtt ...

  8. 百万级开源MQTT消息服务器 搭建

    下载地址:http://emqtt.com/downloads 文档地址:http://emqtt.com/docs/v2/index.html 开始使用EMQ 2.0 消息服务器简介EMQ (Erl ...

  9. Kafka与常见消息队列的对比

    Kafka与常见消息队列的对比 RabbitMQ Erlang编写 支持很多的协议:AMQP,XMPP, SMTP, STOMP 非常重量级,更适合于企业级的开发 发送给客户端时先在中心队列排队.对路 ...

  10. [3] MQTT,mosquitto,Eclipse Paho---怎样使用 Eclipse Paho MQTT工具来发送订阅MQTT消息?

    在上两节,笔者主要介绍了 MQTT,mosquitto,Eclipse Paho的基本概念已经怎样安装mosquitto. 在这个章节我们就来看看怎样用 Eclipse Paho MQTT工具来发送接 ...

随机推荐

  1. PYENV安装与使用

    1.概述 pyenv 是一个python的版本管理软件,通过他,我们可以 方便的安装python 的版本,切换版本,解决版本不同带来问题. 2.安装pyenv 我们可以通过链接下载pyenv http ...

  2. redis 使用lua 生成流水号

    在实际的业务场景中,我们会用到流水号. 之前的流水号做法是,使用redis的全局锁.然后对数据库进行更新,数据库更新 这个也会有一些问题,比如对于同一个流水号,多个线程去更新,由于事务比较长,那么就会 ...

  3. 使用 MOLECULE 迅速包装百度 UEditor

    UEditor: UEditor - 首页http://ueditor.baidu.com/website/ 我们在对话框上放了几个 UEditor,发现第一次弹出对话框时UEditor还没有初始化 ...

  4. ASP.NET Core EventStream (SSE) 使用以及 WebSocket 比较

    在开发环境中,对于实时数据流的需求非常常见,最常用的技术包括 Server-Sent Events (SSE) 和 WebSocket. 什么是 Server-Sent Events (SSE)? S ...

  5. 【C#】【报错解决】分析器错误消息: 无法识别的属性“targetFramework”。请注意属性名称区分大小写。

    服务器:Windows Server 数据中心 2016 问题描述: 在本地测试正常运行,但是上传到服务器却出现该错误 报错: 分析器错误消息: 无法识别的属性"targetFramewor ...

  6. Ubuntu 的网络图标不见了,怎么解决

    1. 问题 Ubuntu 的网络图标不见了 2. 解决 service network-manager status # 此时,你会发现状态是 active(running),不用管 service ...

  7. OpenLens 6.3.0 无法查案日志和进入 Pod Shell 解决方法

    原因 OpenLens 6.3.0开始移除了Pod的查看日志和进入Pod Shell按钮,无法查看日志和进入Pod操作. 解决办法 OpenLens 6.3.0开始这两个功能以插件形式提供,需下载op ...

  8. 【转载】wget命令详解

    导读: wget是Linux中的一个下载文件的工具,wget是在Linux下开发的开放源代码的软件,作者是Hrvoje Niksic,后来被移植到包括Windows在内的各个平台上. 它用在命令行下. ...

  9. Qt/C++离线地图的加载和交互/可以离线使用/百度和天地图离线/支持手机上运行

    一.前言说明 在地图应用中,有很多时候是需要断网环境中离线使用的,一般会采用两种做法,一种是只下载好离线瓦片地图,然后根据不同的缩放和经纬度坐标绘制瓦片.这种方式优点是任何地图都支持,只需要拿到瓦片即 ...

  10. [转]使用Eclipse创建一个简单的servlet项目

    参考链接: 1.使用Eclipse创建一个简单的servlet项目 2.如何使用eclipse创建简单的servlet