持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第12天,点击查看活动详情
1、前言
在上一篇的分享中juejin.cn/post/710612…,我介绍了Mahout的一些基本内容,本篇为大家分享下如何在SpringBoot项目中结合Mahout以及我在实际开发中遇到的一些问题。
2、整合步骤
2.1、引入maven
<!--注意这里要忽略掉mahout中的servlet-api.jar否则会与Spring Boot内置的tomcat冲突-->
<!--mahout推荐相关-->
<dependency>
<groupId>org.apache.mahout</groupId>
<artifactId>mahout-mr</artifactId>
<version>0.13.0</version>
<exclusions>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</exclusion>
</exclusions>
</dependency>
2.2、配置DataModel
DataModel支持
文件形式(CSV、xls等)与数据库获取,从文件读取数据,是一次性将所有数据加载到内存中,进行推荐计算时效率很快。从数据库读取数据就是分批次加载,计算相似度、邻域时就比较耗时,同时仅支持MySQL数据库,如果想要使用其他数据库,需要自行实现接口,编写对于数据获取的SQL。这里的我是选择的文件形式,同时需要在项目启动完成后就进行加载。
/**
* @Title: RecommendDataModel
* @Description: 加载推荐用的DataModel
*/
@Configuration
@Slf4j
public class RecommendDataModel {
//用户行为数据文件路径,有多个文件时,要保证前缀一致。比如:userbehavior.2021.csv、userbehavior.2022.csv,这样两个文件都会被加载
@Value("${userbehavior.ini.dir}")
private String iniFile;
@Resource
private BehaviorDataInitService behaviorDataInitService;
@Bean
public DataModel getDataModel(){
DataModel dataModel = null;
try {
File userBehaviorFile = new File(iniFile);
if (!userBehaviorFile.exists()) {
//程序初次运行时、不存在初始用户行为文件需要loadData一次(将mongo中的数据持久化到文件里)
boolean flag = userBehaviorFile.createNewFile();
if (flag) {
behaviorDataInitService.initHyhUserbehaviorData(userBehaviorFile);
}
}
dataModel = new FileDataModel(userBehaviorFile);
} catch (Exception e) {
log.error("初始化DataModel出错,错误信息:{}", e.getMessage(), e);
}
return dataModel;
}
}
2.3、配置线程池参数
首先,推荐系统提供的推荐接口并不是实时返回结果的,而是收到MQ消息后进行推荐计算,同时结合
@Async注解,实现多线程调用。
/**
* @Title: AsyncConfiguration
* @Description: 异步执行配置类
*/
@Configuration
@EnableAsync
@Slf4j
public class AsyncConfiguration {
/**
* 核心线程数
*/
@Value("${async.core_pool_size}")
private int corePoolSize;
/**
* 最大线程数
*/
@Value("${async.max_pool_size}")
private int maxPoolSize;
/**
* 队列容量大小
*/
@Value("${async.queue_capacity}")
private int queueCapacity;
/**
* 线程池中线程的名称前缀
*/
@Value("${async.thread_name_prefix}")
private String threadNamePrefix;
/**
* 保持线程空闲时间
*/
@Value("${async.keep_alive_seconds}")
private int keepAliveSeconds;
@Bean
public Executor asyncRecommendExecutor(){
log.info("ExecutorConfiguration#asyncSchedulerExecutor start...");
return getDefaultExecutor();
}
@Bean
public Executor asyncEvalutorExecutor(){
log.info("ExecutorConfiguration#asyncEvalutorExecutor start...");
return getDefaultExecutor();
}
/**
* 获取默认执行器
* 线程池--拒绝策略RejectedExecutionHandler
* ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
* ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
* ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
* ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
* @return
*/
private synchronized Executor getDefaultExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setThreadNamePrefix(threadNamePrefix);
executor.setKeepAliveSeconds(keepAliveSeconds);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
//执行初始化
executor.initialize();
return executor;
}
}
# 线程池配置
async:
core_pool_size: 10
max_pool_size: 100
queue_capacity: 1000
thread_name_prefix: recommend-
keep_alive_seconds: 5
好了,到此基本上前期准备工作算是完成了,接下来就是进一步的开发。本期就先介绍到这里,有什么需要交流的,大家可以随时私信我。😊