`
uule
  • 浏览: 6305635 次
  • 性别: Icon_minigender_1
  • 来自: 一片神奇的土地
社区版块
存档分类
最新评论

缓存总结(二)

阅读更多

 

四种套路更新缓存

实时刷新缓存 

分布式之数据库和缓存双写一致性方案解析(重要)

 

 

分类

 

本地缓存(HashMap/ConcurrentHashMap、Ehcache、Guava Cache等),

缓存服务(Redis/Tair/Memcache等)。

 

使用场景

 

什么情况适合用缓存?考虑以下两种场景:

1、短时间内相同数据重复查询多次且数据更新不频繁,这个时候可以选择先从缓存查询,查询不到再从数据库加载并回设到缓存的方式。此种场景较适合用单机缓存。

2、高并发查询热点数据,后端数据库不堪重负,可以用缓存来扛。

 

选型考虑

如果数据量小,并且不会频繁地增长又清空(这会导致频繁地垃圾回收),那么可以选择本地缓存。具体的话,如果需要一些策略的支持(比如缓存满的逐出策略),可以考虑Ehcache;如不需要,可以考虑HashMap;如需要考虑多线程并发的场景,可以考虑ConcurentHashMap。

其他情况,可以考虑缓存服务。目前从资源的投入度、可运维性、是否能动态扩容以及配套设施来考虑,我们优先考虑Tair。除非目前Tair还不能支持的场合(比如分布式锁、Hash类型的value),我们考虑用Redis。

 

设计关键点

 

什么时候更新缓存?如何保障更新的可靠性和实时性?

 

更新缓存的策略,需要具体问题具体分析。这里以门店POI的缓存数据为例,来说明一下缓存服务型的缓存更新策略是怎样的?目前约10万个POI数据采用了Tair作为缓存服务,具体更新的策略有两个:

1、接收门店变更的消息,准实时更新。

2、给每一个POI缓存数据设置5分钟的过期时间,过期后从DB加载再回设到DB。这个策略是对第一个策略的有力补充,解决了手动变更DB不发消息、接消息更新程序临时出错等问题导致的第一个策略失效的问题。通过这种双保险机制,有效地保证了POI缓存数据的可靠性和实时性。

 

缓存是否会满,缓存满了怎么办?

 

对于一个缓存服务,理论上来说,随着缓存数据的日益增多,在容量有限的情况下,缓存肯定有一天会满的。如何应对?

 

① 给缓存服务,选择合适的缓存逐出算法,比如最常见的LRU。

② 针对当前设置的容量,设置适当的警戒值,比如10G的缓存,当缓存数据达到8G的时候,就开始发出报警,提前排查问题或者扩容。

③ 给一些没有必要长期保存的key,尽量设置过期时间

 

缓存是否允许丢失?丢失了怎么办?

 

根据业务场景判断,是否允许丢失。如果不允许,就需要带持久化功能的缓存服务来支持,比如Redis或者Tair。更细节的话,可以根据业务对丢失时间的容忍度,还可以选择更具体的持久化策略,比如Redis的RDB或者AOF。

 

缓存被“击穿”问题

 

对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑另外一个问题:缓存被“击穿”的问题。

概念:缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

 

如何解决:业界比较常用的做法,是使用mutex(互斥)。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。

类似下面的代码:

public String get(key) {
    String value = redis.get(key);      
	if (value == null) { //代表缓存值过期
          //设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db
			if (redis.setnx(key_mutex, 1, 3 * 60) == 1) {  //代表设置成功
				//从数据库获取
				value = db.get(key);
                redis.set(key, value, expire_secs);
                redis.del(key_mutex);
            } else {  
				//这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
                sleep(50);
                get(key);  //重试
            }
    }else {
		return value;      
    }
}

 

setnx 赋值判断原值是否存在,存在不赋值,返回0;不存在才赋值,返回1

setnx name Tom  ---返回值:0,因为name的原有value为zlh,存在值则不赋值。

get name  ---返回值:zlh,因为有值,故上面赋值为tom失败,返回0。

 

setnx phone 18501733702   ---返回值:1,赋值成功,因为原来不存在phone的key与value。

get phone   ---返回值:18501733702,说明上面的setnx赋值成功。

 

。。。

==================================================================

说说通用的缓存策略,有两种,下面来点图



 


 

 第一种方案,客户端使用的比较多,缓存和 DB(或者文件)同步更新,服务端一般都是用第二种方案。

 

=====================================================================

那些年使用缓存踩过的坑--缓存更新策略

https://my.oschina.net/percylee/blog/903295

今天讲的这个话题,我相信是众多工程师和团队的痛。从我刚开始工作,那时候构建本地缓存,到后续memcache, Redis的出现,到现在各种分布式集群的缓存,例如redis Cluster等产品的出现,缓存越来越发达和复杂了,缓存对我们的系统也越发重要,现在很难相信一个后端服务里没有缓存的存在。在这篇文章里,我会和大家分享一下过去踩到的缓存坑,然后试图给出一些解决方案,大家可以一起讨论,最终拿出更好的方法。由于篇幅有限,所以这里的缓存讨论,只局限于后端服务的缓存,并且不涉及具体的框架,对于H5,iOS和Android等前端缓存的讨论,会在以后的文章里呈现出来。

 

案例1,缓存和DB的同步更新不在同一个事务里并且没有重试补偿机制

 

为了减少系统间的依赖,不同系统的数据更新往往不放在同一个事务里,采用MQ来进行通信。大家可以看下图,后台系统CRM更新产品数据到DB,Product系统收到异步消息通知后,更新最新数据到缓存。这是一个最常见的缓存应用场景,我相信很多团队都是这样用的。在这个Case里容易出现的问题在于,如果批处理任务收到消息后服务crash掉了,缓存没有正常更新,就出现了与DB的数据不同步,前端系统一直不能读到最新数据,导致业务异常

  

解决方案:

1.  失败消息一定要建立一定时间间隔的重试机制

2.  系统要有缓存更新的报警机制,方便更新失败或者重试超时后,可以人工介入进行补偿。

 

 

案例2, 同一数据被1个以上的服务执行写操作,其中一个服务的缓存数据没有版本控制

 

这也是两个不同服务更新数据过程中很常见的情况,见下图,CRM系统更新了某个用户的Profile, 保存更新数据库后,通过MQ通知用户系统更新缓存,由于是异步更新延迟,在缓存更新前,用户系统收到前端的指令,读取了当前缓存里的用户数据,做了修改,并更新到DB中。出现的结果就是数据库里的CRM的更新被错误覆盖。

  

解决方案:

缓存里的数据有一个标志位可以作为更新数据库数据的依据(Update_time or Version), 如果缓存里数据时间与数据库时间不能匹配,意味着另外一个服务更新了该数据,那么就先从DB里读取最新数据版本,然后在新版本上提交数据。

  

案例3, 并发查询缓存中同一数据,如果缓存没命中,导致DB瞬时被打爆做促销活动的时候,存在大量用户的并发访问某一个特定商品,该商品数据缓存失效,或者做了数据更改,但是对应缓存还没有更新,那么所有这些访问将同时直接被作用到DB上。

  

解决方案:

做一个计数器或者锁(没有特别复杂逻辑的话,可以直接用HashMap),如果发现某个KEY缓存没有命中,那么在计数器+1, 然后访问数据库,拿到结果更新缓存,清理掉计数器中的key。 在这个过程中,如果有第二个线程或者更多的线程需要访问这个KEY时,发现计数器的值>1 或者被加锁, 那么wait, 直到计数器清理掉,当然,这个技术器阈值是可以在配置文件里配置的,不一定是1。

  

案例4, 缓存没有设置默认值,被攻击,缓存一直保持在被“穿透”状态

这个情况,和案例3比较类似,都是缓存无法命中,但不一样的地方在于,数据的KEY值是无法控制的所以没法简单的用计数器和锁来处理, 比方,被人为攻击,制造的大量的无效userID访问。

 

解决方案:

所有没有在缓存的KEY,全部分配一个默认VALUE “UNKOWN-KEY” ,具体是什么情况下,将默认值分配给没有命中的KEY, 这个可以根据自己的业务系统来定,比方说,可以根据特定的IP段,或者没有命中的总次数等,然后我们就可以决定是否继续访问DB还是直接返回默认值给前端,拒绝本次数据访问。这种做法的核心在于,每次数据访问,都会有缓存结果返回,根据系统的情况来决定是否要进一步访问DB。

  

总结,今天列举的这几个案例,归纳起来,可以总结为以下几点:

 

1. 保证缓存同步

2. 减少缓存并发

3. 杜绝缓存穿透

  

缓存与背后的DB是相互依存的关系,缓存系统的设计原则,就是将访问的异常处理或者压力尽可能的前置处理掉,将DB还原成它最初本来的存储功能

 

 

 

 

 

  • 大小: 162.6 KB
  • 大小: 217.7 KB
  • 大小: 152 KB
分享到:
评论

相关推荐

    Hibernate_二级缓存总结

    它可以使用不同的缓存实现,如 EhCache 、 JBossCache 、 OsCache 等 (二级缓存是缓存实体对象的) 还有一个类型的 CACHE 就是 QueryCache . 它的作用就是缓存一个 Query 以及 Query 返回对象的 Identifier 以及...

    hibernate 二级缓存收集、总结、整理

    hibernate 二级缓存原理规律总结,总结、整理了二级缓存方面的实际运用情况

    Hibernate 二级缓存 总结整理

    NULL 博文链接:https://jinnianshilongnian.iteye.com/blog/1525884

    Hibernate-二级缓存总结 开发技术 - Java.zip

    Hibernate_二级缓存总结 开发技术 - Java.zip

    47-二级缓存 Hibernate-Shiro-MyBatis

    在工作中无处不在的二级缓存,Hibernate -- Shiro -- MyBatis 这三个框架都有二级缓存的技术,总结出详细的配置文档。详解了每步的配置!

    hibernate二级缓存

    这里主要总结一下二级缓存。 1.首先需要在hibernate.cfg.xml中配置,当然需要导入缓存的jar包 <property name="hibernate.cache.use_query_cache">true <property name="hibernate.cache.provider_class">org....

    hibernate缓存总结

    SHH两年工作经验:hibernate缓存总结和优化

    Hibernate缓存技术研究

    Hibernate是一种面向Java环境的ORM工具。系统地分析了Hibernate的缓存结构,并描述了二级缓存的查询过程、缓存策略;同时总结了二级缓存使用中的一些限制,以及使用二级缓存的优化策略。

    dot net memcached 分布式缓存应用类库

    dot net memcached 分布式缓存应用类库 本人收藏了3年的资源 现放出 都是总结了很多系统 软件项目实施过程中的经验的 慢慢积累的

    MemCached 缓存系统配置说明

    这里需要解释说明一下,很多开发者觉得Memcached是一种分布式缓存系统,但是其实Memcached服务端本身是单实例的,只是在客户端实现过程中可以根据存储的主键做分区存储,而这个区就是Memcached服务端的一个或者多个...

    PHP清除缓存的几种方法总结

    PHP清除缓存的几种方法总结 现在开发的项目是用tp3.1版本的,在开发过程中我们常常会遇到页面缓存的问题(特别是html的缓存);刷新后还是旧版的数,再刷新下还是旧版数据,慢慢的...第二:TP框架的缓存目录存放在文件夹p

    window手动操作清理redis缓存的技巧总结

    二、缓存击穿 缓存击穿是指缓存中的一个热点Key(比如一个秒杀商品),在某个时间点过期的时候,恰好在这个时间点访问量剧增,对这个Key有大量的并发请求过来,请求发现缓存过期一般都会从后端DB加载数据并回设回缓存...

    EhCache_Hibernate二级缓存配置_详细

    本人在做项目时用到了Hibernate的二级缓存,使用的是EhCache,结合本人自己的理解总结了如何在java web项目中配合Hibernate使用二级缓存,以提高程序的性能,附带需要的文件,参考的文件,和测试类以及说明。

    Hinernate二级缓存配置

    个人学习中的自我总结,希望给您带来帮助,祝学习愉快!

    C#缓存之SqlCacheDependency用法实例总结

    本文整理汇总了C#缓存的数据库依赖类SqlCacheDependency的...其中参数一代表要启用缓存的数据库,参数二表示缓存的表。在实际使用过程中,只需要指明缓存的数据库和表即可。 方法是属性的应用(代码与CacheDependency

    hybrid app 本地缓存技术预研一期总结1

    1. 预期目标 2. 任务安排与完成情况 3. 性能对比实验结果: 4. 实验结果分析 5. 实验中遇到的问题分析与解决: 6. 结论 7. 遗留问题 8. 二

    浅谈Webpack 持久化缓存实践

    最近在看 webpack 如何做持久化缓存的内容,发现其中还是有一些坑点的,正好有时间就将它们整理总结一下,读完本文你大致能够明白: 什么是持久化缓存,为什么做持久化缓存? webpack 如何做持久化缓存? webpack ...

    mybatis思维导图总结.zip

    mybatis看这一篇就够了MyBatis 思维...6. 一级缓存与二级缓存 一级缓存(本地缓存)的原理和作用。 配置和使用二级缓存。 缓存的失效和刷新机制。 7. 延迟加载与关联查询 延迟加载的概念和使用场景。 使用 、<collec

    论文研究-动态二进制翻译器CrossBit的性能分析与评估.pdf

    CrossBit是一个多元多目标的动态二进制翻译系统,通过对CrossBit二进制翻译器的性能进行的研究,分析动态二进制翻译器性能提升中所必须解决的若干问题,并通过定量的分析总结了一些二进制翻译系统的在不同的配置和...

    详解vue-router数据加载与缓存使用总结

    之前开发了一个单页面应用,按照深度,分为三层:目录页、一级子页(标签页、故事页等)、二级子页(故事编辑页)。 这三类页面都共享一个完整的数据model,从上级页面进入下一级页面时,能够加载相应数据;回到上一...

Global site tag (gtag.js) - Google Analytics