首页 > 编程语言 >不扒瞎,这个程序让我从300s优化到了10s

不扒瞎,这个程序让我从300s优化到了10s

时间:2022-10-03 21:32:56浏览次数:54  
标签:commonRequestDTO om null CommonRequestDTO 300s new 10s 序列化 优化

前天晚上加班完成部门Q4KPI考核计划后,看到业务开发组的几个小伙伴在处理生产问题。我上前了解情况。

 

销管系统,客户交易明细页面,查询客户交易数据的逻辑是:调用远程数据中心接口,拿到原始交易数据集合,然后在内存里通过相关id来给客户名称、服务商名称、销售人员名称、所属部门、上级销售主管赋值。

不扒瞎,这个程序让我从300s优化到了10s_json

 

 

产品经理反馈,销售主管登陆系统查询数据时,非常慢,慢到4~5分钟。

查看日志,发现后台程序处理耗时动辄高达300s。

300s是个庞大的数字!当务之急,是看能不能降低到10s以内。通过分析,其中,获取远程交易数据耗时≈6s,本地内存数据匹配竟然耗时200多秒,incredible!unbelievable!

不扒瞎,这个程序让我从300s优化到了10s_序列化_02

 

 

那接下来要对各个匹配数据的程序段来分析。通过细化耗时,发现在for循环匹配销售数据为销售人员名称、所属部门、上级销售主管赋值时,异常地慢。

贴出来这段代码:

/**
* 查询销售与部门的关联关系
* @param saleId
* @return
*/
public CommonRequestDTO selectSaleDepartRelation(Integer saleId){
List<CommonRequestDTO> relationList = CacheUtil.getCache(SaleCommonConstant.SALE_DEPART_RELATION, SaleCommonConstant.EXPIRY_SECONDS, ()
-> emaxSalerMapper.selectSaleDepartRelation()
);
relationList = relationList.stream().filter(o -> saleId.equals(o.getSaleId())).collect(Collectors.toList());
if(CollectionUtils.isNotEmpty(relationList)){
CommonRequestDTO commonRequestDTO = relationList.get(0);
commonRequestDTO.setSaleName(commonRequestDTO.getSaleName());
commonRequestDTO.setDepartName(commonRequestDTO.getDepartName());
commonRequestDTO.setDepartHeaderName(commonRequestDTO.getDepartHeaderName());
return commonRequestDTO;
}
return null;
}

其中,CacheUtil封装了Redis的get/set操作。
emaxSalerMapper#selectSaleDepartRelation是查数据库获取基础关系数据,共223条数据,耗时6~7ms。
CommonRequestDTO是一个pojo模型类。

 

那么,这段代码也看不出哪里慢呀!

 

仔细一分析,发现端倪。Cc同学怀疑问题出在读redis上。果不其然,for循环里频繁调用redis获取集合数据,尤其是当查询数据记录多循环次数多时,必然拉跨。
当务之急,最好的解决办法,是用本地缓存来搞,HutoolCache登场。

static TimedCache<String ,List<CommonRequestDTO>> cache= cn.hutool.cache.CacheUtil.newTimedCache(SaleCommonConstant.EXPIRY_SECONDS);

/**
* 查询销售与部门的关联关系
* @param saleId
* @return
*/
public CommonRequestDTO selectSaleDepartRelation(Integer saleId){
if (cache.get(SaleCommonConstant.SALE_DEPART_RELATION)==null){
cache.put(SaleCommonConstant.SALE_DEPART_RELATION, emaxSalerMapper.selectSaleDepartRelation());
}

List<CommonRequestDTO> relationList = cache.get(SaleCommonConstant.SALE_DEPART_RELATION);
relationList = relationList.stream().filter(o -> saleId.equals(o.getSaleId())).collect(Collectors.toList());
if(CollectionUtils.isNotEmpty(relationList)){
CommonRequestDTO commonRequestDTO = relationList.get(0);
commonRequestDTO.setSaleName(commonRequestDTO.getSaleName());
commonRequestDTO.setDepartName(commonRequestDTO.getDepartName());
commonRequestDTO.setDepartHeaderName(commonRequestDTO.getDepartHeaderName());
return commonRequestDTO;
}
return null;
}

 

改造完成,再测试,发现这段代码耗时已经到ms级了。整体方法耗时也控制在了10s以内。

那么,回过头来分析,我们看程序里redis-RedisTemplate配置,valueSerializer使用Jackson2JsonRedisSerializer,Jackson2JsonRedisSerializer序列化使用ObjectMapper。

/**
* RedisTemplate配置
* @param lettuceConnectionFactory
* @return
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
// 设置序列化
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, Visibility.ANY);
om.enableDefaultTyping(DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置redisTemplate
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(lettuceConnectionFactory);
RedisSerializer<?> stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer);// key序列化
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);// value序列化
redisTemplate.afterPropertiesSet();
return redisTemplate;
}

ObjectMapper在序列化时,会将所有的字段序列化,无论这些字段是否有值(是否为null)。再看CommonRequestDTO,有多达22个属性,可见在本案List<CommonRequestDTO>中有223个元素时,数据体积无形中增大很多。通过下面对ObjectMapper的测试代码来比较一下,很明显可以看到单个对象序列化后在数据量方面的差异:

@Test
public void testObjectMapper2() throws JsonProcessingException {
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.PUBLIC_ONLY)
.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
System.out.println("序列化所有字段(无论这些字段是否有值) ↓ ↓ ↓");
System.out.println(new String(om.writeValueAsBytes(new CommonRequestDTO())));

om = new ObjectMapper();
om.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.PUBLIC_ONLY)
.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL)
.setSerializationInclusion(JsonInclude.Include.NON_NULL);
System.out.println("不序列化空值字段 ↓ ↓ ↓");
System.out.println(new String(om.writeValueAsBytes(new CommonRequestDTO())));
}
序列化所有字段(无论这些字段是否有值) ↓ ↓ ↓
["com.emax.memberaccount.restapi.vo.CommonRequestDTO",{"enterpriseId":null,"enterpriseBizId":null,"enterpriseName":null,"saleId":null,"product":null,"entStatus":null,"departId":null,"agentId":null,"levyId":null,"departHeaderId":null,"saleIds":null,"enterpriseIds":null,"productList":null,"createTimeBegin":null,"createTimeEnd":null,"saleName":null,"departName":null,"departHeaderName":null,"ifDepartHeader":null,"loginSalerId":null,"selectEnterpriseId":null,"orderEndTime":null,"enterpriseProductDTOS":null}]
不序列化空值字段 ↓ ↓ ↓
["com.emax.memberaccount.restapi.vo.CommonRequestDTO",{}]

 

  • 因此,我们的程序有必要加上这个控制,即只序列化非空字段。
    另外,就像我之前经常提到的,会 is one thing,会用 is another。本案也再一次敲响了警钟:在使用redis分布式缓存时,尤其控制缓存大对象,更要严禁高频访问大对象缓存。

标签:commonRequestDTO,om,null,CommonRequestDTO,300s,new,10s,序列化,优化
From: https://blog.51cto.com/u_15708799/5730499

相关文章

  • 存储优化--查询分离
    上一篇文章中我们讲解了利用数据库分区与冷热分离的方式来优化存储,虽然解决了查询速度慢的问题,但是在海量数据情况下依然会出现查询缓慢问题,并且部分系统中的冷热数据也是需......
  • SQL优化 21 连击
    一、查询SQL尽量不要使用select*,而是具体字段1、反例SELECT*FROMuser2、正例SELECTid,username,telFROMuser3、理由节省资源、减少网络开销。可能用到覆盖索引,减少......
  • webpack配置优化,让你的构建速度飞起
    前言越来越多的项目使用webpack5来构建项目了,今天给大家带来最前沿的webpack5配置,让我们代码在编译/运行时性能更好~我们会从以下角度来进行优化:提升打包构建速度减少......
  • 前端页面性能如何优化?
    加载优化:减少http请求,缓存资源,压缩代码,按需加载,压缩图像,预加载执行优化:css写在头部,js写在尾部并异步避免img,iframe的src为空,空src会重新加载页面,影响速度和......
  • CPU高速缓存行对齐和代码优化
    CacheLine众所周知,计算机将数据从主存读入Cache时,是把要读取数据附近的一部分数据都读取进来这样一次读取的一组数据就叫做CacheLine,每一级缓存中都能放很多的CacheLine两......
  • 01背包&完全背包二维写法的对比,进而理解一维优化后的正逆序
    01背包题解完全背包题解二维写法时两种背包问题核心代码的区别:可以看出,01背包用的是上一层的数据,完全背包用的是当前层的数据所以优化为一维时,01背包需逆序for......
  • 用一个例子理解拉格朗日乘数法解决等式约束优化问题
    首先我们来看看一个实例:\[\begin{aligned}&min&f(x,y)&=x^2+y^2\\&s.t.&xy&=3\end{aligned}\]即:在定义域\(xy=3\)内,求\(f(x,y)\)的最小值。两个函数的图像如下:......
  • Mysql 碎片优化,ibd文件过大优化
    一、ibd文件  我们知道ibd文件是每个数据库里面每个表的数据空间,每个表的数据和索引都会存在自已表空间中。然后建库建表,插入数据,使该表的ibd文件增大在使用mysql......
  • 怎样对react,hooks进行性能优化?
    前言现在越来越多人开始使用ReactHooks+函数组件的方式构筑页面。函数组件简洁且优雅,通过Hooks可以让函数组件拥有内部的状态和副作用(生命周期),弥补了函数组件的不足......
  • 斜率优化
    斜率优化是将一类\(O(n^2)\)的DP状态转移优化至\(O(n\logn)\)甚至\(O(n)\)的方法。用一个atcoderdpcontest的最后一题来讲解:dp_zFrog-3https://atcoder.......