开头我来从一段真实的日常场景开始讲吧。上个月我帮一个朋友排查他们测试环境的问题:同一个MongoDB实例上跑了四个业务库,其中一个业务做了个全表聚合分析,直接把整个实例的WiredTiger缓存打满,另外三个业务的读写延迟瞬间从几十毫秒飙到三秒开外。负责人很无奈地说,早知道当初就不该图省事全塞一个实例里。
这种场景我相信不少团队都遇到过。本地开发时,你同时维护多个项目,一个要MongoDB 4.4,另一个要5.0,还有一个要开启副本集模式做事务测试,你怎么装?一个个下载安装包、改端口、改配置、切换数据目录?折腾一上午是常有的事。更麻烦的是,不同项目的库还容易串数据,一旦连错端口,测试数据全乱套。
后来我给他们搭了一套方案,用Docker在同一台机器上跑多个彼此独立的MongoDB容器,每个容器就像是一个平行宇宙,端口、数据目录、认证账号、版本互不干扰,要用哪个起哪个,三分钟完成环境切换。整套构建流程本质上就是三步:写Compose配置、挂独立数据卷、初始化独立账号。做完之后,那四个业务从单实例双库拆成了两个容器实例,原来被拖垮的那个业务平均响应时间从850毫秒降到270毫秒左右,吞吐大约提升了三倍,说“性能提升200%”在特定场景下是真的能成立的。
这篇文章就围绕这套方案展开。不管是刚接触Docker的新手,还是被多环境和多版本MongoDB折腾过的老手,按下面的步骤操作,你也能在十分钟内搭出一套属于自己的MongoDB平行宇宙。过程中我会把每一步的选型理由、配置逻辑和踩坑记录都写清楚,尽量让你抄作业的时候少碰几个钉子。
1. 先搞清楚什么是“MongoDB平行宇宙”
1.1 一台机器装多个MongoDB的尴尬往事
在没有容器化之前,同一台开发机上装多个MongoDB实例的体验可以用四个字形容:痛不欲生。
先说端口。MongoDB默认监听27017,第二个实例就得改27018,第三个改27019。端口一多,配置文件就要各自维护一份,启动命令也得带上不同的--config参数和--port参数。如果你还用包管理器安装,那更麻烦,systemctl管理的服务名还会互相冲突,你启了一个服务,它会把另一个的进程干掉。
再看数据目录。MongoDB的dbPath默认是/var/lib/mongodb,第二个实例必须手动指定新目录,比如/var/lib/mongodb2。目录权限、日志路径、socket文件路径全都要单独设置一遍。哪怕你严格按照官方文档操作,也很难保证不给后面的自己埋雷。
最崩溃的还不是环境问题,而是数据问题。同一个实例里如果建了多个库,默认情况下任何连接都能看到所有库。你给项目A做测试,一条db.dropDatabase()执行错位置,项目B的数据说没就没。这种风险在我见过的团队里不是小概率事件,是几乎人人中招。
1.2 为什么用Docker,而不是虚拟机或直接装多个实例
有人会问,装虚拟机不也行吗?一台虚拟机装一个MongoDB,隔离性比容器还彻底。确实行,但你要考虑成本:一台完整的虚拟机动辄几个G内存,同时开三台,你的开发机基本就卡死了。而且虚拟机的启动速度是分钟级的,Docker容器是秒级的,体验差距非常大。
也有人会问,那我在Docker里跑一个MongoDB容器,然后一个容器里建多个库,不也能实现“逻辑隔离”吗?这正是很多团队最初的做法,包括我朋友他们。逻辑隔离省事,但问题在性能层面:一个MongoDB进程只有一个WiredTiger存储引擎实例,所有库共享同一块内存缓存。某个库的查询压力一大,就会挤占其他库的缓存空间,磁盘I/O和锁竞争也会波及所有人。这就好比一栋楼共用一个电梯,某层搬家把电梯占住了,整栋楼都得等。
Docker容器的做法不同:每个MongoDB实例是独立的进程、独立的内存缓存、独立的端口和数据目录。它们共享的是宿主机的CPU和内存,但只要你做好资源配额,彼此之间的干扰就能控制在一个很低的水平。这种“多个独立实例互不干扰”的形态,我用“平行宇宙”来形容,应该挺贴切。
1.3 容器和宿主机之间,真的有“量子纠缠”吗
标题里的“量子纠缠”当然是个比喻,但用在这个场景里其实有几分贴切。
首先是端口映射的耦合。容器内部的27017端口和宿主机的27017端口是绑定的,外部连接访问宿主机端口,数据才进入容器——这像是接上了量子纠缠的两端,一边动另一边必定响应。如果你把容器的27017错映射成了宿主机27018,那外部连接连27017就什么都拿不到,这个“纠缠”关系必须看准。
其次是数据卷的耦合。容器的文件系统是临时的,容器一删,容器内所有数据随之消失。但如果挂载了宿主机目录到/data/db,数据就“纠缠”在宿主机上,容器删了数据还在。很多刚开始用Docker的人在这上面栽过跟头,以为删容器等于删数据,结果数据还在,还经常因为旧数据导致新容器起不来。
理解了这两层关系,后面构建平行宇宙的时候你就不会被各种“灵异事件”搞乱。接下来进入正题,第一步先把Compose配置写好。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 第1步:用Docker Compose声明你的平行宇宙
2.1 为什么选Compose而不是一排docker run
我知道有些朋友习惯用docker run直接起容器,一条命令确实很爽,但它有一个致命的问题:不可复现。一周之后你想再起一个相同环境的容器,大概率记不清当初用了哪些参数。而且docker run的参数一多,命令长得跟小作文似的,万一漏了--network或者-e环境变量,定位问题要花半天。
Docker Compose的价值在于把容器的运行配置写进一个YAML文件,这个文件本身就带着注释,还能提交到Git仓库。新同事拉下代码,一条docker compose up -d就能把整套环境跑起来,完全不需要“口口相传”的手工部署经验。
更关键的是,Compose天然支持多容器编排。如果你只想跑一个MongoDB容器,docker run还能忍;可一旦你要跑两个、三个,还要让他们共享同一个自定义网络,Compose的声明式管理优势就体现出来了。你可以在同一个docker-compose.yml里定义mongo-alpha和mongo-beta两个服务,它们启动后自动加入同一个网络,互相可以通过服务名访问。
2.2 docker-compose.yml逐行拆解
先给一份可以直接抄的配置:
yaml复制version: "3.8"
networks:
mongo-universe:
driver: bridge
volumes:
alpha-db-data:
beta-db-data:
services:
mongo-alpha:
image: mongo:5.0
container_name: mongo-alpha
restart: unless-stopped
networks:
- mongo-universe
ports:
- "27017:27017"
volumes:
- alpha-db-data:/data/db
- ./alpha-init:/docker-entrypoint-initdb.d:ro
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: rootpass_alpha
MONGO_INITDB_DATABASE: app_db
mongo-beta:
image: mongo:4.4
container_name: mongo-beta
restart: unless-stopped
networks:
- mongo-universe
ports:
- "27018:27017"
volumes:
- beta-db-data:/data/db
- ./beta-init:/docker-entrypoint-initdb.d:ro
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: rootpass_beta
MONGO_INITDB_DATABASE: order_db
这份配置里,mongo-alpha用的是MongoDB 5.0,对外暴露宿主机27017端口;mongo-beta用的是4.4,对外暴露27018端口。两个容器内部依然是标准的27017,端口映射的差异只发生在宿主机层面,这正是“外部访问互不干扰”的关键。
volumes段定义了两个命名数据卷:alpha-db-data和beta-db-data。命名数据卷和./data这种bind mount方式各有优劣:bind mount方便你在宿主机上直接查看和备份数据文件,但会产生权限问题(常见的是容器内的mongodb用户对宿主机目录没有写权限);命名数据卷由Docker托管,权限管理自动完成,基本不会因为权限问题启动失败。我的做法是,生产或测试环境都用命名数据卷,只有需要频繁查看数据文件时才用bind mount。
2.3 启动前的网络和端口规划
配置写好后不要急着启动,先规划好端口,这一步虽然简单但特别容易出错。
我给你一个实际经验:把宿主机端口范围列成一张表,贴在docker-compose.yml旁边。
| 宇宙名称 | 宿主机端口 | 容器端口 | 数据卷 | 镜像版本 | 用途 |
|---|---|---|---|---|---|
| mongo-alpha | 27017 | 27017 | alpha-db-data | mongo:5.0 | 应用A主库 |
| mongo-beta | 27018 | 27017 | beta-db-data | mongo:4.4 | 应用B主库 |
为什么要单独强调端口规划?因为一旦线上代码里写死了27017,你又把两个实例的宿主机端口都映射成27017,就会有一个容器启动失败。Docker对端口冲突的处理方式是:后启动的容器绑定失败直接退出,而且报错信息可能藏在日志里,你光看docker ps还不一定发现。所以每次新增宇宙之前,先看一眼这张端口表,比启动后再排查省时得多。
端口规划完之后,可以先验证一下网络配置。
bash复制docker compose config
这条命令会把最终的配置渲染出来,如果YAML有语法错误或字段写错,它会直接报错,不会等你启动了容器再看日志。这个习惯我建议你每次都做。
3. 第2步:给每个宇宙配置独立身份与初始数据
3.1 数据卷隔离:删除容器不等于删除宇宙
很多朋友用Docker跑MongoDB时有个误区:以为容器起了就等于数据落盘,容器删了数据也就没了。这其实取决于你有没有把数据挂出来。
在没有挂载数据卷的情况下,MongoDB容器内/data/db的数据确实会随容器删除而消失。有些人就这么不小心“删库跑路”过。但在上面的Compose配置里,我们声明了alpha-db-data和beta-db-data两个命名数据卷,即使执行docker compose down删掉容器,数据卷依然存在,下次docker compose up -d再启动,数据会原样读回来。
这种设计对应到“平行宇宙”的概念就是:每个宇宙有自己的时间线,宇宙崩了(容器删了),时间线还在;你重新拉起一个宇宙,它接着之前的时间线继续跑。这是生产环境必须的保障,开发环境也建议这样做,省得哪天误删容器之后欲哭无泪。
不过要留个心眼:如果你执行的是docker compose down -v,这个-v参数会把数据卷一起删除。这条命令在开发环境无所谓,在测试环境一旦执行,数据全没。我建议把它视为高危操作,尽量不要加-v。
3.2 启用认证并为每个宇宙创建专属账号
容器启动之后,默认情况下MongoDB是不开启认证的,谁连上就能操作所有库。这在隔离环境里问题不大,但你在宿主机上同时跑多个实例时,任何一个实例被扫到都会成为风险敞口。
我在配置里通过环境变量给每个宇宙设置了独立的root账号:
mongo-alpha:用户名root,密码rootpass_alpha,宿主机端口27017mongo-beta:用户名root,密码rootpass_beta,宿主机端口27018
这里有一个很容易被忽略的细节:MongoDB官方镜像的MONGO_INITDB_ROOT_USERNAME和MONGO_INITDB_ROOT_PASSWORD环境变量只在数据目录为空(即首次初始化)时生效。如果数据卷里已经有数据了,你再改这两个环境变量,root密码不会变。想要重置密码,得用其他方式,这点和MySQL等数据库镜像的行为略有不同。
另外,官方镜像还会自动创建一个由MONGO_INITDB_DATABASE指定的数据库,并创建一个只有该库权限的用户。不过这里有个常见的坑:这个“用户”只在镜像内置的初始化脚本里创建,如果MONGO_INITDB_DATABASE和root用户设置不在首次启动时同时生效,后面再设置是没用的。所以你在修改配置前,务必确认数据卷是否已初始化过。
如果单纯跑单机版不需要认证,可以把环境变量里的用户名密码都删掉,MongoDB默认不开启认证。但我个人强烈建议,只要端口暴露在宿主机上,就一定要开启认证,哪怕密码弱一点,也比裸奔强。
3.3 初始化脚本背后的执行时机与坑
MongoDB官方镜像支持一个机制:容器首次启动时,会自动执行/docker-entrypoint-initdb.d目录下的.js、.sh等脚本。我在Compose配置里把./alpha-init和./beta-init分别挂载到了两个容器的/docker-entrypoint-initdb.d目录,这样每个宇宙都可以执行自己的初始化逻辑。
举个例子,我想给每个宇宙创建独立的业务用户,而不是直接用root,可以写一个init.js:
javascript复制db.getSiblingDB("app_db").createUser({
user: "app_user",
pwd: "app_secret",
roles: [
{ role: "readWrite", db: "app_db" }
]
});
把这个文件放进./alpha-init目录,容器首次启动后,初始化脚本会自动执行,之后每次重启都不会再执行。
这里有个非常经典的坑:镜像内置的初始化脚本只会在数据目录为空时执行。也就是说,如果你第一次启动容器时忘了挂./alpha-init目录,等初始化完成之后再补上并重启,脚本是不会执行的。你看到的表象是:容器正常起来了,配置文件也挂上了,但用户没创建成功。
我当时排查这个问题花了两个小时,一直以为是脚本语法的问题,最后才意识到是执行时机的问题。解决办法很简单:删掉数据卷重新初始化。所以初始化脚本一定要在第一次启动之前就放好,不要等容器跑起来之后再补。
4. 第3步:启动、验证与性能实测
4.1 启动顺序与健康检查
配置确认无误后直接启动:
bash复制docker compose up -d
-d参数让容器在后台运行,终端不会卡住。启动完成后,用docker compose ps查看状态:
bash复制$ docker compose ps
NAME IMAGE COMMAND SERVICE STATUS PORTS
mongo-alpha mongo:5.0 "docker-entrypoint.s..." mongo-alpha Up 2 minutes 0.0.0.0:27017->27017/tcp
mongo-beta mongo:4.4 "docker-entrypoint.s..." mongo-beta Up 2 minutes 0.0.0.0:27018->27017/tcp
两个容器都显示Up,说明基本正常。但要注意,MongoDB进程启动到可以接受连接,通常需要几秒到十几秒的时间,尤其是首次初始化时要创建用户、建索引,会更慢。如果你的应用脚本在容器刚Up的瞬间就去连接,可能会报“Connection refused”,这不是容器有问题,而是启动还没完成。
官方镜像提供了mongosh命令,可以方便地检查健康状态:
bash复制docker exec mongo-alpha mongosh --eval "db.runCommand({ping: 1})"
返回{ ok: 1 }就说明可以正常连接了。如果返回错误,再等几秒重试。
4.2 用Compass和命令行双重验证宇宙隔离
服务端正常后,还要验证一下“外部连接是否真的互不干扰”。这一步建议用MongoDB Compass做图形化验证,同时用命令行做一个交叉验证。
用Compass连接两个实例:
mongo-alpha:连接串mongodb://root:rootpass_alpha@localhost:27017mongo-beta:连接串mongodb://root:rootpass_beta@localhost:27018
两个连接串都能正常登录,并且看到的数据库列表互不相同,说明端口隔离和认证隔离都生效了。
命令行可以这样验证:
bash复制mongosh "mongodb://root:rootpass_alpha@localhost:27017/admin" --eval "db.serverStatus().version"
mongosh "mongodb://root:rootpass_beta@localhost:27018/admin" --eval "db.serverStatus().version"
mongo-alpha返回5.0.x,mongo-beta返回4.4.x,版本都不相同,说明两个宇宙各自独立运行着不同版本的MongoDB。这种版本隔离能力,在传统多实例方案里几乎是不可能轻松做到的。
接下来还要验证数据隔离。在mongo-alpha里插入一条数据:
javascript复制db.getSiblingDB("app_db").test.insertOne({ hello: "alpha" });
再到mongo-beta里查一下:
javascript复制db.getSiblingDB("order_db").test.find().toArray();
如果查询结果为空(或者只有beta自己插入的数据),说明两个宇宙的数据没有串扰,平行宇宙的基本属性验证通过。
4.3 性能提升200%的实验数据与前提条件
聊完验证,回到标题里的“性能提升200%”。这不是随便写出来吸引点击的,而是在特定场景下实测出来的结果,但我也要说清楚它的前提条件,免得大家盲目照搬。
先说实验场景。我朋友那个团队,原先是单实例MongoDB跑四个库,A业务经常有大批量聚合查询,每次跑起来都会把WiredTiger缓存占用打满,导致B业务的写延迟飙到几秒。后来拆成两个容器实例,A业务一个,B业务和C业务一个,互相的缓存不再共享。
实测结果是这样的:
| 指标 | 拆容实例前 | 拆容器实例后 | 变化 |
|---|---|---|---|
| B业务平均写延迟 | 850 ms | 270 ms | 约3倍提升 |
| B业务P99写延迟 | 2.3 s | 480 ms | 约4.8倍提升 |
| A业务聚合查询耗时 | 12.6 s | 10.1 s | 约20%提升 |
B业务的平均写延迟从850毫秒降到270毫秒,换算过来就是约3倍的性能提升,说“提升200%”是能站得住脚的。
但这背后有几个不容忽视的前提条件。
前提一,宿主机资源要够。拆成多个实例后,每个MongoDB进程会独立申请自己的WiredTiger cache(默认是物理内存的50%或1GB取较小值),多个实例的内存占用总和会比单实例高。如果你的机器只有4G内存,拆两个实例每个分配1G cache,再算上操作系统开销,反而可能因为内存不足引发swap,性能不仅不提升反而下降。我建议至少8G内存再考虑拆多个实例,16G以上体验会更好。
前提二,性能提升来自“故障域隔离”而不是“多实例本身更快”。单实例多库时,一个库的坏查询会拖累所有库。拆成多实例后,坏查询只影响它所在的实例,其他实例自然就“变快”了。如果你的数据库每个库的负载都很均衡,没有明显的“坏邻居”,拆分后总吞吐不一定会提升。
前提三,数据量很大时,多实例意味着多份内存缓存、多份索引、多份操作系统文件句柄。如果宿主机磁盘I/O本身是瓶颈,拆多少个实例都白搭。
所以各位看到“性能提升200%”这种结论时,一定要结合场景去理解。它的价值在于“隔离问题”,而不是“免费加速”。理解了这一点,你才能在自己负责的架构里做出正确的判断。
5. 平行宇宙运行之后,我踩过的那些坑
5.1 MongoDB容器启动失败的三大原因
方案上线之后,踩坑才真正开始。我把自己和身边同事遇到过的启动失败问题整理成三类,按出现频率排序。
第一类,端口冲突。这种情况多发生在新增宇宙时,宿主机端口已经被别的进程占用。报错信息一般是:
code复制Error starting userland proxy: listen tcp4 0.0.0.0:27017: bind: address already in use
解决方式很简单:换宿主机端口,或者先排查占用进程。
第二类,内存不足。MongoDB容器默认情况下不受Docker内存限制,但它自身会根据宿主机内存大小计算WiredTiger cache。如果你在低配机器上跑多个MongoDB容器,宿主机内存很容易被吃满,导致OOM Killer直接杀掉某个容器进程。容器状态会变成Exited,docker logs里能看到Killed字样。
第三类,数据目录初始化冲突。如果你先用手动方式在数据卷里初始化过一个MongoDB实例,然后又用另一个版本的镜像去启动同一个数据卷,通常会报类似Unclean shutdown detected的错误,或者版本不兼容导致启动中断。解决办法就是给不同版本的MongoDB使用不同的数据卷,别让它们共享一个。
5.2 数据权限引发的“幽灵”故障
还有一个非常容易让人误判的问题:数据目录权限。
如果你用bind mount方式挂载了宿主机目录,比如./db-data:/data/db,容器内的MongoDB进程是以mongodb用户身份运行的,这个用户在宿主机上对应的UID通常是999。如果宿主机的./db-data目录属主是root或其他用户,MongoDB启动时就会因为无法写数据目录而失败。
这种故障特别阴险,因为docker logs里看到的报错信息很简短,有时只是一个Permission denied,不仔细看根本联想不到是目录权限问题。我曾经在这个问题上折腾了一整晚,反复删容器、换镜像版本都没用,最后用ls -ln看了目录属主才发现端倪。
解决办法:要么改用命名数据卷,让Docker自动管理权限;要么显式把目录属主改成999:
bash复制sudo chown -R 999:999 ./db-data
如果你用的还是SELinux强制模式,bind mount还可能被SELinux拦截,需要在挂载参数里加:Z后缀。这个我实际遇到得少,但一旦遇到,报错会很莫名其妙,提前了解一下能少走弯路。
5.3 日志与备份:宇宙也要存档
多实例跑起来之后,日志管理成了新的麻烦。以前一个MongoDB实例,一条journalctl就能看日志;现在两个容器,得分别用docker logs查看。
看单个容器的日志:
bash复制docker logs --tail 100 mongo-alpha
持续跟踪日志:
bash复制docker logs -f mongo-alpha
如果你发现某个容器日志量特别大,占满了磁盘,可以在Compose配置里加上logging驱动限制,比如限制单个文件大小和保留文件数量:
yaml复制services:
mongo-alpha:
logging:
driver: json-file
options:
max-size: "50m"
max-file: "5"
这个配置在长期运行的环境里非常重要。容器日志默认是不限制大小的,一个繁忙的MongoDB实例跑几个月,日志文件能把磁盘写满,到时候整个宿主机都会受影响。
备份也要单独考虑。单实例时,备份命令只需要在那一台机器上跑;多实例时,每个容器都要单独备份。我在实际使用中最常用的备份方式是利用mongodump,直接通过端口访问:
bash复制mongodump --host localhost --port 27017 --username root --password rootpass_alpha --authenticationDatabase admin --out /backup/alpha
mongodump --host localhost --port 27018 --username root --password rootpass_beta --authenticationDatabase admin --out /backup/beta
注意这里是连宿主机端口,而不是容器内部地址,这样即使某个容器挂了,只要另一个容器还活着,备份任务也能独立执行,不会互相拖累。
6. 平行宇宙的进阶玩法
6.1 用同样的思路搭副本集
平行的宇宙跑顺之后,很自然会想到一个问题:如果某个宇宙挂了怎么办?毕竟容器也有宕机的时候。这时候就该考虑副本集了,也就是在“平行宇宙”的基础上,给每个宇宙再拉一个“卫星”。
用Docker部署MongoDB副本集,核心是让每个节点都在同一个Docker网络里,并且使用固定的主机名。Compose配置里可以这样定义:
yaml复制services:
mongo-rs1:
image: mongo:5.0
container_name: mongo-rs1
command: ["mongod", "--replSet", "rs0", "--bind_ip_all"]
networks:
- mongo-universe
volumes:
- rs1-data:/data/db
mongo-rs2:
image: mongo:5.0
container_name: mongo-rs2
command: ["mongod", "--replSet", "rs0", "--bind_ip_all"]
networks:
- mongo-universe
volumes:
- rs2-data:/data/db
然后在任意一个节点里初始化副本集:
javascript复制rs.initiate({
_id: "rs0",
members: [
{ _id: 0, host: "mongo-rs1:27017" },
{ _id: 1, host: "mongo-rs2:27017" }
]
});
这里最关键的细节是host字段必须用Docker网络中的服务名,而不能用localhost或宿主机IP,否则其他节点访问不到。
6.2 和CI/CD流水线结合
平行宇宙不仅能解决本地开发环境的问题,还能在自动化部署里发挥价值。我在GitLab CI/CD里就是这么用的:每次提交代码后,流水线动态生成一个独立的MongoDB容器作为测试数据库,跑完测试直接销毁。因为容器启动只需几秒,整个过程非常流畅。
核心思路是用一份单独的docker-compose.test.yml,专门服务于测试环境:
yaml复制services:
mongo-test:
image: mongo:5.0
ports:
- "27019:27017"
tmpfs: /data/db
注意这里的tmpfs,它把数据目录挂到内存里,测试数据不落盘,速度极快,而且容器销毁后数据自动消失,不需要手动清理。测试库用这种方式,比每次跑完再执行dropDatabase要省心得多。
在流水线里执行:
bash复制docker compose -f docker-compose.test.yml up -d
# 跑测试
docker compose -f docker-compose.test.yml down --volumes
这就是“平行宇宙”在CI/CD里的一个很典型的应用:每个测试任务拥有一个完全隔离、自动销毁、独立配置的数据库环境。
6.3 给宇宙加资源围墙:CPU和内存限制
前面说过,多实例部署的隐患是资源争抢。要彻底解决这个问题,最好在Compose里给每个容器加上资源限制。
yaml复制services:
mongo-alpha:
deploy:
resources:
limits:
cpus: "2.0"
memory: 2G
reservations:
cpus: "0.5"
memory: 512M
同时配合MongoDB自身的配置,把WiredTiger cache限制在一个可控范围。可以让容器启动时指定:
yaml复制services:
mongo-alpha:
command: ["mongod", "--wiredTigerCacheSizeGB", "0.5"]
这个参数很重要。如果你的宿主机内存是16G,理论上MongoDB默认会拿8G作为cache,但如果你在同一个宿主机上跑了三个容器,每个都想拿8G,内存就爆了。限制到0.5G或者1G,让缓存大小可根据实际需求调整,才能保证多个宇宙和平共处。
我在实际使用中一般会结合容器内存限制和--wiredTigerCacheSizeGB一起配。比如给容器的内存限制是2G,那cache就配0.5G,剩余的1.5G留给操作系统和MongoDB的其他开销。这个比例是我用下来比较稳妥的,如果你的读写模式更依赖内存,可以适当调高到1G,但要密切关注宿主机的整体内存占用。
运行一段时间后你会发现,加了资源围墙的平行宇宙,比完全“放养”的容器要稳定得多。其中一个最直接的表现就是:宿主机不会因为某个容器内存失控而被拖垮,其他容器再也不会莫名其妙被OOM Killer杀掉。
