MongoDB性能实践总结

2020-05-22 00:00:00 索引 字段 文件 记录 组合

MongoDB性能优化,有很多优化的方案,因自己曾参与过一段IoM 1.3的性能优化工作,这里只总结了一下我们实践过的性能要点,作为回顾。


一. MongoDB服务端性能优化点
1. 限制连接数
Mongod 的服务模型是每个网络连接由一个单独的线程来处理,每个线程配置了1MB 的栈空间,当网络连接数太多时,过多的线程会导致上下文切换开销变大,同时内存开销也会上涨。另外,每个连接都要打开一个文件句柄,当然从成本上讲,这个消耗相对内存是小了很多。但换个角度,文件句柄也被其他模块消耗着,比如WT存储引擎,就需要消耗大量的文件句柄。

分布式shard集群部署环境的大连接数通过route进程的配置文件的 net.maxIncomingConnections 指定,默认值为1000000,相当于没有限制,生产环境强烈建议根据Mongodb节点的实际需求配置,以避免客户端误用导致mongodb负载过高 。


2. 关闭数据库文件的atime
atime是linux文件系统记录的文件访问时间,大部分时候,它是没有用的。所以,在高IO,CPU wait高的情况下,关闭atime,可以提高性能。

【配置方法】由于针对设备,所以不会影响其他部件
在/etc/fstable中设置,例如
/dev/dm-6 /var/ceilometer ext3 defaults,noatime


3.使用XFS 文件系统

MongoDB在WiredTiger存储引擎下建议使用XFS文件系统。Ext4为常见,但是由于ext文件系统的内部journal和WiredTiger有所冲突,所以在IO压力较大情况下表现不佳


3.提高Linux大进程数/默认文件描述符限制

Linux默认的文件描述符数和大进程数对于MongoDB来说一般会太低。官方建议把这个数值设为64000。因为MongoDB服务器对每一个数据库文件以及每一个客户端连接都需要用到一个文件描述符。如果这个数字太小的话在大规模并发操作情况下可能会出错或无法响应。
目前IoM给予的都比较大。
ulimit -n 64000
ulimit -u 64000


4.调小readhead
readhead 是磁盘预读字节,默认值是512KB。但是,由于MongoDB 随机访问数据的特点,所以不需要预读那么多,反而浪费内存。

【配置方法】
(1)查看readhead (RA)

# blockdev --report
RO RA SSZ BSZ StartSec Size Device
rw 1024 512 4096 0 193269334016 /dev/dm-0

# ll /dev/mapper/
total 0
crw-rw---- 1 root root 10, 236 Apr 6 03:07 control
lrwxrwxrwx 1 root root 7 Apr 6 03:07 vg1-lv1 -> ../dm-0

(2)设置readhead,预读从512KB降到128KB。256*512字节/扇区=128KB

–setra N 设置预读扇区(512字节)为N个.Set readahead to N 512-byte sectors.

#blockdev --setra 256 /dev/dm-0

# blockdev --report
RO RA SSZ BSZ StartSec Size Device
rw 256 512 4096 0 193269334016 /dev/dm-0



二. MongoDB应用端性能优化点

1. 为cm,dm等模块访问DB的实际情况设置合适的MongoDB连接池大小

通常 MongoClient 使用默认100的连接池(具体默认值以 Driver 的文档为准)都没问题,当访问同一个 Mongod 的源比较多时,则需要合理的规划连接池大小。举个例子,Mongod 的连接数限制为2000,应用业务上有40个服务进程可能同时访问 这个Mongod,这时每个进程里的 MongoClient 的连接数则应该限制在 2000 / 40 = 50 以下 (连接复制集时,MongoClient 还要跟复制集的每个成员建立一条连接,用于监控复制集后端角色的变化情况)。


2. 对于多个字段的查询,建议使用组合索引,比交叉索引效率更好

如果你的查询会使用到多个字段,MongoDB有两个索引技术可以使用:交叉索引和组合索引。交叉索引就是针对每个字段单独建立一个单字段索引,然后在查询执行时候使用相应的单字段索引进行索引交叉而得到查询结果。交叉索引目前触发率较低,所以如果你有一个多字段查询的时候,建议使用组合索引能够保证索引正常的使用。

例如,如果应用需要查找所有年龄小于30岁的深圳市马拉松运动员:

db.T_DeviceData.find({deviceId: "584519b9-3340-4226-ab4e-49311a8b1c3d", appId: "HIxvSb_1dieBJr5IZY2g1zkb8Jga"})

则需要这样的一个索引:
db.T_DeviceData.ensureIndex({deviceId:1, appId:1});


3. 组合索引字段顺序:匹配条件在前,范围条件在后

在创建组合索引时如果条件有匹配和范围之分,那么匹配条件(deviceId: “584519b9-3340-4226-ab4e-49311a8b1c3d”) 应该在组合索引的前面。范围条件比如字段应该放在组合索引的后面。


db.T_DeviceData.find({deviceId: "584519b9-3340-4226-ab4e-49311a8b1c3d", appId: "HIxvSb_1dieBJr5IZY2g1zkb8Jga",timeStamp:{ $lt: new Date(1486828800000) }})

db.T_DeviceData.find({timeStamp:{ $lt: new Date(1486828800000) },deviceId: "584519b9-3340-4226-ab4e-49311a8b1c3d", appId: "HIxvSb_1dieBJr5IZY2g1zkb8Jga",})

的索引性能更好



4. 重要但是优化代价大的还是业务逻辑上的优化

将变化相对较少的数据缓存在redis,memcache, 减少应用端对DB的访问频率,特别是设备登录,上报数据等频繁发生的业务 。



. MongoDB的profilling工具使用


开启profiling功能

有两种方式可以控制 Profiling 的开关和级别,种是直接在启动参数里直接进行设置。启动MongoDB 时加上–profile=级别 即可。也可以在客户端调用db.setProfilingLevel(级别) 命令来实时配置,Profiler 信息保存在system.profile 中。我们可以通过db.getProfilingLevel()命令来获取当前的Profile 级别,类似如下操作:


db.setProfilingLevel(2);

相关文章