redis与mysql的数据一致性

1. MySQL持久化数据,Redis只读数据

    redis在启动之后,从数据库加载数据。

       读请求:

      不要求强一致性的读请求,走redis,要求强一致性的直接从mysql读取

      写请求:

      数据首先都写到数据库,之后更新redis(先写redis再写mysql,如果写入失败事务回滚会造成redis中存在脏数据)

2.MySQL和Redis处理不同的数据类型

    MySQL处理实时性数据,例如金融数据、交易数据

    Redis处理实时性要求不高的数据,例如网站最热贴排行榜,好友列表等

      在并发不高的情况下,读操作优先读取redis,不存在的话就去访问MySQL,并把读到的数据写回Redis中;写操作的话,直接写MySQL,成功后再写入Redis(可以在MySQL端定义CRUD触发器,在触发CRUD操作后写数据到Redis,也可以在Redis端解析binlog,再做相应的操作)
    在并发高的情况下,读操作和上面一样,写操作是异步写,写入Redis后直接返回,然后定期写入MySQL

  推荐先更新DB,然后再更新或者淘汰缓存,原因如下

  如果先更新缓存,然后更新DB的动作失败了,下一个读请求过来,读取到的缓存数据就是未曾更新到DB的数据,这样的数据明显是业务错误的。因此建议先更新DB,再后续选择操作缓存。
  如果采用先淘汰缓存,后更新DB操作,如果在DB更新完成之前,来了一个新的读请求,那么就会查询出数据库的旧数据,缓存到redis,导致DB更新完成之后,两者数据不一致。
  如果先更新DB,然后操作缓存失败,客户端读取到的是旧的数据,此时也是存在DB与缓存不一致,但是实际业务上,更多的还是以DB数据为准,这种读取到旧数据的业务影响可能比读取到为未更新到DB的数据影响要小。
  使用消息队列实现最终一致性的消息保证

  先更新DB数据,然后通过发送操作缓存的消息到消息队列,进行更新缓存操作,这里还需要的一个操作就是利用消息队列的重试机制,保证缓存能够更新成功,如果多次消费失败,可能是由于网络原因或者redis服务挂了,此处可以添加告警处理。

  更新缓存还是淘汰缓存

  严格上来说,不论是更新缓存,还是淘汰缓存,都是可能出现缓存不一致的,但是从分析上来说,更新缓存的方式出现缓存不一致的可能性更大,如果是业务上对缓存不一致容忍性比较大,那么选择更新还是淘汰都是可以的,下面来分析一下,两个综合的方案可能出现的问题。

  采用先更新DB,后更新缓存可能出现问题
  采用更新缓存的方式,需要考虑在操作完DB之后,后续的更新缓存操作,是否需要比较复杂的操作才能得到应该set进缓存中值?例如需要复杂计算或者DB交互查询。如果是的话,那么不建议使用更新缓存的方式,而是采用淘汰缓存的方式
  并发写问题。假如两个写请求A和B同时进行写DB操作,A先于B,B基于A做更新,此时假如是使用传值进行更新缓存,可能出现的问题就是B的写缓存操作,后于A的写缓存操作,从而导致缓存被A的更新数据覆盖,缓存中的数据变成旧的数据。出现缓存不一致。
采用mq重试和告警,更新DB与更新缓存之间也可能存在断层。
  所以,如果要使用更新缓存的方式来保证缓存与DB数据一致性,需要考虑以上三个问题。

  采用先更新DB,后淘汰缓存可能出现问题
  采用mq重试和告警,更新DB与淘汰缓存之间也可能存在断层。

  第一种方案:采用延时双删策略

  在写库前后都进行redis.del(key)操作,并且设定合理的超时时间。

  伪代码如下:

public void write(String key,Object data){
 redis.delKey(key);
 db.updateData(data);
 Thread.sleep(500);
 redis.delKey(key);
 }

  具体的步骤就是:

  1)先删除缓存

  2)再写数据库

  3)休眠500毫秒

  4)再次删除缓存

  那么,这个500毫秒怎么确定的,具体该休眠多久呢?需要评估自己的项目的读数据业务逻辑的耗时。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。

  当然这种策略还要考虑redis和数据库主从同步的耗时。最后的的写数据的休眠时间:则在读数据业务逻辑的耗时基础上,加几百ms即可。比如:休眠1秒。

设置缓存过期时间

  从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。所有的写操作以数据库为准,只要到达缓存过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。

  该方案的弊端

  结合双删策略+缓存超时设置,这样最差的情况就是在超时时间内数据存在不一致,而且又增加了写请求的耗时。

第二种方案:异步更新缓存(基于订阅binlog的同步机制)

  1.整体思路:

  MySQL binlog增量订阅消费+消息队列+增量数据更新到redis

  1)读Redis:热数据基本都在Redis

  2)写MySQL:增删改都是操作MySQL

  3)更新Redis数据:MySQL的数据操作binlog,来更新到Redis

  2.Redis更新

  1)数据操作主要分为两大块:

  • 一个是全量(将全部数据一次写入到redis)
  • 一个是增量(实时更新)

  增量,指的是mysql的update、insert、delate变更数据。

  2)读取binlog后分析 ,利用消息队列,推送更新各台的redis缓存数据

  这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。

  其实这种机制,很类似MySQL的主从备份机制,因为MySQL的主备也是通过binlog来实现的数据一致性。

  这里可以结合使用canal(阿里的一款开源框架),通过该框架可以对MySQL的binlog进行订阅,而canal正是模仿了mysql的slave数据库的备份请求,使得Redis的数据更新达到了相同的效果。


  

  

posted on 2019-08-24 12:01  溪水静幽  阅读(1886)  评论(0)    收藏  举报