Skip to content

并发优化

鉴于部分企业拥有员工规模超过千人,为确保其能够有效承载多人同考的需求,同时维护系统架构的简洁性,我们在未引入额外中间件的前提下,做了一次系统性能调优。测试的最终结果表明,当前的系统性能已充分满足企业的实际应用需求。

热点数据

对部分热点数据进行了缓存处理,以确保业务能够平稳过渡,并全面提升考试系统的整体性能。以下列举的是实现该目标的关键步骤,但并非全部细节。

缓存数据

当考试即将开始,程序会事先将用户信息及试卷等,相关数据载入缓存中,该缓存数据将在未被访问两小时后自动清除。

java
while (true) {
    TimeUnit.SECONDS.sleep(1);

    List<Callable<Boolean>> taskList = examCacheService.getExamingList().stream()//
            .filter(exam -> !examIds.contains(exam.getId())// 已缓存则不在执行
                    && (exam.getStartTime().getTime() - curTime <= 30 * 1000
                            && exam.getStartTime().getTime() - curTime >= 6 * 1000)) // 开考前30秒-开考前6秒,中间的时间用来缓存数据
            .map(exam -> new Callable<Boolean>() {
                @Override
                public Boolean call() throws Exception {
                    examIds.add(exam.getId());
                    examCacheService.getExam(exam.getId());
                    List<MyExam> myExamList = examCacheService.getMyExamList(exam.getId());
                    for (MyExam myExam : myExamList) {
                        User examUser = baseCacheService.getUser(myExam.getUserId());
                        baseCacheService.getOrg(examUser.getOrgId());
                        examCacheService.getMyExam(myExam.getExamId(), myExam.getUserId());
                        myPaperService.generatePaper(myExam.getExamId(), myExam.getUserId(), false, false);
                    }
                    return null;
                }
            }).collect(Collectors.toList());

    EXECUTOR_SERVICE.invokeAll(taskList);
}
java
@Cacheable(value = ExamConstant.EXAM_CACHE, key = ExamConstant.EXAMING_LIST_KEY, sync = true)
public List<Exam> getExamingList() {
    List<Exam> examingList = examDao.getExamingList();
    return examingList;
}
@Cacheable(value = ExamConstant.MYEXAM_CACHE, key = ExamConstant.MYEXAM_KEY_PRE
        + "#examId + ':' + #userId", sync = true)
public MyExam getMyExam(Integer examId, Integer userId) {
    MyExam myExam = myExamDao.getMyExam(examId, userId);
    return myExam;
}
xml
<cache name="EXAM_CACHE"
    maxEntriesLocalHeap="100000"
    timeToIdleSeconds="7200">
</cache>
<cache name="MYEXAM_CACHE"
    maxEntriesLocalHeap="100000"
    timeToIdleSeconds="7200">
</cache>

清理数据

考试用户进行答题、交卷等任务时,自动清空相关的缓存数据。

java
@Caching(evict = { //
        @CacheEvict(value = ExamConstant.MYEXAM_CACHE, key = ExamConstant.MYEXAM_KEY_PRE
                + "#examId + ':' + #userId"),
        @CacheEvict(value = ExamConstant.MYEXAM_CACHE, key = ExamConstant.MYEXAM_LIST_KEY_PRE + "#examId"),
        @CacheEvict(value = ExamConstant.MYEXAM_CACHE, key = ExamConstant.MYEXAM_UNMARK_LIST_KEY), }) // 字段变更就清除缓存,保持和数据库一致
public void paperHandle(Integer examId, Integer userId)

@CacheEvict(value = ExamConstant.MYQUESTION_CACHE, key = ExamConstant.MYQUESTION_LIST_KEY_PRE
        + "#examId + ':' + #userId")
public void answer(Integer examId, Integer userId, Integer questionId, String[] answers)

@Caching(evict = { //
        @CacheEvict(value = ExamConstant.MYEXAM_CACHE, key = ExamConstant.MYEXAM_KEY_PRE
                + "#examId + ':' + #userId"),
        @CacheEvict(value = ExamConstant.MYEXAM_CACHE, key = ExamConstant.MYEXAM_LIST_KEY_PRE + "#examId"), //
        @CacheEvict(value = ExamConstant.MYEXAM_CACHE, key = ExamConstant.MYEXAM_UNMARK_LIST_KEY), //
public void finish(Integer examId, Integer userId)

数据库调优

  • 增加线程池大小
  • 添加索引
yaml
spring:
    hikari:
      minimum-idle: 10
      maximum-pool-size: 100
sql
ALTER TABLE `EXM_MY_QUESTION` ADD INDEX `MY_QUESTION_EUQ` ( `EXAM_ID`,`USER_ID`,`QUESTION_ID` );
ALTER TABLE `EXM_MY_EXAM` ADD INDEX `MY_EXAM_EU` ( `EXAM_ID`,`USER_ID` );

小猫考试