痛苦的源头
我们线上有个小程序,主要用来实时查询某省所有地市上报的一些信息,这些信息在后台,每5分钟推送一次Excel文件到我们的Kafka服务,这些信息就存储在Excel文件中。
我要做的是,监听Kafka消息,得到Excel文件后,处理16个地市的消息,并将结果存储为图片(比较耗时),处理完之后,提供给小程序查询。
为了追求开发速度,我直接按照地市顺序解析了Excel,截图,将图片url以及一些相关信息存储在MySQL数据库。
但是,问题出现了:
- 虽然勉强能够保证大部分时候5分钟内能处理完16个地市的数据,但是Excel内容多的时候,在5分钟内信息有时处理不完;
- 抛开5分钟内处理不完数据的情况,还有查询上的速度也很慢,这对于前端小程序用户来说,是不能够忍受的
解决问题
针对出现的问题,我分别对数据处理和查询两部分内容作了优化。
线性处理改为并行处理
之前按照顺序处理16个地市的数据,运行速度实在太慢,于是考虑将地市分开来处理,每个地市开启一个线程,该线程只跑自己的数据。
因此,我准备一个线程池:
static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(8,
16,
0,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(128),
new ThreadPoolExecutor.DiscardPolicy());
只要监听到有数据推送(一次推送16个地市的数据)过来,我就开启16个线程放到线程池中,让他们并行处理:
//开启16个线程,跑16个地市的数据
String[] cityArray = cities.split(",");
List<Future> futures = new ArrayList<>();
for (String city : cityArray) {
ProcessRunnable runnable = new ProcessRunnable(city, baseData, siteAlarmListener, redisTemplate);
Future future = threadPool.submit(runnable);
futures.add(future);
}
for (Future future : futures) {
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
这样一番处理之后,经过多次实测,执行速度从原来的5分钟提升到了1分钟之内处理完毕(解析,填充模板,截图等一系列处理)!速度提升了5倍!
缓存数据&查询:我选Redis
Redis既能做数据库,也能做缓存,我们的小程序具体需求不太看重历史数据,主要目的是要获取当前最新的信息。
基于此种需求,我直接将最新的数据放到Redis,这样查询的时候就快了(直接从内存中查)!然后做一个定时任务,将数据持久化的MySQL。
查询基本上是秒出结果:
附上部分代码:
- 引入spring-data-redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 配置redis服务器
spring:
redis:
host: you ip
port: you redis port
password: 有则填写,无则不需要此属性
- 保存数据
需要将数据准备好,我是把数据放在list中了,然后用StringRedisTemplate进行操作。
//StringRedisTemplate
private StringRedisTemplate redisTemplate;
@Autowired
public void setRedisTemplate(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
LOGGER.info("===========开始保存数据到redis==========");
//此处需要准备好数据,我是将数据放在list中了,然后
redisTemplate.opsForValue().set("sal", JSON.toJSONString(list));
LOGGER.info("===========保存到redis end==========");
- 查询数据
String result = redisTemplate.opsForValue().get("sal");
List<YourEntity> resultList = JSONObject.parseArray(result, YourEntity.class);
小结
本次线上调优,通过数据处理和数据存储两个方面对代码进行了优化。
把原来的串行处理模式改成使用线程池的方式进行处理,大大缩减了数据处理的时间;
在查询方面,从传统的数据库查询改成查缓存,查询效率也有了质的提升!
- 线程池,池化技术
- Redis,快
欢迎阅读我的其他Java基础文章
- 从一道面试题进入Java并发新机制—J.U.C
- synchronized底层实现知多少?synchronized加锁还会升级?
- 你知道Object o = new Object()在内存中占多少字节吗?
- Java8新特性Stream还有这种操作?
- 终于看懂别人的代码了!总结Java 8之Lambda表达式的写法套路
看完点赞,养成习惯。
举手之劳,赞有余香。