一次caffeine引起的CPU飙升问题

news2024/11/14 2:59:22

背景

背景是上游服务接入了博主团队提供的sdk,已经长达3年,运行稳定无异常,随着最近冲业绩,流量越来越大,直至某一天,其中一个接入方(流量很大)告知CPU在慢慢上升且没有回落的迹象,dump文件能看到缓存的holder占用4个G,那不用说了,责无旁贷

打开他们的内存监控,G1的老年代占用大概是这个样子

在这里插入图片描述

可以看到,老年代每次gc后使用量都在上升,说明每次能gc的内存越来越少,而cpu也是蹭蹭往上追,直至崩掉

原因

查看dump,能看到我们的缓存对象cacheHoler占用高达4g,其中cacehKey的数量更是高达900w!

首先是惊呆了,这个缓存当初设置的maximumSize可只有2w啊,这900w什么鬼!

caffeine介绍

官方是这么说的,一句话,目前最牛逼的本地java缓存库

	Caffeine 是一个高性能Java 缓存库,提供接近最佳的命中率

我们先弄清楚caffeine的原理,是不是使用姿势有问题?

官方的一个小demo

LoadingCache<Key, Graph> cache = Caffeine.newBuilder()
    .maximumSize(10_000)
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .build(key -> createExpensiveGraph(key));

对比一下我们的适用方式

Caffeine.newBuilder()
 			.executor(executorService)
            .refreshAfterWrite(12000, TimeUnit.SECONDS)
            .expireAfterWrite(600, TimeUnit.SECONDS)
            .maximumSize(20000)
            .buildAsync(key -> {
                 return callForDemo("demo");
            });

不同之处也就是我们用了异步的cache,定义了自己的线程池,指定了refreshAfterWrite参数为1200秒

好像姿势没什么问题?
这不咱也找到了官方的异步用法

AsyncLoadingCache<Key, Graph> cache = Caffeine.newBuilder()
    .maximumSize(10_000)
    .expireAfterWrite(10, TimeUnit.MINUTES)
    // Either: Build with a synchronous computation that is wrapped as asynchronous 
    .buildAsync(key -> createExpensiveGraph(key));
    // Or: Build with a asynchronous computation that returns a future
    .buildAsync((key, executor) -> createExpensiveGraphAsync(key, executor));

思考每个参数的含义

  1. maximumSize:指定缓存可以包含的最大条目数。请注意,缓存可能会在超过此限制之前逐出某个条目,或者在逐出时暂时超过阈值。当缓存大小增长到接近最大值时,缓存会逐出不太可能再次使用的条目。例如,缓存可能会逐出某个条目,因为它最近未使用或非常经常使用。
  2. expireAfterAccess:指定在条目创建、最近替换其值或最后一次读取条目后经过固定持续时间后,应自动从缓存中删除每个条目
  3. refreshAfterWrite:指定在创建条目或最近替换其值后经过固定持续时间后,活动条目就有资格进行自动刷新。当出现对条目的第一个过时请求时,将执行自动刷新。触发刷新的请求将进行异步调用,并立即返回旧值。

一个重要信息:maximumSize的缓存不会立即驱逐
那为题是不是就出现在这里?

多线程模拟

那我们用1k个线程模拟一下从maximumSize为20的缓存实例获取缓存

        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            new Thread(() -> {
                UserCharacter uc = new UserCharacter();
                uc.setUid(finalI +"");
                uc.setStationId(finalI +"111");
 
                configHolder.getAbResultCache(uc, "demo")
                    .getAbResults();
            }).start();
        }
        //Thread.sleep(2000);
        configHolder.getStats();
public void getStats() {
        System.out.println( abResultCache.synchronous().asMap());
    }

第一次调用,多线程获取缓存后立即查看缓存中的快照map数量为1000,明显超过maximumSize定义的20

第二次调用,添加代码Thread.sleep(5000);查看缓存中的快照map数量为20,正好是maximumSize定义的20

第三次调用,添加代码Thread.sleep(2000);查看缓存中的快照map数量为100-300不等,说明大于20的缓存条目正在被驱逐

结论

caffeine的缓存驱逐速度在高并发情况下跟不上缓存添加速度,造成内存gc不下来

且旧的缓存会被超过maximumSize的新缓存驱逐,所以20000个缓存其实根本没起到缓存的作用,很快就会被新缓存驱逐,10个线程一直被抢着来进行缓存的添加和驱逐,这也是为什么CPU快要被干爆了

那要怎么优化呢?

驱逐策略

caffeine提供了三类驱逐策略

基于size或者weigh

// Evict based on the number of entries in the cache
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
    .maximumSize(10_000)
    .build(key -> createExpensiveGraph(key));

// Evict based on the number of vertices in the cache
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
    .maximumWeight(10_000)
    .weigher((Key key, Graph graph) -> graph.vertices().size())
    .build(key -> createExpensiveGraph(key));

maximumSize:指定缓存可以包含的最大条目数。请注意,缓存可能会在超过此限制之前逐出某个条目,或者在逐出时暂时超过阈值。当缓存大小增长到接近最大值时,缓存会逐出不太可能再次使用的条目。例如,缓存可能会逐出某个条目,因为它最近未使用或非常经常使用

weigher:如果不同的服务器空间具有不同的“权重”——例如,如果您的服务器值具有不同的内存占用——您可以指定一个权重函数Caffeine.weigher(Weigher)和一个最大的服务器权重Caffeine.maximumWeight(long)

基于时间

// Evict based on a fixed expiration policy
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
    .expireAfterAccess(5, TimeUnit.MINUTES)
    .build(key -> createExpensiveGraph(key));
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .build(key -> createExpensiveGraph(key));

// Evict based on a varying expiration policy
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
    .expireAfter(new Expiry<Key, Graph>() {
      public long expireAfterCreate(Key key, Graph graph, long currentTime) {
        // Use wall clock time, rather than nanotime, if from an external resource
        long seconds = graph.creationDate().plusHours(5)
            .minus(System.currentTimeMillis(), MILLIS)
            .toEpochSecond();
        return TimeUnit.SECONDS.toNanos(seconds);
      }
      public long expireAfterUpdate(Key key, Graph graph, 
          long currentTime, long currentDuration) {
        return currentDuration;
      }
      public long expireAfterRead(Key key, Graph graph,
          long currentTime, long currentDuration) {
        return currentDuration;
      }
    })
    .build(key -> createExpensiveGraph(key));

咖啡因提供了快速定时驱动方法:

expireAfterAccess:(long, TimeUnit):指定在条目创建、最近替换其值或最后一次读取条目后经过固定持续时间后,应自动从缓存中删除每个条目。所有缓存读写操作(Cache.asMap().put(K, V)和Cache.asMap().get(Object))都会重置访问时间,但不会通过对 Cache#asMap 的集合视图的操作来重置访问时间。

expireAfterWrite:(long, TimeUnit):指定在创建条目或最近替换其值后经过固定持续时间后,活动条目就有资格进行自动刷新

expireAfter(Expiry):分别定义缓存创建、更新、读取多久后过期

Scheduler: Caffeine.scheduler(Scheduler)使用接口和方法指定调度线程,而不是依赖其他服务器活动来触发实例行维护。提供的调度器可能无法提供实时保证。该计划是尽最大努力的,并且不会对何时删除过期的条目做出任何硬性保证。

基于引用 weakKeys/weakValues/softValues

指定存储在缓存中的每个键或值都应包装在 {@link WeakReference} 中或{@link SoftReference} (默认情况下,使用强引用)。使用以上方法时,生成的缓存将使用标识 ({@code ==}) 比较来确定键的相等性

注意值value支持weakValues和softValues,而key只支持weakKeys

WeakReference:gc就会被回收
SoftReference:gc时如果没有足够的内存时会被回收,如何量化这个内存是否充足,点这里

以上驱逐策略官方建议优先采用maximumSize,除非你对WeakReference和SoftReference的适用相当熟悉并清楚由此产生的后果,不然不建议使用引用驱逐策略。

优化

回归本案例,高峰期我们的cacheHolder里面有900w个缓存实例,而maximumSize设置仅为20000,由于cacheKey是用户维度的,显然20000个key对一下c端服务来说太少了,但是调高maximumSize又会引起cacheHolderi自身占用过多内存,调高线程池的最大线程数又会对争抢正常业务的CPU资源

可能的优化方案有:

  1. 降低缓存kv的大小,比如缓存v的大小从1k降低到20byte
  2. 将缓存的过期时间从10分钟调整到1分钟,加速缓存淘汰速度
  3. 当前缓存获取属于IO密集型业务,可以适当调高线程池最大线程数,以便有更多线程资源被拿来进行缓存驱逐
  4. 使用专门的Scheduler,与put缓存的线程隔离,专门用来维护缓存的过期刷新等
  5. 碍于内存压力,考虑使用引用驱逐策略,在内存不足时优先GC缓存
  6. 如果以上方案都不适用,使用别的方案代替caffeine,比如本地内存、分布式缓存redis等等

参考:https://github.com/ben-manes/caffeine/wiki/Eviction

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2041712.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Godot《躲避小兵》实战之创建玩家场景

项目设置完之后&#xff0c;我们就可以开始处理玩家控制的角色。 这里我们将玩家放在一个单独的场景当中&#xff0c;这样做的好处是在游戏的其他部分做出来之前&#xff0c;我们就可以对其进行单独测试。 节点结构 场景是一个节点树结构&#xff0c;因此一个场景需要有一个…

设计模式六大原则之:依赖倒置原则

1. 依赖倒置原则简介 依赖倒置原则(Dependency Inversion Principle, DIP) 是面向对象设计的核心原则之一&#xff0c;由罗伯特马丁(Robert C. Martin)提出&#xff0c;旨在降低类间的依赖度&#xff0c;使之更易于维护和扩展。该原则主张高层模块不应该依赖于底层模块&#x…

江科大/江协科技 STM32学习笔记P23

文章目录 DMA直接存储器存取DMA简介存储器映像DMA框图DMA基本结构存储器到存储器的数据转运ADC扫描模式和DMA配合使用流程 DMA直接存储器存取 DMA简介 DMA进行存储器到存储器的数据转运&#xff0c;比如Flash里的一批数据转运到SRAM里&#xff0c;需要软件触发&#xff0c;使用…

JQuery实现的时间插件源码附注释

HTML页面代码 <!DOCTYPE HTML> <html> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta content="width=device-width, initial-scale=1, maximum-scale=1,user-scalable=no;" name="…

【方案】SRM系统整体设计方案(解决方案+实现源码)

一、项目理解 二、总体解决方案概述 三、业务解决方案详述 四、端到端的采购流程管理 1. 采购计划 2. 采购寻源(招投标、询报价、竞价) 3. 合同管理 4. 订单执行与供应商协同 5. 采购分析与评估 五、支撑流程 1. 物资主数据管理 2. 供应商管理 3. 目录管理 4. 评标专家库管理 5…

grom接入Prometheus,grafana

在同级目录下分别创建 docker-compose.yml&#xff0c;与prometheus.yml 配置文件 version: 3.8services:prometheus:image: prom/prometheuscontainer_name: prometheusports:- "9090:9090" # Prometheus Web UI 端口volumes:- ./prometheus.yml:/etc/prometheus…

opencv-python实战项目八:根据颜色抠出图片中感兴趣区域

文章目录 一&#xff0c;简介二&#xff0c;实现方案三、算法实现步骤3.2 处理颜色蒙版&#xff1a;3.3 取出图片中蒙版对应区域 四&#xff0c;整体代码五&#xff0c;效果&#xff1a; 一&#xff0c;简介 本项目旨在开发一个基于OpenCV的图像处理工具&#xff0c;实现根据颜…

商贸城小程序系统开发制作方案

商贸城作为集批发、零售、展示、交流于一体的综合性商业体。通过商贸城小程序系统促进商家与消费者之间的互动&#xff0c;实现线上线下流量的无缝对接。一、用户需求分析 1、顾客需求&#xff1a; 快速查找店铺信息&#xff1b; 在线浏览商品和服务&#xff1b; 实现线上预约、…

【网站项目】SpringBoot675学生心理压力咨询评判

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

Vue启动时报异常 ‘error:03000086:digital envelope routines::initialization error‘

问题描述 启动Vue项目时&#xff0c;突发报如下异常&#xff1a; opensslErrorStack: [error:03000086:digital envelope routines::initialization error,error:0308010C:digital envelope routines::unsupported],library: digital envelope routines,reason: unsupported,…

CentOS7下载与安装 即配置网卡

CentOS 7是什么? CentOS7是基于RHEL的企业级Linux操作系统&#xff0c;引入了Systemd、XFS文件系统和Docker支持。它提供了新的软件包、工具和性能调优选项&#xff0c;同时加强了系统安全和稳定性。总的来说&#xff0c;CentOS7是一个稳定、安全、长期支持的操作系统&#xf…

【wiki知识库】09.欢迎页面展示(浏览量统计)SpringBoot部分

&#x1f34a; 编程有易不绕弯&#xff0c;成长之路不孤单&#xff01; 大家好&#xff0c;我是熊哈哈&#xff0c;这个项目从我接手到现在有了两个多月的时间了吧&#xff0c;其实本来我在七月初就做完的吧&#xff0c;但是六月份的时候生病了&#xff0c;在家里休息了一个月的…

【和可被 K 整除的子数组】python刷题记录

R4-前缀和专题 class Solution:def subarraysDivByK(self, nums: List[int], k: int) -> int:ret0# 存储当前位置的上一个位置的前缀和的余数加上当前位置的值对K的余数pre_mod0dictdefaultdict(int)dict[0]1for i in range(len(nums)):pre_mod(pre_modnums[i])%k# 如果能在…

Kubernetes 基础概念介绍

1. 应用部署方式 传统部署&#xff1a;互联网早期&#xff0c;会直接将应用程序部署在物理机上 优点&#xff1a;简单&#xff0c;不需要其它技术的参与 缺点&#xff1a;不能为应用程序定义资源使用边界&#xff0c;很难合理地分配计算资源&#xff0c;而且程序之间容易产生影…

投票小程序App功能开发源码技术实现

随着移动互联网的快速发展&#xff0c;小程序作为一种轻量级应用&#xff0c;因其无需安装、即用即走的特点&#xff0c;在各类应用场景中迅速普及。投票小程序作为其中的一种&#xff0c;因其便捷性和实用性&#xff0c;广泛应用于各类活动、问卷调查及意见收集中。本文将围绕…

Linux-Shell入门-05

1.Shell的概念 1.1 什么是shell Shell脚本语言是实现Linux/UNIX系统管理及自W动化运维所必备的重要工具&#xff0c; Linux/UNIX系统的底层及基础应用软件的核心大都涉及Shell脚本的内容。Shell是一种编程语言, 它像其它编程语言如: C, Java, Python等一样也有变量/函数/运算符…

上海“创投十九条”明确政府引导基金带动作用,银行如何挖掘投贷联动增长潜力?

发展创业投资是促进科技、产业、金融良性循环的重要举措。为促进创业投资高质量发展&#xff0c;近几个月来&#xff0c;“国创投十七条”“上海创投十九条”等政策陆续发布&#xff0c;明确指出发挥政府引导基金带动作用&#xff0c;进一步加大对战略性新兴产业和未来产业的支…

空指针异常(NullPointerException)以及解决方案

之前我们提到过&#xff0c;在学习数组这一篇章时&#xff0c;有两种运行时异常&#xff0c;可能会反复的出现,首先第一个就是我们之前所讲述的&#xff0c;数组下标越界异常&#xff08;ArrayIndexOutOfBoundsException&#xff09;&#xff0c;如需查看详情&#xff0c;可跳转…

tekton通过ceph挂载node_modules的时候报错failed to execute command: copying dir: symlink

分析&#xff1a; 如果ceph的mountPath和workingDir路径一致的话&#xff0c;就会报错。 解决&#xff1a;node_modules挂载到/workspace下&#xff0c;workingDir的代码mv到/workspace下进行构建。

Spring Boot和OCR构建车牌识别系统

​ 博客主页: 南来_北往 系列专栏&#xff1a;Spring Boot实战 OCR介绍 OCR&#xff08;Optical Character Recognition&#xff09;是光学字符识别技术的缩写&#xff0c;它能够将图像中的文本转换为机器可读和编辑的数字文本格式。这种技术广泛应用于数据输入、文档管理…