干程序员这么多年了,零零碎碎也接触过关于优化这方面的东西,类似于创建索引、计算前置、异步编排等等。但是一直都没有过一个系统的总结,最近公司里由于有些接口响应实在太慢,于是借着这个解决问题的机会,好好谈谈 关于程序优化那些事。
首先,项目给你需求,让优化接口,你也不知道到底是因为什么原因导致的响应慢,是sql的问题,还是代码嵌套多层循环的问题,还是前端调用了多个接口导致的性能问题,还是并发问题,或者是架构问题。
所以在这种情况之下,只能一个一个排查:
数据库层面:
优化索引
不单单局限于简单的索引,复合索引、覆盖索引等都可以去考虑,这个往往成本很低但是收效很大;
优化sql
劲量避免子查询语句
用小表驱动大表
where语句后面先接最容易筛选的条件
sql尽量简单,计算移步到应用程序中
排序和分组很消耗数据库性能,如果可以尽可能放在代码里
考虑是否优化表结构
适当的冗余字段其实可以避免一些关联查询,效果还是不错的
数据量大的时候(千万级),考虑是否进行水平拆分
当字段个数太多,可以垂直拆分出经常使用的字段
数据库是否返回了不必要的字段,比如仅仅需要10个字段,一次性返回了100个
查询中避免造成索引失效
其他待补充
代码层面:
考虑代码逻辑执行顺序对性能的影响
比如接口代码分三块,ABC顺序执行,有时候调整顺序能够收效显著。
考虑代码细节
是否在循环里执行了数据库查询语句,能不能放到循环外面一次查出来
多次的数据库插入语句能否合并,修改语句合并
不必要的循环语句
禁止超过两次的循环嵌套
不必要的实时计算(可以考虑计算前置)
是否依赖的下游方法响应慢,影响这边性能
考虑是否需要缓存中间件
会被并发访问的数据库资源,得加缓存,防止大量请求直接打到数据库
本地缓存+redis双重控制,设计缓存策略
缓存更新三大类:自动失效、定时更新、主动通知更新(变更极少,通过MQ通知更新)
当然一切是有利有弊的,当使用缓存时,得避免缓存雪崩、击穿和穿透等问题。另外,数据库缓存一致性也是个很值得去探讨的问题
使用异步编排
对于顺序执行的几块代码,如果是互相之间没有什么相关性,可以考虑异步的方式
对于遍历一个大list,可以考虑去将list切分,异步分别遍历
对主要性能影响不大的代码,例如注册成功发送短信,也可以异步
需求层面:
考虑是否需求合理,是不是必要的?有时候修改稍微修改产品设计可以显著减少代码复杂度
前端层面:
考虑是否请求了不必要的接口,有时候不必要整个页面接口刷新
总之数据库资源是宝贵的,尽量让数据库只进行简单的查询插入修改删除操作,计算都移步到内存中。
如果以上方法都无法起效,可以考虑是不是机器性能问题,对于财大气粗的公司可以直接选择堆机器,
当然也可能是大的架构方面的问题,这个需要提出来共同商讨。
Map和List在声明的时候,设置容量
尽量指定类、方法的final修饰符
如果指定了一个类为final,则该类所有的方法都是final的。Java编译器会寻找机会内联所有的final方法,内联对于提升Java运行效率作用重大,
当进行大量字符串拼接的时候,用StringBuilder代替String
尽可能使用局部变量
调用方法时传递的参数和方法中使用的变量,都存储在栈中,速度较快,其他变量,如静态变量,实例变量都在堆中创建,速度较慢,而且栈随着方法结束就关闭了,不需要额外的垃圾回收
及时关闭流数据库连接,IO流等
尽量减少对变量的重复计算
明确一个概念,即使是只有一行代码的方法,调用也是有消耗的,包括创建栈帧,维护栈内存安全,调用结束弹栈等,都是消耗一定性能的,所以尽量减少重复不必要的调用,比如
for(int i = 0;i<list.size();i++){} // 替换成 int length = list.size(); for(int i = 0;i<length;i++){}
尽量采用懒加载,用的时候才创建对象
异常处理
使用异常首先会创建新的对象,Throwable接口的构造函数调用名为fillInStackTrace()的本地同步方法,fillInStackTrace()方法检查堆栈,收集调用跟踪信息。只要有异常被抛出,Java虚拟机就必须调整调用堆栈,因为在处理过程中创建了一个新的对象。异常只能用于错误处理,不应该用来控制程序流程。
当复制大量数据时,建议使用System.arraycopy()命令
当乘法和除法是2的整次幂的时候,建议采用移位操作
循环内不要重复创建对象
for (int i = 1; i <= count; i++) {Object obj = new Object(); } // 替换为 Object obj = null; for (int i = 0; i <= count; i++) {obj = new Object(); }
基于效率和类型检查的考虑,应该尽可能使用array,无法确定数组大小时才使用ArrayList
尽量使用HashMap,ArrayList,StringBuilder,除非考虑到线程安全才去考虑HashTable,Vector,StringBuffer。后三者使用同步机制而导致性能开销
尽量避免使用静态变量,静态变量常驻内存。
对于锁的合理使用
避免发生死锁
建议使用同步代码块的替代同步方法
常量声明为static final
不要创建不使用的对象和不需要引用的类
避免使用反射
使用数据库连接池和线程池
使用缓冲流
顺序插入和随机访问次数多用ArrayList,随机插入和元素删除多用LinkedList
if语句多个条件时,将容易判断的的条件放最左面
equal()时,常量放左边
数组禁止toString()
禁止对超过范围的long进行下转型
基本数据类型转换成String 最快是包装类.toString(),String.value() 次之,+"" 最次
最快遍历map的方法是使用Iterator
对多个资源的close() 应该分别close()
对于ThreadLocal使用前或者使用后一定要先remove
当前一般会使用线程池技术,在使用结束后线程不会销毁而是重新放入线程池,在下次使用时可能get上次set的数据,这一般很难发现
重写方法加@Override
清楚知道这个方法由父类继承而来
getObj() 和get0bj() 能立马确定是否继承成功
在抽象类或者接口中修改方法名,实现类中会报错
用Objects代替equals
多线程获取随机数时,用ThreadLocalRandom而非Random
待补充
本文发布于:2024-02-04 21:28:22,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170716837759775.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |