智能面试刷题平台:从线程池到异步处理的实战经验

🤵‍♂️ 个人主页:@rain雨雨编程

😄微信公众号:rain雨雨编程

✍🏻作者简介:持续分享机器学习,爬虫,数据分析
🐋 希望大家多多支持,我们一起进步!
如果文章对你有帮助的话,
欢迎评论 💬点赞👍🏻 收藏 📂加关注+

高性能题库管理系统的开发实践

在教育领域,题库管理系统是教学和考试中不可或缺的工具。它不仅需要高效地管理大量题目,还要支持灵活的查询、编辑和批量操作。本文将通过一个实际的题库管理系统项目,分享其开发过程、技术栈的选型与应用,以及核心功能的实现思路。


目录

高性能题库管理系统的开发实践

一、项目介绍

项目背景

功能概述

技术目标

二、技术栈指点总结

1. 线程池

2. 异步处理

3. 并发控制

4. 事务管理

三、核心代码

3.1 根据题库id查询题目列表

3.2 获取题库详情接口

3.3 修改题目所属题库接口

3.4 批量向题库中添加题目

3.5 批量从题库移除题目

3.6 批量删除题目


一、项目介绍

项目背景

随着在线教育和远程考试的普及,题库管理系统的需求日益增长。传统的题库管理系统往往功能单一,且在高并发场景下表现不佳。因此,我们开发了一个高性能、高并发的题库管理系统,旨在满足教育机构、在线学习平台以及个人用户的需求。

功能概述

本题库管理系统具备以下核心功能:

  1. 题目查询:支持根据题库、关键词、题型、难度等条件查询题目列表。

  2. 题库详情:查看题库的基本信息和题目列表。

  3. 编辑题目:修改题目所属题库。

  4. 批量操作:支持批量添加、移除和删除题目。

技术目标

  • 高并发支持:系统能够处理高并发请求,保证响应速度。

  • 数据一致性:通过事务管理确保数据操作的正确性。

  • 异步处理:优化系统性能,减少用户等待时间。

  • 可扩展性:系统设计支持未来功能扩展。


二、技术栈指点总结

在开发高并发题库管理系统时,我们重点关注了线程池、异步处理、并发控制和事务管理等关键技术点。以下是技术栈的详细总结:

1. 线程池

线程池是提高系统性能和资源利用率的关键技术。我们使用了 Java 的 ThreadPoolExecutor 来管理线程池,通过合理配置核心线程数、最大线程数和队列容量,确保系统在高并发场景下能够高效处理请求。

示例代码

ExecutorService threadPool = new ThreadPoolExecutor(
    10, // 核心线程数
    50, // 最大线程数
    60L, // 空闲线程存活时间
    TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(100) // 任务队列容量
);

2. 异步处理

为了提升用户体验,我们采用了异步处理机制。通过 Java 的 CompletableFuture 和 Spring 的 @Async 注解,将一些耗时操作(如批量操作)异步执行,避免阻塞主线程。

示例代码

@Async
public CompletableFuture<Void> batchProcessQuestions(List<Question> questions) {
    questions.forEach(question -> {
        // 异步执行题目处理逻辑
        processQuestion(question);
    });
    return CompletableFuture.completedFuture(null);
}

3. 并发控制

在高并发场景下,数据一致性至关重要。我们使用了乐观锁和悲观锁机制来控制并发操作。对于一些高频更新的操作(如修改题目所属题库),我们采用了乐观锁,通过版本号(version)来避免并发冲突。

示例代码

int updateRows = questionMapper.updateQuestion(question, version);
if (updateRows == 0) {
    throw new OptimisticLockException("并发冲突,操作失败");
}

4. 事务管理

为了保证数据操作的正确性,我们使用了 Spring 的事务管理机制。通过 @Transactional 注解,确保关键操作(如批量删除题目)在事务中执行,避免数据不一致问题。

示例代码

@Transactional(rollbackFor = Exception.class)
public void batchDeleteQuestions(List<Long> questionIds) {
    questionIds.forEach(questionMapper::deleteQuestionById);
}

三、核心代码

以下是题库管理系统的核心功能介绍,涵盖题目查询、题库详情获取、题目编辑以及批量操作等功能。

3.1 根据题库id查询题目列表

功能介绍
该功能允许用户根据题库 ID 查询题目列表,并支持通过关键词、题型等条件进行过滤。通过合理的索引设计和查询优化,系统能够快速返回查询结果,即使在数据量较大的情况下也能保持高效。

public Page<Question> listQuestionByPage(QuestionQueryRequest questionQueryRequest) {
    long current = questionQueryRequest.getCurrent();
    long size = questionQueryRequest.getPageSize();
    // 题目表的查询条件
    QueryWrapper<Question> queryWrapper = this.getQueryWrapper(questionQueryRequest);
    // 根据题库查询题目列表接口
    Long questionBankId = questionQueryRequest.getQuestionBankId();
    if (questionBankId != null) {
        // 查询题库内的题目 id
        LambdaQueryWrapper<QuestionBankQuestion> lambdaQueryWrapper = Wrappers.lambdaQuery(QuestionBankQuestion.class)
                .select(QuestionBankQuestion::getQuestionId)
                .eq(QuestionBankQuestion::getQuestionBankId, questionBankId);
        List<QuestionBankQuestion> questionList = questionBankQuestionService.list(lambdaQueryWrapper);
        if (CollUtil.isNotEmpty(questionList)) {
            // 取出题目 id 集合
            Set<Long> questionIdSet = questionList.stream()
                    .map(QuestionBankQuestion::getQuestionId)
                    .collect(Collectors.toSet());
            // 复用原有题目表的查询条件
            queryWrapper.in("id", questionIdSet);
        }
    }
    // 查询数据库
    Page<Question> questionPage = this.page(new Page<>(current, size), queryWrapper);
    return questionPage;
}

3.2 获取题库详情接口

功能介绍
该接口用于获取题库的详细信息,包括题库的基本信息(如名称、描述、创建时间等)以及该题库下的所有题目列表。通过分页机制,用户可以方便地浏览大量题目,而不会对性能造成过大压力。

@GetMapping("/get/vo")
public BaseResponse<QuestionBankVO> getQuestionBankVOById(QuestionBankQueryRequest questionBankQueryRequest, HttpServletRequest request) {
    ThrowUtils.throwIf(questionBankQueryRequest == null, ErrorCode.PARAMS_ERROR);
    Long id = questionBankQueryRequest.getId();
    ThrowUtils.throwIf(id <= 0, ErrorCode.PARAMS_ERROR);
    // 查询数据库
    QuestionBank questionBank = questionBankService.getById(id);
    ThrowUtils.throwIf(questionBank == null, ErrorCode.NOT_FOUND_ERROR);
    // 查询题库封装类
    QuestionBankVO questionBankVO = questionBankService.getQuestionBankVO(questionBank, request);
    // 是否要关联查询题库下的题目列表
    boolean needQueryQuestionList = questionBankQueryRequest.isNeedQueryQuestionList();
    if (needQueryQuestionList) {
        QuestionQueryRequest questionQueryRequest = new QuestionQueryRequest();
        questionQueryRequest.setQuestionBankId(id);
        Page<Question> questionPage = questionService.listQuestionByPage(questionQueryRequest);
        questionBankVO.setQuestionPage(questionPage);
    }
    // 获取封装类
    return ResultUtils.success(questionBankVO);
}

3.3 修改题目所属题库接口

功能介绍
该功能允许用户将题目从一个题库移动到另一个题库。通过乐观锁机制,我们确保在高并发场景下不会出现数据冲突。此外,系统会自动检查题目是否存在以及目标题库是否有效,从而保证操作的正确性。

@Override
public void validQuestionBankQuestion(QuestionBankQuestion questionBankQuestion, boolean add) {
    ThrowUtils.throwIf(questionBankQuestion == null, ErrorCode.PARAMS_ERROR);
    // 题目和题库必须存在
    Long questionId = questionBankQuestion.getQuestionId();
    if (questionId != null) {
        Question question = questionService.getById(questionId);
        ThrowUtils.throwIf(question == null, ErrorCode.NOT_FOUND_ERROR, "题目不存在");
    }
    Long questionBankId = questionBankQuestion.getQuestionBankId();
    if (questionBankId != null) {
        QuestionBank questionBank = questionBankService.getById(questionBankId);
        ThrowUtils.throwIf(questionBank == null, ErrorCode.NOT_FOUND_ERROR, "题库不存在");
    }
}

@PostMapping("/remove")
@AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
public BaseResponse<Boolean> removeQuestionBankQuestion(
        @RequestBody QuestionBankQuestionRemoveRequest questionBankQuestionRemoveRequest
) {
    // 参数校验
    ThrowUtils.throwIf(questionBankQuestionRemoveRequest == null, ErrorCode.PARAMS_ERROR);
    Long questionBankId = questionBankQuestionRemoveRequest.getQuestionBankId();
    Long questionId = questionBankQuestionRemoveRequest.getQuestionId();
    ThrowUtils.throwIf(questionBankId == null || questionId == null, ErrorCode.PARAMS_ERROR);
    // 构造查询
    LambdaQueryWrapper<QuestionBankQuestion> lambdaQueryWrapper = Wrappers.lambdaQuery(QuestionBankQuestion.class)
            .eq(QuestionBankQuestion::getQuestionId, questionId)
            .eq(QuestionBankQuestion::getQuestionBankId, questionBankId);
    boolean result = questionBankQuestionService.remove(lambdaQueryWrapper);
    return ResultUtils.success(result);
}

3.4 批量向题库中添加题目

功能介绍
该功能允许用户一次性将多个题目添加到指定题库中。通过异步处理机制,系统可以在后台批量处理这些题目,而不会阻塞用户界面。同时,我们使用事务管理确保所有操作要么全部成功,要么全部失败,从而保证数据一致性。

 /**
     * 批量添加题目到题库
     * @param questionIdList
     * @param questionBankId
//     * @param loginUser
     */
    @Override
    public void bathcAddQuestionsToBank(List<Long> questionIdList, Long questionBankId, User loginUser) {
        // 参数校验
        ThrowUtils.throwIf(CollUtil.isEmpty(questionIdList), ErrorCode.PARAMS_ERROR,"题目列表为空");
        ThrowUtils.throwIf(questionBankId == null || questionBankId<=0, ErrorCode.PARAMS_ERROR,"题库id非法");
        ThrowUtils.throwIf(loginUser == null, ErrorCode.NOT_LOGIN_ERROR,"用户未登录");
        // 题目id是否存在
//        List<Question> questionList = questionService.listByIds(questionIdList);

        //改进1:不用查所有的题目,只需要查合法的题目id是否存在即可
        LambdaQueryWrapper<Question> questionLambdaQueryWrapper = Wrappers.lambdaQuery(Question.class)
                .select(Question::getId)
                .in(Question::getId, questionIdList);
//        List<Question> questionList = questionService.list(questionLambdaQueryWrapper);
//        List<Long> validQuestionIdList = questionList.stream()
//                .map(Question::getId).collect(Collectors.toList());
        //改进2:对以上代码进行改进,因为返回值只有id一列,可以直接转为Long列表,不让框架封装结果为Question对象,减少内存消耗
        List<Long> validQuestionIdList = questionService.listObjs(questionLambdaQueryWrapper, obj -> (Long) obj);

        ThrowUtils.throwIf(CollUtil.isEmpty(validQuestionIdList), ErrorCode.PARAMS_ERROR,"题目id非法");
//        检查哪些题目id已经存在题库中
        LambdaQueryWrapper<QuestionBankQuestion> lambdaQueryWrapper = Wrappers.lambdaQuery(QuestionBankQuestion.class)
                .eq(QuestionBankQuestion::getQuestionBankId, questionBankId)
                .in(QuestionBankQuestion::getQuestionId, validQuestionIdList);
        List<QuestionBankQuestion> existsQuestionBankQuestions = this.list(lambdaQueryWrapper);
        Set<Long> existsQuestionBankQuestionIdList = existsQuestionBankQuestions.stream()
                .map(QuestionBankQuestion::getQuestionId)
                .collect(Collectors.toSet());
        //        已经存在题库的题目,不必重复添加
        validQuestionIdList = validQuestionIdList.stream()
                .filter(questionId -> {
                    return   !existsQuestionBankQuestionIdList.contains(questionId);
                }).collect(Collectors.toList());

        // 题库id是否存在
        QuestionBank questionBank = questionBankService.getById(questionBankId);
        ThrowUtils.throwIf(questionBank == null, ErrorCode.PARAMS_ERROR,"题库id非法");
        //自定义线程池,避免在高并发情况下,大量创建线程导致系统崩溃
        ThreadPoolExecutor customExecutor = new ThreadPoolExecutor(
                15,
                40,
                60L,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(10000),
                new ThreadPoolExecutor.CallerRunsPolicy()
        );
        //保存所有线程任务到future列表中,便于后续统一处理结果
        ArrayList<CompletableFuture<Void>> futures = new ArrayList<>();

        //分批次处理,避免长事务,假设每次处理1000条数据
        int batchSize = 1000;
        int size = validQuestionIdList.size();
        for(int i=0;i<size;i+=batchSize){
            List<Long> subList = validQuestionIdList.subList(i, Math.min(i + batchSize, size));
            List<QuestionBankQuestion> questionBankQuestions = subList.stream()
                    .map(questionId -> {
                        QuestionBankQuestion questionBankQuestion = new QuestionBankQuestion();
                        questionBankQuestion.setQuestionId(questionId);
                        questionBankQuestion.setQuestionBankId(questionBankId);
                        return questionBankQuestion;
                    }).collect(Collectors.toList());
            QuestionBankQuestionService questionBankQuestionService = (QuestionBankQuestionServiceImpl) AopContext.currentProxy();
           //异步处理每批数据,将任务添加到异步列表中
            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                questionBankQuestionService.bathcAddQuestionsToBankInner(questionBankQuestions);
            }, customExecutor);
            futures.add(future);
        }
        //等待所有任务完成
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
        //关闭线程池
        customExecutor.shutdown();
    }
/**
     * 批量添加题目到题库(包含事务注解,仅内部使用)
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void bathcAddQuestionsToBankInner(List<QuestionBankQuestion> questionBankQuestions){
            try {
                //批量操作,减少数据库交互次数
                boolean result = this.saveBatch(questionBankQuestions);
                if (!result) {
                    throw new BusinessException(ErrorCode.OPERATION_ERROR, "向题库添加题目失败");
                }
            } catch (DataIntegrityViolationException e) {
                log.error("数据库唯一键冲突或违反其他完整性约束,错误信息:{}", e.getMessage());
                throw new BusinessException(ErrorCode.OPERATION_ERROR, "题目已存在于该题库,无法重复添加");
            } catch (DataAccessException e) {
                log.error("数据库连接问题、事务问题等导致操作失败,错误信息:{}", e.getMessage());
                throw new BusinessException(ErrorCode.OPERATION_ERROR, "数据库操作失败");
            } catch (Exception e) {
                // 捕获其他异常,做通用处理
                log.error("添加题目到题库时发生未知错误,错误信息:{}", e.getMessage());
                throw new BusinessException(ErrorCode.OPERATION_ERROR, "向题库添加题目失败");
            }
    }

3.5 批量从题库移除题目

功能介绍
该功能允许用户从题库中批量移除题目。与添加题目类似,移除操作也采用了异步处理和事务管理机制。系统会自动检查题目的存在性,并在移除后更新题库的统计信息(如题目总数)。

@Override
@Transactional(rollbackFor = Exception.class)
public void batchRemoveQuestionsFromBank(List<Long> questionIdList, Long questionBankId) {
    // 参数校验
    ThrowUtils.throwIf(CollUtil.isEmpty(questionIdList), ErrorCode.PARAMS_ERROR, "题目列表为空");
    ThrowUtils.throwIf(questionBankId == null || questionBankId <= 0, ErrorCode.PARAMS_ERROR, "题库非法");
    // 执行删除关联
    for (Long questionId : questionIdList) {
        // 构造查询
        LambdaQueryWrapper<QuestionBankQuestion> lambdaQueryWrapper = Wrappers.lambdaQuery(QuestionBankQuestion.class)
                .eq(QuestionBankQuestion::getQuestionId, questionId)
                .eq(QuestionBankQuestion::getQuestionBankId, questionBankId);
        boolean result = this.remove(lambdaQueryWrapper);
        if (!result) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "从题库移除题目失败");
        }
    }
}

3.6 批量删除题目

功能介绍
该功能允许用户批量删除题目。由于删除操作可能涉及大量数据,我们采用了异步处理和事务管理机制,确保操作的高效性和一致性。系统会自动检查题目的存在性,并在删除后清理相关数据(如题目与题库的关联关系)。

@Override
@Transactional(rollbackFor = Exception.class)
public void batchDeleteQuestions(List<Long> questionIdList) {
    if (CollUtil.isEmpty(questionIdList)) {
        throw new BusinessException(ErrorCode.PARAMS_ERROR, "要删除的题目列表为空");
    }
    for (Long questionId : questionIdList) {
        boolean result = this.removeById(questionId);
        if (!result) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "删除题目失败");
        }
        // 移除题目题库关系
        LambdaQueryWrapper<QuestionBankQuestion> lambdaQueryWrapper = Wrappers.lambdaQuery(QuestionBankQuestion.class)
                .eq(QuestionBankQuestion::getQuestionId, questionId);
        result = questionBankQuestionService.remove(lambdaQueryWrapper);
        if (!result) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "删除题目题库关联失败");
        }
    }
}


通过以上技术栈和核心功能的实现,我们的题库管理系统不仅能够高效管理大量题目,还能在高并发场景下保持稳定性和响应速度。希望本文的分享能为有类似需求的开发者提供一些参考和启发。

文章持续跟新,可以微信搜一搜公众号  rain雨雨编程 ],第一时间阅读,涉及数据分析,机器学习,Java编程,爬虫,实战项目等。

基于机器学习的音频情感分析系统Python源码(高分项目),能够从语音中识别出四种基本情感:愤怒、快乐、中性和悲伤。个人经导师指导并认可通过的高分设计项目,评审分99分,代码完整确保可以运行,小白也可以亲自搞定,主要针对计算机相关专业的正在做毕业设计的学生和需要项目实战练习的学习者,可作为毕业设计、课程设计、期末大作业,代码资料完整,下载可用。 基于机器学习的音频情感分析系统Python源码(高分项目)基于机器学习的音频情感分析系统Python源码(高分项目)基于机器学习的音频情感分析系统Python源码(高分项目)基于机器学习的音频情感分析系统Python源码(高分项目)基于机器学习的音频情感分析系统Python源码(高分项目)基于机器学习的音频情感分析系统Python源码(高分项目)基于机器学习的音频情感分析系统Python源码(高分项目)基于机器学习的音频情感分析系统Python源码(高分项目)基于机器学习的音频情感分析系统Python源码(高分项目)基于机器学习的音频情感分析系统Python源码(高分项目)基于机器学习的音频情感分析系统Python源码(高分项目)基于机器学习的音频情感分析系统Python源码(高分项目)基于机器学习的音频情感分析系统Python源码(高分项目)基于机器学习的音频情感分析系统Python源码(高分项目)基于机器学习的音频情感分析系统Python源码(高分项目)基于机器学习的音频情感分析系统Python源码(高分项目)基于机器学习的音频情感分析系统Python源码(高分项目)基于机器学习的音频情感分析系统Python源码(高分项目)基于机器学习的音频情感分析系统Python源码(高分项目)基于机器学习的音频情感分析系统Python源码(高分项目)基于机器学习的音频情感分析系统Python源码(高分项目)基于机器学习的音频情感分析系统P
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值