zoukankan      html  css  js  c++  java
  • 高并发下缓存和数据库一致性问题(更新淘汰缓存不得不注意的细节)

    缓存和数据库一致性问题

    本文讨论的背景是,cache如memcache,redia等缓存来缓存数据库读取出来的数据,以提高读性能,如何处理缓存里的数据和数据库数据的一致性是本文讨论的内容:

    正常的缓存步骤是:

    1查询缓存数据是否存在,2不存在即查询数据库,3将数据添加到缓存同时返回结果,4下一次访问发现缓存存在即直接返回缓存数据。那么当更新数据库数据的时候,该如果更新缓存呢,至少要考虑尽量短时间的一致性,这个看业务需求,比如用户信息缓存时间越短越好,比如排行榜可能是一天更新一次,本文纯技术讨论,就是尽量缩短非一致性的时间以此来学习思路。

    1、当更新数据库时候,缓存应该如何更新     
        1.1、更新缓存VS淘汰缓存

         答:更新缓存很直接,但是涉及到本次更新的数据结果需要一堆数据运算(例如更新用户余额,可能需要先看看有没有优惠券等),复杂度就增加了。而淘汰缓存仅仅会增加一次cache miss,代价可以忽略,所以建议淘汰缓存

       1.2、先淘汰后写数据库vs先写数据库后淘汰

        答: 先写后淘汰,如果淘汰失败,cache里一直是脏数据

               先淘汰后写,下次请求的时候缓存就会miss hit一次,这个代价是可以忽略的,(如果淘汰失败return false)

               综合比较统,推荐先淘汰缓存再写数据库

              下次请求直接从数据库取然后再写在缓存里。(当然这里可能会大并发一起击穿(通过下面1.3的方式可以解决),还有在淘汰缓存再写数据库的这一瞬间,再来一个读取请求,这个读取比上一个请求的写先完成,那么就会出现脏数据。网上有人说 修改数据库的连接池方法,就是对于同一个ID的数据请求,比如query(id),edit(id)都使用同一个连接对象,这样来保证先来的先完成,貌似还是挺复杂的,关于后来的读取请求先与先来的写请求完成,只能通过这样的串行方式执行)

               关于脏数据,如果需要强一致性

               1.2.1、可以通过数据库无论是读或写操作都是通过一个请求db connection连接完成(目的是串行),这样就需要修改连接池

               1.2.2、可以采用更新缓存而不是淘汰缓存,前提是更新的代价比较低

               1.2.3、可以先更新数据库再淘汰缓存(更新的原则是谁影响小先更新谁,此处倒着来了,一般推荐先更新数据库再淘汰缓   存),不过一般情况,淘汰缓存失败的可能性很小,可以以缓存处理100%不失败为前期。

               1.2.4、双淘汰发,即:淘汰缓存-更新数据库-淘汰缓存,可以尽量减少脏数据的留存时间。

               1.2.5、以上实现起来,要么极短时间的不一致要么一致性代价比较高,实际项目我会这样处理,更新数据库的地方和读取的地方上同样key的分布式锁,这样就能保证,先操作(或读或写)数据的先获得结果,实际中这样的强一致需求比较少,参考思路即可。

               当然数据既然都缓存起来了,绝大部分都不要求强一致性,为了尽可能的缩短一致性的时间,可以如下处理:

               1.2.6、异步消息总线esb更新法,即:修改数据库往消息总线里发送一个消息,在接收端去处理这个消息更新缓存,缺点是有代码入侵

               1.2.7,异步binlog扫描更新法,增量的去扫描binlog中的修改记录,符合条件的更新缓存,相比消息总线法没有代码入侵

        1.3、在1.2缓存miss hit的时候,此时大并发请求这个过程,会出现什么异常

         答:缓存击穿(关于缓存丢失导致雪崩击穿参考:https://blog.csdn.net/zeb_perfect/article/details/54135506)

               主要是热点key的请求或者一直没写缓存成功会出现这种情况,解决方案网上很多,这里我写下的我解决方案:

                伪代码:

                //主要是数据库查询的时候串行,会带来毫秒级的卡顿,综合复杂度性能等,推荐此方法  

    String json="";
    cache=redis.get(key);
    if(cache is not null)
    {
    return cache
    }
    }
    else
    {
    lock();//如果分布式部署,这里有用分布式锁哦,分布式锁来锁住数据库查询请求,应尽量避免锁,这样程序就是单线程达不到并发要求,这里使用锁主要是因 极少概率会穿透到数据库,锁一点点时间不影响性能
    //为什么再来一次判断,自行想象下高并发场景下
    cache=redis.get(key);
    if(cache is not null)
    {
    return cache;
    }
    data=json=server.Query(Sql);
    redis.set(data,key);
    unlock();
    return data;
    }

    分布式锁:

      

    //分布式锁
    String get(String key) {
    String value = redis.get(key);
    if (value == null) {
    if (redis.setnx(key_mutex, "1")) {
    // 3 min timeout to avoid mutex holder crash
    redis.expire(key_mutex, 3 * 60)
    value = db.get(key);
    redis.set(key, value);
    redis.delete(key_mutex);
    } else {
    //其他线程休息50毫秒后重试
    Thread.sleep(50);
    get(key);
    }
    }
    }

    备注:设计缓存的时候,尤其是热点key过期的时候 需要考虑击穿,以及雪崩,穿透等情形对下游DB的并发请求带来的影响

    https://blog.csdn.net/zeb_perfect/article/details/54135506

    https://blog.csdn.net/wang0112233/article/details/79558612

    2、1中的方案都是在单主库的环境下讨论的,如果涉及到主从数据库如何处理呢?

          一般主从都是 写主读从,写主后,立马读从,而从还没有更新,有一定的延迟,这个延迟时间我们经验总结暂定500ms,超过500ms超时返回。如果主从的话涉及到强一致性更复杂,这里暂且按照弱一致性的需求,只是要尽量的缩短非一致性的时间

          2.1、淘汰缓存,修改完数据,thread.wait(500s),再次淘汰缓存,下次读从库就是最新数据,在期间有可能500ms的旧数据

          2.2、1.2.6和1.2.7一样,只是在处理更新缓存的时候加上500ms的延迟时间,以此来保证从库更新完成,再更新缓存

    3、主从一致性,即修改完立马就要读取到最新的数据(本方案不涉及到缓存的同步,如果涉及可以结合全篇思路去设计) 方案如下:

          3.1、半同步复制,理应数据库原生的功能,等从库同步完才返回结果,缺点吞吐量下降

          3.2、强制读主库,部分有一致性要求的,代码中强制读取主库,这个时候一定要结合好缓存,提高读性能

          3.3、数据库中间件,一般情况数据库中间件把写路由到主,把读路由到从,此处是记录所以写的key,在500ms内读主库,超过500ms后读从库,能保证绝对的一致性,缺点是成本比较高

          3.4、缓存记录写key法,发生写操作,把此key记录在缓存里过期时间500ms,key存在表示刚更新过,还没完成同步,强制路由到主库,没有则路由到从库

         关于强一致的需求,现实是不多的,本身就使用cache了还要求强一致,貌似本末倒置,但是不排除特殊情况的存在,主要是思路和大家分享。

    --------------------- 作者:zlhzhj 来源:CSDN 原文:https://blog.csdn.net/ZLHZHJ/article/details/80176988?utm_source=copy 版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    微博二级评论爬取
    爬取genome的网页和图片
    一个数据结构转换的问题
    SQLAlchemy ORM教程之二:Query
    SQLAlchemy中filter()和filter_by()有什么区别
    词云加显示条形图
    智联招聘的python岗位数据词云制作
    Python标准库——collections模块的Counter类
    MySQL5.6 windows msi安装介绍
    ICSharpCode.SharpZipLib.Zip
  • 原文地址:https://www.cnblogs.com/axtkdd/p/9760786.html
Copyright © 2011-2022 走看看