当前位置: 首页 > news >正文

物联网平台中的MongoDB(二)性能优化与生产监控

上一篇我们聊了MongoDB在物联网平台中的基础应用,不过光会用还不够。当系统真正运行起来,设备从几百台增长到几万台,数据量从GB级别增长到TB级别,这时候就会遇到各种性能问题。

我记得当时我们项目刚上线,MongoDB用着还挺顺手的。但是随着业务增长,设备接入量快速增加,系统就开始出现问题了——查询变慢,连接池不够用,各种问题接踵而来。那段时间确实比较辛苦,经常收到系统告警。

后来经过一番性能调优和监控改进,系统总算稳定下来了。今天就把这些实践经验分享出来,希望大家能少走一些弯路。

1. 性能优化策略

1.1 连接池配置:别让连接成为瓶颈

连接池这块确实需要重点关注。一开始我们图方便,直接用默认配置,结果高峰期就出现了连接超时的问题。后来发现是连接池设置太小,很多请求在排队等连接。

物联网场景的特点是设备数据写入频繁,查询请求也比较多,对连接池要求比较高。下面这几个参数建议重点调整:

mongodb:host: localhostport: 27017database: iot_platformconnection-timeout: 10000socket-timeout: 10000max-connections-per-host: 100min-connections-per-host: 10

几个关键参数说明:

  • max-connections-per-host:最大连接数,需要根据并发量调整,我们设置的是100,可以作为参考
  • min-connections-per-host:最小连接数,保持一定数量的连接池,避免频繁创建连接的开销
  • connection-timeout:连接超时时间,避免长时间等待,我们设置的是10秒
  • socket-timeout:读取超时时间,防止慢查询影响整个系统,同样是10秒

1.2 索引策略:让查询飞起来

索引策略确实是性能优化的重点。我们一开始没有重视这个问题,结果查询性能很差,用户体验不好。后来仔细分析了查询模式,发现物联网场景的查询其实很有规律,主要是按设备编码、时间、告警状态这几个维度进行查询。

基于这个分析,我们重新设计了索引策略:

// 单字段索引:支持按设备编码快速查询
db.device_data.createIndex({"deviceCode": 1})
// 时间索引:支持按时间倒序查询最新数据
db.device_data.createIndex({"timestamp": -1})
// 复合索引:设备+时间,支持单设备历史数据查询
db.device_data.createIndex({"deviceCode": 1, "timestamp": -1})
// 告警状态索引:快速筛选告警数据
db.device_data.createIndex({"alarmStatus": 1})// 三字段复合索引:支持复杂的组合查询条件
// 索引字段顺序:查询频率高的字段在前
db.device_data.createIndex({"deviceCode": 1,    // 最常用的查询条件"timestamp": -1,    // 时间范围查询"alarmStatus": 1    // 告警状态过滤
})

1.3 查询优化:让索引发挥作用

光建索引还不够,查询语句也要写得合理才行。我们之前就遇到过这个问题,索引建得不错,但查询还是很慢,后来发现是查询条件写得有问题,没有有效利用索引。

下面这个时间范围查询的写法,我们用了很久了,效果还不错:

public List<Map<String, Object>> findDeviceDataByTimeRange(String deviceCode, long startTime, long endTime) {// 构建查询条件Map<String, Object> query = new HashMap<>();query.put("deviceCode", deviceCode);  // 精确匹配设备编码// 构建时间范围查询条件Map<String, Object> timeRange = new HashMap<>();timeRange.put("$gte", startTime);     // 大于等于开始时间timeRange.put("$lte", endTime);       // 小于等于结束时间query.put("timestamp", timeRange);// 设置排序规则:按时间倒序,获取最新数据Map<String, Object> sort = new HashMap<>();sort.put("timestamp", -1);// 执行查询:限制返回1000条记录,从第0条开始return mongoDBService.find("device_data", query, 1000, 0, sort);
}

2. 错误处理和监控:保证系统稳定运行

2.1 异常处理:避免单点故障影响整个系统

物联网环境确实比较复杂,网络可能不稳定,设备可能断线,数据量可能突然增大,各种异常情况都可能出现。如果异常处理做得不好,很容易导致系统崩溃。

我们采用分层异常处理的策略,针对不同类型的异常采用不同的处理方式,下面是我们一直在使用的代码:

@Service
public class MongoDBServiceImpl implements IMongoDBService {@Overridepublic boolean insert(String collectionName, Map<String, Object> document) {try {// 第一层:参数合法性验证,快速失败validateParameters(collectionName, document);// 第二层:执行核心业务逻辑boolean result = MongoDBUtils.insertOne(collectionName, document);// 记录操作结果,便于问题排查if (result) {log.info("MongoDB向集合{}插入文档成功", collectionName);} else {log.error("MongoDB向集合{}插入文档失败", collectionName);}return result;} catch (IllegalArgumentException e) {// 参数异常:直接抛出,由上层处理log.error("参数验证失败: {}", e.getMessage());throw e;} catch (MongoException e) {// MongoDB特定异常:记录日志但不中断流程log.error("MongoDB操作异常: {}", e.getMessage());return false;} catch (Exception e) {// 未知异常:记录完整堆栈信息log.error("未知异常: {}", e.getMessage(), e);return false;}}/*** 参数验证方法:确保输入参数的有效性*/private void validateParameters(String collectionName, Map<String, Object> document) {if (StringUtils.isEmpty(collectionName)) {throw new IllegalArgumentException("集合名称不能为空");}if (document == null || document.isEmpty()) {throw new IllegalArgumentException("文档数据不能为空");}}
}

2.2 健康检查:主动发现系统问题

监控方面,健康检查是必不可少的。我们以前就遇到过问题,数据库连接断了都不知道,等用户反馈才发现,这样很被动。

现在我们使用Spring Boot Actuator监控MongoDB,连接状态有任何变化都能及时发现:

@Component
public class MongoDBHealthIndicator implements HealthIndicator {@Overridepublic Health health() {try {// 执行ping操作检查连接状态// ping是轻量级操作,不会对数据库造成负担MongoDBUtils.ping();// 返回健康状态,包含数据库基本信息return Health.up().withDetail("database", MongoDBUtils.getDatabaseName()).withDetail("status", "连接正常").withDetail("timestamp", System.currentTimeMillis()).build();} catch (Exception e) {// 连接异常时返回DOWN状态,包含错误信息return Health.down().withDetail("error", e.getMessage()).withDetail("status", "连接异常").withDetail("timestamp", System.currentTimeMillis()).build();}}
}

2.3 性能监控:定位性能瓶颈

光知道系统慢还不够,需要知道具体哪里慢。我们使用AOP切面监控所有MongoDB操作,找出性能瓶颈,这样既不需要修改业务代码,又能全面收集性能数据。

这个切面比较实用,推荐使用:

@Aspect
@Component
@Slf4j
public class MongoDBPerformanceAspect {// 定义切点:拦截MongoDB服务实现类的所有方法@Around("execution(* com.xinye.iot.data.mongodb.service.impl.MongoDBServiceImpl.*(..))") public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {// 记录方法开始执行时间long startTime = System.currentTimeMillis();String methodName = joinPoint.getSignature().getName();try {// 执行目标方法Object result = joinPoint.proceed();// 计算方法执行耗时long duration = System.currentTimeMillis() - startTime;// 根据执行时间判断是否需要告警if (duration > 1000) { // 超过1秒的操作记录警告,可能存在性能问题log.warn("MongoDB操作{}执行时间过长: {}ms", methodName, duration);} else {// 正常执行时间,记录调试日志log.debug("MongoDB操作{}执行时间: {}ms", methodName, duration);}return result;} catch (Exception e) {// 方法执行异常时,记录耗时和错误信息long duration = System.currentTimeMillis() - startTime;log.error("MongoDB操作{}执行失败,耗时: {}ms, 错误: {}", methodName, duration, e.getMessage());throw e; // 重新抛出异常,不影响原有异常处理逻辑}}
}

3. 测试验证:确保代码质量

3.1 单元测试:避免bug进入生产环境

关于测试,这确实是很重要的经验。我们之前就因为测试不充分,导致一个小问题在生产环境引发了较大的影响。现在我们对MongoDB的每个操作都会编写完整的单元测试。

这里分享一下我们的测试用例,用@Order注解来控制执行顺序,这样可以模拟真实的业务流程:

@SpringBootTest
@TestMethodOrder(OrderAnnotation.class)  // 确保测试按顺序执行
class MongoDBServiceTest {@Autowiredprivate IMongoDBService mongoDBService;private static final String TEST_COLLECTION = "test_collection";private static String testDocumentId;  // 用于在测试方法间传递文档ID@Test@Order(1)  // 第一步:测试插入操作void testInsert() {// 构造测试文档数据Map<String, Object> document = new HashMap<>();document.put("name", "测试设备");document.put("type", "sensor");document.put("value", 25.5);document.put("timestamp", System.currentTimeMillis());// 执行插入操作并验证结果boolean result = mongoDBService.insert(TEST_COLLECTION, document);assertTrue(result, "文档插入应该成功");}@Test@Order(2)  // 第二步:测试查询操作void testFind() {// 构造查询条件Map<String, Object> query = new HashMap<>();query.put("type", "sensor");// 执行查询操作List<Map<String, Object>> results = mongoDBService.find(TEST_COLLECTION, query);assertFalse(results.isEmpty(), "应该找到匹配的文档");// 验证查询结果的数据正确性Map<String, Object> document = results.get(0);assertEquals("测试设备", document.get("name"));assertEquals("sensor", document.get("type"));// 保存文档ID,供后续测试使用testDocumentId = document.get("_id").toString();}@Test@Order(3)  // 第三步:测试更新操作void testUpdate() {// 构造更新数据Map<String, Object> update = new HashMap<>();update.put("value", 30.0);update.put("lastModified", System.currentTimeMillis());// 执行更新操作boolean result = mongoDBService.updateById(TEST_COLLECTION, testDocumentId, update);assertTrue(result, "文档更新应该成功");// 验证更新结果:重新查询文档确认数据已更新Map<String, Object> updated = mongoDBService.findById(TEST_COLLECTION, testDocumentId);assertEquals(30.0, updated.get("value"));}@Test@Order(4)  // 第四步:测试计数操作void testCount() {// 测试集合总文档数long count = mongoDBService.count(TEST_COLLECTION);assertTrue(count > 0, "集合中应该有文档");// 测试条件查询的文档数Map<String, Object> query = new HashMap<>();query.put("type", "sensor");long sensorCount = mongoDBService.count(TEST_COLLECTION, query);assertTrue(sensorCount > 0, "应该有sensor类型的文档");}@Test@Order(5)  // 第五步:测试删除操作(清理测试数据)void testDelete() {// 执行删除操作boolean result = mongoDBService.deleteById(TEST_COLLECTION, testDocumentId);assertTrue(result, "文档删除应该成功");// 验证删除结果:确认文档已不存在Map<String, Object> deleted = mongoDBService.findById(TEST_COLLECTION, testDocumentId);assertNull(deleted, "文档应该已被删除");}
}

3.2 集成测试:验证完整流程

单元测试还不够,需要集成测试来验证整套流程。以前为了配置测试环境的数据库比较麻烦,现在用TestContainers就方便多了,直接启动一个真实的MongoDB容器来测试。

这样做的好处是,每次都是全新的环境,不用担心数据污染:

@SpringBootTest
@Testcontainers  // 启用TestContainers支持
class MongoDBIntegrationTest {// 定义MongoDB容器,使用4.4版本@Containerstatic MongoDBContainer mongoDBContainer = new MongoDBContainer("mongo:4.4").withExposedPorts(27017);  // 暴露MongoDB默认端口// 动态配置Spring Boot属性,使用容器的连接信息@DynamicPropertySourcestatic void configureProperties(DynamicPropertyRegistry registry) {// 获取容器分配的主机地址registry.add("mongodb.host", mongoDBContainer::getHost);// 获取容器映射的端口号registry.add("mongodb.port", mongoDBContainer::getFirstMappedPort);// 设置测试数据库名称registry.add("mongodb.database", () -> "test_db");}@Autowiredprivate IMongoDBService mongoDBService;@Testvoid testCompleteWorkflow() {// 测试完整业务流程// 主要验证这几个方面:// 1. 大批量数据插入的性能表现// 2. 复杂查询和分页的处理能力// 3. 批量更新的系统影响// 4. 删除操作的执行效率// 5. 高并发场景下的系统稳定性// 简单的性能测试示例long startTime = System.currentTimeMillis();// 这里编写具体的测试逻辑long duration = System.currentTimeMillis() - startTime;// 验证执行时间是否在可接受范围内}
}

文章转载自:

http://nNnmz2ZN.sgLcg.cn
http://HHL7jigA.sgLcg.cn
http://KjO5w3a7.sgLcg.cn
http://KYNPWWqE.sgLcg.cn
http://r40ITecN.sgLcg.cn
http://Scbj22Ax.sgLcg.cn
http://1hAp2pnq.sgLcg.cn
http://MCa9Xqez.sgLcg.cn
http://sklGyuCm.sgLcg.cn
http://uU84RfYK.sgLcg.cn
http://skZZlTFb.sgLcg.cn
http://OkZ6UWrN.sgLcg.cn
http://VAKscYDa.sgLcg.cn
http://otkzFdSW.sgLcg.cn
http://k0RCbxj0.sgLcg.cn
http://Tk9PnXO1.sgLcg.cn
http://EI3eeEB0.sgLcg.cn
http://Uz1qcMIN.sgLcg.cn
http://z2tTUX3O.sgLcg.cn
http://4L5TBcV1.sgLcg.cn
http://CoR3vkU6.sgLcg.cn
http://VItvXXmX.sgLcg.cn
http://PO0a0KaT.sgLcg.cn
http://gXtkpIdp.sgLcg.cn
http://G7E3ZsoK.sgLcg.cn
http://IiIJF4fG.sgLcg.cn
http://jRSW70Vx.sgLcg.cn
http://z1vtgRBM.sgLcg.cn
http://Yqyr5rhR.sgLcg.cn
http://dlH7uRqS.sgLcg.cn
http://www.dtcms.com/a/376484.html

相关文章:

  • 性能测试-jmeter9-逻辑控制器、定时器压力并发
  • 网络编程;TCP控制机械臂;UDP文件传输;0910;ps今天没写出来
  • Firefox Window 开发详解(一)
  • 无公网 IP 也能轻松访问家中群晖 NAS:神卓 NAT 盒子使用记
  • 01数据结构-B树
  • 2025年最强XPath定位工具:SelectorsHub在Chrome与Firefox中的全方位使用指南
  • 如何将音乐从Redmi手机转移到Redmi手机
  • 大数据与云计算知识点
  • 第5篇、 Kafka 数据可靠性与容错机制
  • EasyExcel部署Docker缺少字体报错
  • CentOS Steam 9安装 Redis
  • 将GitHub远程仓库修改为ssh
  • 什么是测试
  • 在pycharm终端安装torch
  • P1141 01迷宫
  • 大模型中的位置编码详解
  • 【华为OD】贪吃的猴子
  • 【CS32L015C8T6】下载Hex文件配置及异常现象解决方法
  • PySpark EDA 完整案例介绍,附代码(三)
  • 强化学习 Reinforcement Learing
  • 数据库物理外键与逻辑外键全解析
  • 分布式专题——8 京东热点缓存探测系统JDhotkey架构剖析
  • 计算机系统性能、架构设计、调度策略论文分类体系参考
  • Mujoco学习记录
  • [react] react-router-dom是啥?
  • uniapp,vue2 置顶功能实现,默认右边半隐藏,点击一次移出来,点击二次置顶,一段时间不操隐藏
  • 佩京VR重走长征路模拟系统
  • HTML详解
  • ai生成文章,流式传输(uniapp,微信小程序)
  • JVM 内存参数设置详解!