页面高并发优化
2024年11月30日大约 3 分钟
页面高并发优化
一、问题描述
性能问题
今天开发完功能在本地测试时,发现有一个页面加载缓慢,经排查发现是一个接口响应时间过长导致的。
性能指标
可以看到页面加载平均耗时1秒左右,对用户体验影响较大。
二、问题排查
问题定位
随后我对该接口进行了排查,发现以下问题:
- 接口耗时主要集中在数据库查询上
- SQL语句存在较多join操作,导致查询效率低下
- 部分查询在循环中执行,不仅会大量占用数据库连接池,还会影响查询效率
三、优化方案
在机器性能一定的情况下,提高单机并发能力就是要尽可能缩短业务的响应时间,而对响应时间影响最大的往往是对数据库的操作。而从数据库角度来说,我们的业务无非就是读或写两种类型。
读多写少场景优化
- 优化代码和SQL语句,建立合适的索引
- 使用缓存,将查询结果缓存起来,减少数据库查询次数
- 使用异步查询,将查询操作放到异步线程中执行,减少查询时间
写多读少场景优化
- 优化代码和SQL语句,减少数据库写操作
- 使用缓存,将写操作缓存起来,最后合并到一起写入数据库
- 使用异步写操作,将写操作放到异步线程中执行,或采用MQ消息队列
四、具体实现
优化思路
对于本次问题,主要是读多写少的场景,要查询出指定阈值规则的详细信息。由于用户还可能修改规则,所以还要查出:
- 所有设备类型及其对应的数据类型
- 该类型规则可以选择的设备信息
- 已经选择的设备信息
主要从三个方面进行优化:
- 优化SQL语句
- 建立合适索引
- 移除循环中的查询
优化前代码
Map<String, List<String>> map = new HashMap<>();
Map<String, List<EqDTO>> opMap = new HashMap<>();
List<EquipmentType> eqTypeList = equipmentFacilityTypeDao.getAllEquipmentTypes();//获取所有设备类型
for (EquipmentType eq : eqTypeList) {
List<String> dataNameList = dataTypeMapper.selectByEquipmentTypeId(eq.getEquipmentTypeId()).stream().map(DataType::getDataTypeName).toList();
map.put(eq.getTypeName(), dataNameList);
List<Equipment> eqList = equipmentDao.getEquipmentIdAndNameByTypeId(eq.getEquipmentTypeId());
List<Long> list = vo.getSelectedEquipmentList().stream().map(EqDTO::getEquipmentId).toList();//获取已选设备id集合
eqList = eqList.stream().filter(eq1 -> !list.contains(eq1.getEquipmentId())).toList();
List<EqDTO> eqDTOList = eqList.stream().map(eq1 -> new EqDTO(eq1.getEquipmentId(), eq1.getEquipmentName())).toList();
opMap.put(eq.getTypeName(), eqDTOList);
}
vo.setEquipmentTypeDataTypeMap(map);
vo.setOptionalEquipmentMap(opMap);
优化后代码
Map<String, List<String>> map = new HashMap<>();
Map<String, List<EqDTO>> opMap = new HashMap<>();
// 获取所有设备类型
List<EquipmentType> eqTypeList = equipmentFacilityTypeDao.getAllEquipmentTypes();
// 批量查询所有设备类型对应的数据类型
List<Long> equipmentTypeIds = eqTypeList.stream().map(EquipmentType::getEquipmentTypeId).toList();
Map<Long, List<DataType>> dataTypeMap = dataTypeMapper.selectByEquipmentTypeIds(equipmentTypeIds).stream()
.collect(Collectors.groupingBy(DataType::getEquipmentTypeId));
// 批量查询所有设备类型对应的设备
Map<Long, List<Equipment>> equipmentMap = equipmentDao.getEquipmentIdAndNameByTypeIds(equipmentTypeIds).stream()
.collect(Collectors.groupingBy(Equipment::getEquipmentTypeId));
// 获取已选设备id集合
List<Long> selectedEquipmentIds = vo.getSelectedEquipmentList().stream().map(EqDTO::getEquipmentId).toList();
for (EquipmentType eq : eqTypeList) {
// 获取数据类型名称
List<String> dataNameList = dataTypeMap.getOrDefault(eq.getEquipmentTypeId(), Collections.emptyList()).stream()
.map(DataType::getDataTypeName).toList();
map.put(eq.getTypeName(), dataNameList);
// 获取未选设备
List<Equipment> eqList = equipmentMap.getOrDefault(eq.getEquipmentTypeId(), Collections.emptyList()).stream()
.filter(eq1 -> !selectedEquipmentIds.contains(eq1.getEquipmentId())).toList();
List<EqDTO> eqDTOList = eqList.stream().map(eq1 -> new EqDTO(eq1.getEquipmentId(), eq1.getEquipmentName())).toList();
opMap.put(eq.getTypeName(), eqDTOList);
}
优化说明
- 本项目使用SQL Server数据库
- 为最耗时的两个查询除主键聚簇索引外,建立了非聚簇索引
- 为equipmentDao.getEquipmentIdAndNameByTypeId查询建立了覆盖索引
- 移除循环中的查询,改为一次性批量查询
五、优化效果
可以看到页面加载平均耗时从1秒左右优化到了300毫秒左右,优化效果还是比较明显的。