Java后端常用技术选型 |(六)避坑手册
Java后端常用技术选型 |(六)避坑手册
- 第一章:基础概念(为什么需要避坑手册)
- 第二章:核心避坑选型表(精简版)
- 第三章:分领域详细避坑指南
- 一、依赖管理避坑
- 坑点1:Maven/Gradle依赖冲突导致启动失败
- 坑点2:依赖范围(scope)配置错误导致线上故障
- 二、并发编程避坑
- 坑点1:ArrayList在多线程下并发修改异常(`ConcurrentModificationException`)
- 坑点2:线程池配置不合理导致OOM或任务积压
- 三、数据库操作避坑
- 坑点1:慢SQL导致数据库雪崩
- 坑点2:事务滥用导致并发性能暴跌
- 四、Spring框架避坑
- 坑点1:Bean循环依赖导致启动失败或内存泄漏
- 坑点2:Spring事务不生效(@Transactional失效)
- 五、分布式系统避坑
- 坑点1:分布式事务不一致(数据最终一致性失效)
- 坑点2:分布式配置不一致导致集群行为异常
- 六、线上运维避坑
- 坑点1:日志泛滥导致磁盘打满
- 坑点2:发布回滚不及时导致故障扩大
- 第四章:国产化适配避坑(信创项目)
第一章:基础概念(为什么需要避坑手册)
- 避坑手册定义:针对Java后端开发各技术领域的常见陷阱、错误实践、隐性风险的汇总指南,核心价值是提前识别风险、减少试错成本、提升交付质量。
- 典型踩坑场景:依赖冲突导致启动失败、并发竞态引发数据不一致、SQL写烂导致数据库雪崩、框架配置错误引发内存泄漏、线上运维操作失误导致服务故障等。
- 选型逻辑:以“高频踩坑场景”为核心,覆盖依赖管理、并发编程、数据库、框架、分布式、线上运维等领域,每个坑点包含“场景描述→错误原因→解决方案→最佳实践”。
第二章:核心避坑选型表(精简版)
| 技术领域 | 典型坑点类型 | 致命程度 | 避坑关键动作 | 行业踩坑率 |
|---|---|---|---|---|
| 依赖管理 | 依赖冲突、版本不兼容 | ★★★★☆ | 统一版本管理,使用依赖分析工具 | 90%+ |
| 并发编程 | 竞态条件、死锁、线程泄漏 | ★★★★★ | 优先用并发容器,避免手动锁 | 85%+ |
| 数据库操作 | 慢SQL、事务滥用、连接泄漏 | ★★★★☆ | 强制走索引,控制事务粒度 | 95%+ |
| Spring框架 | Bean循环依赖、事务不生效 | ★★★☆☆ | 禁用循环依赖,明确事务传播行为 | 80%+ |
| 分布式系统 | 分布式事务不一致、配置不一致 | ★★★★☆ | 选成熟中间件,配置中心化管理 | 88%+ |
| 线上运维 | 日志泛滥、内存溢出、发布回滚 | ★★★★★ | 日志分级,提前压测,灰度发布 | 92%+ |
第三章:分领域详细避坑指南
一、依赖管理避坑
坑点1:Maven/Gradle依赖冲突导致启动失败
- 场景描述:引入多个依赖后,项目启动报
ClassNotFoundException或MethodNotFoundException,日志提示类版本不兼容。 - 错误原因:不同依赖引入了同一类库的不同版本,ClassLoader加载了旧版本类,导致方法/字段缺失。
- 解决方案:
- 使用
mvn dependency:tree(Maven)或gradle dependencies(Gradle)分析依赖树,找到冲突版本。 - 通过
<exclusions>排除低版本依赖,或强制指定统一版本(如在dependencyManagement中锁定版本)。
- 使用
- 最佳实践:
- 所有项目使用
dependencyManagement统一管理版本,避免散点配置。 - 引入新依赖前,先检查其传递依赖是否会引发冲突。
- 所有项目使用
坑点2:依赖范围(scope)配置错误导致线上故障
- 场景描述:本地测试正常,线上部署后报类缺失,或测试依赖(如
test范围)被打入生产包导致体积膨胀。 - 错误原因:对
compile/test/provided/runtime等scope理解错误,导致依赖在运行时缺失或冗余。 - 解决方案:
- 明确各scope含义:
compile(编译+运行)、test(仅测试)、provided(编译需要,运行时由容器提供,如servlet-api)、runtime(运行时需要,编译不需要)。 - 线上包通过
mvn clean package -DskipTests打包,避免测试依赖混入。
- 明确各scope含义:
二、并发编程避坑
坑点1:ArrayList在多线程下并发修改异常(ConcurrentModificationException)
- 场景描述:多线程同时操作
ArrayList(添加/删除元素),运行时抛出ConcurrentModificationException。 - 错误原因:
ArrayList是线程不安全集合,迭代器遍历期间集合被修改会触发快速失败机制。 - 解决方案:
- 替换为线程安全集合:
CopyOnWriteArrayList(读多写少场景)或ConcurrentHashMap(需List时可封装)。 - 加锁:用
synchronized或ReentrantLock包裹操作,但性能较低。
- 替换为线程安全集合:
- 最佳实践:优先选JUC包下的并发容器(
ConcurrentHashMap、CopyOnWriteArrayList等),避免手动加锁。
坑点2:线程池配置不合理导致OOM或任务积压
- 场景描述:线程池任务提交后,要么内存暴涨(OOM),要么任务长时间排队不执行。
- 错误原因:
- 核心线程数/最大线程数设置过大,导致创建过多线程占用内存;
- 队列容量设置不合理(如无界队列导致任务堆积);
- 拒绝策略选择错误(如默认
AbortPolicy直接抛异常,未做降级)。
- 解决方案:
- 合理配置参数:
- 核心线程数:
CPU核心数 + 1(CPU密集型)或CPU核心数 * 2(IO密集型); - 最大线程数:根据机器资源适当放大,避免过大;
- 队列:选有界队列(如
ArrayBlockingQueue),容量根据业务压测确定; - 拒绝策略:选
CallerRunsPolicy(调用者线程执行任务,实现降级)或自定义策略。
- 核心线程数:
- 结合监控:通过Prometheus监控线程池活跃数、队列长度,动态调整参数。
- 合理配置参数:
三、数据库操作避坑
坑点1:慢SQL导致数据库雪崩
- 场景描述:某接口请求突然变慢,数据库CPU打满,其他接口也受影响(雪崩)。
- 错误原因:SQL未走索引(全表扫描)、关联表过多、查询数据量过大,导致数据库性能急剧下降。
- 解决方案:
- 紧急处理:kill慢SQL进程(通过
show processlist定位),临时限流该接口。 - 长期优化:
- 加索引:通过
explain分析执行计划,给过滤条件、关联字段加索引; - 优化SQL:拆分大查询、避免
select *、控制返回行数(limit); - 分库分表:数据量超大时(千万级以上),采用Sharding-JDBC等中间件拆分。
- 加索引:通过
- 紧急处理:kill慢SQL进程(通过
- 最佳实践:
- 所有SQL上线前必须走
explain分析,确保索引命中; - 配置数据库慢查询日志(如MySQL的
slow_query_log),定期review慢SQL。
- 所有SQL上线前必须走
坑点2:事务滥用导致并发性能暴跌
- 场景描述:高并发接口(如订单创建)响应时间长,并发量上不去。
- 错误原因:事务粒度太大(将非核心逻辑也包含在事务中),或事务隔离级别过高(如用
SERIALIZABLE),导致锁竞争激烈。 - 解决方案:
- 缩小事务粒度:仅将数据变更操作(如insert、update)包含在事务中,查询、日志记录等操作移出。
- 调整隔离级别:根据业务选最低可用级别(如读已提交
READ_COMMITTED),避免不必要的锁。 - 异步化:将非实时逻辑(如消息发送、日志记录)异步执行,减少事务内耗时。
四、Spring框架避坑
坑点1:Bean循环依赖导致启动失败或内存泄漏
- 场景描述:Spring项目启动时报
CircularReferenceException,或启动后出现内存泄漏。 - 错误原因:两个或多个Bean互相依赖(如A依赖B,B依赖A),且未正确配置循环依赖处理(Spring默认支持单例Bean的循环依赖,但构造器注入或非单例Bean会失败)。
- 解决方案:
- 重构代码:拆解循环依赖(如提取公共服务类)。
- 配置调整:
- 若为单例Bean,允许循环依赖(Spring默认支持,但若使用构造器注入需改为Setter注入);
- 若为原型Bean,必须拆解依赖,因为Spring不支持原型Bean的循环依赖。
- 最佳实践:
- 代码评审时强制检查循环依赖,优先通过设计避免,而非依赖框架支持。
坑点2:Spring事务不生效(@Transactional失效)
- 场景描述:加了
@Transactional注解,但异常发生后数据未回滚。 - 错误原因:
- 方法非public(Spring事务代理仅对public方法生效);
- 异常被捕获(未抛出,事务无法感知);
- 异常类型不匹配(如默认只回滚
RuntimeException, checked异常需显式配置rollbackFor); - 自调用(同一类中方法调用,事务代理未生效)。
- 解决方案:
- 确保方法是public;
- 异常必须抛出,且配置
rollbackFor = Exception.class(覆盖所有异常); - 自调用场景需通过Spring上下文获取代理对象(如
AopContext.currentProxy())。
- 最佳实践:
- 所有事务方法显式配置
rollbackFor = Exception.class; - 用单元测试验证事务是否生效。
- 所有事务方法显式配置
五、分布式系统避坑
坑点1:分布式事务不一致(数据最终一致性失效)
- 场景描述:跨服务操作(如订单创建→库存扣减),某一步失败后,其他服务数据未回滚,导致数据不一致。
- 错误原因:未采用可靠的分布式事务方案(如Seata、本地消息表),或中间件配置错误(如消息队列未开启事务)。
- 解决方案:
- 选成熟方案:
- 强一致场景:用Seata的AT/TCC模式;
- 最终一致场景:用本地消息表+RocketMQ事务消息。
- 配置验证:确保中间件(如Seata、RocketMQ)的事务配置正确,事务日志持久化。
- 选成熟方案:
坑点2:分布式配置不一致导致集群行为异常
- 场景描述:集群中部分节点配置与其他节点不同,导致功能不一致(如部分节点限流、部分节点不限流)。
- 错误原因:配置未中心化管理,各节点本地配置不一致,或配置更新后未实时同步。
- 解决方案:
- 配置中心化:使用Nacos、Apollo等配置中心,所有节点从配置中心拉取配置。
- 配置热更新:确保配置变更后,所有节点能实时感知并刷新配置(如Nacos的监听机制)。
六、线上运维避坑
坑点1:日志泛滥导致磁盘打满
- 场景描述:线上服务突然不可用,排查发现磁盘被日志占满(如某接口日志打印过多,或未配置日志滚动策略)。
- 错误原因:
- 日志级别配置过低(如
DEBUG级别日志全量输出); - 日志未配置滚动策略(如单文件无大小限制,持续写入);
- 异常堆栈未做收敛(重复打印大量相同异常)。
- 日志级别配置过低(如
- 解决方案:
- 紧急处理:清理日志(
logrotate工具),临时调整日志级别为WARN。 - 长期优化:
- 配置日志级别:生产环境只输出
INFO及以上级别日志, debug日志仅在测试环境开启; - 配置滚动策略:如Logback配置
maxFileSize和maxHistory,限制单文件大小和保留天数; - 异常收敛:对频繁出现的异常,增加频次统计,避免重复打印堆栈。
- 配置日志级别:生产环境只输出
- 紧急处理:清理日志(
坑点2:发布回滚不及时导致故障扩大
- 场景描述:新功能发布后出现严重bug,但回滚流程不清晰,导致故障持续时间过长。
- 错误原因:
- 发布流程无灰度(全量发布,回滚影响面大);
- 回滚步骤未演练,线上回滚时操作失误;
- 版本管理混乱(无法快速定位回滚版本)。
- 解决方案:
- 灰度发布:使用蓝绿部署、金丝雀发布,先小范围验证,再全量发布。
- 自动化回滚:将回滚步骤脚本化,确保一键回滚。
- 版本管理:使用Git标签或版本号规范,确保每个版本可追溯、可回滚。
第四章:国产化适配避坑(信创项目)
- 国产数据库(达梦、人大金仓)避坑:
- 坑点:SQL语法差异(如达梦不支持MySQL的
limit ? offset ?,需用rownum替代)。 - 解决方案:提前梳理语法差异,封装适配层(如MyBatis方言配置)。
- 坑点:SQL语法差异(如达梦不支持MySQL的
- 国产中间件(华为云DMS、阿里RocketMQ国产版)避坑:
- 坑点:配置项与开源版本不同(如华为DMS的 Topic 权限配置更严格)。
- 解决方案:严格按照国产化中间件的官方文档配置,提前做兼容性测试。
- 国产JDK(中科方德、华为毕昇)避坑:
- 坑点:对部分开源库的兼容性问题(如早期版本对Lambda表达式支持不足)。
- 解决方案:优先选用经过国产化认证的开源库版本,或提前做兼容性测试。
