缓存架构不够好,系统容易瘫痪
The following article is from 架构师修行之路 Author 菜v菜
缓存能大幅度提高系统性能,也能大幅度提高系统瘫痪几率 怎么样防止缓存系统被穿透? 缓存的雪崩是不是可以完全避免?
“缓存的最终目的,是在保证请求低延迟的情况下,尽最大努力提高系统的吞吐量
“缓、存穿透是指:当一个请求到来的时候,在缓存中没有查找到对应的数据(缓存未命中),业务系统不得不从数据库(这里其实可以笼统的成为后端系统)中加载数据
1、发生缓存穿透的原因根据场景分为两种:
请求的数据在缓存和数据中都不存在
请求的数据在缓存中不存在,在数据库中存在
2、回写空值
当有大量的空值被写入缓存系统中,同样会占用内存,不过理论上不会太多,完全取决于key的数量。而且根据缓存淘汰策略,可能会淘汰正常的数据缓存项
空值的过期时间应该短一些,比如正常的数据缓存过期时间可能为2小时,可以考虑空值的过期时间为10分钟,这样做一是为了尽快释放服务器的内存空间,二是如果业务产生相应的真实数据,可以让缓存的空值快速失效,尽快做到缓存和数据库一致。
//获取用户信息
public static UserInfo GetUserInfo(int userId)
{
//从缓存读取用户信息
var userInfo = GetUserInfoFromCache(userId);
if (userInfo == null)
{
//回写空值到缓存,并设置缓存过期时间为10分钟
CacheSystem.Set(userId, null,10);
}
return userInfo;
}
3、布隆过滤器
“布隆过滤器:将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力
占用内存非常小 对于判断一个数据不存在百分百正确
缓存雪崩
“缓存雪崩是指缓存中数据大批量同时过期,造成查询数据库数据量巨大,引起数据库压力过大导致系统崩溃。
设置不同过期时间
给缓存的每个key设置不同的过期时间是最简单的防止缓存雪崩的手段,整体思路是给每个缓存的key在系统设置的过期时间之上加一个随机值,或者干脆是直接随机一个值,有效的平衡key批量过期时间段,消掉单位之间内过期key数量的峰值。
public static int SetUserInfo(int userId)
{
//读取用户信息
var userInfo = GetUserInfoFromDB(userId);
if (userInfo != null)
{
//回写到缓存,并设置缓存过期时间为随机时间
var cacheExpire = new Random().Next(1, 100);
CacheSystem.Set(userId, userInfo, cacheExpire);
return cacheExpire;
}
return 0;
}
后台单独线程更新
这种场景下,可以把缓存设置为永不过期,缓存的更新不是由业务线程来更新,而是由专门的线程去负责。当缓存的key有更新时候,业务方向mq发送一个消息,更新缓存的线程会监听这个mq来实时响应以便更新缓存中对应的数据。不过这种方式要考虑到缓存淘汰的场景,当一个缓存的key被淘汰之后,其实也可以向mq发送一个消息,以达到更新线程重新回写key的操作。
缓存的可用性和扩展性
和数据库一样,缓存系统的设计同样需要考虑高可用和扩展性。虽然缓存系统本身的性能已经比较高了,但是对于一些特殊的高并发的热点数据,还是会遇到单机的瓶颈。举个栗子:假如某个明星出轨了,这个信息数据会缓存在某个缓存服务器的节点上,大量的请求会到达这个服务器节点,当到达一定程度的时候同样会发生down机的情况。类似于数据库的主从架构,缓存系统也可以复制多分缓存副本到其他服务器上,这样就可以将应用的请求分散到多个缓存服务器上,缓解由于热点数据出现的单点问题。
和数据库主从一样,缓存的多个副本也面临着数据的一致性问题,同步延迟问题,还有主从服务器相同key的过期时间问题。
至于缓存系统的扩展性同样的道理,也可以利用“分片”的原则,利用一致性哈希算法将不同的请求路由到不同的缓存服务器节点,来达到水平扩展的要求,这一点和应用的水平扩展道理一样。
总结
推荐阅读:
一个工作三年的同事,居然还搞不清深拷贝、浅拷贝...Netty、Kafka中的零拷贝技术到底有多牛?
每日打卡赢积分兑换书籍入口
👇🏻👇🏻👇🏻