MySQL缓存策略

提高MySQL性能的方法有如下几种:

  1. 连接池:阻塞IO+线程池
  2. 异步连接:非阻塞IO
  3. sql执行过程优化:不选即使执行,而选择预编译执行(跳过词法句法分析、权限验证、优化器,直接选择前面已经生成的执行计划(接口:prepare))
  4. 读写分离
  5. 缓存分案

一、MySQL主从复制

主从复制主要解决单点故障问题。主从复制实现的是最终一致性。

1. 主从复制流程

  1. 主库更新事件(update、insert、delete)通过io-thread写到binlog;
  2. 从库请求读取binlog,通过io-thread写入(write)从库本地relay log(中继日志);
  3. 从库通过sql-thread读取(read)relay log,并把更新事件在从库中执行(replay)一遍

2. 具体复制流程

  1. . Slave上面的IO进程连接上Master,并请求从指定日志文件的指定位置(或者从最开始的日志)拷贝之后的日志内容。
  2. Master接收到来自Slave的IO进程的请求后,负责复制的IO进程会根据请求信息读取日志指 定位置之后的日志信息,返回给Slave的 IO进程。返回信息中除了日志所包含的信息之外, 还包括本次返回的信息已经到Master端的bin-log文件的名称以及bin-log的位置。
  3. Slave的IO进程接收到信息后,将接收到的日志内容依次添加到Slave端的relay-log文件的最 末端,并将读取到的Master端的 bin-log的文件名和位置记录到master-info文件中,以便在 下一次读取的时候能够清楚的告诉Master从何处开始读取日志。
  4. . Slave的Sql进程检测到relay-log中新增加了内容后,会马上解析relay-log的内容成为在 Master端真实执行时候的那些可执行的内容,并在自身执行。

二、读写分离

读写分离能优化数据库的写性能。但是读写分离场景难布署。

1. 最终一致性

写主读从。

数据写入主数据库,主从同步,从从数据库读取数据。

2. 强一致性

数据库写入主数据库。

当一致性要求高时,从主数据库读取数据;当一致性要求不高时,从从数据库读取数据。

三、缓存方案

缓存方案包括读写策略(主要是写策略)和同步策略。

写策略主要考虑是遵守最终一致性还是强一致性。

1. 前提

读多写少,单个主节点能支撑整个项目的数据量;数据的主要依据是mysql。

讲热点读数据在缓存数据库备份,讲热点读操作转移到缓存数据库。

2. 缓冲层

2.1 mysql的缓冲层

mysql有缓冲层,它的作用是用来缓存热点数据,这些数据包括数据文件、索引文件等;mysql缓冲层是从自身触发,跟具体业务无关;这里的缓冲策略主要是经过优化的LRU。

mysql数据主要存储在磁盘当中,适合大量重要数据的存储;磁盘当中的数据一般是远大于内存当中的数据;一般业务场景关系型数据库(mysql)作为主要数据库。

2.2 外置缓冲层

缓存数据库可以选用redis、memcached;它们所有数据都存储在内存当中,当然也可以将内存当中的数据持久化到磁盘当中;内存的数据库和磁盘的数据是一比一的。

2.3 存储比较

3. 几项重要的数据

  1. 内存的访问速度是磁盘访问速度的10万倍(数量级倍率);内存的访问速度大约是100ns,而一次磁盘访问大约是10ms;访问mysql时访问磁盘的次数跟b+树的高度相关。
  2. 一般大部分项目中,数据库读操作是写操作的10倍左右。

4. 应用场景分析

  1. 内存访问速度是磁盘访问速度的10万倍
  2. 大多数业务场景读次数是写次数的10倍以上
  3. mysql自身的缓冲层跟业务无关,我们需要跟业务相关的缓存数据
  4. mysql作为项目主要数据库,便于统计分析
  5. 缓存数据库作为辅助数据库,存放热点数据

缓存方案主要优化的是读性能。

5. 同步问题分析

5.1 原因

没有缓存层之前,我们对数据库的读写都是基于mysql,所以不存在同步问题。(这句话不是绝对的,比如读写分离的场景也会存在同步问题)。

引入了缓存数据库后,需要引入同步策略,使缓存数据库和mysql的数据保持一致。

5.2 存在的数据状态

  1. mysql有数据,缓存无数据(通过策略将mysql数据同步到redis)
  2. mysql无,缓存有(不可以接受,通过策略避免)
  3. 都有数据,但是数据不一致(不可以接受,通过策略避免)
  4. 都有且数据一致(策略的目标状态)
  5. 都没有(这是正常状态)

由于数据的主要依据是mysql,所以最重要的是保证mysql数据的正确性。对于情况2、3,这是比较危险的数据状态,我们应该通过缓存策略避免这种情况的发生。

当缓存不可用时,我们的整个系统应该能保持正常工作;当mysql不可用时,系统应停止对外服务。

5.3 目标场景

当只有一个数据请求中心时,数据请求中心直接访问数据库+缓存。如部分游戏业务场景,有单独的DBServer。

当有多个数据请求中心时,我们可以在数据请求中心和数据库+缓存间设置一个代理层,将多个数据请求中心伪装成一个数据请求中心。如大部分的web项目,server端不缓存数据,直接操作数据库。

5..4 缓存设置建议

对于重要数据,不应该作为热点数据存储在cache中;频繁变动的数据也不适合放在cache中。

6. 最终一致性解决方案

6.1 读策略

先读缓存,缓存存在直接返回;缓存不存在,去访问mysql获取,再写redis。

6.2 写策略

直接写mysql,等到mysql同步到缓存。

这种写操作可能会带来的问题是,写mysql时,缓存中可能存在相关的旧数据,此时并发读操作可能没有读到最新的数据。

最终一致性保证的是数据库和缓存最终能数据一致,允许中间出现不一致状态。适用于一致性要求不高的场景,如博客的修改更新。

7. 强一致性解决方案

因为多个数据中心最终能转换成一个数据中心,所以我们此处只分析一个数据中心的情况。

7.1 读策略

先读缓存,缓存存在直接返回;缓存不存在,去访问mysql获取,再写redis。

7.2 写策略

写策略有两种:

  1. 先删除缓存,再写mysql,等到mysql同步到缓存
  2. 直接在缓存写入新的数据并设置该条数据的过期时间,再写mysql,等到mysql同步到缓存(当同步到缓存时记得删除该数据的过期时间)

从性能分析,第二种写策略比第一种写策略的性能要好,特别是频繁修改数据时。但是,第二种写策略也带来了丢失更新的问题。

当我们选择第二种写策略时,当对mysql的修改时,mysql宕机导致修改失败,而在此前又有客户端通过cache获取了新数据。此时,对于mysql而已,丢失了更新;对于客户端而言,因为mysql没有更新成功,所以读取的是错误数据(mysql宕机,此时服务端会停摆,所以客户端的后续不会影响数据库,不会影响到最终数据的正确性)。

针对第二种写策略,缓存中新数据的过期时间的设置应该考虑访问mysql后mysql同步到cache的时间。

8. 数据同步方案

数据同步是在对mysql的写操作完成后发生的,负责将mysql的数据更新同步到cache。

8.1 触发器+UDF

该方案的缺点比较明显,UDF不具备事务,不能回滚;并且该方案效率低。

https://gitee.com/josinli/mysql_redis

8.2 go-mysql-transfer

go-mysql-transfer是伪装从数据库 从mysql复制数据。它的缺点是需要引入zk等实现高可用。

https://www.cnblogs.com/unrealCat/p/16114732.html

8.3 cancel+cancel客户端

cancel也是伪装成从数据库从mysql复制数据。但是它的流程因为有一个客户端中间件,所以比go-mysql-transfer多一步流程。但是cancel+cancel客户端方案具备高可用性。(常用于java)

9. 缓存故障

缓存穿透常见于黑客攻击。缓存击穿和缓存雪崩的区别是,缓存击穿的同个数据的并发请求,而雪崩是大量数据的缓存丢失。

9.1 缓存穿透

假设某个数据redis不存在,mysql也不存在,而且一直尝试读怎么办?缓存穿透,数据最终压力 依然堆积在mysql,可能造成mysql不堪重负而崩溃;

解决方案:

  1. 发现mysql不存在,将redis设置为 <key, nil=""> 设置过期时间 下次访问key的时候 不再访问 mysql 容易造成redis缓存很多无效数据; 
  2. 布隆过滤器,将mysql当中已经存在的key,写入布隆过滤器,不存在的直接pass掉;

9.2 缓存击穿

某一些数据redis没有,但是mysql有;此时当大量这类数据的并发请求,同样造成mysql 访问量过大;

解决方案:

  1. 使用分布式锁。请求数据的时候获取锁,如果获取成功,则操作,获取失败,则休眠一段时间(200ms)再去获取;获取成功,则释放锁。读取数据的流程为:首先读redis,不存在,读mysql,存在,写redis,写完释放key的锁。
  2. 将很热的key设置为不过期。

9.3 缓存雪崩

在一段时间内,缓存集中失效(redis无 mysql 有),导致请求全部走mysql,有可能搞垮数据库, 使整个服务失效;

解决方案:

缓存数据库在整个系统不是必须的,也就是缓存宕机不会影响整个系统提供服务;

  1. 如果因为缓存数据库宕机,造成所有数据涌向mysql,可采用高可用的集群方案,如哨兵模式、cluster模式。
  2. 如果因为设置了相同的过期时间,造成缓存集中失效,泽设置随机过期值或者其他机制错开失效时间。
  3. 如果因为系统重启时,造成缓存数据消失。当重启所需时间短,redis开启持久化(过期信息也会持久化)就行了;当重启时间较长,则选择提前将热数据导入redis当中。

 

posted @   幻cat  阅读(162)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示