Mysql 缓存策略

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
简介: Mysql 缓存策略

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

  • 效率优先:以 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、场景分析

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

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

存储层

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

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

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

缓存层

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

热点数据

热点 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 建立连接,进行数据同步。效率低,不建议。

伪装从库

image.png

伪装从数据库原理


伪装从数据库以 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:" .. 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

启动 mysql, redis, go-mysql-transfer

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

2.5、缓存方案的弊端

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

3、* 缓存故障

3.1、缓存雪崩

问题描述

  • 特征:缓存层无,存储层有
  • 原因:大量缓存数据同时过期缓存数据库宕机
  • 描述:同一时间,大量缓存数据集中失效。若此时有大量用户请求,无法在 redis 处理,于是所有请求涌向数据库,从而导致数据库压力骤增,甚至数据库崩溃宕机。

解决方案

针对大量数据同时过期

  • 均匀设置 key 过期时间:避免将大量数据同一时刻过期,设置 key 间隔过期或者随机过期。
  • 分布式锁:保证同一时间只有一个请求构建缓存。
  • 双 key 策略:主 key 设置过期时间,备 key 永久,主 key 过期返回备 key 内容
  • 后台更新缓存:定时更新,消息队列通知更新

针对缓存数据库宕机

  • 构建缓存高可用集群:主从节点构建缓存高可用集群,当缓存数据库宕机时,主从切换。
  • 服务熔断或请求限流机制:当缓存数据库宕机时,服务熔断(暂停业务线程对缓存服务的访问,直接返回错误),请求限流机制(指限制访问数据库的请求数量)。

缓存数据库宕机重启

  • 缓存数据库开启持久化,宕机重启后恢复数据。
  • 缓存数据库预热,预先导入热数据,构建缓存。

3.2、缓存击穿

问题描述

  • 特征:缓存层无,存储层有
  • 原因:热点数据过期
  • 描述:热点数据过期,大量用户请求该热点数据,无法在 redis 处理,高并发的请求涌向数据库。

解决方案

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

3.3、缓冲穿透

问题描述

  • 特征:缓存层无,存储层无
  • 原因:请求不存在的数据。一般有两种情况,业务误操作删除缓存和数据库中的数据;黑客恶意攻击,故意大量请求不存在数据的业务。
  • 描述:大量请求缓存和数据库都不存在的数据,无法在 redis 处理,高并发的请求涌向数据库。

解决方案

非法请求限制

判断如果是恶意请求,则直接返回错误。

缓存空值

当发现数据库不存在该 key,针对查询的数据,在缓存设置 <key, nil> 并设置过期时间,后续请求就可以从缓存中直接读取,不再访问数据库。但是会造成缓存数据库缓存很多无效数据,浪费内存;而且过期时间这段时间内缓存层和存储层数据不一致。

布隆过滤器

布隆过滤器是一种概率型的数据结构,特点是高效地插入和查询,能判断某个字符串一定不存在或可能存在。查询布隆过滤器若数据不存在,则数据库中该数据一定不存在;若数据存在,则数据库中该数据可能存在。

将 key 写入数据库时,使用布隆过滤器做标记。当用户请求到来,业务线程确认缓存失效后,可以查询布隆过滤器快速判断数据是否存在,若不存在,则不需要通过查询数据库来判断数据是否存在。即使发生了缓存穿透,大量请求也只会查询 Redis 和布隆过滤器,不会查询数据库。实际中,最好在缓存数据库上部署布隆过滤器。

布隆过滤器通过 3 个操作完成标记:

  • 第一步,使用 N 个哈希函数分别对数据做哈希计算,得到 N 个哈希值;
  • 第二步,将第一步得到的 N 个哈希值对位图数组的长度取模,得到每个哈希值在位图数组的对应位置。
  • 第三步,将每个哈希值在位图数组的对应位置的值设置为 1

小结

  • 缓存雪崩和缓存击穿主要原因是数据不在缓存。区别在于,缓存雪崩是面(大量数据),缓存击穿是点(热点数据)。
  • 缓存穿透主要原因是数据不在数据库。缓存层和存储层一起穿透。

4、参考

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

推荐镜像

更多