Redis和MySQL保证双写一致性的问题解析

 更新时间:2023年11月17日 09:43:21   作者:仍沫  
Redis和MySQL的双写一致性指的是在同时使用缓存和数据库存储数据的时候,保证Redis和MySQL中数据的一致性,那么如何才能保证他们的一致性呢,下面小编就来为大家详细讲讲

Redis和MySQL的双写一致性指的是在同时使用缓存和数据库存储数据的时候,保证Redis和MySQL中数据的一致性。

用户发起请求,先从Redis中查取数据,有数据就直接返回,没有数据就从MySQL中查询数据,并且存储到Redis中,然后返回。从MySQL中查询到数据再存入Redis中这个步骤称为回写。

上述这种有回写的缓存称为读写缓存,仅仅用于查询的缓存称为只读缓存,只读缓存中的数据是通过命令或者批量脚本从MySQL中写到Redis的。

对于读写缓存,如果需要尽可能保证数据库和缓存数据一致,使用同步直写策略,写数据库后也同步写Redis缓存;如果数据库和缓存的数据同步容许有一定的时间间隔,比如仓库系统,就可以使用异步缓写策略,写数据库的一段时间后再同步缓存,当出现异常情况需要对数据进行修补的时候,也可能需要使用异步换写策略,比如用Kafka或RabbitMQ之类的消息中间件重写数据。

源码地址,文中只展示关键代码。

双检加锁策略

从缓存中查询两次,并且加上互斥锁。

func (dao *UserDAO) FindByID(c context.Context, userID int64) (u domain.User, err error) {
	db := dao.db
	rdb := dao.rdb
	key := fmt.Sprintf("user:%v", userID)

	// 1. 从缓存中查询数据,如果有数据就返回
	var user domain.User
	val, err := rdb.Get(c, key).Result()
	if val != "" && err == nil {
		err := json.Unmarshal([]byte(val), &user)
		if err == nil {
			return user, nil
		}
	}
	// 2. 没有查到数据就加锁再查一次
	mu.Lock()
	defer mu.Unlock()
	val, err = rdb.Get(c, key).Result()
	// 2.1 从缓存中查到数据就直接返回
	if val != "" && err == nil {
		err := json.Unmarshal([]byte(val), &user)
		if err == nil {
			return user, nil
		}
	}
	// 2.2 没有从缓存中查到数据就从数据库中查询
	err = db.Where("id=?", userID).First(&user).Error
	if err != nil {
		return user, err
	}
	// 3. 将从数据库中拿到的数据写到缓存中
	userStr, err := json.Marshal(user)
	if err == nil {
		rdb.Set(c, key, userStr, 1000*time.Second)
	}
	return user, nil
}

数据库和缓存一致性的几种更新策略

上面说的是查询策略,接下来说一下数据库和缓存一致性的更新策略。

可以停机的情况:

​ 比如先往MySQL中灌入1万条数据,再同步到Redis中,可以在凌晨升级,给出升级提示。

不可以停机的情况:

1.先更新数据库,再更新缓存(不可行)

异常情况1:

更新Redis出现异常时导致的问题。

异常情况2:

并发情况下执行顺序的不确定性导致的问题。

2.先更新缓存,再更新数据库(不可行)

和1一样,因为并发可能造成MySQL和Redis中的数据不一致。并且一般要把MySQL作为底单数据,保证最后解释。

3.先删除缓存,再更新数据库(不可行)

两个并发操作,一个时更新操作,一个是查询操作,由于执行顺序的不确定性,可能导致缓存中存储的是旧数据,并且一直是旧数据。

可以悲观地认为在A更新数据期间,一定会有B来读取数据,在A写完数据库之后,延迟一段时间,再次删除缓存中的数据。但是当业务中读取数据库和写缓存的时间不好估算时,这个延迟的时间不好设置。

4.先更新数据库,再删除缓存

先更新数据库也不是完全能保证数据一致性的,但是造成的影响比较小。只是在缓存删除失败或者来不及删除的时候,导致查询请求访问Redis时缓存命中,读取到的是缓存旧值。

func (dao *UserDAO) UpdateUserData(c context.Context, userID int64, name string) (user User, err error) {
   db := dao.db
   rdb := dao.rdb
   key := fmt.Sprintf("user:%v", userID)
   user.ID = userID

   // 先更新数据库中的数据
   u := User{
   	Name: name,
   }
   err = db.Model(&user).
   	Select("Name").
   	Where("id=?", userID).Updates(u).Error
   if err != nil {
   	return user, err
   }

   // 再删除缓存中的数据
   err = rdb.Del(c, key).Err()
   if err != nil {
   	return user, err
   }
   return user, nil
}

5.比较稳妥的方式

通过非业务代码订阅MySQL的binlog日志,将对应的缓存删除,如果没有删除成功,就将未成功的数据发送到消息队列中,从消息队列中读取数据进行删除缓存的重试,删除缓存成功就把对应数据从消息队列中删掉,重试超过一定次数后向业务层报错,提醒开发或者运维人员进行处理。

到此这篇关于Redis和MySQL保证双写一致性的问题解析的文章就介绍到这了,更多相关Redis MySQL双写一致性内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 使用Redis实现点赞取消点赞的详细代码

    使用Redis实现点赞取消点赞的详细代码

    这篇文章主要介绍了Redis实现点赞取消点赞的详细代码,通过查询某实体(帖子、评论等)点赞数量,需要用到事务相关知识,结合示例代码给大家介绍的非常详细,需要的朋友可以参考下
    2022-03-03
  • macOS上Redis的安装与测试操作

    macOS上Redis的安装与测试操作

    这篇文章主要介绍了macOS上Redis的安装与测试操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-07-07
  • Redis exists命令bug分析(案例详解)

    Redis exists命令bug分析(案例详解)

    Redis EXISTS 命令用于检查给定 key 是否存在,本文重点给大家介绍Redis exists命令bug分析,感兴趣的朋友跟随小编一起看看吧
    2022-02-02
  • Redis的Expire与Setex区别说明

    Redis的Expire与Setex区别说明

    这篇文章主要介绍了Redis的Expire与Setex区别说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • Redis调用Lua脚本及使用场景快速掌握

    Redis调用Lua脚本及使用场景快速掌握

    Redis 是一种非常流行的内存数据库,常用于数据缓存与高频数据存储。大多数开发人员可能听说过redis可以运行 Lua 脚本,但是可能不知道redis在什么情况下需要使用到Lua脚本
    2022-03-03
  • 详解Redis数据结构之跳跃表

    详解Redis数据结构之跳跃表

    这篇文章主要介绍了Redis数据结构中的跳跃表的相关知识,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-11-11
  • Redis未授权访问配合SSH key文件利用详解

    Redis未授权访问配合SSH key文件利用详解

    这篇文章主要给大家介绍了关于Redis未授权访问配合SSH key文件利用的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-09-09
  • Redis分布式锁详细介绍

    Redis分布式锁详细介绍

    大家好,本篇文章主要讲的是Redis分布式锁详细介绍,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下,方便下次浏览
    2021-12-12
  • Redis Set 集合的实例详解

    Redis Set 集合的实例详解

    这篇文章主要介绍了 Redis Set 集合的实例详解的相关资料,Redis的Set是string类型的无序集合。集合成员是唯一的,并且不重复,需要的朋友可以参考下
    2017-08-08
  • Redis字符串对象实用笔记

    Redis字符串对象实用笔记

    这篇文章主要给大家介绍了关于Redis字符串对象的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Redis具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-04-04

最新评论