Mysql 缓存策略
创始人
2024-04-02 08:51:39
0

文章目录

    • 1、访问性能提升
      • 1.1、读写分离
        • mysql 主从复制
      • 1.2、连接池
      • 1.3、异步连接
    • 2、缓存方案
      • 2.1、场景分析
        • 2.1.1、存储层
        • 2.1.2、缓存层
        • 2.1.3、热点数据
      • 2.2、一致性状态分析
      • 2.3、读写策略
        • 2.3.1、读策略
        • 2.3.2、写策略
          • 安全优先
          • 效率优先
      • 2.4、同步策略
        • 伪装从库
    • 3、缓存故障
      • 3.1、缓冲穿透
      • 3.2、缓存击穿
      • 3.3、缓存雪崩
      • 3.4、缓存方案的弊端

工程中通常权衡效率和安全

  • 效率优先:以 redis 作为主数据库读写,mysql 作为备份数据库,性能最高,安全性较差,可以使用伪装从数据库 pika,从 redis 拉取数据,持久化。
  • 安全优先:以 mysql 作为主数据库读写,redis作为缓存数据库,缓存热点数据,用户只能从 redis 获取热点数据,降低 mysql 的读写压力。

本文主要讨论第二种方案。

1、访问性能提升

1.1、读写分离

为了解决读压力,设置多个从数据库,读操作在从数据库,写操作在主数据库,主数据库提供数据的主要依据。

在这里插入图片描述

原理:主从复制。主从复制是异步复制方式,主从数据会有差异,读写分离只能保证数据最终一致性。若要保证强一致性,则读操作去读主数据库。

mysql 主从复制

在这里插入图片描述

主从复制流程

  • 主库更新事件(DML 操作)通过 io-thread 写到 binlog
  • 从库请求 binlog,通过 io-thread 写入从库本地 relaylog 中继日志
  • 从库通过 sql-thread 读取 relay-log,并把更新事件再从库中重放 repaly 一遍

1.2、连接池

为了并发提升数据库访问性能,在服务端创建多个与数据库的连接,同时复用连接,避免连接建立、连接断开、安全验证的开销。

原理:mysql 网络模型(IO 多路复用 select + 阻塞 IO)

特别地,如果发送一个多语句事务,那么该事务必须在一个连接中处理。

1.3、异步连接

减少网络传输时间,在服务创建一个非阻塞连接,使用非阻塞 IO

2、缓存方案

2.1、场景分析

业务场景中,读的需求远远大于写的需求,应当主要解决读性能。对于写没必要优化,必须保证写的数据正确落盘。所有数据存放在主数据库,热点数据存放在缓存数据库,用户只从缓存数据库获取热点数据,减少主数据库的读压力。

对于整个系统来说,缓存数据库不可用,系统仍然保持正常工作;主数据库不可用,系统停止对外提供服务。

2.1.1、存储层

主数据库选择一般选择关系型数据库,例如 mysql,原因是:

  • 存储大量数据,磁盘存储
  • 数据统计分析,关系型数据库

注:mysql 的自身缓存层(缓存索引,记录)与业务无关。

2.1.2、缓存层

缓存数据库可以选用内存数据库 redis, memcached,作为辅助数据库,存放热点数据。

2.1.3、热点数据

热点 key 总是在同一条连接操作。具体来说,同一个热点 key 在同一个 mysql 连接操作,同一个 redis 连接操作。以 hash(key) 的方式实现。

原因是保证同一个 key 没有并发问题,避免加锁。

2.2、一致性状态分析

以主数据库 mysql 和缓存数据库 redis 为例,获取热点数据分别操作 redis 和 mysql

数据的状态可能有:

  1. mysql 有,redis 无
  2. mysql 无,redis 有
  3. 都有,但数据不一致
  4. 都有,数据一致
  5. 都没有

状态 1 4 5,没有问题。对于状态1,获取数据的主要依据是 mysql,只需要将 mysql 的数据正确同步到 redis 就可以了。

状态 2 3,存在数据不一致的问题。对于状态2,考虑 mysql 会主动断开长时间没有操作的连接,来减少系统资源消耗,此时若同时向 redis 和 mysql 写操作,redis 写入成功,mysql 写入失败,产生脏数据。对于状态3,mysql 同步到 redis 是异步复制,短时间内会出现数据不一致。

因此设置读写策略,就是为了解决上述状态 2 3 存在的数据不一致问题。

2.3、读写策略

2.3.1、读策略

准确来说,是热点数据读策略,非热点数据直接读数据库。

先读缓存层

  • 没有,读 mysql
    • 没有,则返回没有
    • 有,mysql 读到的数据同步到 redis
  • 有,直接返回

2.3.2、写策略

安全优先

先删除 redis 缓存,再写 mysql,等待 mysql 数据同步到 redis。- 状态1

问题:为了安全降低效率,不断删除缓存,设置缓存没有了意义。

效率优先

先写 redis 缓存并设置过期时间,再写 mysql,等待 mysql 同步到 redis。若 mysql 写失败,redis 缓存数据过期失效;若 mysql 写成功,mysql 数据同步到 redis 会覆盖之前设置的过期时间。

设置过期时间大致为:mysql 网络传输时间 + mysql 处理时间 + 同步时间,200ms。

问题:过期时间内,缓存层和存储层数据不一致。如果没有写入主数据库,则这段时间内,提供了脏数据服务。

2.4、同步策略

如何将 Mysql 数据同步到 redis 中

  • 伪装从数据库:阿里 canal,go-mysql-transfer 等

  • 触发器 + udf:把热点数据表设置触发器,在触发器中调用 udf,udf 与 redis 建立连接,进行数据同步。效率低,不建议。

伪装从库

在这里插入图片描述

伪装从数据库以 go-mysql-transfer 为例,缺点是需要引入 zk,etcd 等实现高可用

安装 go-mysql-transfer

# 安装 Golang 1.14 及以上版本
wget https://golang.google.cn/dl/go1.17.8.linux-amd64.tar.gz
tar -zxvf go1.17.8.linux-amd64.tar.gz
# 配置
vim /etc/profile
export PATH=$PATH:/opt/go/bin  # 配置 go 环境变量# 安装 go-mysql-transfer
git clone https://gitee.com/mirrors/go-mysql-transfer.git
GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
go build

修改 mysql 配置文件,位置:/etc/mysql/my.cnf

log-bin=mysql-bin # 开启 binlog
binlog-format=ROW # 选择 ROW 模式
server_id=1 # 配置 MySQL replaction 需要定义,不要和slave_id重复

修改 app,yml,配置 mysql 和 redis,配置热点数据

# mysql配置
addr: 127.0.0.1:3306
user: root
pass: 123456
charset : utf8
slave_id: 1001 #slave ID# redis连接配置
redis_addrs: 127.0.0.1:6379 # redis地址,多个用逗号分隔
redis_pass: 123456 # redis密码# 配置热点数据
schema: mark # 数据库名称
table: t_user # 表名称
order_by_column: id #排序字段,存量数据同步时不能为空
column_underscore_to_camel: true # 列名称下划线转驼峰,默认为false
lua_file_path: lua/t_user.lua   # lua脚本文件位置
# redis相关    
redis_structure: hash # 数据类型。

编写 Lua 同步逻辑

local ops = require("redisOps") --加载redis操作模块local row = ops.rawRow()  --当前数据库的一行数据,table类型,key为列名称
local action = ops.rawAction()  --当前数据库事件,包括:insert、update、delete-- 同步方法
if action == "insert" or action == "update" then -- 只监听insert事件local id = row["id"] --获取ID列的值local key = "user:" .. idlocal name = row["nick"] --获取USER_NAME列的值local sex = row["sex"]local height = row["height"] --获取PASSWORD列的值local age = row["age"]ops.HSET(key, "id", id) -- 对应Redis的HSET命令ops.HSET(key, "nick", name) -- 对应Redis的HSET命令ops.HSET(key, "sex", sex) -- 对应Redis的HSET命令ops.HSET(key, "height", height) -- 对应Redis的HSET命令ops.HSET(key, "age", age) -- 对应Redis的HSET命令
elseif action == "delete" thenlocal id = row['id']local key = "user:" .. idops.DEL(key)
end

启动 mysql, redis, go-mysql-transfer

# 全量数据同步,初次启动
./go-mysql-transfer -stock
# 启动
nohup go-mysql-transfer &

3、缓存故障

3.1、缓冲穿透

问题

  • 特征:缓存层无,存储层无
  • 描述:一直读取缓存层和存储层都不存在的数据,缓存穿透,可能造成数据库崩溃
  • 原因:非法请求不存在数据,误操作删除已存在数据

解决

  • 缓存空对象:当发现存储层不存在该 key,缓存层设置 并设置过期时间,下次访问 key 的时候不再访问存储层。但是会造成缓存数据库缓存很多无效数据,浪费内存;而且过期时间这段时间内缓存层和存储层数据不一致。
  • 非法请求限制:判断若是恶意请求,直接返回错误。
  • 部署布隆过滤器:将存储数据库已经存在的 key,写入布隆过滤器。即使发生了缓存穿透,大量请求也只会查询缓存数据库和布隆过滤器。最好在缓存数据库上部署布隆过滤器。

3.2、缓存击穿

问题

  • 特征:缓存层无,存储层有
  • 原因:频繁访问的热点数据过期
  • 描述:缓存热点数据过期,大量并发连接请求该热点数据,可能造成数据库崩溃

解决

  • 过热数据不过期:热点 key 不过期
  • 分布式锁:保证同一时刻只能有一个线程更新缓存,其他请求线程只能等待该线程运行完毕,重新从 redis 中获取数据

3.3、缓存雪崩

问题

  • 特征:缓存层无,存储层有
  • 原因:大量缓存数据同时过期,缓存数据库宕机
  • 描述:同一时间,大量缓存数据集中失效,所有请求涌向数据库,可能造成数据库崩溃。

解决

  • 大量缓存同时过期:设置 key 间隔过期或者随机过期,均匀设置过期时间。
  • 缓存数据库宕机:构建缓存高可用集群,主从切换。
  • 缓存数据库重启:重启时间短,缓存数据库开启持久化;重启时间长,缓存数据库预热(预先导入热数据)。

3.4、缓存方案的弊端

不能处理多语句事务。这是因为缓存数据库不支持回滚,造成缓存数据库 redis 与存储数据库 mysql 数据不一致。

相关内容

热门资讯

公公73岁寿宴上,儿媳哽咽感谢... 近日,陕西西安的毛女士在公公73岁寿宴上哽咽致谢,感谢老人主动帮忙带娃,该视频引发热议。 毛女士对记...
巴拿马总统府下令:立即在原址修... 来源:红星新闻 据新华社巴拿马城12月28日电 巴拿马总统府28日发布公告,明确反对拆毁位于巴拿马运...
中指·政策要闻丨住建部部署20... 获取最新政策解读报告 ☞ 戳这里,加入地产/物业/投拓/产城 摘要: 全国住建工作会议召开,部署2...
专业文章丨跨境投资中对东道国法... 【珠海律师、珠海法律咨询、珠海律师事务所、京师律所、京师珠海律所】 (本文转载自北京市京师郑州律师事...
刚见完特朗普,泽连斯基称他将与... 【环球网报道】据美国哥伦比亚广播公司(CBS)等媒体报道,乌克兰总统泽连斯基与美国总统特朗普会晤后表...
亚特兰大0-1小胜国米,赛后评... 在意甲联赛第17轮的较量中,国际米兰在客场以1-0小胜亚特兰大,继续稳居积分榜首位。然而,赛后的评分...
詹姆斯24+5东契奇34+5+... 【搜狐体育战报】北京时间12月29日NBA常规赛,主场作战的湖人以125-101击败国王。艾顿11分...
原创 挑... 高市早苗政府近期对中国发起的一系列挑衅,似乎是一场注定要失败的豪赌。自从她11月7日发表了一些极具争...
最高法:助力完善破产制度,畅通... 最高人民法院12月29日发布7件人民法院惩治逃废债典型案例。据介绍,此次发布的典型案例覆盖面广,扩大...
黑龙江妇幼健康惠民政策再升级 人民网哈尔滨12月29日电 (记者张齐)近年来,黑龙江省卫生健康委员会扎实推进妇女儿童健康保障工作,...