处理双写策略中的数据同步

举报
码乐 发表于 2024/09/20 12:26:08 2024/09/20
【摘要】 1 简介如果使用缓存提升性能,可能有缓存和数据库的数据同步协调问题。这里简单聊聊。其中一个直接的策略就是双写策略,写完数据库写缓存。 2 缓存Redis是一个开源的数据库缓存系统,是一个高性能的Key-Value存储系统。Redis 提供五种数据类型: String、Hash、List、Set 及ZSset(Sorted Set). (1)String是最简单的类型,--个key对应...

1 简介

如果使用缓存提升性能,可能有缓存和数据库的数据同步协调问题。这里简单聊聊。

其中一个直接的策略就是双写策略,写完数据库写缓存。

2 缓存

Redis是一个开源的数据库缓存系统,是一个高性能的Key-Value存储系统。
Redis 提供五种数据类型: String、Hash、List、Set 及ZSset(Sorted Set).

    (1)String是最简单的类型,--个key对应--个 value。
    (2)List是一个链表结构,主要功能是push、 pop、获取一个范围的所有值等等。使用List结构,可以轻松地实现最新消息排队功能。(3)Hash是一个String类型的field(字段)和value (属性)的映射表,Hash 特别适合用于存储对象。一个Hash可以存多个key-value,类似一个对象的多 个字段和属性。
    (4)Set是String类型的无序集合。集合成员是不可重复的。
    (5)ZSet是有序集合,每个元素都会关联一个double 类型的权重参数(score),使得集合中的元素能够按score进行有序排列。

比如电商实现当前热销商品排名的功能,则应该选择使用ZSet结构。
若系统采用Redis 作为数据库缓存,将数据持久化存储在MYSQL数据库中,则必然需要解决二者的数据实时同步问题。解决Redis和MySQL数据实时同步问题的常见方案是:

	1 应用程序读数据时先读取Redis中的key,如读到而且未失效则返回kev对应的数据。
	2 如读不到或key失效,则读取数据库,并同步Redis;
	3 写数据时先写数据库,并设置内存对应的key失效。

3 缓存使用注意事项

  • 避免bigkey

String类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000, 这是为了拒绝bigkey(防止网卡流量、慢查询)。

  • 使用时要选择适合的数据类型。

不少人只用Redis的String类型,上来就是set和get。
实际上,Redis提供了「丰富的数据结构类型」,有些业务场景,更适合hash、zset等其他数据结果。

  • 慎用O(n)复杂度命令,

如hgetall、smember,lrange等,因为Redis是单线程执行命令的。
hgetall、smember等命令时间复杂度为O(n),当n持续增加时,会导致 Redis CPU 持续飙高,阻塞其他命令的执行。

  • 慎用Redis的monitor命令

Redis Monitor 命令用于实时打印出Redis服务器接收到的命令,如果我们想知道客户端对redis服务端做了哪些命令操作,就可以用Monitor 命令查看,但是它一般「调试」用而已,尽量不要在生产上用!
因为monitor命令可能导致redis的内存持续飙升。

  • 生产环境不能使用 keys指令

Redis Keys 命令用于查找所有符合给定模式pattern的key。
如果想查看Redis 某类型的key有多少个,不少小伙伴想到用keys命令。

禁止使用flushall、flushdb,
Flushall 命令用于清空整个 Redis 服务器的数据(删除所有数据库的所有 key )。
Flushdb 命令用于清空当前数据库中的所有 key。

这两命令是原子性的,不会终止执行。一旦开始执行,不会执行失败。

  • 避免使用SORT、SINTER等复杂度过高的命令。

  • 小心使用del命令
    删除key你一般使用什么命令?是直接del?如果删除一个key,直接使用del命令当然没问题。
    但是,你想过del的时间复杂度是多少嘛?我们分情况探讨一下:

    如果删除一个String类型的key,时间复杂度就是O(1),「可以直接del」。
    如果删除一个List/Hash/Set/ZSet类型时,它的复杂度是O(n), n表示元素个数。
    因此,如果你删除一个List/Hash/Set/ZSet类型的key时,元素越多,就越慢。
    「当n很大时,要尤其注意」,会阻塞主线程的。
    那么,如果不用del,应该怎么删除呢?


    如果是List类型,你可以执行lpop或者rpop,直到所有元素删除完成。
    如果是Hash/Set/ZSet类型,你可以先执行hscan/sscan/scan查询,再执行hdel/srem/zrem依次删除每个元素。

4 缓存同步实例

在Go中实现Redis和MySQL之间的数据实时同步,常见的方案包括:

  • 双写策略

这种方式要求在更新数据库(MySQL)的同时,也要更新Redis缓存。
这种策略较为简单,但可能面临一致性问题,特别是在MySQL写成功而Redis写失败的情况下。

实现步骤:

数据库操作:更新MySQL中的数据。
缓存操作:更新Redis中的缓存数据。
事务处理:确保MySQL和Redis操作的事务一致性。

示例代码:

	package main

	import (
	    "fmt"
	    "log"
	    "github.com/go-redis/redis/v8"
	    "gorm.io/driver/mysql"
	    "gorm.io/gorm"
	    "context"
	)

	var ctx = context.Background()

	func updateDataInMySQLAndRedis(db *gorm.DB, rdb *redis.Client, key string, value string) error {
	    // MySQL事务
	    tx := db.Begin()
	    if tx.Error != nil {
	        return tx.Error
	    }

	    // 更新MySQL
	    err := tx.Exec("UPDATE your_table SET your_column = ? WHERE your_key = ?", value, key).Error
	    if err != nil {
	        tx.Rollback()
	        return err
	    }

	    // 更新Redis
	    err = rdb.Set(ctx, key, value, 0).Err()
	    if err != nil {
	        tx.Rollback()
	        return err
	    }

	    // 提交MySQL事务
	    if err := tx.Commit().Error; err != nil {
	        return err
	    }

	    return nil
	}

	func main() {
	    // 连接MySQL
	    dsn := "user:password@tcp(127.0.0.1:3306)/dbname"
	    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	    if err != nil {
	        log.Fatal("Failed to connect to MySQL:", err)
	    }

	    // 连接Redis
	    rdb := redis.NewClient(&redis.Options{
	        Addr: "localhost:6379",
	    })

	    // 更新数据
	    key := "user:123"
	    value := "John Doe"
	    err = updateDataInMySQLAndRedis(db, rdb, key, value)
	    if err != nil {
	        log.Fatal("Failed to update data:", err)
	    }

	    fmt.Println("Data updated successfully in both MySQL and Redis")
	}
  • 延时双删策略

当使用双写策略时,可能会有短时间内的缓存不一致问题。延时双删策略解决了这个问题:

删除缓存:更新MySQL数据前,先删除Redis缓存中的数据。

更新MySQL:更新MySQL数据库中的数据。

延时删除缓存:在一定时间后(如500ms),再次删除Redis缓存中的数据,确保缓存过期。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。