随着互联网的高速发展,市面上也出现了愈来愈多的网站和app
。咱们判断一个软件是否好用,用户体验就是一个重要的衡量标准。好比说咱们常常用的微信,打开一个页面要十几秒,发个语音要几分钟对方才能收到。相信这样的软件你们确定是都不肯意用的。软件要作到用户体验好,响应速度快,缓存就是必不可少的一个神器。缓存又分进程内缓存和分布式缓存两种:分布式缓存如redis
、memcached
等,还有本地(进程内)缓存如ehcache
、GuavaCache
、Caffeine
等。html
缓存做为一个数据数据模型对象,那么它有一些什么样的特征呢?下面咱们分别来介绍下这些特征。java
hitCount
(命中次数)。适用于保证高频数据有效性场景。本地缓存的话是咱们的应用和缓存都在同一个进程里面,获取缓存数据的时候纯内存操做,没有额外的网络开销,速度很是快。它适用于缓存一些应用中基本不会变化的数据,好比(国家、省份、城市等)。redis
进程缓存的话,通常能够在应用启动的时候,把须要的数据加载到系统中。更新缓存的话能够采起定时更新(实时性不高)。具体实现的话就是在应用中起一个定时任务(ScheduledExecutorService、TimerTask等),让它每隔多久去加载变动(数据变动以后能够修改数据库最后修改的时间,每次查询变动数据的时候均可以根据这个最后变动时间加上半小时大于当前时间的数据)的数据从新到缓存里面来。若是以为这个比较麻烦的话,还能够直接所有全量更新(就跟项目启动加载数据同样)。这种方式的话,对数据更新可能会有点延迟。可能这台机器看到的是更新后的数据,那台机器看到的数据仍是老的(机器发布时间可能不同)。因此这种方式比较适用于对数据实时性要求不高的数据。若是对实时性有要求的话能够经过广播订阅mq
消息。若是有数据更新mq
会把更新数据推送到每一台机器,这种方式的话实时性会比前一种定时更新的方法会好。可是实现起来会比较复杂。
算法
常见本地缓存有如下几种实现方式:
从上述表格咱们看出性能最佳的是Caffeine
。关于这个本地缓存的话我仍是强烈推荐的,里面提供了丰富的api
,以及各类各样的淘汰算法。如需了解更加详细的话能够看下之前写的这个篇文章《本地缓存性能之王Caffeine》。数据库
redis
、MemCache
等。在高并发的环境下,好比春节抢票大战,一到放票的时间节点,分分钟大量用户以及黄牛的各类抢票软件流量进入12306
,这时候若是每一个用户的访问都去数据库实时查询票的库存,大量读的请求涌入到数据库,瞬间Db
就会被打爆,cpu
直接上升100%
,服务立刻就要宕机或者假死。即便进行了分库分表也是没法避免的。为了减轻db的压力以及提升系统的响应速度。通常都会在数据库前面加上一层缓存,甚至可能还会有多级缓存。api
指大量缓存同一时间段集体失效,或者缓存总体不能提供服务,致使大量的请求所有到达数据库
对数据CPU
和内存形成巨大压力,严重的会形成数据库宕机。所以而造成的一系列连锁反应形成整个系统奔溃。
解决这个问题能够从如下方面入手:缓存
redis
节点下线,缓存仍是能够用。通常稍微大点的公司还可能会在多个机房部署Redis。key
的过时时间,定时去轮询这些key
的过时时间。例如一个key
的value
设置的过时时间是30min
,那咱们能够为这个key设置它本身的一个过时时间为20min
。因此当这个key
到了20min的时候咱们就能够从新去构建这个key
的缓存,同时也更新这个key
的一个过时时间。指查询一个不存在的数据,每次经过接口或者去查询数据库都查不到这个数据,好比黑客的恶意攻击,好比知道一个订单号后,而后就伪造一些不存在的订单号,而后并发来请求你这个订单详情。这些订单号在缓存中都查询不到,而后会致使把这些查询请求所有打到数据库或者SOA接口。这样的话就会致使数据库宕机或者你的服务大量超时。
这种查询不存在的数据就是缓存击穿。
解决这个问题能够从如下方面入手:服务器
是指缓存里面的一个热点key
(拼多多的五菱宏光神车的秒杀)在某个时间点过时。针对于这一个key有大量并发请求过来而后都会同时去数据库请求数据,瞬间对数据库形成巨大的压力。
这个的话能够用缓存雪崩的几种解决方法来避免:微信
key
的过时时间,定时去轮询这些key
的过时时间。例如一个key
的value
设置的过时时间是30min
,那咱们能够为这个key设置它本身的一个过时时间为20min
。因此当这个key到了20min的时候咱们就能够从新去构建这个key
的缓存,同时也更新这个key
的一个过时时间。key
的状况下,好比你有100
个并发请求都要来取A
的缓存,这时候咱们能够借助redis分布式锁来构建缓存,让只有一个请求能够去查询DB其余99个(没有获取到锁)都在外面等着,等A查询到数据而且把缓存构建好以后其余99
个请求都只须要从缓存取就行了。原理就跟咱们java
的DCL
(double checked locking)思想有点相似。咱们通常的缓存更新主要有如下几种更新策略:网络
若是想要真正的设计好一个缓存,咱们仍是必需要掌握不少的知识,对于不一样场景,缓存有各自不一样的用法。好比实际工做中咱们对于订单详情的一个缓存。咱们可能会根据订单的状态来来构建缓存。咱们就以机票订单为例,已出行、或者已经取消的订单咱们基本上是不会去管的(订单状态已经终止了),这种的话数据基本也不会变了,因此对于这种订单咱们设置的过时时间是否是就能够久一点,好比7天或者30天。对于未出行即将起飞的订单,这时候顾客是否是就会频繁的去刷新订单看看,看看有没有晚点什么的,或者登机口是在哪。对于这种实时性要求比较高的订单咱们过时时间仍是要设置的比较短的,若是是须要更改订单的状态查询的时候能够直接不走缓存,直接查询master
库。毕竟这种更改订单状态的操做仍是比较有限的。大多数状况都是用来展现的。展现的话是能够容许实时性要求没那么高。总的来讲须要开具体的业务,没有通用的方案。看你的业务需求的容忍度,毕竟脱离了业务来谈技术都是耍流氓,是业务驱动技术。
站在巨人的肩膀上摘苹果:
https://juejin.im/post/6844903665845665805
https://tech.meituan.com/2017/03/17/cache-about.html
http://www.javashuo.com/article/p-ftdcqtnl-cx.html