鉴于自己的开发经验,以及常见容易产生bug及性能问题的点做个记录.
1.数据库
如果开发人员的经验不足,Java通过ORM(Mybatis)对数据库的操作的性能问题比较隐蔽.因为不压测或者异常case没发生的时候一般发现不了问题.特别是异常case发生的时候.
- 除配置表以外的sql都要经过explain分析表扫描范围.必须控制在range级别及以下
- Mybatis防sql注入: #{}传入的参数在SQL中显示为字符串,#{}方式能够很大程度防止sql注入.${}传入的参数在SqL中直接显示为传入的值,${}方式无法防止Sql注入
- selectList之类的操作,必须要限定返回的行数.有时候没有条件传入很可能就是全表扫描返回了.
- Mybatis的LambdaQueryWrapper的 in()函数入参有上限.超过则会报错
- 禁止多表联合join
- 多多利用主键索引或唯一索引提升查询效率
- 用批量插入代替循环迭代插入
- 表设计时,后期若有分库分表需求的,主键不可使用自增id,不方便迁移数据.可以使用分布式主键生成器,例如Snowflake算法生成的ID
- 表设计时,建立合理的联合索引,覆盖索引,最大程度覆盖查询需求
- 表设计时,尽量不要允许null值,状态类字段用精简的字符串枚举
- 查询数据库数据只返回必要的字段,一般不使用select *
2.Java基础API
- HashMap迭代使用entrySet() 获取Map 的key 和value
- 使用Collection.isEmpty()而不是Collection.size()来判空, O(1)
- 初始化集合,Map,队列一般都要指定初始大小.谨慎使用无界队列
- 使用StringBuilder 拼接字符串
- 字符串转化使用String.valueOf(value),可以避免NPE
- 方法返回值不要返回null,返回空集合或者对象都行
- 使用equals方法时,常量放前面
- 尽量不要出现魔法值,多多使用枚举,枚举的属性字段必须是私有且不可变
- 工具类设计成单例或者池化,工具类中屏蔽(private)构造函数
- 使用线程池时要注意队列大小以及拒绝策略,若发生拒绝时该如何保证业务逻辑原子性
- 若使用http调用必须要有超时限制,以及超时处理
- 加锁时,专锁专用,顺序加锁,避免死锁发生的可能
- 使用线程池配合ThreadLocal时养成remove的习惯,规避潜在的溢出风险
3.Spring
- 事务失效问题,注意事务传播特性,特别注意嵌套方法catch异常的问题,导致业务数据不完整
- 避免大事务
- 分布式事务影响性能,可以选择无事务的最终一致性实现
- 使用构造函数注入而不是字段注入,以提高代码的可测试性和可维护性
- 有大量的@Scheduled任务需要同一时刻执行时,调大默认Scheduled线程的数量.否则可能会导致一些任务阻塞住
- 避免频繁的对象创建和销毁,使用合适的对象池或缓存
- 关键日志信息打印,合理使用日志级别.代码注释完备
4.性能提升
- 比较重要的就是接口性能优化,利用并发工具类等手段提升接口响应时间.比如用completeFuture CountDownLatch等并发处理业务数据
- 涉及其他服务调用的接口,必须有快速熔断,不能因为上游服务的问题影响本身的服务.需要充分考虑别的服务返回给自身的数据异常的情况
- 利用分布式缓存提升接口性能
- 可以异步执行的逻辑可以放到消息队列或异步线程.让当前接口快速响应.提升用户体验
- 代码实现选用业界最佳实践,比如用Disruptor队列替代ArrayBlockingQueue
- 多多了解各个场景的业界最佳实践,比如用NIO替代AIO
5.设计实现
- 接口设计实现是否符合单一职责、开闭原则等设计原则
- 使用接口而不是具体实现进行注入,以降低耦合性
- 是否有冗余、重复的代码,是否可以进行重构
- 代码设计实现尽量符合设计模式,提升健壮性和可维护性
- 沉淀基础能力,不要重复造轮子.比如动态线程池工具,分布式锁工具 可以封装成Springboot starter包.一键式装配应用