🎬 HoRain云小助手:个人主页
🔥 个人专栏: 《Linux 系列教程》《c语言教程》
⛺️生活的理想,就是为了理想的生活!
⛳️ 推荐
前些天发现了一个超棒的服务器购买网站,性价比超高,大内存超划算!忍不住分享一下给大家。点击跳转到网站。
专栏介绍
专栏名称 | 专栏介绍 |
本专栏主要撰写C干货内容和编程技巧,让大家从底层了解C,把更多的知识由抽象到简单通俗易懂。 | |
本专栏主要是注重从底层来给大家一步步剖析网络协议的奥秘,一起解密网络协议在运行中协议的基本运行机制! | |
全面深入解析 docker 容器,从基础到进阶,涵盖原理、操作、实践案例,助您精通 docker。 | |
本专栏主要撰写Linux干货内容,从基础到进阶,知识由抽象到简单通俗易懂,帮你从新手小白到扫地僧。 | |
本专栏着重撰写Python相关的干货内容与编程技巧,助力大家从底层去认识Python,将更多复杂的知识由抽象转化为简单易懂的内容。 | |
本专栏主要是发布一些考试和练习题库(涵盖软考、HCIE、HRCE、CCNA等) |
目录
核心矛盾与权衡
策略 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
先更新数据库 | 保证数据持久化优先 | 可能短暂读取到旧缓存 | 强一致性要求高,写后读少 |
先更新缓存 | 读性能更高 | 可能丢失数据(缓存成功但数据库失败) | 读多写少,允许短暂不一致 |
典型场景与解决方案
1. 读多写少场景(如商品详情页)
- 策略:Cache-Aside(旁路缓存) + 延迟双删
// 伪代码示例 public void updateProduct(Product product) { // 1. 先删除缓存(防止旧数据被后续读取) cache.delete(product.getId()); // 2. 更新数据库 db.update(product); // 3. 延迟二次删除(应对步骤1和2之间可能出现的脏读) threadPool.schedule(() -> { cache.delete(product.getId()); }, 500, TimeUnit.MILLISECONDS); }
- 关键点:
- 延迟时间需根据业务读写耗时调整(通常 100ms~1s)
- 通过消息队列(如Kafka)保证二次删除的可靠性
2. 写多读多场景(如秒杀库存)
- 策略:Write-Behind(写缓冲) + 合并更新
// 使用本地缓存合并短时间内的多次写操作 ConcurrentHashMap<Long, AtomicInteger> buffer = new ConcurrentHashMap<>(); public void deductStock(Long itemId, int count) { buffer.compute(itemId, (k, v) -> { if (v == null) v = new AtomicInteger(0); v.addAndGet(count); return v; }); // 异步批量更新数据库 asyncFlushBuffer(); } private void asyncFlushBuffer() { buffer.forEach((itemId, delta) -> { // 使用CAS操作保证原子性 db.execute("UPDATE stock SET count = count - ? WHERE item_id = ?", delta.get(), itemId); cache.decr(itemId, delta.get()); }); buffer.clear(); }
- 关键点:
- 本地缓冲层减少数据库压力
- 最终一致性需结合版本号或时间戳
3. 强一致性场景(如金融交易)
- 策略:2PC(两阶段提交) + 分布式锁
public boolean transfer(TransferRequest request) { // 1. 获取分布式锁(如Redis RedLock) Lock lock = redisson.getLock("lock:transfer:" + request.getId()); if (!lock.tryLock(3, TimeUnit.SECONDS)) throw new RetryException(); try { // 2. 预提交阶段:检查余额并预扣款 Account from = db.selectForUpdate(request.getFromAccount()); if (from.getBalance() < request.getAmount()) return false; // 3. 同时更新缓存和数据库(原子操作) cache.decr("balance:" + from.getId(), request.getAmount()); db.execute("UPDATE accounts SET balance = balance - ? WHERE id = ?", request.getAmount(), from.getId()); // 4. 提交后释放锁 return true; } finally { lock.unlock(); } }
- 关键点:
- 牺牲部分性能换取强一致性
- 需配合数据库行锁(
SELECT ... FOR UPDATE
)
高级优化技术
1. 版本号防ABA问题
-- 数据库表设计增加版本字段
ALTER TABLE products ADD version INT DEFAULT 0;
// 更新时校验版本
public boolean updateWithVersion(Product product) {
int rows = db.update(
"UPDATE products SET name = ?, price = ?, version = version + 1 " +
"WHERE id = ? AND version = ?",
product.getName(), product.getPrice(), product.getId(), product.getVersion());
if (rows > 0) {
cache.set(product.getId(), product.withVersion(product.getVersion() + 1));
return true;
}
return false;
}
2. 读写分离 + 数据同步
- 架构设计:
客户端 → 代理层(ShardingSphere) → 主库(写) / 从库(读) ↓ 缓存集群(Redis)
- 同步工具:
- MySQL Binlog → Canal → Kafka → 缓存更新服务
- 确保数据库变更实时反映到缓存
3. 熔断降级策略
# Hystrix配置示例
hystrix:
command:
default:
circuitBreaker:
requestVolumeThreshold: 20
errorThresholdPercentage: 50
sleepWindowInMilliseconds: 5000
fallback:
enabled: true
- 降级逻辑:
- 缓存不可用时直接读数据库(牺牲性能保可用)
- 数据库压力大时返回静态默认值
决策树:如何选择更新顺序?
-
是否需要强一致性?
- 是 → 2PC + 分布式锁(牺牲性能)
- 否 → 进入下一步
-
写后是否立即高频读取?
- 是 → 先更新缓存(减少缓存击穿)
- 否 → 进入下一步
-
是否有批量写场景?
- 是 → Write-Behind + 合并更新
- 否 → 延迟双删策略
性能压测数据参考
策略 | QPS(万级) | 平均延迟(ms) | 数据不一致时间窗口(ms) |
---|---|---|---|
先更新数据库 | 1.2~1.5 | 8~12 | 50~200 |
先更新缓存 | 2.5~3.0 | 3~5 | 0(但可能永久不一致) |
延迟双删 | 1.8~2.2 | 10~15 | 10~50 |
2PC强一致性 | 0.3~0.5 | 20~50 | 0 |
总结建议
- 常规场景:优先采用 Cache-Aside + 延迟双删,通过消息队列保证最终一致性
- 秒杀类场景:使用 本地写缓冲 + 异步批量更新,结合缓存版本号
- 金融交易场景:必须使用 2PC + 分布式锁,确保强一致性
- 兜底方案:所有缓存设置过期时间(如5~30秒),防止长期不一致
通过合理的策略组合,可以在一致性、可用性和性能之间找到最佳平衡点。实际生产中建议根据业务特点进行A/B测试,使用Arthas等工具监控缓存命中率和数据库负载。
❤️❤️❤️本人水平有限,如有纰漏,欢迎各位大佬评论批评指正!😄😄😄
💘💘💘如果觉得这篇文对你有帮助的话,也请给个点赞、收藏下吧,非常感谢!👍 👍 👍
🔥🔥🔥Stay Hungry Stay Foolish 道阻且长,行则将至,让我们一起加油吧!🌙🌙🌙