隨著 Dubbo 社區維護放緩,眾多企業開啟 Spring Cloud 微服務遷移之路。本文基於真實項目經驗,系統總結遷移背景、核心組件選型、跨系統調用參數傳遞、重試機制治理、部署端口一致性、Spring Boot 整合坑、統一異常處理、服務註冊發現疑惑八大實戰專題,提供可落地的解決方案與架構思考。
現象: 服務提供方接收不到 Feign 調用傳遞的對象參數(POJO 為 null)。
原因: Feign 默認以 application/x-www-form-urlencoded 編碼發送請求,SpringMVC 默認不解析此類型的請求體為對象。
解決: 提供方 Controller 方法參數加 @RequestBody,強制解析請求體 JSON。
// 消費方 Feign Client
@FeignClient(name = "user-service")
public interface UserClient {
@PostMapping("/users")
void saveUser(@RequestBody User user); // 消費方也建議加上
}
// 提供方 Controller
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping
public void saveUser(@RequestBody User user) { // 必須加 @RequestBody
userService.save(user);
}
}
現象: Ribbon/Feign 默認重試導致非冪等業務(如扣款、下單)重複執行,產生髒數據。
解決:雙層配置全關閉或精準控制
// 1. Feign 客戶端級關閉重試(推薦:業務層自行控制冪等/重試)
@Configuration
public class FeignConfig {
@Bean
public Retryer feignRetryer() {
return Retryer.NEVER_RETRY; // 完全禁用 Feign 級重試
}
}
// 2. Ribbon 客戶端級配置(application.yml)
ribbon:
MaxAutoRetries: 0 # 同一實例重試次數(不含首次)
MaxAutoRetriesNextServer: 0 # 切換實例重試次數
OkToRetryOnAllOperations: false # 所有操作是否重試(默認 false,僅 GET)
ConnectTimeout: 3000
ReadTimeout: 5000
# 服務級覆蓋(user-service 為服務名)
user-service:
ribbon:
MaxAutoRetries: 0
MaxAutoRetriesNextServer: 1 # 集群環境可保留 1 次換節點重試,單節點設 0
核心原則: 基礎設施層關閉非冪等操作重試,業務層通過冪等鍵(Token/流水號)+ 分布式鎖/數據庫唯一約束保證語義冪等。
現象: 服務在 Eureka/Nacos 註冊正常,但消費方調用報「連接拒絕」。
原因: Spring Boot server.port 配置與打包部署的 Tomcat/容器實際監聽端口不一致(如 Docker 映射、外置 Tomcat 配置)。註冊中心拿到的是 server.port,消費方按此端口發起調用,實際服務不在該端口。
解決:
server.port=8080,Dockerfile EXPOSE 8080,運行 -p 8080:8080,保證容器內外端口一致。SpringBootServletInitializer 且確保 server.port 與 Tomcat Connector port 一致,或直接用 Jar 包內嵌容器部署。server.port=0 隨機端口,配合 eureka.instance.prefer-ip-address=true 及 instance-id 包含端口,確保註冊元數據實時準確。現象: 應用啟動正常,但訪問接口 404。
原因: @SpringBootApplication(含 @ComponentScan)默認掃描啟動類所在包及其子包。若 Controller/Service 在啟動類包之外(如同級或父包),不會被掃描註冊。
解決:
// 方案 1:調整啟動類位置至基礎包(推薦)
// com.company.project.Application → 掃描 com.company.project.**
// 方案 2:顯式指定掃描基礎包
@SpringBootApplication(scanBasePackages = "com.company.project")
// 或
@ComponentScan(basePackages = {"com.company.controller", "com.company.service"})
現象: AJAX 請求 /api/user 正常,瀏覽器訪問 /user/page 返回 404/下載 JSON。
原因: Spring Boot 自動配置多個 HttpMessageConverter 與 ViewResolver,內容協商策略導致響應類型判斷錯誤。
解決:
@RestController(含 @ResponseBody),URL 無後綴或 .json。@Controller + @GetMapping("/page") 返回視圖名,配置 spring.mvc.view.prefix/suffix 或 WebMvcConfigurer 添加 InternalResourceViewResolver。@RestController 不參與視圖解析,徹底隔離。場景: 傳統 Spring MVC 應用調用 Spring Cloud Gateway/服務。
解決: 使用 HttpClient / RestTemplate / WebClient 設置 Header:
HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); // 關鍵!Feign/Gateway 默認解析 JSON headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); HttpEntity<String> entity = new HttpEntity<>(jsonBody, headers); ResponseEntity<Result> resp = restTemplate.exchange(url, HttpMethod.POST, entity, Result.class);
痛點: 每個 Feign Client 方法需對應 fallback 方法,代碼膨脹,邏輯分散。
方案:全局異常攔截 + 統一降級響應
// 1. 全局異常處理器(ControllerAdvice)
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(FeignException.class)
public Result handleFeignException(FeignException e) {
log.error("Feign 調用失敗: {}", e.getMessage());
return Result.fail("服務調用失敗: " + e.status());
}
@ExceptionHandler(Exception.class)
public Result handleException(Exception e) {
log.error("系統異常", e);
return Result.fail("系統內部錯誤");
}
}
// 2. Feign 關閉 Hystrix(若不需要細粒度熔斷)
feign:
hystrix:
enabled: false
// 3. 統一使用 Resilience4j / Sentinel 做限流熔斷(現代替代方案)
// 配置切面/註解級熔斷,非入侵業務代碼
缺點: 網絡超時/連接失敗等「無響應」異常可能無法被 ControllerAdvice 捕獲(發生在 Client 端),需結合 Client 端異常處理或網關層統一治理。
作者疑惑: 傳統 Spring War 包部署,能否像 Spring Cloud 服務一樣註冊到 Nacos/Eureka,實現統一服務發現?
分析:
spring-cloud-starter-netflix-eureka-client 或 nacos-client,配置 @EnableDiscoveryClient,Spring 應用啟動即註冊。微服務遷移是系統工程,而非單純技術棧替換。以業務價值為導向、以穩定性為底線、以演進式架構為方法,方能在享受雲原生紅利的同時,避開「分布式單體」與「過度設計」的雙重陷阱。