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脚本,但是入参为不同数据情况,其他类型脚本应该也可以用该方式优化