缓存解决方案

在业务系统系统中,缓存解决方案一共有三种,第一种是最为常见的集中式缓存Redis、第二种是HashMap、Guava Cache、CaffeineEhCache等本地缓存。

第三种,则是集中式缓存Redis + 任意一种本地缓存所组成的双缓存机制。

如下图所示:

无论选择哪种解决方案,其核心作用无外乎两点:提升系统性能和减轻数据库访问压力。

接下来我们来详细盘点一下,这三种缓存解决方案的各自适用场景和优缺点。

集中式缓存

90%的业务场景下,无脑选择Redis作为系统缓存就对了,这是目前最为主流的解决方案,其优点有很多。

1、丰富的数据类型

我们最常用的有五种,String、Hash、List、Set、ZSet,基本上满足于系统中绝大多数业务场景。

除此之外,我们还可以通过BitMap实现用户签到、HyperLogLog实现网站UV统计、Bloom Filter实现黑白名单,等等。

2、优秀的查询性能

Redis中做了很多性能优化的策略,如下图所示:

除此之外,在Redis 6.0中还引入了多线程机制,旨在解决几万QPS的高并发读场景下的IO瓶颈问题,将性能至少提升一倍以上。

3、较低的内存消耗

Redis并没有按照标准的LRU算法进行实现,因为其双向链表的实现方式,对内存消耗的代价是不可接受的。

且Redis接收到每次请求,双向链表也需要进行同步操作,性能影响也不小。

因此,Redis采用了引入LRU时钟值 + 待淘汰数据池的"近似LRU"的实现方式,有效地规避了上述问题。

4、高可用性、扩展性

Redis Cluster支持动态扩容的数据分片机制,来保证其横向扩展能力,以及通过主从复制 + 自动故障转移机制保证高可用性。

致命缺点

但是,Redis纵然有这么多优点,也掩盖不了它的一个致命缺点,那就是高并发场景下的大Key问题。

大Key其实说的是,该Key所对应的value值比较大(如:大于1MB),存储它会占用比较大的内存空间,在进行读取的时候也会占用很大的网络带宽。

大Key本身是没什么问题的,用户每秒钟请求它一两次的不会有任何影响,最怕的就是大Key + 热Key的Buff叠满。

我们以常见的服务器千兆网卡来计算,其最大传输速度为每秒钟128MB。

这也就就意味着,Redis中的某个大Key为1MB、每秒钟有128个及以上请求打到这台服务器上,就能将服务器的网卡打满,从而影响系统可用性。

一旦Redis Cluster中存储大热Key节点的网卡被打满,就会导致集群中的资源消耗倾斜,不仅大热Key的请求被影响,就连访问该节点的其他请求也会被影响。

如下图所示:

也正是由于Redis这个致命缺点,才有了HashMap、Guava Cache、Caffeine、EhCache等本地缓存的"用武之地"。

本地缓存

在HashMap、Guava Cache、Caffeine、EhCache这些本地缓存中,最常用的是HashMap和Caffeine,前者优势为简单易用,后者则具有更加强悍的性能。

Caffeine性能对比图如下:

Caffeine的功能也比较强大,其中包括:

(1)在缓存淘汰算法上,支持结合了LRU和LFU两者优点的W-TinyLFU算法,在流量分布稳定和突发流量上都能保证最佳的命中率。

(2)在缓存淘汰策略上,支持基于容量、基于时间和基于引用三种策略,基于时间的淘汰策略上,支持写入后过期、访问后过期和自定义过期。

(3)在缓存加载策略上,支持手动加载、自动加载、手动异步加载和自动异步加载四种策略。

接下来,我们讲讲这些本地缓存的"用武之地",也就是说,为什么它能够解决Redis的大热Key问题。

Redis的大热Key:

本地缓存中的大热Key:

如图中所示,这个大热Key的流量只会打在Redis集群中的一个节点上,但本地缓存可以用集群中的多个应用服务器均摊流量,这样服务器的网卡就不再成为瓶颈了。

本地缓存还有一个比较经典的应用场景,那就是在EurekaNacos这些配置中心和服务注册中心上,底层实现会在客户端本地缓存一份数据。

这样做可以提升数据查询性能,减轻对Server端的访问压力,并且也降低了对Server端的强依赖,就算Server端挂了功能也能正常运行。

相比较而言,本地缓存既然不如集中式缓存Redis主流,原因在于前者存在一些令人无法忍受的缺点,我们就来盘点一下。

1、数据一致性问题

本地缓存的最大缺点在于,缓存更新的场景下,需要将集群中多个应用服务器的缓存数据全部更新。

无论是通过API全部调用的方式,还是通过消息队列的发布订阅模式,都很难保证绝对意义上的数据一致性问题,这可能会给业务系统埋下很大的坑。

因此,本地缓存只适合所缓存的数据不变更、或是很少变更的业务场景。

2、需数据预热

应用服务器重启或功能发布上线的时候,本地缓存中的数据会全部丢失,此时需要有个将数据全部吃进缓存的数据预热步骤,增加了系统实现的复杂性。

另外,若在本地缓存中缓存数据过多,数据预热时间也会很久。

3、导致GC频繁

本地缓存的数据在强引用状态下不会进行GC,若缓存数据过多会导致GC频繁。

双缓存机制

双缓存机制适用于QPS极高的业务场景,一般将本地缓存用于缓存访问频率极高的热点数据,如:新闻网站上的头条爆点新闻,电商平台中的大促秒杀商品。而Redis则用于缓存访问频率一般的数据,如:新闻网站上的普通新闻、电商平台中正常售卖的商品。

在双缓存机制更有利于系统容灾,无论本地缓存还是Redis出现问题,都有另一个进行兜底。其不足之处在于,数据一致性的问题更难以解决了,仍然只适合所缓存的数据不变更、或是很少变更的业务场景。

相关推荐
海兰16 分钟前
使用 Spring AI 打造企业级 RAG 知识库第二部分:AI 实战
java·人工智能·spring
历程里程碑33 分钟前
二叉树---二叉树的中序遍历
java·大数据·开发语言·elasticsearch·链表·搜索引擎·lua
小信丶1 小时前
Spring Cloud Stream EnableBinding注解详解:定义、应用场景与示例代码
java·spring boot·后端·spring
无限进步_1 小时前
【C++】验证回文字符串:高效算法详解与优化
java·开发语言·c++·git·算法·github·visual studio
亚历克斯神1 小时前
Spring Cloud 2026 架构演进
java·spring·微服务
七夜zippoe1 小时前
Spring Cloud与Dubbo架构哲学对决
java·spring cloud·架构·dubbo·配置中心
海派程序猿1 小时前
Spring Cloud Config拉取配置过慢导致服务启动延迟的优化技巧
java
阿维的博客日记1 小时前
为什么不逃逸代表不需要锁,JIT会直接删掉锁
java
William Dawson1 小时前
CAS的底层实现
java
九英里路1 小时前
cpp容器——string模拟实现
java·前端·数据结构·c++·算法·容器·字符串