Spring Boot 搭建微服务确实快——几个注解、一套配置,服务就起来了。但“跑起来”和“跑得稳”之间,隔着数不清的坑。很多项目上线后出问题,不是架构选错了,而是在一些看似不起眼的实践细节上翻了车。下面这五个坑,是我见过最多人踩的。
一、Controller里写业务逻辑——最常见也最致命
新手最容易犯的错,就是把Controller当成“万能入口”——HTTP处理、库存校验、实体创建、状态判断、持久化、响应映射,全塞在一个方法里。代码能跑,但问题在后面:三个月后这个Controller涨到500行,所有人都不敢碰它。
正确的做法:Controller只做一件事——把HTTP请求翻译成应用行为,再把结果翻译回HTTP响应。业务逻辑应该下沉到Service层,用@Service类封装独立的用例。瘦Controller好测、好改、好理解,而胖Controller就是一个“穿了HTTP外套的Service”。
判断标准:如果你的Controller里有业务规则、有数据库操作、有外部调用——它已经变味了,赶紧重构。
二、直接把Entity当API返回——图省事埋大雷
“接口要返回数据,直接return Entity多省事。”这是第二个高频坑。
JPA Entity是持久化模型,API响应是对外契约,这两者根本不是一回事。直接返回Entity,密码字段、内部标记可能就泄露了。更坑的是懒加载——序列化时触发额外查询,或者直接报LazyInitializationException。
正确做法:定义独立的DTO(Data Transfer Object)作为API的请求和响应对象,在Service层完成Entity到DTO的转换。虽然多写几个类,但换来的是接口契约的稳定和安全。
三、依赖冲突视而不见——启动报错找不到北
微服务项目依赖复杂,Spring Boot全家桶、各种starter、第三方库堆在一起,依赖冲突几乎是必然的。
典型症状是NoSuchMethodException、ClassNotFoundException、NoClassDefFoundException——遇到这三种异常,第一反应就该检查依赖冲突。比如整合gRPC时引入protobuf-java-util,就和接口层的proto依赖发生冲突,控制台不报明显错误,只有在debug执行到相关代码时才提示类型转换失败。
解决工具:Maven Helper插件。安装后打开pom文件,下方会显示依赖冲突爆红以及具体的依赖引用情况,直接在冲突的依赖上exclude即可。别等到部署到生产环境才发现问题,本地构建时就把依赖关系理清楚。
四、Nacos注册IP错误——容器环境下的经典陷阱
把Spring Boot微服务部署到Docker或K8s环境时,服务注册到Nacos的IP往往是容器内网IP,导致其他服务无法访问。原因是Docker默认使用bridge网络模式,容器IP对宿主机不可见。
解决方案:在Nacos的discovery配置中强制指定宿主机IP:
spring: cloud: nacos: discovery: ip: ${HOST_IP:127.0.0.1} prefer-among-ip: true或者在Docker Compose中通过环境变量传递HOST_IP。这个问题看似小,但排查起来非常耗时——服务注册成功了,调用却总是超时,折腾半天才发现是IP地址的问题。
五、线程池配置随意——生产环境无声的杀手
微服务架构下,一个服务里可能同时运行着Web容器线程、Feign调用线程、消息队列监听线程、Redis连接池线程、业务@Async线程……如果不加管理,线程数会不受控制地暴涨。
我见过一个真实案例:某微服务节点线程总数超过5000,最终触发了“unable to create new native thread”的错误。排查发现:Undertow的worker线程设置过大(1000),业务线程没有用线程池导致不停创建新线程,Hystrix线程也没有合理配置。
合理配置思路:Web容器的IO线程数设为CPU核数,worker线程设为IO线程数的8倍左右;所有异步任务统一使用自定义线程池,别依赖@Async的默认行为;Feign、Hystrix等组件的线程参数也要根据实际流量调优,别用默认值。
写在最后
这五个坑的共性是:代码能跑,但经不起生产环境的考验。微服务开发不能只满足于“能跑起来”,更要考虑“跑得稳、跑得久”。Controller分层、DTO隔离、依赖管理、容器网络配置、线程池治理——这些看似琐碎的实践细节,恰恰是区分“能用”和“好用”的关键。别等到线上出P0故障了才回头补课。