被动式缓存
大型网站架构的三要素缓存
,异步
和并行计算
,其中缓存是最简单也是最常见的。
简单的缓存实现
所有人都会看到过,代码中无处不在都充斥着这类的实现,其实就是一种最简易的缓存实现。
if(data == null){
data = getData();
}
return data;
而这种实现在多线程并发下,就会出现数据重复获取的问题,设置会出现数据不一致的问题。
多线程下的缓存实现
为了解决数据重复获取的问题,我们只需要简单的使用锁即可解决。
if(data == null){
lock(data){
if(data == null){ // double checked
data = getData();
}
}
}
return data;
基本来说,一个小型网站到这一步基本就足够了。但是一个大型网站,这个实现仍然是有问题的。
一个大型网站,基本都会有很多服务器,使用分布式部署。解决了多线程并发问题,但是加锁也无法解决多服务器并发问题。
因为缓存是散列在各个服务器之中,独立存在。所以很容易出现用户看到的数据不一致的问题。
数据一致性的问题
为了解决数据一致性问题,我们一般的做法是使用独立的缓存服务器这种方案。
大概讲解一下缓存的分类:
我一般缓存把分为两大类本地
和远程
。
同时又分别细分内核
、内存
和硬盘
缓存三种。
本地 -- | -- 内核
| -- 内存
| -- 硬盘
远程 -- | -- 内核
| -- 内存
| -- 硬盘
性能优劣排行,从上到下,从本地内核最优到远程硬盘最次。
独立的缓存服务器一般使用的技术是Memcache
或者Redis
之类的。都是属于远程内存的方案。
以使用Redis为例,可以轻松的达到20w的QPS。基本可以满足了绝大多数的网站性能要求。
但是对于一个大型网站,只使用远程型的缓存,20w的QPS显然是不够的。
多级缓存
多级缓存其实我们并不陌生,几乎所有科班出身的都会学过计算机原理,CPU就是一个典型的多级缓存使用案例。
使用多级缓存的时候,无非就是在远程缓存服务器存一份,本地也同时存一份。
data = getLocalData();
if(data == null){
data = getRemoteData();
if(data == null){
data = getData();
setLocalData(data);
setRemoteData(data);
}
}
}
return data;
但是也因此引入了两个问题:
双倍过期时间问题
假设缓存过期时间设置是10s。
A服务器获取数据,分别在缓存服务器缓存10s,本地缓存10s。
9秒900ms后,B服务器去缓存服务器上获取数据,成功获取,并且缓存10s。
A 服务器 |----10s----|
远程服务器 |----10s----|
B 服务器 |----10s----|
实际的缓存时间 |------ 19s900ms -----|
为了解决这个问题,只需要在缓存的数据内添加缓存的到期时间即可。
class doubleCacheModel<T>{
Datetime expireDatetime;
T data;
}
B服务器从缓存服务器取到的数据后,同时取到当前缓存的过期时间,B服务器缓存到指定的时间即可实现缓存过期时间一致的效果。
数据不一致的问题
沿用上面的假设,C服务器在5s的时候修改了数据,更新了本地缓存数据,同时更新了缓存服务器的数据。
但是A服务器上的数据仍然是旧的。而用户的访问随机分布在A和C之间。那么就会出现,每一次刷新,拿到的数据都不一样的灵异事件了。
为了解决数据一致性问题,我们只需开启一个监听线程即可,当缓存数据发生修改时,发起通知,收到通知的服务器进行相关的数据更新即可。
结束
小小的一个缓存,在做之前根本想不到会这么多问题。可惜即便做了这么多,性能还是没有达标。不知不觉已经这么晚了,留下一个思考,如果我还记得的话再来更新。
思考:访问性能波峰问题
假设,缓存时间是10s,读取数据的时间是100ms,那么访问的效果大概如下图:
|-----10s----|--|----10s-----|--|-----10s----|--|----10s-----|
每10s就会有一波访问阻塞的问题。
长缓存时间,可以缓解这个问题,但是就会加剧数据延迟问题。
短缓存时间,可以缓解数据延迟,但是阻塞等待却越发明显。