分类
本地缓存(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还原成它最初本来的存储功能
相关推荐
它可以使用不同的缓存实现,如 EhCache 、 JBossCache 、 OsCache 等 (二级缓存是缓存实体对象的) 还有一个类型的 CACHE 就是 QueryCache . 它的作用就是缓存一个 Query 以及 Query 返回对象的 Identifier 以及...
hibernate 二级缓存原理规律总结,总结、整理了二级缓存方面的实际运用情况
NULL 博文链接:https://jinnianshilongnian.iteye.com/blog/1525884
Hibernate_二级缓存总结 开发技术 - Java.zip
在工作中无处不在的二级缓存,Hibernate -- Shiro -- MyBatis 这三个框架都有二级缓存的技术,总结出详细的配置文档。详解了每步的配置!
这里主要总结一下二级缓存。 1.首先需要在hibernate.cfg.xml中配置,当然需要导入缓存的jar包 <property name="hibernate.cache.use_query_cache">true <property name="hibernate.cache.provider_class">org....
SHH两年工作经验:hibernate缓存总结和优化
Hibernate是一种面向Java环境的ORM工具。系统地分析了Hibernate的缓存结构,并描述了二级缓存的查询过程、缓存策略;同时总结了二级缓存使用中的一些限制,以及使用二级缓存的优化策略。
dot net memcached 分布式缓存应用类库 本人收藏了3年的资源 现放出 都是总结了很多系统 软件项目实施过程中的经验的 慢慢积累的
这里需要解释说明一下,很多开发者觉得Memcached是一种分布式缓存系统,但是其实Memcached服务端本身是单实例的,只是在客户端实现过程中可以根据存储的主键做分区存储,而这个区就是Memcached服务端的一个或者多个...
PHP清除缓存的几种方法总结 现在开发的项目是用tp3.1版本的,在开发过程中我们常常会遇到页面缓存的问题(特别是html的缓存);刷新后还是旧版的数,再刷新下还是旧版数据,慢慢的...第二:TP框架的缓存目录存放在文件夹p
二、缓存击穿 缓存击穿是指缓存中的一个热点Key(比如一个秒杀商品),在某个时间点过期的时候,恰好在这个时间点访问量剧增,对这个Key有大量的并发请求过来,请求发现缓存过期一般都会从后端DB加载数据并回设回缓存...
本人在做项目时用到了Hibernate的二级缓存,使用的是EhCache,结合本人自己的理解总结了如何在java web项目中配合Hibernate使用二级缓存,以提高程序的性能,附带需要的文件,参考的文件,和测试类以及说明。
个人学习中的自我总结,希望给您带来帮助,祝学习愉快!
本文整理汇总了C#缓存的数据库依赖类SqlCacheDependency的...其中参数一代表要启用缓存的数据库,参数二表示缓存的表。在实际使用过程中,只需要指明缓存的数据库和表即可。 方法是属性的应用(代码与CacheDependency
1. 预期目标 2. 任务安排与完成情况 3. 性能对比实验结果: 4. 实验结果分析 5. 实验中遇到的问题分析与解决: 6. 结论 7. 遗留问题 8. 二
最近在看 webpack 如何做持久化缓存的内容,发现其中还是有一些坑点的,正好有时间就将它们整理总结一下,读完本文你大致能够明白: 什么是持久化缓存,为什么做持久化缓存? webpack 如何做持久化缓存? webpack ...
mybatis看这一篇就够了MyBatis 思维...6. 一级缓存与二级缓存 一级缓存(本地缓存)的原理和作用。 配置和使用二级缓存。 缓存的失效和刷新机制。 7. 延迟加载与关联查询 延迟加载的概念和使用场景。 使用 、<collec
CrossBit是一个多元多目标的动态二进制翻译系统,通过对CrossBit二进制翻译器的性能进行的研究,分析动态二进制翻译器性能提升中所必须解决的若干问题,并通过定量的分析总结了一些二进制翻译系统的在不同的配置和...
之前开发了一个单页面应用,按照深度,分为三层:目录页、一级子页(标签页、故事页等)、二级子页(故事编辑页)。 这三类页面都共享一个完整的数据model,从上级页面进入下一级页面时,能够加载相应数据;回到上一...