众所周知,缓存的主要目的是为了加快访问速度,缓解数据库压力。最常用的缓存是分布式缓存,比如redis。面对大部分并发场景或者一些流量不大的中小公司,使用redis基本可以解决问题。但是在大流量的情况下,可能不得不使用本地缓存,比如Guava的LoadingCache和快手开源的ReloadableCache。三种缓存的使用场景这部分会介绍redis的使用场景和局限性,比如guava的LoadingCache和快手开源的ReloadableCache。通过这部分的介绍,可以知道在什么业务场景下应该使用哪种缓存,以及Why。Redis的使用场景和局限性如果广义地说什么时候用redis,那么自然是用在用户访问量太大的地方,从而加快访问速度,缓解数据库压力。如果再细分的话,也分为单节点问题和非单节点问题。如果一个页面有比较高的用户访问量,但是他们访问的不是同一个资源。比如用户详情页,访问量比较高,但是每个用户的数据是不一样的。这种情况下,显然只能使用分布式缓存了。如果使用redis,key就是用户的唯一键,value就是用户信息。redis导致的缓存崩溃。但是需要注意一点,必须设置过期时间,不能同时设置过期。比如用户有活动页面,活动页面可以看到用户在活动期间的获奖数据。粗心的人可能会将用户数据的过期时间设置为事件结束,这样会造成单(热点)点问题。单节点问题说最重要的是redis的单节点的并发问题,因为同一个key会落在redis集群的同一个节点上,那么如果这个key的访问量过高,那么就有一个隐藏这个redis节点有并发的危险,这个key叫做hotkey。如果所有用户访问同一个资源,比如小爱同学app首页为所有用户显示相同的内容(初期),服务器返回同样大的json给h5,显然要用到缓存。首先我们考虑一下使用redis是否可行。由于redis存在单点问题,如果流量过大,那么所有的用户请求都会到达redis的同一个节点。需要评估节点是否能够承受如此大的流量。我们的规则是如果单节点的qps达到千级,就要解决单点问题(即使redis号称可以承受十万级的qps),最常见的方式是使用本地缓存.很明显,小爱同学首页的流量也就一百,用redis是没问题的。LoadingCache的使用场景及局限性对于上面提到的hotkey问题,我们最直接的做法就是使用本地缓存,比如大家最熟悉的guava的LoadingCache,但是使用本地缓存需要能够接受一定的脏数据,因为如果你更新首页后,本地缓存不会更新,只会按照一定的过期策略重新加载缓存,但是在我们的场景下是完全没问题的,因为一旦后台推送了首页,它不会再被改变了。就算改了也没问题。可以设置写入过期时间为半小时,半小时后重新加载缓存。我们可以在这么短的时间内接受脏数据。LoadingCache导致的缓存崩溃虽然本地缓存和机器强相关,虽然代码层面写的是半小时过期,但是由于每台机器的启动时间不同,所以缓存的加载时间不同,并且过期时间也不一样。也不可能机器上的所有请求都在缓存过期后同时请求数据库。但是对于单机来说,也会造成缓存穿透。如果有10台机器,每台1000个qps,只要有一个缓存过期,这1000个请求可能会同时打到数据库。这种问题其实比较容易解决,但是也很容易被忽略,就是在设置LoadingCache的时候,使用LoadingCache的load-miss方法,而不是直接判断cache.getIfPresent()==null,然后请求db;前者会加一个虚拟机级别的锁,保证只有一个请求命中数据库,从而完美解决这个问题。但是如果实时性要求高,比如一段时间内活动频繁,我需要保证活动页面能够近乎实时的更新,也就是在操作配置好活动信息后后台,需要近乎实时的显示在C端对于这次配置的活动信息,此时使用LoadingCache肯定是不行的。ReloadableCache的使用场景及局限性对于上面提到的LoadingCache无法解决的实时性问题,可以考虑使用ReloadableCache,ReloadableCache是??快手的开源本地缓存框架。最大的特点是支持多台机器同时更新缓存。假设我们修改了Home页面信息,然后请求命中了机器A,此时ReloadableCache被重新加载,然后它会发送一个通知,其他监听同一个zk节点的机器收到后会重新更新缓存通知。使用这种缓存一般要求是将全量数据加载到本地缓存中,所以如果数据量过大,肯定会给gc带来压力,这种情况不能使用。由于小爱同学的首页是有状态的,在线状态一般只有两个,所以可以使用ReloadableCache只加载在线状态的首页。总结至此,三种缓存基本介绍完毕。总结一下:对于非热点数据的访问,比如用户维度的数据,可以直接使用redis;对于热点数据访问,如果流量不是很高,想都没想就用redis就可以了;对于热数据,如果在一定时间内允许有脏数据,可以使用LoadingCache;对于热数据,如果你对一致性要求高,数据量不大,可以使用ReloadableCache;无论什么样的提示,虽然本地缓存配备了虚拟机级的锁定来解决故障问题,但意外总是会以意想不到的方式发生。为了保险起见,可以使用两级缓存,即本地缓存+redis+db。缓存的使用简介。关于redis的使用这里就不多说了。相信很多人对api的使用比我对LoadingCache的使用更熟悉。这个网上很多是guava提供的,但是有两点需要注意。对于加载未命中,要么使用Vget(Kkey,Callableloader);或者在使用build时使用build(CacheLoaderloader)。这时候就可以直接使用get()了。另外建议在getIfPresent==null时使用load-miss而不是去查数据库,这样可能会导致缓存崩溃;使用load-miss因为它是线程安全的。如果缓存失败,多个线程调用get有时会只有一个线程去查询db,其他线程需要等待,也就是说这是线程安全的。?
