SpringBoot整合Mahout

827 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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

好了,到此基本上前期准备工作算是完成了,接下来就是进一步的开发。本期就先介绍到这里,有什么需要交流的,大家可以随时私信我。😊