MySQL缓存策略

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 Tair(兼容Redis),内存型 2GB
简介: MySQL缓存策略

一、MySQL缓存方案的作用


首先。我们进行一些具体的场景分析:


1)业务场景中,读的需求远远大于写的需求,因此应当主要关注如何提高读的性能。对于写没必要优化,但必须保证让数据正确的落盘。


2)另外,内存访问速度是磁盘访问速度的10万倍,访问磁盘的速度比较慢,因此尽量使得数据是从内存中获取。


3)项目中需要存储的数据应该远大于内存的容量,同时需要进行数据统计分析,所以数据存储获取的依据应该是关系型数据库,如MySQL 数据主要存储在磁盘当中。


4)MySQL自身缓冲层跟业务无关,这是由于 MySQL的缓冲层不由用户来控制,也就是不能由用户来控制缓存具体数据。MySQL的缓冲策略主要是 LRU。因此引入缓冲层,即需要一个缓存数据库,可以存储用户自定义的热点数据。如redis、memcached。


综合以上几点,MySQL缓存方案是:所有数据存放在主数据库,缓存数据库作为辅助数据库存放自定义的热点数据。这样用户可以直接从缓存获取热点数据,降低主数据库的读压力。


接下来,我们介绍几种提高MySQL访问性能的方式。


二、提高MySQL访问性能的方式


2.1 读写分离

2.1.1 是什么?

读写分离会设置多个从数据库,写操作依然在主数据库,而读操作在从数据库。


需要注意的是,从数据库可能会在多个机器中,主数据库是提供数据的主要依据。如果读操作有强一致性要求,那么还是需要去读主数据库。

9c39be4ae607402706513ca0b39e11f2_379fe2186610424894e2b4b8e97d7e3c.png


2.1.2 解决了什么?

读写分离提高设置多个从数据库来解决主数据库的读压力。


2.1.3 原理是什么?

读写分离主要依据MySQL的主从复制原理,因为MySQL的主从复制是异步复制的,所以读写分离只能保证数据的最终一致性,不能保证实时一致性。如果读操作有强一致性要求,那么需要读操作去读主数据库。


MySQL主从复制


主从复制流程:


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

9feb6930caa69c07842b8223be06cc7d_84762b4839764f729cae572d9bb10ba9.png

2.2 连接池

2.1.1 是什么?

连接池是在服务端当中创建多个与数据库的连接。参考Linux组件之数据库连接池

11a26bec4386469b1539ac9826ee32f4_89b4d621c8ab4aa58047db1e52f9b8e9.png


2.1.2 解决了什么?

数据库连接池并发提升了数据库的访问性能。同时复用连接,避免连接建立、断开、以及安全验证带来的额外开销。


2.1.3 原理是什么?

MySQL网络模型(select + 阻塞IO模型)


特别的,如果发送一个事务(多个sql语句),那么这个事务必须在一个连接中执行。


2.3 异步连接

2.1.1 是什么?

异步连接是在服务端创建一个连接,针对这个连接采用非阻塞IO


2.1.2 解决了什么?

异步连接节省了网络传输时间。


2.1.3 原理是什么?

使用了非阻塞IO


三、缓存方案


3.1 缓存和MySQL一致性状态分析

引入缓冲层后,我们对数据的获取需要分别操作缓存数据库和MySQL;那么这个时候数据可能存在几个状态:


  1. MySQL有,缓存无
  2. MySQL无,缓存有
  3. 都有,但数据不一致
  4. 都有,数据一致
  5. 都没有

对于以上的几种情况:


  • 4 和 5显然是没问题的
  • 低于1,我们获取数据的主要依据是 mysql,只需要将mysql 的数据正确同步到缓存数据库就可以了。
  • 对于2,缓存有,mysql 没有,这比较危险,此时我们可以认为该数据为脏数据。需要在同步策略中避免该情况发生。
  • 对于3,mysql 和缓存都有数据,但是数据不一致。这是mysql 同步到 redis 是异步复制,短时间内会出现数据不一致。这种也需要在同步策略中避免。

需要注意的是:缓存不可用,整个系统依然要保持正常工作;mysql 不可用的话,系统停摆,停止对外提供服务;


3.2 读写策略解决数据同步问题

3.2.1 读策略

准确来说,是热点数据读缓存,非热点数据直接读主数据库。


先读缓存

1)若缓存存在,直接返回;

2)若缓存不存在,再访问mysql

若 mysql 没有,则返回没有

若 mysql 有,同步数据到redis


3.2.1 写策略

写策略分两种:以安全为主、以效率为主。


1、以安全为主的写策略

先删除redis当中的数据,然后再写MySQL,最后将MySQL数据同步到redis中(交由 go-

mysql-transfer 等中间件处理)。这种策略将状态 3 转化成状态 1;


为什么先删除redis数据?

先删除缓存,是为了避免其他服务读取旧的数据;也是告知系统这个数据已经不是最新,建议从 mysql 获取数据。


存在的问题:

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


2、以效率为主的写策略

先写缓存并设置过期时间(如 200ms),再写MySQL,等待MySQL同步到redis中(交由中间件处理)。


这里设置的过期时间是预估时间,大致上是 :

与MySQL网络传输时间 + MySQL处理时间 + MySQL同步到redis的时间


存在的问题:

在过期时间内,如200ms,如果写的过程中 mysql 停止服务,或数据没写入 mysql,则200 ms 内提供了脏数据服务;但仅仅只有 200ms 的数据错乱。


3.3 同步方案

同步方案主要解决如何将 Mysql 数据同步到 redis 中。主要有两种方法:

1)伪装从数据库。比如阿里 canal,go-mysql-transfer 等。

2)触发器 + udf:把热点数据表设置触发器,在触发器中调用 udf,udf 与 redis 建立连接,进行数据同步。udf全称User-defined function,是MySQL提供的一种可扩展代码。UDF不具备事务,不能回滚。总体而言这种方法效率较低,不建议。


3.3 伪装从数据库

3.3.1 阿里canal

Canal可以实时捕获MySQL等数据库中的数据变更,并将这些变更事件传递给redis等缓存数据库,实现数据的实时同步和复制。并且canal会考虑分布式问题,如果一个canal宕机了,会有从canal顶替上来,保证服务正常提供。

96f18901efa7ebe47af1c56c69488a2b_bfaa287d66c5483ba633684c0aebc296.png

3.3.2 go-mysql-transfer

go-mysql-transfer是一个基于Go语言开发的数据库变更数据传输工具,它可以实时捕获MySQL数据库中的数据变更,并将变更事件传输到给redis等缓存数据库。go-mysql-transfer只有一个节点,相对canal简单些,没有解决分布式问题。


缺点是需要引入etcd、zk等实现高可用。


go-mysql-transfer的项目地址go-mysql-transfer产品手册


具体流程是:

1)安装 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

2)修改 mysql 配置文件为主从模式,位置:/etc/mysql/my.cnf


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

3)修改 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 # 数据类型。

4)编写 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:" .. id
    local 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" then
    local id = row['id']
    local key = "user:" .. id
    ops.DEL(key)
end

5)启动 mysql, redis, go-mysql-transfer

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


四、缓存故障及解决


4.1 缓存穿透

如果某个数据在redis缓存和MySQL中都不存在,但此时一直尝试读这个不存在的数据,最后数据压力堆积在MySQL,可能会造成MySQL崩溃。


例如恶意攻击者可以通过构造大量不存在的查询请求来压垮数据库。


解决办法:

1)缓存设置<key,nil>:当发现MySQL不存在某个数据,将redis中对应的key设置为<key,nil>并设置过期时间。通过这样的标识,使得下次访问key的时候不要再去访问MySQL,并且到期自动删除这个key。但是这种方法会造成redis缓存数据库缓存很多无效数据,浪费内存。


2)部署布隆过滤器:将 MySQL当中已经存在的 key,写入布隆过滤器,不存在的直接 pass 掉。即使发生了缓存穿透,通过布隆过滤器在缓存层拦截无效的请求,避免无效查询到达MySQL。最好在缓存数据库上部署布隆过滤器。


4.2 缓存击穿

如果某个频繁访问的热点数据在redis缓存不存在(过期或被淘汰),但在MySQL中存在。此时有大量的并发连接请求该热点数据,会直接访问数据库,导致MySQL数据库压力骤增,可能造成MySQL数据库崩溃


解决办法:

1)过热数据不过期,即不要对频繁访问的热点数据设置过期时间。


2)使用分布式锁机制,保证只有一个请求能够访问数据库,其他请求在等待时可以从缓存中获取数据。


4.3 缓存雪崩

redis缓存中的大量数据同时过期或失效,但是在MySQL中存在,导致大量请求直接访问MySQL数据库,造成系统性能下降甚至崩溃。


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

解决办法:

1)如果因为缓存数据库宕机,造成所有数据涌向 MySQL。采用高可用的集群方案,如哨兵模式、cluster模式。


2)如果因为设置了相同的过期时间,造成缓存集中失效。设置随机过期值或者其他机制错开失效时间。


3) 如果因为系统重启的时候,造成缓存数据消失。重启时间短,redis 开启持久化(过期信息也会持久化)就行了; 重启时间长,提前将热数据导入 redis 当中。


4.4 缓存方案的弊端

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

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
23天前
|
缓存 关系型数据库 MySQL
MySQL索引策略与查询性能调优实战
在实际应用中,需要根据具体的业务需求和查询模式,综合运用索引策略和查询性能调优方法,不断地测试和优化,以提高MySQL数据库的查询性能。
|
1月前
|
缓存 关系型数据库 MySQL
MySQL执行计划选择策略:揭秘查询优化的艺术
【10月更文挑战第15天】 在数据库性能优化中,选择最优的执行计划是提升查询效率的关键。MySQL作为一个强大的关系型数据库管理系统,提供了复杂的查询优化器来生成执行计划。本文将深入探讨如何选择合适的执行计划,以及为什么某些计划更优。
89 2
|
2月前
|
缓存 算法 数据挖掘
深入理解缓存更新策略:从LRU到LFU
【10月更文挑战第7天】 在本文中,我们将探讨计算机系统中缓存机制的核心——缓存更新策略。缓存是提高数据检索速度的关键技术之一,无论是在硬件还是软件层面都扮演着重要角色。我们会详细介绍最常用的两种缓存算法:最近最少使用(LRU)和最少使用频率(LFU),并讨论它们的优缺点及适用场景。通过对比分析,旨在帮助读者更好地理解如何选择和实现适合自己需求的缓存策略,从而优化系统性能。
58 3
|
6天前
|
缓存 API C#
C# 一分钟浅谈:GraphQL 中的缓存策略
本文介绍了在现代 Web 应用中,随着数据复杂度的增加,GraphQL 作为一种更灵活的数据查询语言的重要性,以及如何通过缓存策略优化其性能。文章详细探讨了客户端缓存、网络层缓存和服务器端缓存的实现方法,并提供了 C# 示例代码,帮助开发者理解和应用这些技术。同时,文中还讨论了缓存设计中的常见问题及解决方案,如缓存键设计、缓存失效策略等,旨在提升应用的响应速度和稳定性。
26 13
|
13天前
|
SQL 关系型数据库 MySQL
PHP与MySQL的高效协同开发策略####
本文深入探讨了PHP与MySQL在Web开发中的协同工作机制,通过优化配置、最佳实践和高级技巧,展示了如何提升数据库交互性能,确保数据安全,并促进代码可维护性。我们将从环境搭建讲起,逐步深入到查询优化、事务管理、安全防护及性能调优等核心环节,为开发者提供一套实战驱动的解决方案框架。 ####
|
1月前
|
SQL 缓存 关系型数据库
美团面试:Mysql 有几级缓存? 每一级缓存,具体是什么?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴因未能系统梳理MySQL缓存机制而在美团面试中失利。为此,尼恩对MySQL的缓存机制进行了系统化梳理,包括一级缓存(InnoDB缓存)和二级缓存(查询缓存)。同时,他还将这些知识点整理进《尼恩Java面试宝典PDF》V175版本,帮助大家提升技术水平,顺利通过面试。更多技术资料请关注公号【技术自由圈】。
美团面试:Mysql 有几级缓存? 每一级缓存,具体是什么?
|
23天前
|
存储 缓存 安全
在 Service Worker 中配置缓存策略
Service Worker 是一种可编程的网络代理,允许开发者控制网页如何加载资源。通过在 Service Worker 中配置缓存策略,可以优化应用性能,减少加载时间,提升用户体验。此策略涉及缓存的存储、更新和检索机制。
|
27天前
|
监控 关系型数据库 MySQL
MySQL自增ID耗尽应对策略:技术解决方案全解析
在数据库管理中,MySQL的自增ID(AUTO_INCREMENT)属性为表中的每一行提供了一个唯一的标识符。然而,当自增ID达到其最大值时,如何处理这一情况成为了数据库管理员和开发者必须面对的问题。本文将探讨MySQL自增ID耗尽的原因、影响以及有效的应对策略。
90 3
|
28天前
|
监控 关系型数据库 MySQL
Linux环境下MySQL数据库自动定时备份策略
在Linux环境下,MySQL数据库的自动定时备份是确保数据安全和可靠性的重要措施。通过设置定时任务,我们可以每天自动执行数据库备份,从而减少人为错误和提高数据恢复的效率。本文将详细介绍如何在Linux下实现MySQL数据库的自动定时备份。
35 3
|
27天前
|
存储 监控 关系型数据库
MySQL自增ID耗尽解决方案:应对策略与实践技巧
在MySQL数据库中,自增ID(AUTO_INCREMENT)是一种特殊的属性,用于自动为新插入的行生成唯一的标识符。然而,当自增ID达到其最大值时,会发生什么?又该如何解决?本文将探讨MySQL自增ID耗尽的问题,并提供一些实用的解决方案。
36 1