![0214dadb7d2ce99eb0794e05af5d4353.png](https://i-blog.csdnimg.cn/blog_migrate/df262688355e65c8e29e4cb0be482532.jpeg)
解决问题之前,先在脑海中演绎一下当时场景
某日早上八点半,笔者接到客户反馈,门户首页待办访问异常缓慢,经常出现“访问异常,点击重试”。当时直觉告诉我,应该是大量用户高并发访问 MongoDB 库,导致 MongoDB 库连接池出问题了,因为上线发版时,功能是正常的。
![96b748675bfa99880053567af7c4e3f1.png](https://i-blog.csdnimg.cn/blog_migrate/bce1f9338cd93c89d02a73fb4fbed8e2.jpeg)
由于是上周五晚上发版验证后,周六日使用门户的用户比较少,一直没发现问题,直到下周一才集中爆发门户访问不可用。
请开始我的表演
一开始运维组认为是加了 MongoDB 审计日志造成的,因为有大量针对 MongoDB 做写审计日志写操作,确实会降低服务器性能。 通过查看服务日志,也发现非常多的 MongoDB 访问 timeout 异常信息。
com.mongodb.MongoTimeoutException: Timed out after 30000 ms while waiting for a server that matches ReadPreferenceServerSelector{readPreference=primary}. Client view of cluster state is {type=UNKNOWN, servers=[{address=10.236.2.183:27017, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {java.net.ConnectException: Connection refused (Connection refused)}}, {address=10.236.2.184:27017, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {java.net.ConnectException: Connection refused (Connection refused)}}, {address=10.236.2.185:27017, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {java.net.ConnectException: Connection refused (Connection refused)}}] at com.mongodb.connection.BaseCluster.createTimeoutException(BaseCluster.java:377) ~[mongodb-driver-core-3.4.2.jar!/:na] at com.mongodb.connection.BaseCluster.selectServer(BaseCluster.java:104) ~[mongodb-driver-core-3.4.2.jar!/:na] at com.mongodb.binding.ClusterBinding$ClusterBindingConnectionSource.(ClusterBinding.java:75) ~[mongodb-driver-core-3.4.2.jar!/:na] at com.mongodb.binding.ClusterBinding$ClusterBindingConnectionSource.(ClusterBinding.java:71) ~[mongodb-driver-core-3.4.2.jar!/:na] at com.mongodb.binding.ClusterBinding.getReadConnectionSource(ClusterBinding.java:63) ~[mongodb-driver-core-3.4.2.jar!/:na] at com.mongodb.operation.OperationHelper.withConnection(OperationHelper.java:402) ~[mongodb-driver-core-3.4.2.jar!/:na] at com.mongodb.operation.MapReduceWithInlineResultsOperation.execute(MapReduceWithInlineResultsOperation.java:381) ~[mongodb-driver-core-3.4.2.jar!/:na] at com.mongodb.operation.MapReduceWithInlineResultsOperation.execute(MapReduceWithInlineResultsOperation.java:70) ~[mongodb-driver-core-3.4.2.jar!/:na] at com.mongodb.Mongo.execute(Mongo.java:836) ~[mongodb-driver-3.4.2.jar!/:na] at com.mongodb.Mongo$2.execute(Mongo.java:823) ~[mongodb-driver-3.4.2.jar!/:na] at com.mongodb.DBCollection.mapReduce(DBCollection.java:1278) ~[mongodb-driver-3.4.2.jar!/:na] at org.springframework.data.mongodb.core.MongoTemplate.mapReduce(MongoTemplate.java:1406) ~[spring-data-mongodb-1.10.1.RELEASE.jar!/:na] at org.springframework.data.mongodb.core.MongoTemplate.mapReduce(MongoTemplate.java:1383) ~[spring-data-mongodb-1.10.1.RELEASE.jar!/:na] at com.unicom.portal.taskquery.common.TaskManager.selectPendingCountByType(TaskManager.java:1002) ~[classes!/:1.0.0] at com.unicom.portal.taskquery.service.impl.TaskServiceImpl.getPendingInfo(TaskServiceImpl.java:233) ~[classes!/:1.0.0] at com.unicom.portal.taskquery.controller.TaskPendController.getPendingInfo(TaskPendController.java:164) ~[classes!/:1.0.0]
同时运维人员通过监控告警发现 MongoDB 数据库的连接数达到 10499(平时监控为几百),而 MongoDB 数据库凭空多出惊人的一万多张临时表记录。
![b5953af96d1a0a0071c5b9f0f748d0f7.png](https://i-blog.csdnimg.cn/blog_migrate/95f8c709c327f2d6a5cba56dca72ae37.jpeg)
查看 K8s 容器平台服务器资源情况,发现待办服务 CPU 资源使用高达 7G 多,内存使用高达 12G。平常待办服务的CPU 资源使用都是 0.00 几,明显感觉不正常。
![273869e22243bb61dc2c7f56da4d75d8.png](https://i-blog.csdnimg.cn/blog_migrate/1627beca97133c0eaa6613adc3aadd50.jpeg)
这似乎更加验证了是加了审计日志造成的,于是运维组开始了非常耗时的 Mongos 停止并重启操作,但很遗憾的是“ Mongos 重启后不久又自动停止了”( 后来跟运维组沟通,加的审计日志跟 MongoDB 半毛钱关系都没有)。 由于这个误导,导致门户大概四十分钟不可用。笔者没办法,只能仔细分析 docker 容器日志,发现大部分错误由同一个方法造成的。
com.unicom.portal.taskquery.common.TaskManager.selectPendingCountByType(TaskManager.java:1002)
通过代码走读发现 TaskManager.selectPendingCountByType 这个方法用到了 MongoDB 的 mapReduce 方法。
org.springframework.data.mongodb.core.MongoTemplate.mapReduce
通过查阅 MongoDB 官方文档知悉, mapReduce 方法类似于 MySQL 中的 group by 语句,非常适合做表字段聚合(分组)分类统计功能。 了解 Hadoop 的同学知道,Hadoop 中的 Map 和 Reduce 会拆成多个子任务进行后台跑批计算的。而 MongoDB 的 mapReduce 方法同样如此,不同的是 mapReduce 方法会把子任务发送到不同的分片(sharding)服务器上去执行,而这个过程是非常耗时的。 平常几十个人使用这个功能不会觉察到访问有问题,但是门户每天近 12W 的用户同时在八点半之后访问这个功能,后果就不堪设想了。 结果是“ 修改后的待办待阅查询服务在读取/存储过程中会创建大量临时表,高并发时会造成待办 MongoDB 数据库频繁执行建和删表操作,致使服务器资源异常占满,MongoDB 数据库进程异常关闭。”