一、DB优化
- SQL查询走索引
- 拆分复杂SQL
- 读写分离
二、业务优化
- Log4j2异步
- 缓存(Redis)
- 业务异步处理
- 循环内不要操作数据库、缓存
- sleep绝对不允许
- 批量获取数据
- 用空间换时间
下面逐步拆分每种情况的优化分案:
1、所有的SQL查询尽量走索引,不要全表扫描。把SQL都放到数据库中看一下执行计划。比如,使用mysql可以把每条SQL前加上explain,看一下执行计划
2、不要写负责的SQL,不要将业务逻辑抛给数据库去处理,应在程序中处理这些业务逻辑,数据库只是用来取基础数据,尽可能的单表查询走索引,拿到数据之后根据业务需求在程序中处理
3、数据库要读写分离
4、尽可能的去掉无用日志,代码写的规范不用过多的日志,看代码就知道业务逻辑。日志打印使用异步的方式,生产环境关闭debug日志。
5、循环内部不要有数据库、缓存操作,把这些操作都移到循环外,通过批量的方式一次性获取数据,然后在程序中对这些数据进行处理,转换成想要的格式。比如,
①. 不要在for循环内执行 orderInfo.getById(id),而是在循环外执行 orderInfo.batchById(List),然后把返回的数据以id做key,每行记录做value存储到Map中,循环内只需要根据id就可以获取到每条记录。
②. 不要在for循环内执行 redisTemplate.hget(key,field),而是在循环外执行 redisTemplate.hmget(key, fieldList),一次性的获取所有值,按照顺序获取每个值就OK了。
6、代码中绝对不能写sleep,要是有这样的代码怎么优化都白扯,TPS肯定上不去。
7、把耗时并且不影响业务主流程的逻辑异步处理,启用线程去处理这些业务。比如,用户每次下单前都需要自动释放上一笔没有支付的订单,像这种情况可以启动一个线程单独处理,并且不用等待线程的返回接口。
8、一个大的业务方法中,各子模块相互独立,互不依赖,可以考虑使用并发处理,即多线程并发查询数据。有个前提是该接口应至少高于20ms,如果再低,线程切换等因素可能会抵消掉多线程带来的性能提高。比如,首页接口需要返回3块数据,每块都是独立的,并且都比较耗时,所以可以启动3个线程同时获取数据,最后等待每个线程返回结果。
9、业务数据增加Redis缓存。这是减少数据库查询,提升TPS的有效手段。但是要根据实际业务情况来使用Redis,合理的设计好缓存key的结构,避免缓存重复数据浪费空间。使用缓存的时候需要注意缓存穿透、缓存雪崩等问题。
10、异步执行任务的时候需要使用线程池,不能直接创建线程并启动。创建线程池时需要使用无界队列,若使用有界队列,当并发量大,并且等待线程超过队列大小时就会报错,线程无法放入队列导致任务丢失。无界队列创建方式:
public static ExecutorService noticePushPool = new ThreadPoolExecutor(50,
100,0L, TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<>(), new ThreadFactoryBuilder()
.setNameFormat(“notice-push-pool-%d”).build(), new ThreadPoolExecutor.AbortPolicy());
LinkedBlockingDeque 不指定大小默认创建大小为int的最大值,也可以手动设置队列大小,但是不建议。
以上优化都属于外部优化,要是还需要深度优化的话,可以考虑优化业务逻辑代码,尽量减少循环次数,提高代码的执行效率,降低时间复杂度,所以操作时间复杂度尽快能达到O(1)。