Internal vs Public
GitLab Self-Managed公司網路怎麼建起來的DNS內網每台機器實際拿哪個 IP一步步summaryGitLab Self-Managed 是公司自己安裝、自己維運的一套 GitLab,可以部署在公司內部網路、私有雲,甚至做成完全離線的環境。和 GitLab.com(SaaS 版)最大的差別在於:這套 instance 完全由公司掌控,主機、網路、權限、備份、存取規則全都是自己管。
核心元件
元件
負責的事
Web / API 服務
你平常打開網頁、呼叫 API 的入口
PostgreSQL
儲存帳號、專案、issue、pipeline 等資料
Redis
快取與背景工作協調
Gitaly / Git 儲存
真正存放 repository 的地方
Sidekiq
背景工作,例如寄通知、執行非同步任務
Registry / Runner
延伸元件,視公司是否啟用
把整套 GitLab 比做一間公司:
前台(使用者介面)→ Web / API 服務
倉庫(放程式碼)→ Gitaly / rep ...
跨店跨會員
🧱 多租戶系統的核心:Tenant Boundary(租戶邊界)不論是「店」或「會員」,都必須有一個一致的「租戶識別」,且整個系統要從上到下都尊重這邊界
1使用者 → Token → Context → DbContext / Repository → SQL
只要其中一層沒守好,就可能造成資料外洩想像你有「很多間分店」都使用同一套後台系統
A 店的會員不能看到 B 店的訂單
B 店的商品不能被 A 店誤改
C 店的營收絕不能被其他店家查到
👉 ShopId 就像房間鑰匙,沒有鑰匙就不能進別人的房間
🧱 後端層面防範策略(最重要的三層)這三層是「後端防呆」的核心,只要其中一層沒做好,就可能讓使用者查到不是自己的資料
1️⃣ Token / 身分驗證層:登入時就綁 ShopId(第一道鎖)當會員登入後,你發 JWT 時,「必須」把該會員的 ShopId 寫進 Token
12345{ "MemberId": "M123", "ShopId": "A001", ...
EF Core 效能陷阱:IQueryable vs IEnumerable 的致命差異
在 Entity Framework Core 開發中,看似簡單的 LINQ 查詢可能隱藏著巨大的效能陷阱。今天我們來深入分析兩個經典的效能問題,以及它們背後的根本原因。
🚨 案例一:SearchProducts 的效能災難💥 問題程式碼1234567public IEnumerable<Product> SearchProducts(string[] keywords){ return _context.Products.Where(p => keywords.All(k => p.Name.Contains(k)));}// 呼叫方式var results = SearchProducts(new[] { "red", "shoes" });
乍看之下這段程式碼很合理:搜尋包含所有關鍵字的產品。但實際上,這是一個效能炸彈!
� 問題分析核心問題:keywords.All(...) 無法轉換成 SQL
讓我們拆解這個表達式:
1keywords.All(k =&g ...
Host Configuration
ConsoleApp 的啟動流程Host - 是整個 App 的根hostingContext - 目前應用程式主機 (Host) 的執行環境上下文, 帶著關於目前執行環境的所有資訊
hostContext.Configuration - 拿出目前 config
hostingContext.HostingEnvironment - 拿出目前環境
ConfigureAppConfiguration - 讀設定檔
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
ConfigureServices - 註冊服務
hostContext, IServiceCollection
services.AddDbContext<WebStoreDbContext>(options
options = DbContextOptionsBuilder, EF Core 啟動時用來決定連線方式、行為設定的物件
configuration.Ge ...
ReadUncommitted - 應用
它允許「髒讀 (dirty read)」,即能讀到尚未 commit 的資料,讀取過程也不會對任何資料加 lock(不會阻塞其他 transaction)。在高併發的電商場景中,這確實可以用來提升查詢效能,但只能用在「對準確性要求不高」的情境
查詢只讀、不影響交易邏輯
查詢結果可容忍暫時誤差
若使用 ORM可在 connection 層設定 transaction isolation
WITH (NOLOCK)
1️⃣ 商品瀏覽頁(商品列表、搜尋結果)使用者瀏覽商品時,看到的價格或庫存即使延遲幾秒、甚至略有偏差,也不影響體驗。
範例:
12SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;SELECT id, name, price, stock FROM products WHERE category_id = 10 LIMIT 50;
使用者只是在「逛」,非購買瞬間,資料一致性不必嚴格 → 減少 shared lock,提升商品列表查詢速
2️⃣ 熱門商品排行榜(依銷量或瀏覽量排序)排行榜本身是近似實時資料,但不需要精確到 ...
扣庫存
牽涉到三個核心風險:
超賣(Overselling)
鎖競爭(Lock Contention)
效能瓶頸(Performance Bottleneck)
庫存扣減要即時又要安全,但越嚴格的隔離會越慢,要怎麼平衡?
在扣庫存的交易中,最怕的不是「讀到舊資料」,而是「兩個人同時看到同一個庫存,然後都扣成功」。這其實不是 read 的問題,而是 concurrent update 的問題
關鍵是交易期間必須鎖定庫存那一筆資料,確保扣減是序列化的(serial)
通常在電商系統,我們會選擇交易層級用 READ COMMITTED 或 REPEATABLE READ,配合「明確加鎖查詢」
模式
隔離級別
特性
是否會鎖住那筆庫存
備註
(1) READ COMMITTED + UPDLOCK
預設
防髒讀
✅ 是
最常用、安全
(2) SERIALIZABLE
最嚴格
防髒讀 + 幻讀
✅ 是 (範圍鎖)
太重,僅用於財務對帳
(3) SNAPSHOT / RCSI
基於版本
不會 block,但需要行版本控制
❌ 否
不適合庫存扣減(可能超賣)
範例一: ...
結帳系統
加入購物車限流器單位時間內,限制可以加入購物車的數量
兩層票匣(Token Bucket)+ 秒級時間窗可以把每個方塊視為「某個時間窗內允許的票(tokens)」:
By SkuId 的票匣:每個 SKU 每秒最多 100 次加入(圖的 100 只是示意)。同一秒內,第 101 個想加的,因為他拿不到票,就被「請稍後再試」。
By ShopId 的票匣:同一商店合計每秒最多 1000 次加入。即使很多 SKU 分散加,也不能超過商店總上限,避免把該商店的後端資源用光。
時間軸(1s、2s、3s、…、ns):代表「固定秒級視窗」或「每秒補票」。每過一秒,票匣重新裝滿(或按速率補票),下一秒再算。
🌳 「要通過,得同時有 Sku 的票 + Shop 的票」。任一票用完就被擋
〈每家店獨立設定〉—配額可調、資源隔離Shop A、Shop B 各自一套配額
A 店的白/藍/黃(SKU)可以是每秒 100。
B 店的大小中 SKU 也各有每秒 100。
營運面可調。大店、熱門品給高一點;新店、長尾品給保守值。
把限流放在「加入購物車」前段,Fail-fast ...
Rate Limiter
限流設定太嚴活動秒殺時,限流設定太嚴導致一堆使用者被攔在外,但系統明明負載還有餘裕
限流規則以「API層」為單位,還是以「使用者動作意圖」為單位深層解析
多數限流設計只看 API 請求頻率(例如每秒可呼叫幾次),但購物行為其實是「任務導向」的。同樣的 API,在不同情境下(加購、改數量、結帳前檢查)代表不同優先度。若只看技術層頻率,會錯殺真實用戶。這是「技術限流」與「業務語意限流」的衝突。
具體例子
A 用戶在秒殺活動中加購一件商品,B 用戶在修改購物車,兩者都觸發相同限流規則,但其實 A 的行為應該更被優先處理。
限流的錯誤回饋,是「資訊透明」還是「行為引導」目前許多限流回饋只是彈窗:「因搶購熱烈,請稍後再試」。但這會導致用戶亂重試、加重流量。若能在訊息中加入「重試建議」或「倒數回應」,就能把限流轉化為節流(引導行為)。這是「被動防守」與「主動引導」的矛盾。
具體例子
使用者被告知「請稍後重試」不如告訴他「系統將於3秒後自動重試」或「已為你保留名額30秒」。
限流策略是否考慮跨服務依賴(例如庫存、票匣、購物車)之間的同步深層解析
目前流程是線性檢查(庫存 → 全店票匣 → SKU票 ...
Circuit Breaker
在軟體系統中,「斷路器」是一種保護機制,用來避免系統在發生連續錯誤時被拖垮。它的核心目標是當外部服務或資源連續失敗時,暫時中斷請求,讓系統有時間恢復
家裡的電路,如果有電器短路,會造成電流暴增。斷路器就會「跳電」,暫時切斷電力,避免整個家被燒掉。在軟體中也是一樣,如果外部 API 一直回錯誤(例如 Timeout 或 500 Error),我們不應該一再重試,否則會耗盡 Thread、CPU、甚至把整個應用拖慢。所以斷路器會「跳閘」,暫停對那個外部服務的呼叫一段時間
狀態
意義
行為
Closed(關閉)
一切正常
請求會照常發送。如果有失敗,會記錄下來。
Open(開啟)
外部服務被認定「壞掉」
直接阻擋呼叫,立即回錯誤,不再嘗試。
Half-Open(半開啟)
試探階段
過一段時間後,允許少量請求試試看服務是否恢復。成功就恢復 Closed。
Polly12345678910111213141516171819202122232425262728293031323334using Polly;using Polly.CircuitBreaker;using ...
Circuit Breaker
熔斷並不是「修補不穩定」的設計,而是預防連鎖崩潰的一種保險。就像電路裡的保險絲,當某個服務過載或回應過慢時,熔斷器會暫時中斷請求,防止所有流量一起壓垮整個系統。這其實是一種「系統彈性」設計哲學
熔斷後要多久恢復?如果恢復太慢會影響體驗,太快又可能反覆熔斷?這是一種「穩定 vs 敏捷」的矛盾。恢復時間太短,會導致「抖動效應」——服務剛好起來又被壓垮;太長則使用者體驗差。理想的策略是設一個半開狀態(Half-Open):部分流量試探性放行
假設支付服務在 10 秒內失敗了 5 次後被熔斷。30 秒後熔斷器自動進入「半開」,只允許 10% 的請求通過測試;若成功率高於 80%,則恢復正常運行
熔斷與重試(Retry)會不會互相衝突「重試」希望挽救暫時錯誤,「熔斷」希望阻止持續錯誤,若兩者交錯使用不當,會導致雪崩效應(大量重試瞬間壓垮服務)
在支付請求中若每次失敗都重試3次,而熔斷器設定在10次錯誤觸發,實際上只要4個使用者同時錯誤,就可能觸發熔斷。










