这个问题很微妙,可能这位同学内心深处,觉得 Redis 是所有应用缓存的标配。缓存的世界很广阔,对于应用系统来讲,我们经常将缓存划分为本地缓存和分布式缓存。本地缓存 :应用中的缓存组件,缓存组件和应用在同一进程中,缓存的读写非常快,没有网络开销。但各应用或集群的各节点都需要维护自己的单独缓存,无法共享缓存。分布式缓存:和应用分离的缓存组件或服务,与本地应用隔离,多个应用可直接共享缓存。减小 CPU 消耗
将原来需要实时计算的内容提前算好、把一些公用的数据进行复用,这可以减少 CPU 消耗,从而提升响应性能。
减小 I/O 消耗
将原来对网络、磁盘等较慢介质的读写访问变为对内存等较快介质的访问,从而提升响应性能。
HashMap
HashMap 是一种基于哈希表的集合类,它提供了快速的插入、查找和删除操作。可以将键值对作为缓存项的存储方式,将键作为缓存项的唯一标识符,值作为缓存项的内容。
ConcurrentHashMap
ConcurrentHashMap 是线程安全的 HashMap,它在多线程环境下可以保证高效的并发读写操作。
LinkedHashMap
LinkedHashMap 是一种有序的 HashMap ,它保留了元素插入的顺序,可以按照插入顺序或者访问顺序进行遍历。
TreeMap
TreeMap 是一种基于红黑树的有序 Map,它可以按照键的顺序进行遍历。
核心流程:1、红包系统启动后,初始化一个 ConcurrentHashMap 作为红包活动缓存 ;2、数据库查询所有的红包活动,并将活动信息存储在 Map 中;3、定时任务每隔 30 秒 ,执行缓存加载方法,刷新缓存。为什么红包系统会将红包活动信息存储在本地内存 ConcurrentHashMap 呢 ?红包系统是高并发应用,快速将请求结果响应给前端,大大提升用户体验;
红包活动数量并不多,就算全部放入到 Map 里也不会产生内存溢出的问题;
定时任务刷新缓存并不会影响红包系统的业务。
下图展示了 Caffine 框架的使用示例。
虽然本地缓存框架的功能很强大,但是本地缓存的缺陷依然明显。1、高并发的场景,应用重启之后,本地缓存就失效了,系统的负载就比较大,需要花较长的时间才能恢复;2、每个应用节点都会维护自己的单独缓存,缓存同步比较头疼。
与本地缓存相比,分布式缓存具有以下优点:1、容量和性能可扩展通过增加集群中的机器数量,可以扩展缓存的容量和并发读写能力。同时,缓存数据对于应用来讲都是共享的。2、高可用性由于数据被分布在多台机器上,即使其中一台机器故障,缓存服务也能继续提供服务。但是分布式缓存的缺点同样不容忽视。1、网络延迟分布式缓存通常需要通过网络通信来进行数据读写,可能会出现网络延迟等问题,相对于本地缓存而言,响应时间更长。2、复杂性分布式缓存需要考虑序列化、数据分片、缓存大小等问题,相对于本地缓存而言更加复杂。笔者曾经也认为无脑上缓存 ,系统就一定更快,但直到一次事故,对于分布式缓存的观念才彻底改变。2014 年,同事开发了比分直播的系统,所有的请求都是从分布式缓存 Memcached 中获取后直接响应。常规情况下,从缓存中查询数据非常快,但在线用户稍微多一点,整个系统就会特别卡。通过 jstat 命令发现 GC 频率极高,几次请求就将新生代占满了,而且 CPU 的消耗都在 GC 线程上。初步判断是缓存值过大导致的,果不其然,缓存大小在 300k 到 500k 左右。解决过程还比较波折,分为两个步骤:修改新生代大小,从原来的 2G 修改成 4G,并精简缓存数据大小 (从平均 300k 左右降为 80k 左右);
把缓存拆成两个部分,第一部分是全量数据,第二部分是增量数据(数据量很小)。页面第一次请求拉取全量数据,当比分有变化的时候,通过 websocket 推送增量数据。
离用户越近,速度越快;
减少分布式缓存查询频率,降低序列化和反序列化的 CPU 消耗;
大幅度减少网络 IO 以及带宽消耗。
2018 年,笔者服务的一家电商公司需要进行 app 首页接口的性能优化。笔者花了大概两天的时间完成了整个方案,采取的是两级缓存模式,同时利用了 Guava 的惰性加载机制,整体架构如下图所示:
缓存读取流程如下:1、业务网关刚启动时,本地缓存没有数据,读取 Redis 缓存,如果 Redis 缓存也没数据,则通过 RPC 调用导购服务读取数据,然后再将数据写入本地缓存和 Redis 中;若 Redis 缓存不为空,则将缓存数据写入本地缓存中。2、由于步骤 1 已经对本地缓存预热,后续请求直接读取本地缓存,返回给用户端。3、Guava 配置了 refresh 机制,每隔一段时间会调用自定义 LoadingCache 线程池(5 个最大线程,5 个核心线程)去导购服务同步数据到本地缓存和 Redis 中。优化后,性能表现很好,平均耗时在 5ms 左右。最开始我以为出现问题的几率很小,可是有一天晚上,突然发现 app 端首页显示的数据时而相同,时而不同。也就是说:虽然 LoadingCache 线程一直在调用接口更新缓存信息,但是各个 服务器本地缓存中的数据并非完成一致。说明了两个很重要的点:1、惰性加载仍然可能造成多台机器的数据不一致2、LoadingCache 线程池数量配置的不太合理,导致了线程堆积最终,我们的解决方案是:1、惰性加载结合消息机制来更新缓存数据,也就是:当导购服务的配置发生变化时,通知业务网关重新拉取数据,更新缓存。2、适当调大 LoadigCache 的线程池参数,并在线程池埋点,监控线程池的使用情况,当线程繁忙时能发出告警,然后动态修改线程池参数。
全部0条评论
快来发表一下你的评论吧 !