oscache之刷新缓存flushEntry的使用
========================== 困扰和痛苦多时的oscache刷新缓存start =======================
Everyday都不同于2015-9-19 周六15:30
【前言】一般而言,oscache缓存常用于在高并发的情形下。当你初次调用缓存的方法时,如果缓存中还没有响应的key,则会去执行底层的sql语句,并把结果缓存起来。等到【下次】你再调用缓存的方法时,如果传入的是与【这次】”同样“的key(当然不同样的key是会去查库的,并把此次不一样的key对应的查询结果同样地给缓存起来,以此类推。),则会去直接从缓存中取出【这次】缓存好的结果。而不必再去连接数据库执行sql语句了。减少与数据库的交互是oscache缓存的一大作用,频繁的连接数据库但查询的结果都一样可不是什么好事儿。
上述的缓存原理大致的可表示为(涉及oschche jar包,需先导入)
import com.opensymphony.oscache.general.GeneralCacheAdministrator; //………………………………………………………………………………………… GeneralCacheAdministrator oscache; //通常由spring注入 public void setOscache(GeneralCacheAdministrator oscache) { this.oscache = oscache; } //定义一个从缓存里根据key取缓存结果的方法 //………………………………………………………… //如果直接传入的key是null或空的,直接返回null; //………………………………………………………… //先尝试从缓存里取,取到了直接从缓存拿结果返回即可; //………………………………………………………………………… //取不到则从库里取,如果从库里取到了结果,则把结果缓存起来,再把结果返回 //……………………………………………………………………………… }
其中,取缓存的语法是:
Object obj = osCache.getFromCache(key)
保存结果到缓存的语法是:
oscache.putInCache(key, obj);
到这儿,我们会一般认为缓存算是真正用起来了。但是,往往容易忽视一个比较重要的东西,那就是——刷新缓存。
从缓存里取东西对应于”查“,但我们谁也不能保证缓存中某个key对应的数据永远是不变的。比如我们从缓存里取机器的信息。但万一某天增加了机器,或修改或删除了机器呢?我们如果还是直接从缓存里面拿原先的数据,则会跟现在最新的数据不同步,造成”滞后“的错误。这是很严重的!
所以除了查缓存,保存结果到缓存之外,我们也不能忽视:”刷新缓存“。
那我们应该怎么办呢?反编译oscache的jar包就会发现它提供了刷新缓存的语法:
oscache.flushEntry(key);
看起来简单吧,但事实上你key如果处理的不好,仍然很容易出错!
就拿刚才举的关于机器的例子来讲,假设你在db里面有1张对应于机器信息的表。你把查询结果放在一个list中,并用一个常量例如"servers"来作为缓存中的保存list的key。没问题,你可以直接用上述讲到的原理保存到缓存,取缓存;但别忘了,一旦你对该表有任何的修改增加或删除操作,记得要在对应的位置放入刷新缓存的方法:oscache.flushEntry("servers");
这样,当下一次调用上述自己封装的,从缓存取结果的方法时,重新进入【取不到则从库里取,如果从库里取到了结果,则把结果缓存起来,再把结果返回】逻辑分支。因为当你执行了oscache.flushEntry("servers");这行代码时,缓存得到了更新,该servers key就不再对应之前的结果了。自然而然的你此时尝试从缓存取不到结果了,所以又得重新查库,把最新的查询结果缓存起来并返回给你。
重点来了!上面说到的key比较好理解,就是一常量。但当你需要参数去查询缓存的时候呢?依然拿机器的例子来说:假设你现在需要根据机器的id以及机器的使用时间判断它是否还能使用(假设机器一个id可对应多个使用时间,机器id是不能随意删除的,因为它对应多个响应时间)(即是否还能使用由机器id和机器使用时间共同决定)。那你的key该如何设计呢?
(其实理解到这儿,我们不妨把第一种无需传过滤条件的缓存作为一种特殊的”父条件“。从这个角度来看,父条件一般是常量或是不能直接被修改的变量)
如果你把key设置为id + 使用时间。从缓存里取结果是可以的,相当于把id + 时间作为缓存的key,对应的使用状态作为value保存起来下次直接取就好了。但如果我修改了机器的使用时间呢?flushEntry(id + time)会生效吗?——答案是不会(本人亲测)。究其原因,time已经是一个新的值了,从而id + time这个key也跟之前的id+time这个key不同了。你不可能指望缓存直接flush一个新的Key吧!同样,增加一个机器的使用时间,time都是新的,更加不用说了;删除呢?删掉一个机器的使用时间,这个time这个都不存在啦,你更不可能去刷新一个不存在的key吧。
所以,在这种根据一个父条件(这里指机器id)的基础上,再根据一个子条件(这里指使用时间)去共同查询结果(即sql里会有这2个或多个需要过滤的条件,这里就考虑俩好了)的情形。我们必须只把父条件作为缓存的key,而子条件不能作为放入到key里面。因为子条件是可能被修改删除和增加的。刷新缓存的时候,它会重新从sql里取结果(此时sql的过滤条件仍然只能是父条件,子条件不能作为sql的过滤条件。具体原因可思考)。只刷新父条件,再次访问从缓存取的方法时,会进入Pojo的查询数据库的方法。
重点是我们如何在Pojo查询数据库并返回结果的方法里返回怎样的数据结构。比如举的这个例子里,你不妨把使用时间作为key,使用状态作为value,返回一个map。到时候你根据查询缓存的方法去取时,缓存的key依然是机器id,只不过取到的不是该机器id对应的所有使用状态信息罢了,结果是你再根据另一个取的条件time,用map.get(time)就好啦!——即你一旦flush了机器id这个缓存key之后,会得到的是一个”新的“以第2个过滤条件time作为key,对应使用状态作为value的map了。你再通过该map get一个新的time,值自然也出来了。当然,从db查询后返回结果情况的不同,数据结构的选择也不同,这是很重要的,决定了我们从缓存拿数据的效率。
总 而 言 之:
1)做缓存一般是在高并发的情形下,一般的增删改查做缓存没有意义;
2)不要只记得保存结果到缓存和从缓存取结果,还别忘记当有数据修改时,要刷新缓存,否则你拿到的缓存中的数据将会和数据库中最新的结果不同步;
3)查缓存和取缓存的key必须一致;当查询缓存的有过滤条件时,缓存所选择的key不能是”子条件“,而是不能直接做修改操作(包括增。删、改)的父条件。
====================== oscache刷新缓存理解end =======================
推荐阅读
-
缓存管理之MemoryCache与Redis的使用
-
从零开始搭建前后端分离的NetCore2.2(EF Core CodeFirst+Autofac)+Vue的项目框架之八MemoryCache与redis缓存的使用
-
Java本地缓存工具之LoadingCache的使用详解
-
缓存管理之MemoryCache与Redis的使用
-
redis之django-redis的简单缓存使用
-
Redis之sql缓存的具体使用
-
从零开始搭建前后端分离的NetCore2.2(EF Core CodeFirst+Autofac)+Vue的项目框架之八MemoryCache与redis缓存的使用
-
oscache之刷新缓存flushEntry的使用
-
Java本地缓存工具之LoadingCache的使用详解
-
Redis之sql缓存的具体使用