MySQL实战经验
目录
1、数据库与缓存不一致问题
1.1、问题产生
为什么用缓存?
- 提升读性能:db受限于磁盘限制,缓存基于内存,磁盘与内存的读性能想差甚远
会产生什么问题?
- 数据不一致:数据冗余意味着不一致
1.2、解决办法
要点:并发(时序)、异常情况 指标:缓存利用率、并发安全、一致性
1.2.1、MySQL单机版
最佳实践:先更新数据库,再删除缓存 方案一:更新数据库、更新缓存
- 思路
- 先更新数据库,再更新缓存
- 先更新数据库,再更新缓存
- 问题
- 异常问题:第一步更新成功,第一步更新失败,必将导致不一致。
- 并发问题:并发必将有时序问题。
- 例子
- A线程更新数据库(X=1),B线程更新数据库(X=2)
- B线程更新缓存(X=2),A线程更新缓存(X=1)
- 结果:X的值,数据库=2,缓存=1,不一致持续时长取决于缓存过期时间
- 例子
- 结论:该方案原则上不推荐使用,主要是时序问题无法保证
方案二:更新数据库、删除缓存
-
思路
- 先删除缓存,再更新数据库
- 先更新数据库,再删除缓存
-
问题
- 异常问题:跟第一种方案同理无法避免,异常必将导致不一致
- 并发问题
- 先删除缓存,再更新数据库
- 例子
- 1、线程A要更新X=2(原值X=1)
- 2、线程A先删除缓存
- 3、线程B读取缓存,发现不存在,从数据库读取旧值(X=1)
- 4、线程A将新值写入数据库(X=2)
- 5、线程B将旧值写入缓存(X=1)
- 结果:X的值,数据库=2,缓存=1
- 分析:读写请求并发,A、B线程交替读写,出现概率还是可能的。
- 例子
- 先更新数据库,再删除缓存
- 例子
- 1、缓存中 X 不存在(数据库 X = 1)
- 2、线程 A 读取数据库,得到旧值(X = 1)
- 3、线程 B 更新数据库(X = 2)
- 4、线程 B 删除缓存
- 5、线程 A 将旧值写入缓存(X = 1)
- 结果:X的值,数据库=2,缓存=1
- 分析:出现时机且满足以下三个条件,现实中出现概率较少,存在理论上的可能
- 线程A读请求时,缓存已失效
- 读写请求并发
- 步骤2中读时长 > 「步骤3+步骤4」中的「更新+删除」动作总时长概率较低
- 例子
- 先删除缓存,再更新数据库
-
结论:「先更新数据库,再删除缓存」从分析结果来看能绝大概率解决并发问题,是该命中的最佳实践。
根据「先更新数据库,再删除缓存」仅解决并发问题,如何解决异常问题?
- 思路一:一致性要求不高,容忍短暂不一致,等待缓存到过期时间,不处理
- 思路二:删缓存动作失败,尝试充实重试
- 同步重试:仅在网络抖动丢包的情况
- 异步重试:写MQ异步读取消费,MQ需保证可靠性直到消费成功
- 思路三:监听MySQL的binlog,思路就跟主从原理类似,实现缓存同步。
- 市场上现有工具可参考阿里的canal,「数据库+缓存」、「数据库 + es索引」
1.2.2、MySQL主从 + 读写分离
问题:MySQL主从 + 读写分离架构中,读请求存在延迟导致不一致?
- 线程 A 更新主库 X = 2(原值 X = 1)
- 线程 A 删除缓存
- 线程 B 查询缓存,没有命中,查询「从库」得到旧值(从库 X = 1)
- 从库「同步」完成(主从库 X = 2)
- 线程 B 将「旧值」写入缓存(X = 1) 结果: X 的值在缓存中是 1(旧值),在主从库中是 2(新值),也发生不一致。 分析:在有大事务或资源抢占明显时,主从延时的概率还是存在的,故发生的概率还是会存在
思路:根据经验,预估主从延时时间,将缓存再次删除
- 方案一:更新数据库,删除缓存,休眠等待延时时间,再次删除缓存
- 方案二(推荐):更新数据库,将删动作写入延时队列(延时时间根据经验1-5s),定期消费消息执行删除动作
- 方案三:选择性读主,更新数据库时,记录「库 + 表 + pk」作为键记录到缓存中,读db时根据该键决定是否读主
总结:最佳实践选择「先更新数据库,再删除缓存」的方案实现数据库与缓存一致,根据业务一致性要求和MySQL主从+读写分离架构考虑是否要使用延迟删除方案。
实践:写多读少场景保db高可用,读多写少做缓存高可用 + 容灾缓存。
其他:很多时候,缓存也存在主从架构,甚至还有跨机房延迟等情况,实际从理论上提出完美方案就非常复杂了。最佳办法还是保证每一个子系统子服务保证一致,写的代码保障高质量避免极端情况出现,故才能达到全局最优的方案。
2、数据库与ES同步问题
问题起源:在用户产品研发中,经常会碰到很多复杂多样的查询,如标签、分类、人群等,而实际的元数据却存在于关系型数据库MySQL,关系型数据库对于复杂查询很难支持,同时它也不适合承担太多索引的职责。于是,引入ES充当索引角色,也就是形成复杂标签与原数据ID成映射关系。
-
读请求:查询es索引,找到索引与元数据ID,通过元数据ID查MySQL具体数据,最终返回
-
写请求
- 方式一(同步写):写MySQL、写ES索引,存在写失败问题,扩展性也不好
- 方式二(异步写)
- 来源一:监听MySQL的binlog,将binlog写入MQ,解析binlog将数据同步到ES
- 来源二:业务制定日志规范,打印用户日志,采集用户日志,流式计算日志,将日志导入ES