0%

缓存和数据库数据一致性问题

一致性哈希算法在1997年由麻省理工学院提出,是一种特殊的哈希算法,在移除或者添加一个服务器时,能够尽可能小地改变已存在的服务请求与处理请求服务器之间的映射关系。在使用一致哈希算法后,哈希表槽位数(大小)的改变平均只需要对K/N个关键字重新映射,其中K是关键字的数量,N是槽位数量。然而在传统的哈希表中,添加或删除一个槽位的几乎需要对所有关键字进行重新映射。一致性哈希算法实现了为服务器和存储信息均实时变化的情况下较为合理地分配网络缓存。

一、渊源

      随着计算机技术的飞速发展,web应用由最初的单一节点到多节点负载均衡再到如今的分布式,每一次发展都会伴随着技术上的革新,同时也会带来新的问题。如现在随随便便一个应用设计都需要考虑的“三高一低”的问题,而要满足这样的要求不可避免的会用到缓存、消息队列等技术,这其中就涉及到缓存和数据库数据一致性的问题。

二、解决方案

  1. 方案一:缓存设置过期时间,最终一致:对写入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。如果数据库写成功,缓存更新失败,那么只要到达过期时间缓存会自动删除,后面的读请求自然会从数据库中读取新值然后回填缓存。

    • 存在问题
      • 短时间内为旧数据
      • 过期时间如何设置(设置不当会出现雪崩)
    • 适用场景
      • 对数据实时性要求不高
  2. 方案二:先更新数据库,再更新缓存(很少使用)

    • 存在问题

      • 资源浪费(频繁更新冷数据,或写多读少的情景,缓存命中率低)
      • 存在脏数据,由于网络原因,可能会出现以下情形,从而导致A数据覆盖了B数据的情况:
      1
      2
      3
      4
      请求A更新了数据库
      请求B更新了数据库
      请求B更新了缓存
      请求A更新了缓存
      • 请求响应时间延长(每次更新缓存值都需要经过复杂的计算)
    • 适用场景

      • 读请求占据网站的大部分流量
      • 网站数据量不大(几十万的文章数据)
      • 很少会去更新数据(一般文章写好后,不会去更新)
  3. 方案三:先更新数据库,再删除缓存(Cache Aside Pattern)

    • 存在问题

      • 存在脏数据,由于网络原因,可能会出现以下情形,从而导致A数据覆盖了B数据的情况:
      1
      2
      3
      4
      5
      6
      用户1请求数据A
      数据A缓存失效
      用户1从数据库中得到旧数据数据A
      用户2更新了数据A(新数据)
      用户2删除了缓存
      用户1将查到旧数据写入了缓存
      • 缓存删除失败,由于某种原因导致缓存删除失败
      1
      2
      3
      用户A更新了数据A
      用户A删除数据A的缓存失败
      用户B读到数据A缓存的旧数据
    • 解决问题

      • 设置缓存有效时间
      • 引入消息队列
        • 更新数据库
        • 删除缓存失败
        • 将需要删除的Key发送到消息队列
        • 隔断时间从消息队列中拉取要删除的key
        • 继续删除,直至成功为止
  4. 方案四:先删除缓存,再更新数据库

    • 存在问题

      • 存在脏数据
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      用户A删除缓存失败
      用户A成功更新了数据
        
      或者

      用户A删除了缓存;
      用户B读取缓存,缓存不存在;
      用户B从数据库拿到旧数据;
      用户B更新了缓存;
      用户A更新了数据。
    • 解决问题

      • 设置缓存有效时间
      • 引入消息队列
      1
      2
      3
      4
      5
      先淘汰缓存;
      更新数据库;
      将需要淘汰的缓存Key发送到消息队列;
      另起一程序拉取消息队列的数据;
      对需要删除的key进行删除,直至删除为止。
  5. 方案五:引入重试机制

    • 更新数据库数据;
    • 缓存因为种种问题删除失败
    • 将需要删除的key发送至消息队列
    • 自己消费消息,获得需要删除的key
    • 继续重试删除操作,直到成功
  6. 方案六:重试+binlog

    • 更新数据库数据
    • 数据库会将操作信息写入binlog日志当中
    • 订阅程序提取出所需要的数据以及key
    • 另起一段非业务代码,获得该信息
    • 尝试删除缓存操作,发现删除失败
    • 将这些信息发送至消息队列
    • 重新从消息队列中获得该数据,重试操作。

三、参考文献

  1. 参考一
  2. 参考二
  3. 参考三