美文网首页
Redis和MySQL数据一致性解决方案(动态KEY)

Redis和MySQL数据一致性解决方案(动态KEY)

作者: 醒了_2440 | 来源:发表于2019-04-10 23:06 被阅读0次

    背景

    我们经常使用redis作为缓存,以减轻直接访问数据度带来的并发压力。基本操作如下:
    查询

    请求进来,先查询redis,如果有值则直接返回;如果没有则查询数据库(不考虑并发情景)并更新redis,再返回结果

    更新

    先删除redis原数据,成功后再更新数据库,然后再写入redis。

    在更新redis时,就有可能带来数据库和redis数据不一致的问题:

    如果在删除redis后,立刻有新的请求进来(很有可能,本来redis就在高并发下使用),则"删除失效"。因为当前数据库事务未提交,查询数据库依旧能读到旧值(数据库事务隔离要保证无脏读,如RC, RR或串行化),redis就被写入旧值。因此可直接考虑在更新redis前,redis还保留旧值的情况

    • 更新redis成功,但是返回exception,此时如果贸然回退数据库,将导致redis保存了最新的值,但是数据库中依旧是旧值
    • 更新redis失败,但是返回exception,此时如果直接提交数据库事务,将导致数据库保留最新值,redis保留旧值。

    上面这种redis写入异常,就会带来"数据不一致"的问题

    思考方向

    有一种解决数据一致性的方案是使用补偿机制,原理大体在更新redis后,数据状态无法确定时,保留异常记录id,后续使用补偿查询的方式以获知"未知记录"的真实情况。并且在明确数据状态之前,记录都无法使用。

    这种方案有时延和数据不可用的缺陷,直接导致服务中断一段时间。

    其实我们不一定要让"数据一致",只要请求得到的结果是预计正确即可。设想一下,在发生写入异常的时候,不管redis更新成功还是失败,只要我们让这条redis记录得不到使用,再把数据库回退,是不是也能让请求的结果是预计正确的。

    设计方案

    1. 现有一个key的结构为PREFIX+参数值,其中PREFIX为常量,如表名等能表示数据含义的字符串。另有一个静态的、初始化后的HashMap<String,String> suffixMap,其key为PREFIX,value表示数据库当前值的\color{red}{版本},用V表示。"版本"值是uiid。
    2. 更新开始(\color{red}{并发控制})
      (1)请求的KEY=PREFIX+参数值+Vi+1,其中Vi = suffixMap.get(PREFIX),Vi表示上一个版本,Vi+1表示的是即将生效版本。
      (2)先update数据库,如果成功,则利用RedisTemplate.opsForValue().set(KEY,记录值)。如果更新数据库失败,程序退出。其中写入redis会出现以下a,b两种情况:
      a.写入异常
    • 回退数据库,其中redis写的情况分为写入成功或者写入失败
    • 如果写入redis成功,Redis和数据库中记录不一致。但是redis中保存的KEY= PREFIX+参数值+Vi+1,而suffixMap并未更新,因此在执行查询操作时,使用到的KEY= PREFIX+参数值+Vi也无法从Redis中读取到“脏数据”。
    • 如果写入失败,数据库回退后两者的数据仍一样。
      b.写入成功
    • 将版本更新,suffixMap.put(PREFIX,Vi+1)
    • 删除redis中的旧值,KEY=PREFIX+参数值+Vi,RedisTemplate.opsForValue().del(KEY)。因为写入redis成功,此时删除失败的概率很低。即使删除旧值失败,也只是在redis中遗留历史数据,在Expired Time过后就会被清理,不会导致redis内存消耗高。

    评价

    暂未进行实际测试,待完善!

    相关文章

      网友评论

          本文标题:Redis和MySQL数据一致性解决方案(动态KEY)

          本文链接:https://www.haomeiwen.com/subject/hliwiqtx.html