前言
这是我在做项目的过程中,遇到的一个可以优化的地方。
先说场景,我的项目有个功能,需要建立一个题库,里面存放题目,并提供全文搜索。
所以我就学习并使用了ElasticSearch作为搜索引擎,因为ES本质是一个非关系型数据库,所以是要把数据先存进去才能搜。为了不破坏原本的业务结构,还是使用MySQL作为主要数据数据持久化服务,ES主要只提供搜索功能。
这样就会有一个问题,就是ES和MySQL的数据同步问题。
我在搜索了相关资料博客,选择了一个性能消耗较少的方案。就是增删改题目时,MySQL作为作为业务的主要持久化数据库,执行同步写,正常执行事务。同时用Redis实现消息队列,当MySQL业务执行成功后,插入成功消息,并通知让ES异步写入数据,这样可以让接口的响应速度达到最快。
当然,这样做可能有种情况就是,ES插入数据失败,消息丢失,这种概率还是比较低的,我选择的解决方案是每天凌晨没人用的时候,执行定时任务,让MySQL与ES数据同步,本篇文章就不涉及这部分了。
代码实现
添加题目代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| @Override @Transactional(propagation = Propagation.REQUIRED) public Result addQuestion(ExaminationQuestionArray examinationQuestionArray, String groupId) { List<String> fileNames = new ArrayList<>(); if (t_examination_courseDao.selectById(examinationQuestionArray.getQuestionList().get(0).getExaminationCourseId())!=null){ for (int i = 0; i < examinationQuestionArray.getQuestionList().size(); i++) { if (groupId.equals("1")) examinationQuestionArray.getQuestionList().get(i).setApproved(true); else examinationQuestionArray.getQuestionList().get(i).setApproved(false);
fileNames.addAll(analyseRichStringGetImgNameList( examinationQuestionArray.getQuestionList().get(i).getQuestionContent()) ); fileNames.addAll(analyseRichStringGetImgNameList( examinationQuestionArray.getQuestionList().get(i).getQuestionAnswer()) ); } if (fileNames.size()!=0){ QueryWrapper wrapper = new QueryWrapper(); wrapper.in("resources_file_name",fileNames); t_static_resources_cacheDao.delete(wrapper); } if (t_examination_questionDaoService.saveBatch(examinationQuestionArray.getQuestionList())){ List<ExaminationQuestion> esQuestionList = ExaminationQuestion.of(examinationQuestionArray.getQuestionList()); redisTemplate.opsForList().leftPush("EsQuestionList",esQuestionList); return Result.success(); } else return Result.error("016","添加试题失败"); }else { return Result.error("016","课程不存在"); } }
|
ES异步插入数据
1 2 3 4 5 6 7 8 9 10
| @Scheduled(cron = "0/5 * * * * ?") public void addQuestionToES(){ if (redisTemplate.opsForList().size("EsQuestionList")>0){ List<Object> questionList = redisTemplate.opsForList().range("EsQuestionList",0,-1); for (Object o : questionList) { esService.addDatas((List<ExaminationQuestion>)o,true); } } }
|
EsService封装的一些调用ES的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
@Override public <T> Boolean addDatas(List<T> list) { BulkRequest.Builder br = new BulkRequest.Builder(); for (T o : list) { Object id = null; for (Field declaredField : o.getClass().getDeclaredFields()) { declaredField.setAccessible(true); DocId annotation = declaredField.getAnnotation(DocId.class); if (annotation != null) { try { id = declaredField.get(o); } catch (IllegalAccessException e) { e.printStackTrace(); } } } if (id == null) { id = UUID.randomUUID().getMostSignificantBits(); } Object finalId = id; br.operations( op -> op.index( idx -> idx.index(getClassAlias(o.getClass())).id(String.valueOf(finalId)).document(o))); } BulkResponse result = null; try { result = client.bulk(br.build()); } catch (IOException e) { e.printStackTrace(); } assert result != null; if (result.errors()) { log.error("Bulk had errors"); for (BulkResponseItem item : result.items()) { if (item.error() != null) { log.error(item.error().reason()); } } return false; } return true; }
|