在互联网的世界里,充斥着各种各样的缓存。商品的缓存,详情页的缓存,活动页缓存等等。除了一级缓存之外,有时还会有二级缓存的存在。而缓存存在的意义除了可以加快系统访问的速度,另外很重要的一点就是分担数据库的压力,把数据访问的责任委托到缓存中去。本文不尝试分析各种缓存的架构,而是简单的从代码的层面上分享下缓存的使用。另外,本文所讲的缓存都是redis。
缓存的选择
我们知道redis是一个基于key-value的数据库,他所支持的数据类型中,我们常用的就是: 、 、 、 、 。要选择合适的类型来缓存我们的数据,就需要了解这些类型是怎么存储数据的。
string
string可以说是redis中最基础的数据类型了,一个string类型的key中可以存放一些值。而这些值可以是字符串,也可是是数字,也可以是json格式的字符串。甚至可以将一个list或set或map对象转换成字符串后存在string类型的key中,总之string类型的key可以保存任意字符串类型的值。要保存一个string类型的值,命令很简单,命令格式为: ,如:
这样就把一个 :dog,保存到了 :animal中去了。要获取一个string类型的值,命令也很简单,命令格式为: ,如:
这样就能返回 :animal中的值了,如果不存在animal这个key的话,则会返回 表示key不存在
hash
hash类型跟java中的map有点像。保存hash类型的数据,命令格式为: 。可以看到和string所不同的是, 变成了 ,另外hash类型比string类型多了一个field。我们可以把hash的 想象成一个map,而 就是map中的key, 就是map中key对应的value。那hash类型 跟string类型一样,都可以保存字符串类型的值。
例如可以通过以上的命令在hash类型的 :animal中保存两种动物的数量,该命令与以下java代码有相同的意义:
获取一个hash类型的值,命令为: 那以下的命令和java代码也表示相同的意义:
list
list类型中可以存放多个值,这些值以列表的形式保存,并且这个列表是一个有方向的列表,有表头和表尾的区别,如:
那数据插入列表中也有两种方式,一个是从表头插入,另一个是从表尾插入。从表头插入的命令是 ,插入的数据会变成新的表头。
从表尾插入的命令是 ,插入的数据会变成新的表尾。举个例子,向 为animal的list中依次从表头插入 , , :1.插入 :
列表变为:
2.插入 :
列表变为:
3.插入 :
列表变为:
而如果从表尾依次插入 , , 后,列表将变为:
PS:需要注意的是,list中的元素是可以重复的。
set
set类型跟list类型有点类似,都是可以插入多个元素,但是不同的是,set中的元素不能重复,并且set没有表头,表尾的概念。除此之外,set类型的数据可以做一些集合的运算,比如取交集:
取并集:
取差集:
往集合中插入元素的命令是:
缓存的命中
一般的我们请求数据时,通过 --> 的方式,先从缓存中请求,请求不到时,在到数据库中请求。那如果未能从缓存中请求到相应的数据,这时又有大量的请求时,则可能会对数据库造成大量的查询。因此当从缓存中没有请求到数据时,在查询数据库之前应该要加锁,只允许一个线程去查询数据库,并将最新的数据重新设置到缓存中去,其他线程等待,一个简单的例子如下:
其中有一个 ,为的是防止有多个线程进入第一个 之后,同步代码块之前,在进入同步代码块之后仍然会执行多次数据库查询,这里加一个 让后进入同步代码块的线程免于查询数据库。
缓存的过期策略
缓存的过期策略设置的好坏非常重要,如果过期时间设置的过短,会造成频繁从数据库中查询数据后设置到缓存中,相反,如果过期时间设置的过长的话,又会导致缓存中的数据不能及时的更新。一般来说,缓存的过期策略跟缓存的更新是相辅相成的。而缓存的过期可以是缓存超时后的被动过期,也可以是数据库中数据更新后将对应的缓存删除的主动过期。对于缓存超时的被动过期,则需要根据实际的应用场景设置一个合适的超时时间。对于那些长时间不会更新或者一天只会更新一次的数据,就要将他的超时时间设置的长一些,对于那些仅仅作为中间计算用的临时的数据,就需要设置一个很短的超时时间。除此之外,应该尽量将缓存的过期时间均匀的分摊到不同的时间上去,防止同一段时间内有大量的缓存过期之后,造成对数据库的大量的查询。
缓存的更新
缓存的更新,我们的做法是,数据库中的数据更新后,则直接将对应缓存的key删除。那么当需要使用缓存的地方发现key不存在了之后,加锁后查询数据库,然后将最新值写入到缓存的key中。
领取专属 10元无门槛券
私享最新 技术干货