并发优化
鉴于部分企业拥有员工规模超过千人,为确保其能够有效承载多人同考的需求,同时维护系统架构的简洁性,我们在未引入额外中间件的前提下,做了一次系统性能调优。测试的最终结果表明,当前的系统性能已充分满足企业的实际应用需求。
热点数据
对部分热点数据进行了缓存处理,以确保业务能够平稳过渡,并全面提升考试系统的整体性能。以下列举的是实现该目标的关键步骤,但并非全部细节。
缓存数据
当考试即将开始,程序会事先将用户信息及试卷等,相关数据载入缓存中,该缓存数据将在未被访问两小时后自动清除。
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` );