java利用Caffeine缓存优化多次执行同一个Groovy脚本方法

705 阅读2分钟

1、问题背景

最近在项目上遇到了一个接口响应时间超过10s的问题,经过排查,是以下代码耗时导致问题:

ScriptEngineManager factory = new ScriptEngineManager();
//每次生成一个engine实例
ScriptEngine engine = factory.getEngineByName("groovy");
assert engine != null;
engine.eval(script);
result = (String)((Invocable)engine).invokeFunction("method", param);

接口入参只有100条数据,但是每条数据都要调用上面代码,导致速度很慢。

2、问题分析

经过排查engine.eval(script);耗时几百ms,而每次通过同一脚本处理都要去重新加载脚本,也耗费大量时间。

3、解决方案

考虑将下面代码生成的引擎加载到缓存中,每次处理数据时直接从缓存中取出对应脚本engine,直接调用方法获取方法返回结果

ScriptEngineManager factory = new ScriptEngineManager();
//每次生成一个engine实例
ScriptEngine engine = factory.getEngineByName("groovy");
assert engine != null;
engine.eval(script);

4、具体实现

添加pom依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>

添加缓存配置并且注入容器,交给CacheManager管理

@EnableCaching
@Configuration
public class CacheConfig {

    @Bean
    public Caffeine<Object, Object> caffeineCache() {
      
        return Caffeine.newBuilder()
                 // 此处可以加上过期时间等其他配置
                // 初始的缓存空间大小
                .initialCapacity(500)
                // 使用自定义线程池
                .maximumSize(1000);
    }
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
        caffeineCacheManager.setCaffeine(caffeineCache());
        // 不缓存空值
        caffeineCacheManager.setAllowNullValues(false);
        return caffeineCacheManager;
    }
}

缓存添加与初始化

@Autowired
private CacheManager cacheManager;
// 参数为缓存名字,获取不到就创建新缓存
Cache scriptCache = cacheManager.getCache("scriptCache");
ScriptEngineManager factory = new ScriptEngineManager();
//每次生成一个engine实例
ScriptEngine engine = factory.getEngineByName("groovy");
engine.eval(script);
assert scriptCache != null;
// id为唯一标识是哪个脚本的engine,建议使用数据库主键即可。
scriptCache.put("id", engine);

从缓存中取出数据,并且调用脚本method方法,获取脚本执行结果,这里执行脚本的类未注入容器,因此使用SpringUtils获取cacheManager

// 
CacheManager cacheManager = (CacheManager)SpringUtils.getBean("cacheManager");
ScriptEngine engine = Objects.requireNonNull(cacheManager.getCache("scriptCache")).get(id,ScriptEngine.class);
assert engine != null;
return  (String)((Invocable)engine).invokeFunction("method", param);

总结

经过测试,优化后的接口响应时间为100ms左右,有时甚至可以到30ms,相比优化前10s的时间速度提升几十甚至几百倍(测试数据为100条),并且数据越多优化效果越好。

本次优化针对执行同一Groovy脚本,但是入参为不同数据情况,其他类型脚本应该也可以用该方式优化