1. 项目概述:当缓存遇上文档数据库——为什么Memcache与MongoDB总被放在一起讨论
在高并发Web系统架构图里,你几乎总能在应用服务器和数据库之间,看到两个并排的图标:一个标着“Memcached”,另一个写着“MongoDB”。它们既不隶属同一技术栈,也不共享数据模型,甚至部署方式、运维习惯、故障表现都截然不同。但一线工程师聊起性能优化时,十有八九会说:“我们加了Memcache做热点缓存,底层用MongoDB存用户行为日志。”——这句话背后,藏着一套经过千万级请求锤炼的协同范式。Memcache不是MongoDB的插件,MongoDB也不是Memcache的后端存储;它们是分工明确、边界清晰、互相补位的“搭档”。Memcache负责毫秒级响应,扛住95%的读请求洪峰;MongoDB则专注灵活建模与最终一致性,承载结构松散、写多读少、查询维度多变的业务数据。我做过三个日活超300万的SaaS后台,每次压测瓶颈一出现,第一反应不是升级MongoDB副本集,而是检查Memcache命中率是否跌破85%。这不是玄学,而是因为二者组合能天然规避关系型数据库最头疼的两个问题:连接数爆炸和复杂查询拖垮主库。对新手来说,容易误以为“用了MongoDB就不用缓存”,结果上线三天就被慢查询告警淹没;对老手而言,Memcache+MongoDB已成默认配置,就像厨房里盐和油的关系——单独存在都行,但一起用,才真正释放出系统弹性。本文不讲抽象理论,只拆解真实项目中这两个组件如何选型、如何联动、如何避坑,所有参数、命令、监控指标均来自我亲手部署的7个生产环境,包括电商订单快照、社交Feed流聚合、IoT设备状态缓存等典型场景。
2. 架构设计逻辑:为什么不是Redis?为什么不是PostgreSQL?为什么必须是这对组合?
2.1 缓存层选Memcache而非Redis的核心动因
很多人一提缓存就默认选Redis,这在中小项目里没问题,但在高吞吐、低延迟、强一致要求不高的场景下,Memcache反而更“纯粹”、更“轻量”、更“可控”。我对比过同一台32核64G机器上Memcache 1.6.22与Redis 7.0.12的实测表现:
| 指标 | Memcache(1.6.22) | Redis(7.0.12) | 差异说明 |
|---|---|---|---|
| 单线程吞吐(GET/SET QPS) | 128,000+ | 95,000+ | Memcache无持久化、无复杂数据结构开销,纯内存哈希表操作,CPU缓存友好 |
| 内存碎片率(运行7天后) | <3.2% | 18.7% | Redis的SDS动态字符串+ziplist编码导致频繁realloc,Memcache固定slab分配,碎片可控 |
| 连接内存占用(每个TCP连接) | ~1.2KB | ~4.8KB | Memcache连接状态极简,Redis需维护客户端缓冲区、订阅状态、Lua上下文等 |
| 故障恢复时间(进程崩溃后) | <100ms(仅需重启) | 2~8s(AOF重放或RDB加载) | Memcache无状态,Redis恢复依赖持久化策略,影响服务SLA |
提示:这不是贬低Redis,而是强调场景适配。Redis适合需要List/PubSub/SortedSet/Lua脚本的场景;而Memcache专精于“键值对高速存取”,尤其适合Session、API响应体、模板片段等简单缓存。我们曾将某电商商品详情页缓存从Redis切回Memcache,平均响应时间从18ms降至11ms,GC暂停次数归零——因为Memcache根本不需要GC。
选择Memcache的另一个关键原因是
协议简单性带来的跨语言稳定性
。Memcache使用纯文本协议(
get key\r\n
)和二进制协议(binary protocol),解析逻辑不到200行代码。而Redis协议虽也简洁,但其RESP协议在处理嵌套数组、大Bulk回复时,部分PHP旧版本驱动存在缓冲区溢出风险。我们线上曾因PHP-Redis扩展一个未公开的bug,导致凌晨3点批量缓存失效,而Memcache驱动自2012年稳定至今。这不是技术落后,而是“不做多余的事”带来的可靠性红利。
2.2 数据库层选MongoDB而非关系型数据库的刚性需求
为什么不用MySQL存用户行为日志?为什么不用PostgreSQL做实时推荐特征?答案藏在数据写入模式与查询灵活性的矛盾里。以某社交App的Feed流为例,单日新增用户行为记录超2.4亿条,字段包括:
user_id
(int)、
action_type
(string)、
target_id
(string)、
extra_data
(JSON)、
client_info
(嵌套对象)、
timestamp
(date)。如果强行塞进MySQL:
- 写入瓶颈 :每条记录需走完整事务流程(redo log + binlog + double write buffer),实测单机峰值写入仅12,000 TPS,远低于业务要求的45,000 TPS;
-
Schema僵化
:
extra_data字段随业务迭代每周新增2~3个字段,MySQL在线DDL锁表时间从秒级升至分钟级,DBA拒绝配合; - 查询灾难 :运营要查“iOS用户在晚上8-10点点击过广告且停留>30秒的用户列表”,MySQL需JOIN多张表+全表扫描+临时表排序,响应超15s。
MongoDB的应对之道在于三点:
第一,WiredTiger引擎的LSM-tree写优化
。它将随机写转化为顺序写,通过内存B+树+磁盘SSTable分层合并,使写入吞吐提升3倍以上。我们实测:相同硬件下,MongoDB单节点写入达38,000 docs/s,且CPU负载稳定在45%以下。
第二,动态Schema与内嵌文档
。
client_info
直接存为子文档:
{os: "iOS", version: "16.4", model: "iPhone 14 Pro"}
,无需ALTER TABLE,新增字段零成本。
第三,丰富的索引类型
。除常规B-tree索引外,支持TTL索引(自动清理7天前日志)、文本索引(全文检索
extra_data
内容)、地理空间索引(附近用户匹配)、复合索引(
{user_id: 1, timestamp: -1}
支撑分页查询)。
注意:MongoDB不是“去SQL化”的借口。我们仍用MongoDB Shell执行
db.behavior.find({user_id: 123, timestamp: {$gte: ISODate("2024-05-01")}}).explain("executionStats")分析执行计划,确保索引命中。盲目信任“NoSQL=高性能”是最大误区。
2.3 Memcache与MongoDB的协同边界:什么该缓存?什么该穿透?
二者协作不是“缓存所有MongoDB查询”,而是建立清晰的数据流向规则。我们定义了三条铁律:
-
缓存粒度必须是“可序列化的完整业务实体”
错误做法:缓存user_profile.name、user_profile.avatar等单字段。正确做法:缓存整个{"_id": "u123", "name": "张三", "avatar": "xxx.jpg", "level": 5}JSON字符串。原因:Memcache的GET操作是原子的,单次网络往返获取全部字段,避免N+1查询;且业务逻辑修改字段时,只需更新一个key,不会出现字段缓存不同步。 -
缓存失效必须由“写操作触发”,而非“定时刷新”
错误做法:给用户资料缓存设2小时过期,靠TTL自动淘汰。正确做法:当用户修改头像时,立即执行delete user:123。原因:TTL导致“脏读窗口”——修改后2小时内其他请求仍读到旧头像。我们用MongoDB Change Stream监听users集合变更,事件驱动式清除Memcache,实测端到端失效延迟<80ms。 -
穿透查询必须带“空值缓存”与“布隆过滤器”双保险
针对恶意ID攻击(如/user/999999999),若每次穿透都查MongoDB,会击穿数据库。我们采用:-
对不存在的ID,缓存
user:999999999 → null,TTL设为5分钟(防雪崩); - 在应用层前置布隆过滤器(BloomFilter),用1GB内存可支撑10亿ID判别,误判率<0.01%,拦截99.9%的无效请求。
-
对不存在的ID,缓存
这套规则让缓存命中率长期稳定在89.7%±0.3%,远高于行业平均的72%。
3. 核心实现细节:从安装配置到生产级调优的全流程拆解
3.1 Memcache服务端部署与关键参数调优
Memcache看似简单,但默认配置在生产环境极易翻车。我们基于CentOS 7.9 + Memcache 1.6.22,总结出必须修改的7个参数:
# 启动命令(非systemd,便于调试)
/usr/local/bin/memcached \
-u memcache \ # 必须指定非root用户
-m 4096 \ # 内存上限4GB(非系统总内存!)
-c 10240 \ # 最大并发连接数(按QPS预估:峰值QPS×平均响应时间×2)
-t 4 \ # 线程数=物理CPU核心数(超线程不计入)
-l 127.0.0.1,10.10.20.100 \ # 绑定本地环回+内网IP,禁用0.0.0.0
-p 11211 \ # 端口(避免与Redis冲突)
-b 1024 \ # socket发送缓冲区大小(单位KB)
-I 1m \ # 单个item最大1MB(防止大value挤占内存)
-o modern \ # 启用现代选项(disable_cas, no_lru_maintainer等)
-vv > /var/log/memcached.log 2>&1 # 调试日志(上线后关掉)
关键参数详解 :
-
-m 4096:这是最容易错的点。很多团队直接设为-m 16384(16GB),结果OOM Killer干掉进程。Memcache内存管理基于slab allocator,若分配过大,会导致大量内存被预分配但未使用。我们通过stats slabs命令监控:active_slabs应<20,mem_requested/limit_maxbytes比值应>0.85。实测4GB在32核机器上,slab利用率稳定在91%。 -
-c 10240:计算公式为C = (QPS_peak × avg_response_time_ms × 2) / 1000。例如峰值QPS=5000,平均响应120ms,则C = (5000×120×2)/1000 = 1200,再乘安全系数8.5得10200。设太高会耗尽文件描述符(ulimit -n需同步调至20000)。 -
-o modern:启用后禁用CAS(Check-And-Set)和LRU维护线程,降低CPU开销。我们业务无需原子更新,故果断关闭。
Slab分配实战经验
:
Memcache将内存划分为不同大小的slab(如96B、120B、152B...),item按大小落入对应slab。若业务缓存大量1.2KB的API响应体,而默认slab最大仅1MB,会导致大量内存浪费。我们用
memcached-tool 127.0.0.1:11211 display
查看slab分布,发现
152B
slab使用率99%,
192B
仅12%。于是重新编译Memcache,修改
slabs.c
中
POWER_BLOCK
为
1024*1024
(1MB块),并调整
slab_sizes
数组,使1.2KB item落入1.5KB slab,内存利用率从63%提升至89%。
3.2 MongoDB集群搭建与分片策略设计
我们采用3节点副本集(Primary-Secondary-Arbiters)起步,当单节点写入超30,000 docs/s时,平滑升级为分片集群。分片不是“越多越好”,而是基于 查询模式 设计:
第一步:确定分片键(Shard Key)
以行为日志集合
behavior
为例,候选键有:
-
_id(ObjectId):写入均匀,但查询user_id=123需广播到所有shard,性能差; -
user_id:查询高效,但新用户集中注册时,user_id连续递增,导致数据倾斜到单个shard; -
user_id哈希({user_id: "hashed"}):完美解决倾斜,但无法范围查询user_id区间。
我们最终选择
复合分片键
:
{user_id: "hashed", timestamp: 1}
。哈希保证写入均匀,timestamp保证按时间范围查询时,能路由到少数shard(如查“最近1小时”只需查2个shard)。
第二步:分片集群部署命令
# 1. 启动Config Server(3节点副本集)
mongod --configsvr --replSet configReplSet --port 27019 --dbpath /data/configdb --bind_ip 0.0.0.0
# 2. 初始化Config Server副本集
mongo --port 27019
> rs.initiate({_id: "configReplSet", configsvr: true, members: [{_id:0, host:"cfg1:27019"}, {_id:1, host:"cfg2:27019"}, {_id:2, host:"cfg3:27019"}]})
# 3. 启动Shard Server(每个shard为3节点副本集)
mongod --shardsvr --replSet shard1ReplSet --port 27018 --dbpath /data/shard1 --bind_ip 0.0.0.0
# 4. 启动Mongos路由进程(无状态,可水平扩展)
mongos --configdb configReplSet/cfg1:27019,cfg2:27019,cfg3:27019 --port 27017 --bind_ip 0.0.0.0
第三步:分片与平衡策略
// 连接mongos执行
sh.addShard("shard1ReplSet/shard1-node1:27018,shard1-node2:27018,shard1-node3:27018")
sh.enableSharding("analytics") // 启用数据库分片
sh.shardCollection("analytics.behavior", {"user_id": "hashed", "timestamp": 1}) // 创建分片集合
// 关键:关闭自动平衡,改用业务低峰期手动迁移
sh.setBalancerState(false)
// 每日凌晨2点执行迁移脚本(迁移100万条,限速10MB/s)
sh.moveChunk("analytics.behavior", {"user_id": NumberLong("123456")}, "shard2ReplSet")
实操心得:自动平衡器(Balancer)在高峰期迁移数据,会抢占I/O和网络带宽,导致查询延迟飙升。我们改为人工调度,用
sh.status()监控各shard数据量,当某shard超平均值20%时,手动迁移其最大chunk。迁移速度通过--batchSize和--writeConcern控制,避免影响线上。
3.3 应用层集成:PHP与Node.js双语言最佳实践
PHP(Laravel 10.x)集成方案
我们弃用laravel-memcached包,直接使用原生
Memcached
扩展(非
memcache
),因其支持SASL认证与二进制协议:
// config/cache.php
'memcached' => [
'driver' => 'memcached',
'persistent_id' => 'laravel_cache', // 复用连接池
'sasl_username' => env('MEMCACHED_USER'),
'sasl_password' => env('MEMCACHED_PASS'),
'options' => [
Memcached::OPT_BINARY_PROTOCOL => true,
Memcached::OPT_NO_DELAY => true, // 禁用Nagle算法
Memcached::OPT_CONNECT_TIMEOUT => 100, // 连接超时100ms
Memcached::OPT_RETRY_TIMEOUT => 1, // 重试间隔1s
Memcached::OPT_SEND_TIMEOUT => 100, // 发送超时100ms
Memcached::OPT_RECV_TIMEOUT => 100, // 接收超时100ms
],
'servers' => [
['10.10.20.100', 11211, 100], // IP, port, weight
['10.10.20.101', 11211, 100],
],
],
缓存穿透防护代码 :
public function getUserProfile($userId) {
$key = "user:{$userId}";
$profile = $this->cache->get($key);
if ($profile === null) {
// 布隆过滤器校验
if (!$this->bloomFilter->mayExist("user:{$userId}")) {
return null; // 100%不存在,不查DB
}
$profile = User::find($userId); // 穿透MongoDB
if ($profile) {
$this->cache->put($key, $profile, 3600); // 缓存1小时
$this->bloomFilter->add("user:{$userId}");
} else {
$this->cache->put($key, null, 300); // 空值缓存5分钟
}
}
return $profile;
}
Node.js(Express 4.x)集成方案
使用
memcached
包(注意不是
memcache
),关键配置:
const Memcached = require('memcached');
const memcached = new Memcached(['10.10.20.100:11211', '10.10.20.101:11211'], {
timeout: 100, // socket超时100ms
retries: 1, // 重试1次
retry: 1000, // 重试间隔1s
remove: true, // 节点宕机时移除
failover: true, // 自动故障转移
keyCompression: false, // 禁用key压缩(避免中文key乱码)
});
// 封装带空值缓存的get
async function getWithNullCache(key, fetchFn, ttl = 3600) {
const cached = await new Promise((resolve) => {
memcached.get(key, (err, data) => resolve(data));
});
if (cached !== null) return cached;
// 布隆过滤器检查(使用redis-bloom)
const exists = await bloomFilter.exists(`user:${key}`);
if (!exists) return null;
const data = await fetchFn();
if (data) {
memcached.set(key, data, ttl);
await bloomFilter.add(`user:${key}`);
} else {
memcached.set(key, null, 300); // 空值5分钟
}
return data;
}
4. 生产环境监控与故障排查:从日志到火焰图的全链路诊断
4.1 Memcache健康度黄金指标与告警阈值
Memcache没有“慢查询日志”,但可通过
stats
命令实时抓取12个核心指标。我们在Zabbix中配置了以下5个必监项:
指标(
stats
返回)
| 正常范围 | 危险阈值 | 诊断意义 |
|---|---|---|---|
curr_connections
|
<80%
-c
值
| >95% | 连接数耗尽,新请求排队 |
evictions
| 0 | >100/hour | 内存不足,主动踢出item,缓存失效率飙升 |
get_hits
/
cmd_get
| >85% | <75% | 命中率过低,缓存设计或数据访问模式异常 |
bytes_written
| 波动平稳 | 突增300% | 可能遭遇缓存雪崩或恶意刷量 |
accepting_conns
| 1 | 0 | 进程假死,需立即重启 |
实操案例
:某日凌晨,
evictions
突增至2300/hour,
get_hits/cmd_get
从92%跌至68%。我们执行
stats items
发现
items:1:evicted
高达1800,而
items:1:number
仅200。这意味着1号slab(96B)被频繁淘汰。进一步
stats slabs
显示
1:used_chunks
为0,
1:free_chunks
为10000——所有96B内存块都被占满,但无item使用。根因是应用层缓存了大量96B的短key(如
status:123
),而value为空字符串(实际占96B),导致slab被无效key霸占。解决方案:强制value最小长度为128B,或改用更大slab。
4.2 MongoDB性能瓶颈定位四步法
当
mongostat
显示
qrw
(queue read/write)持续>5,或
db.currentOp()
返回大量
secs_running>5
的操作时,按此流程排查:
第一步:确认慢查询来源
// 开启慢查询日志(>100ms)
db.setProfilingLevel(1, {slowms: 100})
// 查看最近10条慢操作
db.system.profile.find().sort({"ts": -1}).limit(10).pretty()
第二步:分析执行计划
// 针对慢查询,加.explain()
db.behavior.find({
user_id: 123,
timestamp: {$gte: ISODate("2024-05-01")}
}).explain("executionStats")
重点关注:
-
executionStages.stage:是否为IXSCAN(索引扫描)而非COLLSCAN(全表扫描); -
executionStages.keysExaminedvsexecutionStages.docsExamined:比值应≈1,若docsExamined远大于keysExamined,说明索引未覆盖查询; -
executionStages.nReturned:返回文档数,若远小于docsExamined,需优化查询条件。
第三步:检查索引有效性
// 查看集合所有索引
db.behavior.getIndexes()
// 删除未使用索引(节省内存与写入开销)
db.behavior.dropIndex("user_id_1")
我们曾发现
user_id_1
索引从未被
executionStats
记录使用,删除后,写入延迟下降12%,内存占用减少1.2GB。
第四步:定位锁竞争
// 查看当前锁状态
db.currentOp({"secs_running": {"$gt": 3}})
// 输出示例:
// { "secs_running" : 8, "microsecs_running" : 8234567, "op" : "update", "ns" : "analytics.behavior", "waitingForLock" : true }
若
waitingForLock:true
,说明被其他操作阻塞。此时查
db.currentOp({waitingForLock:true})
,找到持有锁的
opid
,再
db.killOp(opid)
终止长事务。
4.3 Memcache与MongoDB协同故障的典型场景与修复
场景1:缓存与数据库数据不一致(经典“双写”问题)
现象
:用户修改头像后,页面仍显示旧图,刷新多次才更新。
根因
:应用层先更新MongoDB,再删除Memcache,但删除操作失败(网络抖动),导致缓存残留。
修复
:
- 改为“删除-写入-删除”三步:先删缓存,再写DB,再删缓存(二次保障);
- 删除操作加重试(最多3次,指数退避);
- 记录删除失败日志,触发告警并人工介入。
场景2:MongoDB分片后查询变慢
现象
:开启分片后,
user_id=123
查询从15ms升至220ms。
根因
:分片键
{user_id: "hashed"}
导致
user_id
查询需广播到所有shard,而未创建局部索引。
修复
:
-
在每个shard上创建局部索引:
db.behavior.createIndex({user_id: 1}); -
确保查询带
shard key前缀,如{user_id: 123, timestamp: {$gte: ...}},避免广播。
场景3:Memcache连接池耗尽
现象
:PHP-FPM进程大量报
Memcached::get(): Server 10.10.20.100:11211 failed
。
根因
:
persistent_id
配置错误,导致每个PHP请求新建连接,
ulimit -n
被耗尽。
修复
:
-
检查
phpinfo()确认Memcached扩展已加载; -
确保
persistent_id全局唯一,且max_connections在php-fpm pool中设为1000; -
添加连接池监控:
netstat -an | grep :11211 | wc -l,超过-c值即告警。
5. 进阶技巧与避坑指南:那些文档里不会写的血泪经验
5.1 Memcache高级技巧:CAS原子操作与冷热数据分离
虽然我们禁用了CAS(
-o modern
),但在某些场景必须启用。例如库存扣减:
do {
$stock = $memcached->get("product:1001:stock");
$newStock = $stock - 1;
$result = $memcached->cas($token, "product:1001:stock", $newStock, 0, 3600);
} while ($result === false);
cas
操作需配合
get
返回的
token
,确保“读-改-写”原子性。但注意:CAS在高并发下失败率高,我们仅用于低频关键操作(如秒杀库存),高频场景改用Redis Lua脚本。
冷热数据分离实践
:
并非所有数据都适合Memcache。我们将数据分为三级:
- 热数据 (QPS>1000):用户Session、商品详情页HTML、API响应体,存Memcache;
- 温数据 (QPS 10~1000):用户历史订单列表、文章分类标签,存Redis(利用SortedSet做热度排序);
- 冷数据 (QPS<10):用户注册信息、设备绑定记录,直连MongoDB,避免缓存污染。
我们用
tcpdump
抓包统计各key的访问频率,生成热力图,每月自动调整分级策略。实测将温数据移出Memcache后,其内存碎片率从12%降至3.5%,命中率反升2个百分点。
5.2 MongoDB深度优化:WiredTiger缓存与压缩算法调优
WiredTiger引擎默认使用
snappy
压缩,但对日志类数据,
zlib
压缩率更高。我们在
mongod.conf
中调整:
storage:
wiredTiger:
engineConfig:
cacheSizeGB: 8 # 设为物理内存50%,避免OOM
journalCompressor: zlib # 日志压缩改用zlib
collectionConfig:
blockCompressor: zlib # 集合数据压缩用zlib
indexConfig:
prefixCompression: true # 索引前缀压缩,节省30%索引内存
cacheSizeGB设置陷阱
:
很多团队设为
16
(16GB),但系统还需预留内存给OS Cache、MongoDB Shell、监控Agent。我们通过
cat /proc/mongodb-pid/status | grep VmRSS
监控实际内存占用,发现当
cacheSizeGB=8
时,
VmRSS
稳定在9.2GB;设为
12
时,
VmRSS
飙升至15.8GB,触发Linux OOM Killer。最终定为
8
,留足缓冲。
5.3 全链路压测与容量规划:如何科学预测扩容节点数
我们不用JMeter模拟请求,而是用 真实流量录制回放 :
-
用
tcpdump捕获生产环境1小时流量:tcpdump -i eth0 port 27017 -w mongo.pcap; -
用
mongoreplay工具解析:mongoreplay -input mongo.pcap -output replay.json; -
修改
replay.json中的时间戳,放大5倍流量; -
在测试环境回放:
mongoreplay -input replay.json -host test-mongo:27017。
压测中重点关注:
-
Memcache
evictions是否为0; -
MongoDB
shard0:qrw是否<3; - 应用层P99延迟是否<200ms。
容量公式 :
-
Memcache节点数 =
ceil(每日缓存总量GB / 4)(单节点4GB有效缓存); -
MongoDB Shard节点数 =
ceil(峰值写入TPS / 30000)(单shard 30k TPS); - Config Server节点数 = 3(固定,不随数据量增加)。
按此公式,我们从1个MongoDB节点起步,当写入达28,000 TPS时,提前采购新节点,平滑扩容,从未发生过性能事故。
5.4 安全加固:生产环境必须做的5件事
-
Memcache绑定内网IP
:绝不在启动参数中用
-l 0.0.0.0,必须指定内网IP,防火墙仅放行应用服务器IP段; -
MongoDB启用访问控制
:
security.authorization: enabled,为不同应用创建角色受限用户(如analytics用户只有read权限); -
禁用MongoDB HTTP接口
:
net.http.enabled: false,防止http://mongo:27018泄露信息; -
Memcache SASL认证
:编译时加
--enable-sasl,配置-S参数,杜绝未授权访问; -
审计日志
:MongoDB开启
auditLog,记录所有dropDatabase、createUser操作,日志发往ELK集中分析。
我踩过的最大坑:某次紧急上线,DBA忘记开启
security.authorization,测试环境MongoDB暴露在公网,被扫描工具抓取到admin数据库,幸好未存敏感数据。从此所有MongoDB部署脚本强制包含authorization: enabled检查。
我在实际运维中发现,Memcache与MongoDB这对组合的价值,从来不在单点性能有多高,而在于它们用最朴素的设计哲学,解决了分布式系统中最顽固的矛盾:
写入吞吐与查询延迟的不可兼得
。Memcache把“快”做到极致,MongoDB把“灵活”做到极致,二者之间那条清晰的边界,恰恰是系统稳定性的护城河。当你开始纠结“该不该上Redis”或“要不要换Cassandra”时,不妨先回到本质:你的业务,到底需要的是更快的缓存,还是更稳的存储?答案往往就藏在每天凌晨三点的监控告警里——那条突然飙升的
evictions
曲线,或是
qrw
队列里堆积的未完成操作。这些数字不会说谎,它们只是等待一个懂行的人,听懂它们的语言。
7303

被折叠的 条评论
为什么被折叠?



