缓存问题
缓存穿透
缓存穿透是说收到了一个请求,但是该请求缓存里没有,只能去数据库里查询,然后放进缓存。这里面有两个风险,一个是同时有好多请求访问同一个数据,然后业务系统把这些请求全发到了数据库;第二个是有人恶意构造一个逻辑上不存在的数据,然后大量发送这个请求,这样每次请求都会被发送到数据库,可能导致数据挂掉。
怎么应对这种情况呢?对于恶意访问,一个思路是事先做校验,对恶意数据直接过滤掉,不要发到数据库层;第二个思路是缓存空结果,就是对查询不存在的数据仍然记录一条该数据不存在在缓存里,这样能有效的减少查询数据库的次数。
那么非恶意访问呢?这个要结合缓存击穿来讲。
缓存击穿
上面提到的某个数据没有,然后好多请求都被发到数据库其实可以归为缓存击穿的范畴:对于热点数据,当数据失效的一瞬间,所有请求都被下放到数据库去请求更新缓存,数据库被压垮。
怎么防范这种问题呢?一个思路是全局锁,就是所有访问某个数据的请求都共享一个锁,获得锁的那个才有资格去访问数据库,其他线程必须等待。但是现在的业务都是分布式的,本地锁没法控制其他服务器也等待,所以要用到全局锁,比如用redis的setnx实现全局锁。
另一个思路是对即将过期的数据主动刷新,做法可以有很多,比如起一个线程轮询数据,比如把所有数据划分为不同的缓存区间,定期分区间刷新数据等等。这第二个思路又和我们接下来要讲的缓存雪崩有关系。
缓存雪崩
缓存雪崩是指比如我们给所有的数据设置了同样的过期时间,然后在某一个历史性时刻,整个缓存的数据全部过期了,然后瞬间所有的请求都被打到了数据库,数据库就崩了。
解决思路要么是分治,划分更小的缓存区间,按区间过期;要么是给每个key的过期时间加个随机值,避免同时过期,达到错峰刷新缓存的目的。
缓存刷新
说到刷新缓存,其实也有坑的。比如我之前的一份工作里,有一次大活动,正是如火如荼的时候,所有的广告位突然都变空白了。后来追查原因,所有的广告素材都在缓存里,然后起了个程序,专门负责刷新缓存,每次把当前的素材全量刷新。
坏就坏在这个全量上。因为大活动的时候流量极大,广告更新压力也很大,把负责提供更新素材的程序压崩了。刷新缓存的程序在请求时,收到了一个返回结果Null。接下来就喜闻乐见了,刷新程序根据这个null,清空了整个缓存,所有广告素材都失效了。
在哪里用缓存?
一切地方。例如:
- 我们从硬盘读数据的时候,其实操作系统还额外把附近的数据都读到了内存里
- 例如,CPU在从内存里读数据的时候,也额外读了许多数据到各级cache里
- 各个输入输出之间用buffer保存一批数据统一发送和接受,而不是一个byte一个byte的处理
上面这是系统层面,在软件系统设计层面,很多地方也用了缓存:
- 浏览器会缓存页面的元素,这样在重复访问网页时,就避开了要从互联网上下载数据(例如大图片)
- web服务会把静态的东西提前部署在CDN上,这也是一种缓存
- 数据库会缓存查询,所以同一条查询第二次就是要比第一次快
- 内存数据库(如redis)选择把大量数据存在内存而非硬盘里,这可以看作是一个大型缓存,只是把整个数据库缓存了起来
- 应用程序把最近几次计算的结果放在本地内存里,如果下次到来的请求还是原请求,就跳过计算直接返回结果
缓存设计
后台配置缓存的三种方式
1集群配置
各个redis服务器之间的连接采用的是ping-pong机制使每个服务器间互相通信,检测一个节点是否挂掉采用的是节点间的相互检测(相互投票),当一个节点被该集群中超过半数的节点检测出有问题,即认为该节点挂掉,所以要想投票过半,节点个数至少为3个(当然两个节点亦可以搭建集群,这样高可用性不强,不能采取投票的方式检测节点是否正常工作)【附】。ok那么为什么上图有6个节点,因为一般服务器为了数据安全都会采用一个备用服务器(slave),备用个数至少为一个,所以一共需要6个服务器即使3主3备用。
2Redis请求过多可设置缓存池
3业务端做一层本地缓存(特别集中的高频次极少量数据访问,又不需要对每个用户做特化的)
文章持续优化更新