mysql缓存

读写分离

在这里插入图片描述
这里解释是什么,为什么,怎么办。将mysql分为多个库,其中一部分用于读,另一部分用于写,这样的操作叫读写分离。mysql需要读写分离的原因,是因为mysql是对磁盘进行操作,读性能较低。而在实际应用过程中,mysql读操作次数大于写操作次数,读写分离就可以使mysql读写性能提升。如果一致性要求高,写主读主;一致性要求不高,写主读从。

主从复制

所有数据库的主从复制都是很重要的,包括redis。主从复制有多种方法,一是利用socket协议实现主从复制。另一种,介绍mysql的方法。
在这里插入图片描述
binlog与存储引擎没有关系,myisam与innodb都会产生binlog。此外,binlog是在事务提交之后产生,所以不需担心回滚操作会对binlog产生影响。另外,对于innodb而言,redolog在事务提交之后会有刷盘的操作,binlog记录的是行信息。
读写分离是基于主从复制实现的,主要应用在对数据实时性要求不高的时候。比如,刷微博时,看到一篇文章,点进去一看却提示文章已删除。这种延迟能够接受。读写分离的缺点是部署困难,且减轻了一定程度的读写压力。

缓存

缓存的应用有前提,是在读多写少,单个主节点能⽀撑项⽬数据量的情况下。这个时候,数据的主要依据还是mysql,缓存只是提高了读的速度。

mysql缓冲层

为了回答前提的问题,这里先讲两个数据。
1、内存的访问速度是磁盘访问速度的10万倍(数量级倍率)。内存的访问速度⼤约是100ns,⽽⼀次磁盘访问⼤约是10ms,访问mysql时访问磁盘的次数跟b+树的⾼度相关。
2、⼀般⼤部分项⽬中,数据库读操作是写操作次数的10倍左右。
mysql本身有缓冲层,redolog和undolog在内存中都有相应的缓存,插入有insert buffer,修改有change buffer,查询也会将之前查询的结果放入缓存。mysql缓冲层是从⾃身出发,跟具体的业务⽆关,而且这⾥的缓冲策略主要是lru,当然是经过优化的lru,与我们所需的缓冲层功能不一致,不是针对业务进行缓存的,所以需要我们自己设计缓存。

缓存数据库

缓存数据库可以选⽤redis,memcached。它们所有数据都存储在内存当中,当然也可以将内存当中的数据持久化到磁盘当中,内存的数据和磁盘的数据是⼀⽐⼀的。为了更好对比,放个图
在这里插入图片描述
提示几点:
1、由于mysql的缓冲层不由⽤户来控制,也就是不能由⽤户来控制缓存具体数据。
2、访问磁盘的速度⽐较慢,尽量获取数据从内存中获取。
3、缓存主要提升读的性能,因为写没必要优化,必须让数据正确的落盘,如果写性能出现问题,那么请使⽤横向扩展集群⽅式来解决。
4、项⽬中需要存储的数据应该远⼤于内存的容量,同时需要进⾏数据统计分析,所以数据存储获取的依据应该是关系型数据库。
5、缓存数据库可以存储⽤户⾃定义的热点数据。

数据同步问题

引⼊缓冲层后,我们对数据的获取需要分别操作缓存数据库和mysql,那么这个时候数据可能存在缓存和mysql数据不一致的问题。这里缓存和mysql数据是否一致有五种可能:
1、mysql有,缓存⽆。这种情况可以接受,但是要尽量避免,也就是说,通过策略使缓存有该数据。
2、mysql⽆,缓存有。这种情况是错误的,不可接受。
3、都有,但数据不⼀致。不可接受。
4、都有,数据⼀致。正常情况,我们想要达到的就是这种结果。
5、都没有。也正常,说明这个数据就从来没出现过。
下面给出解决数据同步问题的方案,先声明一点,同步是否成功的依据来源于mysql是否同步到redis,即使没有同步成功,也没关系。另外,由于mysql是主要依据,所以同步前应该先判断与mysql的连接是否可用。如果不可用,缓存数据库都不需要写。

强⼀致性

写流程:先删除缓存,再写mysql,后⾯数据的同步交由go-mysql-transfer。
先删除缓存,是为了避免其他服务读取旧的数据,也是告知系统这个数据已经不是最新,建议从mysql获取数据。
强⼀致性只适⽤于单数据中⼼的模型下。多数据中⼼的模型下,不管先操作redis还操作mysql都会引起分布式异常问题的产⽣,此时可以通过加分布式锁的⽅式解决,但是这得不偿失,可以将多数据中⼼转化为单数据中⼼,或者强⼀致性需求读写都⾛mysql,其他⼀致性需求低的⾛最终⼀致性。
在这里插入图片描述

最终⼀致性

主库将数据同步到从库是需要时间的,那么在同步期间,主从之间数据有差异,但是这种差异在一些业务场景中是可以接受的,只要同步过程结束之后,最终数据是一致的就行。
这⾥有写两种⽅案:
第⼀种,直接写mysql,等待mysql同步数据到redis。
第⼆种,先写redis,设置key的过期时间为200ms(经验值),等待mysql回写redis,覆盖key,设置更⻓的过期时间(如2h)。这样在同步的过程中也是通过redis就可以读,不需要访问mysql,减轻了mysql压力。这里200ms 指的是写mysql到mysql同步到redis的时⻓,这个需要根据实际环境进⾏设置。
在这里插入图片描述

同步方案

加入中间件,让中间件处理同步问题,有以下两种中间件:
1、canal
订阅了binlog,伪装成slave与master进行通信,再通过一个client对binlog进行解析,解析后同步到redis。
在这里插入图片描述
2、go-mysql-transfer
将获取binlog与解析同步功能做到一个组件中。
在这里插入图片描述
mysql操作

/*
mysql 配置⽂件 my.cnf
log-bin=mysql-bin # 开启 binlog
binlog-format=ROW # 选择 ROW 模式
binlog redolog binlog 事务提交后产⽣的 不要纠结回滚了怎么办?
server_id=1 # 配置 MySQL replaction 需要定义,不要和 go-mysql-transfer 的
slave_id 重复
*/
CREATE table `t_user`
(
    `id` bigint,
    `name` varchar(100),
    `height` INT8,
    `sex` varchar(1),
    `age` INT8,
    `createtime` datetime,
    PRIMARY KEY(`id`)
);
insert into `t_user` values (10001, 'mark', 180, '1', 30, '2020-06-01');
update `t_user` set `age` = 31 where id = 10001;
delete from `t_user` where id = 10001;

go-mysql-transfer操作

-- go-mysql-transfer
--[[
安装步骤:
GO111MODULE=on
git clone https://gitee.com/0k/go-mysql-transfer.git
go env -w GOPROXY=https://goproxy.cn,direct
go build
修改 app.yml
执⾏ go-mysql-transfer
]]
local ops = require("redisOps") --加载redis操作模块
local row = ops.rawRow() --当前数据库的⼀⾏数据,table类型,key为列名称
local action = ops.rawAction() --当前数据库事件,包括:insert、updare、delete
if action == "insert" then -- 只监听insert事件
    local id = row["id"] --获取ID列的值
local name = row["name"] --获取USER_NAME列的值
local key = name .. ":" .. id
local sex = row["sex"]
local height = row["height"] --获取PASSWORD列的值
local age = row["age"]
local createtime = row["createtime"] --获取CREATE_TIME列的值
ops.HSET(key, "id", id) -- 对应Redis的HSET命令
ops.HSET(key, "name", name) -- 对应Redis的HSET命令
ops.HSET(key, "sex", sex) -- 对应Redis的HSET命令
ops.HSET(key, "height", height) -- 对应Redis的HSET命令
ops.HSET(key, "age", age) -- 对应Redis的HSET命令
ops.HSET(key, "createtime", createtime) -- 对应Redis的HSET命令
end

此外,还可以使用触发器实现数据同步。但是不建议使用。因为触发器具备事务性,外键也具备事务。例如A表与B表通过触发器连接,B表加锁。A表插入,A表阻塞;A表回滚,B表也回滚。但是,有事务的场景同步容易出错,虽然保证了真正的强⼀致性,但是如果回滚了,而redis却不会因此回滚。此外,使用触发器效率也不高。

缓存异常

考虑几个缓存的异常情况

缓存穿透

某个数据redis不存在,mysql也不存在,⽽且⼀直尝试读怎么办?缓存穿透,数据最终压⼒依然堆积在mysql,可能造成mysql不堪重负⽽崩溃。
解决措施:
1、发现mysql不存在,将redis设置为 <key, nil> 并设置过期时间 ,下次通过redis访问key的时候,不再访问mysql。缺点是容易造成redis缓存很多⽆效数据。
2、布隆过滤器,将mysql当中已经存在的key,写⼊布隆过滤器,不存在的直接pass掉,之前讲解过,不再赘述。

缓存击穿

某些数据redis没有,但是mysql有。此时当⼤量这类数据的并发请求,同样造成mysql压力过⼤。
解决措施:
1、加锁
请求数据的时候获取锁,如果获取成功,则操作,获取失败,则休眠⼀段时间(200ms)再去获取,操作结束后释放锁。
⾸先读redis,不存在,读mysql,存在,写redis key的锁,整个流程⾛完,释放锁,才让后⾯的服务器访问。
2、将很热的key,设置不过期。

缓存雪崩

⼀段时间内,缓存集中失效(redis⽆,mysql有),导致请求全部⾛mysql,有可能搞垮数据库,使整个服务失效。
解决措施:
1、如果因为缓存数据库宕机,造成所有数据涌向mysql。采⽤⾼可⽤的集群⽅案,如哨兵模式、cluster模式。
2、如果因为设置了相同的过期时间,造成缓存集中失效。设置随机过期值或者其他机制错开失效时间。
3、如果因为系统重启的时候,造成缓存数据消失。重启时间一般较短,redis开启持久化(过期信息也会持久化)就⾏了,重启时间⻓提前将热数据导⼊redis当中。
注意与缓存击穿区分,缓存击穿是一类数据失效,而缓存雪崩是大量数据同时失效(如宕机)。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值