解决MySQL与Redis缓存一致性的问题

背景

考试系统中,教师会在后台发布一场考试,考试会存储在MySQL和Redis里面,考试有时候是会出错的,我们需要后台修改,如果多个教师在后台并发修改(概率不大),可能会出现数据库缓存不一致的问题,我们需要解决一下,刚好联系一下这一块的知识

基础知识理论

四种解决办法:

  1. 先更新数据库,再更新缓存
  2. 先更新缓存,后更新数据库
  3. 先删除缓存,后更新数据库
  4. 先更新数据库,后删除缓存

这四种情况,都会出现缓存不一致的问题,但是第四种出现缓存不一致的情况概率上比较低,因为缓存的写入回远远快于数据库的写入,我们可以使用【先更新数据库+再更新缓存】的方式来解决数据库不一致的问题,为了确保万无一失,我们还可以加上一个过期时间。

但是有两个问题

  1. 我们需要保证先更新数据库+再更新缓存是一个原子操作,不然如果更新缓存失败了,导致缓存中的数据还是旧值
  2. 「先更新数据库,再删除缓存」的方案虽然保证了数据库与缓存的数据一致性,但是每次更新数据的时候,缓存的数据都会被删除,这样会对缓存的命中率带来影响。

对于第一个问题,解决办法是消息队列重试机制、订阅MySQL binlog,再操作缓存

对于第二个问题是,我们使用**「更新数据库 + 更新缓存」**的方式来实现,但是这个方案本身固有一些问题,解决办法是,分布式锁和过期时间

另外先删除缓存,再更新数据库的解决方案是延迟双删

实战-先更新数据库,后删除缓存

此种方案存在问题是两个操作不是原子性的,如何保证两个操作都能成功呢?

方案一:gin框架+消息队列

我们实现一个中间件

Go 复制代码
func Canal() gin.HandlerFunc {
	//封装成一个中间件,当我们更新考试的时候,会自动监测到
	return func(c *gin.Context) {
		c.Next()
		redisDB := global.GVA_REDIS
		get, exists := c.Get("DeleteRedisKey")
		DeleteKey := get.(string)
		if exists {
			err := redisDB.Del(context.Background(), DeleteKey).Err()
			if err == redis.Nil {
				//无需删除
				c.Next()
			} else if err != nil {
				//删除失败,加入到redis异步队列中,再次删除
				addToDelayedQueue(context.Background(), redisDB, DeleteKey, time.Now().Add(10*time.Second))
			}else {
				global.GVA_LOG.Info("更新数据成功")
			}
		} else {
			zap.L().Info("redis中不存在该键,无需删除")
		}
	}
}

我们在继承这个中间件的接口中的使用c.Set方法传递要删除的redisKey,然后我们在上述中间件中使用c.Get方法来接收要删除的redisKey,然后加入到异步队列中删除

现在来模拟一下是否能够成功,项目启动之后,把redis停机,然后删除redis的key就会失败,然后该key就会加入延时队列

方案一:消息队列订阅binlog

里面有两个难点,一个是订阅binlog服务,一个是消息队列

如何订阅binlog服务,我们使用阿里巴巴的canal ,支持Go语言,具体参考Golang通过alibabaCanal订阅MySQLbinlog_大杯无糖的博客-CSDN博客

接下来是用redis的zset实现一个异步消息队列

相关推荐
.Bryce.9 分钟前
三协议合一实战:用MCP+A2A+ANP搭建一个智能研究助手
网络·redis·agent·codex·mcp·a2a·anp
大侠归来15 分钟前
Redis Cluster 的 16384 个槽(Slot)是什么?
数据库·redis·缓存
猫咪很可爱1 小时前
MySQL 定时 DDL 导致 mysqldump 备份失败:场景模拟、排查与优化建议
mysql
杨运交1 小时前
[079][消息模块]基于 Redis Stream 的高可靠消息队列实现——Spring Data Redis 实战解析
数据库·redis·spring
数据库小学妹1 小时前
MySQL内存占用过高怎么排查?分层定位与OOM根因
数据库·经验分享·mysql·内存·oom
毕设单片机2 小时前
springboot志愿服务平台的设计与实现 36731
java·spring boot·mysql·志愿服务平台
xixingzhe22 小时前
秒杀库存三种缓存实现方案
数据库·redis·缓存
Dxy12393102163 小时前
MySQL分库分表如何选择ID
mysql
思考着亮3 小时前
1.Redis 基础知识
redis
牛战士coding3 小时前
Java 对象写入 Redis 后类型变了?Jackson 多态序列化问题复盘
java·开发语言·redis