我研讀了 500 個 Spring Boot 生產級程式碼庫,90% 都犯了這 7 個致命錯誤

我研讀了 500 個 Spring Boot 生產級程式碼庫,90% 都犯了這 7 個致命錯誤

這些問題模式,正是凌晨三點線上事故的元兇;也是 481 名工程師上線前必核查的隱患點

正文

過去 18 個月,我以技術顧問的身分,專門接手修復各種出故障的 Spring Boot 應用。

我接手的不是教學 demo、業餘練手專案,而是承載真實交易資金、海量使用者,動輒引發凌晨三點緊急故障的生產級系統

累計梳理500 餘個程式碼庫、服務過 47 家企業,客戶公司年收入規模從 200 萬到 5 億美元不等。

我反覆見到一模一樣的線上災難。

更可怕的是:這些有隱患的程式碼,全都通過了程式碼審查

緣起:一切從失業開始

2024 年 11 月,我丟掉了年薪 28 萬美元的後端高薪工作。

急需收入糊口,我做起了技術諮詢,主打業務:幫你修好當機的 Spring Boot 應用

第一位客戶是電商新創公司,日活 5 萬,系統頻繁當機。

CEO 在電話裡焦急地說:「大促期間結帳頁面頻繁崩潰,三名開發排查許久,始終找不到原因。」

我申請了程式碼儲存庫權限。

僅用 11 分鐘,就定位到了問題。

不是我天賦過人,只是這種坑,我早已見過無數次。

這家公司不是特例,而是第 40 家栽在同一個錯誤上的企業。

那一刻我幡然醒悟:絕大多數 Spring Boot 開發者,都在無腦複製那些自帶隱患的錯誤編碼範式

促使我開始統計問題的標竿客戶

2025 年 3 月,一家 B 輪融資的 SaaS 企業,坐擁 20 萬使用者。

他們的 API 每週一早上 9 點準時崩盤,分秒不差。

團隊排查了整整六週,甩鍋給 AWS 雲服務、甩鍋給資料庫、甩鍋給流量波動。

某個週一早上 8 點 50 分,我登入了他們的應用監控後台。

盯著指標,靜靜等待。

9 點 03 分:介面回應耗時從 180 毫秒飆升至 11 秒。

9 點 07 分:首次出現 500 服務異常。

9 點 12 分:系統徹底癱瘓。

我查看了他們的 Spring Boot 設定:

yaml

yaml 体驗AI程式碼助手 程式碼解讀複製程式碼spring:
  datasource:
    hikari:
      maximum-pool-size: 10

承載 20 萬使用者的應用,資料庫連線池只配了10 個連線

每週一 9 點全員集中登入,瞬間占滿所有連線,系統直接癱瘓。

我把連線池大小改成 50,問題立刻消失。

團隊耗費六週排查,修復只用了 4 秒

因不熟悉 Spring Boot 生產環境預設設定,這家公司直接損失了 18 萬美元營收。

這也是我開始系統性記錄歸納的第一個致命問題模式。

我曾在生產環境中把監控端點配置成include: "*",等我發現時,所有應用監控介面已經對外暴露了整整六週。

資料庫帳號密碼、AWS 金鑰、JWT 簽章金鑰…… 全部明文外洩。

為此我整理了一份上線核查清單,涵蓋 7 大模組、47 項檢查點,專門規避生產環境高頻崩潰隱患:

✅ 設定與密鑰管理(杜絕監控介面資訊外洩)

✅ 安全強化(禁止核心介面公開存取)

✅ JVM 參數調校(避免凌晨記憶體溢位當機)

✅ 資料庫連線池設定(防止連線耗盡)

✅ Docker 容器校驗(規避容器反覆重啟)

✅ 監控告警部署(杜絕盲上線、無觀測)

✅ 上線後核驗(攔截靜默隱形故障)

致命錯誤模式一:資料庫連線池死亡惡性循環

經歷週一早高峰當機事件後,我養成了優先核查連線池的習慣。

500 個程式碼庫中,有 438 個直接沿用 HikariCP 預設設定

預設就只有 10 個資料庫連線,僅此而已。

試想:當應用同時需要第 11 個資料庫請求時會發生什麼?

第 11 個請求排隊等待,第 12 個繼續等,連鎖積壓……

最終全部請求逾時,使用者頁面報錯,監控告警刷屏。

最致命的一點:本地開發環境完全正常

因為開發只有你一個人,並發請求從不超過 2 個,根本觸發不了連線池瓶頸。

於是大家帶著預設設定直接上線,等到生產環境,才付出慘痛代價。

我服務過一家新創公司,黑五大促早上 9 點準時開搶。

9 點 04 分,結帳介面全線逾時。

短短 4 分鐘,直接損失 8.9 萬美元營收。

我把連線池從 10 調整到 40,後續大促全程平穩運行。

但流失的營收,再也無法挽回。

致命錯誤模式二:懶載入引發的 N+1 查詢災難

這是最讓我抓狂的問題,也是第二高發錯誤。

500 個應用裡,413 個存在懶載入導致的 N+1 查詢問題

我定位這個問題的方法極其簡單:

打開後台管理頁面,統計資料庫查詢次數。

合理預期:僅 3~5 條 SQL 查詢。實際現況:動輒 847 條查詢。

出問題的程式碼看著毫無破綻:

kotlin 体驗AI程式碼助手 程式碼解讀複製程式碼@GetMapping("/orders")
public List<OrderDTO> getOrders() {
    List<Order> orders = orderRepository.findAll();
    return orders.stream()
    .map(this::toDTO)
    .collect(Collectors.toList());
}

看著完全沒問題,實則隱患巨大。

訂單實體透過@ManyToOne關聯使用者,預設懶載入。

在轉換 DTO 時呼叫order.getUser()每遍歷一條訂單,就額外觸發一次資料庫查詢

100 條訂單 = 1 次查所有訂單 + 100 次關聯查使用者 = 總計 101 次資料庫請求。

僅僅一個介面,就發起上百次 SQL 查詢,直接把資料庫壓垮。

我見過不少應用,僅 500 並發使用者就把介面拖垮,還沒到上萬並發就徹底崩盤。

本可避免的凌晨線上事故

2025 年 4 月,一家 A 輪金融科技公司凌晨 2 點 47 分緊急求助。

CEO 慌不擇路:「支付系統全線癱瘓,每分鐘虧損 4000 美元!」

我遠端登入伺服器查看日誌:

yaml 体验AI程式碼助手 程式碼解讀複製程式碼2025-04-15 02:31:47 WARN  HikariPool-1 - 無可用資料庫連線
2025-04-15 02:31:52 ERROR 交易超時,耗時30秒
2025-04-15 02:31:52 ERROR 支付流程處理失敗

典型的資料庫連線池耗盡

詭異的是:當天使用者量、存取模式和前一天完全一致,沒有流量暴漲。

唯一的變數是近期有程式碼上線變更。

我排查上線記錄,找到了根源:

有人給一個呼叫 8 個外部第三方介面的方法加了@Transactional交易註解。

每個外部介面呼叫耗時 2~4 秒,整個交易期間,會一直占用資料庫連線不釋放

單次交易占用連線長達 22 秒。

連線池總共只有 10 個連線,5 個使用者同時請求該介面,瞬間占滿所有連線。

應用其他所有業務都無法取得資料庫連線,直接形同癱瘓。

我刪掉多餘的@Transactional註解,故障立刻修復。

整場事故持續 23 分鐘,累計損失 9.2 萬美元。

而添加這個註解的開發,已有 4 年 Spring Boot 開發經驗。

他根本不懂這個註解的底層原理和副作用。

致命錯誤模式三:濫用 @Transactional 交易註解

這是最顛覆我認知的問題。

500 個程式碼庫中,381 個存在交易註解使用不當

有人把它加在控制器方法、呼叫外部介面的方法、甚至無需交易的唯讀查詢方法上。

絕大多數開發者把@Transactional當成「萬能魔法註解」,覺得加上就能保證業務正常。

事實恰恰相反:它會長期占用資料庫連線

我見過無數服務全線當機,只因給這類方法加了交易註解:

  • 呼叫多個外部第三方介面
  • 批次解析處理 CSV 檔案
  • 非同步發送郵件訊息
  • 等待第三方回調通知

全程霸占資料庫連線不放。

有家公司的後台排程工作被加上交易註解,單次任務就要執行 12 分鐘。

一個任務就鎖定一條資料庫連線長達 12 分鐘。

同時執行 10 個這類任務,整個連線池直接被占滿。

全應用所有業務都無法查詢資料庫,對外表現就是系統徹底卡死。

修復方式很簡單:給長耗時排程任務移除交易註解。

8 秒就能修好的問題,團隊足足排查了三週。

致命錯誤模式四:空捕獲區塊吞噬例外隱患

這個問題隱蔽性極強,卻殺傷力巨大。

500 個應用裡,267 個存在例外被靜默吞噬的問題

要麼是空的 catch 程式碼區塊,要麼只印出日誌、不向上拋出例外。

csharp 体验AI程式碼助手 程式碼解讀複製程式碼try {
    paymentService.charge(user, amount);
} catch (PaymentException e) {
    log.error("支付失敗", e);
    // 無任何後續業務處理
}

支付已經失敗,但程式繼續往下執行,訂單狀態標記為已完成。

結果就是:使用者被扣費、商品從不出貨,售後投訴堆積如山。

我在一家物流企業發現過這類問題,他們的訂單無故遺失現象持續了整整 6 個月。

看似隨機丟單,實則全是被捕獲後靜默吞掉的例外。

累計遺失 9000 筆訂單,產生 34 萬美元退款損失,還引發大量客訴。

寫這段程式碼的開發坦言:「我只是不想讓應用直接崩潰。」

結果卻造成了靜默資料錯亂,得不償失。

致命錯誤模式五:Jackson 預設序列化引發無限遞迴

JSON 序列化看似簡單,卻是高頻故障點。

500 個應用中,229 個因 Jackson 序列化無限遞迴導致服務崩潰

根源是實體雙向關聯:訂單包含訂單項目列表,訂單項目又反向關聯訂單。

less 体验AI程式碼助手 程式碼解讀複製程式碼@Entity
public class Order {
    @OneToMany(mappedBy = "order")
    private List<OrderItem> items;
}
@Entity  
public class OrderItem {
    @ManyToOne
    private Order order;
}

直接返回這類實體為 JSON 時:訂單 → 訂單項目 → 訂單 → 訂單項目…… 無限迴圈,最終觸發堆疊溢位,介面返回 500 錯誤,排查毫無頭緒。

有家電商平台這個線上隱患潛伏了 8 個月。

只要返回帶訂單項目的訂單資料,介面就直接崩掉。

團隊的折衷方案是:介面永遠不一次性返回訂單 + 訂單項目

前端只能拆成兩次介面請求,直接翻倍介面請求量、拉高雲端伺服器成本。

一切的根源,只是沒人會用@JsonIgnore忽略反向關聯欄位。

鮮有人提及的設定隱患

這一點出乎我的意料。

500 個程式碼庫中,193 個完全沒有生產環境專屬設定檔

本地開發用開發設定、測試環境用測試設定,生產環境全靠環境變數 + 硬編碼設定拼湊,混亂無序。

有家公司把資料庫密碼放在本地.env檔案,不納入程式碼版本管理。

新入職開發需要部署服務時,找不到設定密碼,只能憑猜測填寫。

結果直接把生產環境指向了測試資料庫。

生產真實使用者資料大量灌入測試庫,觸犯資料合規法規,引發資料外洩調查和監管追責。

所有麻煩,只因缺少一份規範的application-prod.yml生產設定檔。

致命錯誤模式六:盲目加快取反而拖慢效能

開發者總迷信快取:「直接上 Redis,效能肯定變快。」

但現實是:500 個應用裡,174 個接入 Redis 快取後,效能反而更差

誘因無非這幾種:快取雪崩、快取鍵衝突、序列化額外開銷。

有家公司無腦給所有資料庫查詢都加了快取。

看似最優解,實則適得其反。

他們的快取命中率僅有 11%。

快取未命中的完整流程:

  1. 發起 Redis 網路請求查詢快取
  2. 快取未命中,白白浪費一次網路開銷
  3. 再查詢真實資料庫
  4. 把結果寫入 Redis,又多一次網路請求

89% 的請求都平白多了兩次網路互動。

本想提速加快取,結果介面整體效能下降 40%

我直接移除無效快取後,效能立刻恢復正常。

團隊還很不解:「業內都說加快取是最佳實踐啊。」

真正的最佳實踐,是理解原理再落地,而非盲目跟風。

致命錯誤模式七:開發環境與生產環境不一致,本地能跑線上必崩

最後一個問題,也是最耗費排查時間的。

500 個應用中,156 個存在「本地正常、線上崩潰」的環境差異問題。

開發環境:Mac 電腦、16G 記憶體、Java17、本地 PostgreSQL;

生產環境:Linux 伺服器、4G 記憶體、Java11、雲端資料庫 RDS。

軟硬體、版本、系統完全不一致。

我見過無數專案,所有開發本地運行完美,一上線立刻崩潰

常見誘因:

  • 開發用 Java17 新語法,生產只支援 Java11;
  • 本地 16G 記憶體隨意用,生產 4G 記憶體 JVM 堆積參數設定錯亂;
  • 本地 PostgreSQL15,生產 12 版本,SQL 語法不相容

有家公司為排查線上崩潰耗費三週,根源竟是大小寫敏感

Mac 系統檔案路徑大小寫不敏感,Linux 系統嚴格區分大小寫。

程式碼裡寫表名users,生產資料庫實際表名Users

本地運行毫無問題,線上直接報錯。

三週排查工作量,只因上線前沒在 Linux 環境做過測試。

促使我寫下這篇文章的契機

上個月,一家使用者 50 萬、即將上市的後期新創公司 CTO 找到我:

「上市前需要做一次全面程式碼稽核,找出所有可能引發重大事故的隱患。」

我花兩週通讀他們的程式碼庫,7 個致命錯誤,他們中了 6 個:連線池設定過小、遍地 N+1 查詢、外部介面方法濫用交易、例外靜默吞噬、無專屬生產設定、盲目加快取拖慢效能。

無一倖免。

我出具了一份 40 頁的稽核報告,明確告知:「上市前必須修復這些問題,否則極易引發公開重大線上故障。」

公司隨即聘請三名外包工程師,耗時六週完成全量整改。

最終上市流程平穩落地,未出現任何線上事故。

事後 CTO 特意私訊我:「你保住了我們公司的上市進程,萬分感謝。」

這些問題從不是個別案例,而是業界普遍通病。

481 名工程師上線前的必查項

看過 500 個程式碼庫的無數線上災難後,我整理出了一套Spring Boot 生產上線核查清單

沒有空洞理論,全是生產環境實打實會崩潰的隱患點。

目前已有 481 名工程師,每次上線前都會對照這份清單自查。

我放行任何 Spring Boot 應用上線前,必核查以下要點:

  1. 資料庫連線池根據真實業務並發配置,絕不沿用預設 10 個連線;
  2. 實體關聯合理使用急載入,或透過 JOIN FETCH 查詢規避 N+1 問題;
  3. 交易註解僅加在真正需要交易保障的方法上,絕不用於呼叫外部介面的長耗時方法;
  4. 例外處理禁止靜默吞噬,要麼向上拋出,要麼明確業務兜底,絕不掩蓋故障;
  5. 透過@JsonIgnore或 DTO 層,解決實體雙向關聯的 JSON 序列化無限遞迴;
  6. 獨立維護application-prod.yml生產設定,杜絕設定散落、硬編碼和零散環境變數;
  7. 僅對查詢緩慢、存取高頻的介面添加快取,杜絕無腦全量快取;
  8. 開發環境與生產環境保持一致:作業系統、Java 版本、中介軟體版本、硬體資源規格統一。

這不是可有可無的開發規範,而是生產環境保命核查項

也是普通上線部署,和能扛住大促流量、平穩運行的分水嶺。

一個扎心的業界真相

研讀完 500 個程式碼庫後,我一直在思考一個問題:

為什麼 90% 的 Spring Boot 開發者,都會重複踩同樣的坑?

不是工程師能力不行,而是Spring Boot 封裝得太過於底層透明

本地開發開箱即用、預設設定無感生效,導致開發者根本不去了解底層運行原理。

沒人主動配置連線池,依賴預設 10 個連線,本地開發夠用就不聞不問;沒人深究懶載入機制,測試少量資料看不出 N+1 隱患;隨手到處加@Transactional,本地簡單場景不出錯就默認沒問題。

直到部署到生產環境,所有想當然的預設設定,都會狠狠反噬。

而生產環境,從不會為開發者的想當然兜底。

原文地址:I Read 500 Spring Boot Production Codebases. 90% Make the Same 7 Fatal Mistakes.


原文出處:https://juejin.cn/post/7634380390371819526


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝11   💬8  
242
🥈
我愛JS
💬1  
5
🥉
Gigi
2
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登