mysql为什么需要缓存?
当随着业务的扩大,数据量逐渐增大,会增加mysql读写压力。
一般在中间增加redis作为mysql的缓存。
mysql缓存方案:缓存用户定义的热点数据,用户直接从缓存获取热点数据,降低数据的读压力。
mysql缓存的使用场景?
- 内存访问速度是磁盘速度的10万倍(数量级)
- 读的需求远远大于写的需求
- MySQL自身缓冲层跟业务无关(比如buffer pool)
- MySQL作为项目主要数据库,便于统计分析
- 缓存数据库作为辅助数据库,存放热点数据
MySQL主从复制
- 主库更新事件 ( update、 insert、 delete) 通过 io-thread写到 binlog;
binlog:用于数据备份/主从复制,确保主从数据一致。
redolog:事务持久化/确保本地数据一致。 - 从库请求读取 binlog,通过 io-thread 写入从库本地 relay-log(中继日志);
- 从库通过 sql-thread 读取 relay-log,并把更新事件在从库中重放(replay)一遍;
复制流程:
4. Slave 上面的 IO 线程连接上 Master,并请求从指定日志文件的指定位置(或者从最开始的日志)之后的日志内容。
5. Master 接收到来自 Slave 的 IO 线程的请求后,负责复制的IO 线程会根据请求信息读取日志指定位置之后的日志信息,返回给 Slave 的 IO 线程。返回信息中除了日志所包含的信息之外,还包括本次返回的信息已经到 Master 端的 binlog 文件的名称以及 binlog 的位置。
6. Slave 的 IO 线程接收到信息后,将接收到的日志内容依次添加到 Slave 端的 relay-log 文件的最末端,并将读取到的Master 端的 binlog 的文件名和位置记录到master-info 文件中,以便在下一次读取的时候能够清楚的告诉 Master 从何处开始读取日志。
7. Slave 的 Sql 进程检测到 relay-log 中新增加了内容后,会马上解析 relay-log 的内容成为在 Master 端真实执行时候的那些可执行的内容,并在自身执行。
提高MySQL性能的方法
读写分离、连接池、异步连接
为什么需要缓冲层?
读多写少,单个主节点能支撑项目数据量;数据的主要依据是 mysql;
MySQL原理及结构
mysql 有缓冲层,它的作用也是用来缓存热点数据,这些数据包括索引、记录等;mysql 缓冲层是从自身出发,跟具体的业务无关;这里的缓冲策略主要是 lru;
mysql 数据主要存储在磁盘当中,适合大量重要数据的存储;磁盘当中的数据一般是远大于内存当中的数据;一般业务场景关系型数据库(mysql)作为主要数据库;
缓冲层的选择
缓存数据库可以选用 redis,memcached;它们所有数据都存储在内存当中,当然也可以将内存当中的数据持久化到磁盘当中;
总结
- 由于 mysql 的缓冲层不由用户来控制,也就是不能由用户来控制缓存具体数据;
- 访问磁盘的速度比较慢,尽量获取数据从内存中获取;
- 主要解决读的性能;因为写没必要优化,必须让数据正确的落盘;如果写性能出现问题,那么可以使用横向扩展集群方式来解决;
- 项目中需要存储的数据应该远大于内存的容量,同时需要进行数据统计分析,所以数据存储获取的依据应该是关系型数据库;
- 缓存数据库可以存储用户自定义的热点数据;以下的讨论都是基于热点数据的同步问题;
缓冲层存在的问题
没有缓冲层之前,我们对数据的读写都是基于 mysql;所以不存在同步问题;这句话也不是必然,比如读写分离就存在同步问题(数据一致性问题);
引入缓冲层后,我们对数据的获取需要分别操作缓存数据库和 mysql;那么这个时候数据可能存在几个状态?
- mysql 有,缓存无
- mysql 无,缓存有
- 都有,但数据不一致
- 都有,数据一致
- 都没有
其中1、4、5都可以接受的状态,需要制定读写策略来避免2,3情况的产生。
读写策略
读策略:先读redis:没命中数据,则读Mysql;redis有数据,则直接返回(是4的情况,不能是2,3)。
读Mysql中:没命中数据,则返回没有;命中数据,则将Mysql读到的数据同步到redis。
写策略:分为安全优先和效率优先两种(insert,update,delete)
安全优先:删除缓存,然后写mysql,接着mysql把数据同步到redis
效率优先:先写redis,如果是插入和更新操作,把key设置过期时间(比如200ms),然后再写mysql,最后mysql同步到redis。(安全问题只会影响200ms)。
通过读写策略可以解决缓冲层的问题,但是在MySQL数据同步到redis时有一些同步方案。
同步方案
- 触发器+UDF函数
- 伪装数据库
触发器+UDF函数
步骤:
- 在MySQL中对要操作的数据设置触发器Trigger,监听操作
- 客户端(NodeServer)向MySQL中写入数据时,触发器会被触发,触发之后调用MySQL的UDF函数
- UDF函数可以把数据写入到Redis中,从而达到同步的效果
结果分析:
这种方案适合于读多写少,并且不存并发写的场景
因为MySQL触发器本身就会造成效率的降低,如果一个表经常被操作,这种方案显示是不合适的
伪装数据库
伪装数据库伪装成从数据库从MySQL中读取binlog数据,数据库读取到数据之后,解析Bin log,然后将数据写入写入同步到Redis中,然后客户端从Redis读数据。
阿里巴巴开源了一个Canal就是作为伪装数据库解决同步问题。
工作原理:
canal模拟mysql slave的交互协议,伪装自己为mysql slave,向mysql master发送dump协议
mysql master收到dump请求,开始推送binary log给slave(也就是canal)
canal解析binary log对象(原始为byte流)
缓存异常情况
缓存穿透
假设某个数据 redis 不存在,mysql 也不存在,而且一直尝试读怎么办?缓存穿透,数据最终压力依然堆积在 mysql,可能造成 mysql 不堪重负而崩溃;redis中没有做一个保护。
解决方法:
- 发现 mysql 不存在,将 redis 设置为 <key, nil> 设置过期时间 下次访问 key 的时候 不再访问 mysql 容易造成 redis 缓存很多无效数据;
- 布隆过滤器(能够确认一定不存在的key),将 mysql 当中已经存在的 key,写入布隆过滤器,不存在的直接 pass 掉;(解决使用不同的非法key来攻击数据库)
缓存击穿
某些数据 redis 没有,但是 mysql 有;此时当大量这类数据的并发连接请求,同样造成 mysql 过大;
解决:
- 分布式锁
请求数据的时候获取锁,若获取成功,则操作后释放锁;若获取失败,则休眠一段时间(200ms)再去获取,当获取成功,操作后释放锁。
优点:没有额外的内存消耗,保证一致性,实现简单
缺点:线程需要等待,性能受影响
- 将非常热点的 key,设置不过期;
缓存雪崩
表示一段时间内,缓存集中失效(redis 无, mysql 有),导致请求全部走 mysql,有可能搞垮数据库,使整个服务失效;
mysql 主要的数据的依据;redis 可有可无的状态;
解决:
缓存数据库在整个系统不是必须的,也就是缓存宕机不会影响整个系统提供服务;
- 如果因为缓存数据库宕机,造成所有数据涌向 mysql;采用高可用的集群方案,如哨兵模式、cluster模式;
- 如果因为设置了相同的过期时间,造成缓存集中失效;
设置随机过期值或者其他机制错开失效时间; - 如果因为系统重启的时候,造成缓存数据消失;
重启时间短,redis 开启持久化(过期信息也会持久化)就行了; 重启时间长提前将热数据导入 redis 当中;
缓存的弊端
不能处理多语句的事务
redis不支持回滚
造成redis,mysql不一致
连接池问题
连接池:热点key总是在同一个连接;同一个key必须要走同一个mysql连接,或者同一个redis连接(通过hash实现)。目的是确保同一个没有并发问题。