量保障不靠運(yùn)氣:Impeccable項(xiàng)目構(gòu)建可控交付鏈路)
Impeccable 是我在團(tuán)隊(duì)內(nèi)部發(fā)起的一個(gè)質(zhì)量改進(jìn)項(xiàng)目的代號(hào)中文直譯過來就是“無可挑剔”。當(dāng)初起這個(gè)名字的時(shí)候我就知道它早晚會(huì)被現(xiàn)實(shí)打臉——因?yàn)闆]有任何系統(tǒng)可以做到真正意義上的零缺陷但這并不妨礙我們把它當(dāng)成一個(gè)方向去逼近。這個(gè)項(xiàng)目要解決的問題也很樸素怎么讓每一次代碼提交、每一次發(fā)布都不再靠個(gè)別同事的責(zé)任心和運(yùn)氣而是靠一套機(jī)制穩(wěn)穩(wěn)托住。如果你正被線上事故反復(fù)折騰或者被“測(cè)試環(huán)境好好的一上生產(chǎn)就翻車”的魔咒困擾又或者只是想讓自己的交付結(jié)果更靠譜一點(diǎn)那這篇文章應(yīng)該能給你一些參考。我會(huì)把 Impeccable 從立項(xiàng)到落地、從原則到細(xì)節(jié)的過程都拆開講包括我們踩過的坑、改過的配置、以及那些看起來很小但實(shí)際很致命的檢查項(xiàng)。1. 為什么要把“無可挑剔”當(dāng)成一個(gè)項(xiàng)目來做1.1 質(zhì)量問題不該靠某幾個(gè)人的責(zé)任心撐住我見過太多團(tuán)隊(duì)把質(zhì)量掛在嘴上但實(shí)際行動(dòng)全靠“這次發(fā)布大家小心一點(diǎn)”。小心是什么是沒有任何標(biāo)準(zhǔn)的口頭約定。代碼寫得快的人會(huì)少做測(cè)試資歷深的工程師會(huì)偷偷繞過規(guī)范新來的同學(xué)根本不知道哪些環(huán)節(jié)是必須的——最后的結(jié)果就是線上三天兩頭出問題運(yùn)維和測(cè)試背鍋研發(fā)覺得自己很冤。Impeccable 最初的導(dǎo)火索是一次很小的數(shù)據(jù)修復(fù)腳本。腳本語法沒問題邏輯也正確但因?yàn)檠h(huán)里少了一個(gè)限速參數(shù)上線后直接把數(shù)據(jù)庫連接池打滿了。回滾花了十五分鐘但數(shù)據(jù)庫恢復(fù)用了快兩個(gè)小時(shí)。復(fù)盤的時(shí)候大家發(fā)現(xiàn)我們的發(fā)布檢查單里根本沒有“性能影響”這一項(xiàng)也沒有任何機(jī)制阻止這種腳本直接部署到生產(chǎn)環(huán)境。那次之后我就想明白一件事質(zhì)量問題從來不是測(cè)試一個(gè)環(huán)節(jié)的問題而是從單人提交到最終上線這一整條鏈路的問題。如果不把這條鏈路里的關(guān)鍵節(jié)點(diǎn)都變成可檢查、可執(zhí)行、可追責(zé)的規(guī)則那質(zhì)量就只是一個(gè)漂亮的形容詞不是工程結(jié)果。1.2 項(xiàng)目整體拆成四條主線Impeccable 的計(jì)劃沒有上來就買工具、上平臺(tái)而是先把問題分層。我們把自己能控制的環(huán)節(jié)拆成四條主線規(guī)范線、工具線、流程線、文化線。規(guī)范線負(fù)責(zé)統(tǒng)一標(biāo)準(zhǔn)比如編碼風(fēng)格、提交信息格式、接口設(shè)計(jì)約定、數(shù)據(jù)庫變更規(guī)范。工具線負(fù)責(zé)用程序去強(qiáng)制這些標(biāo)準(zhǔn)比如 lint、單元測(cè)試、依賴掃描、CI 門禁。流程線負(fù)責(zé)人在協(xié)作過程中的動(dòng)作比如合并請(qǐng)求模板、發(fā)布檢查單、灰度發(fā)布規(guī)則。文化線則負(fù)責(zé)讓這些規(guī)則被真正接受而不是被當(dāng)成形式主義。很多團(tuán)隊(duì)一上來就搞平臺(tái)搞流水線但標(biāo)準(zhǔn)都沒定清楚工具只是把一個(gè)混亂的過程自動(dòng)化了結(jié)果只是更快地制造混亂。Impeccable 的先后順序是先定標(biāo)準(zhǔn)再做自動(dòng)化再設(shè)流程最后用文化去消化大家的情緒。這個(gè)順序很重要如果倒過來團(tuán)隊(duì)只會(huì)感到被工具和流程綁架。2. 核心細(xì)節(jié)解析與實(shí)操要點(diǎn)2.1 把“開發(fā)完成”重新定義一遍Impeccable 做的第一件具體事情是重新定義什么叫“完成”。以前團(tuán)隊(duì)里說“這個(gè)功能寫完了”意思往往是“代碼已經(jīng)提交了”但它在本地能不能跑、有沒有測(cè)試、有沒有文檔、會(huì)不會(huì)拖垮性能一概沒人回答。于是我們落地了一份 Definition of Done也就是完成標(biāo)準(zhǔn)清單每個(gè)人必須逐項(xiàng)確認(rèn)后才能把合并請(qǐng)求標(biāo)記為可評(píng)審。清單不算復(fù)雜核心項(xiàng)包括功能實(shí)現(xiàn)且自測(cè)通過、編寫或更新單元測(cè)試、更新接口文檔或使用說明、評(píng)估性能與安全性、確認(rèn)回滾方案、核對(duì)數(shù)據(jù)庫變更兼容舊數(shù)據(jù)。對(duì)于緊急修復(fù)可以走簡(jiǎn)化版但至少也要包含自測(cè)、回滾和監(jiān)控告警這三項(xiàng)。落地的時(shí)候我們給每條標(biāo)準(zhǔn)都配上例子避免出現(xiàn)“我說的自測(cè)和你理解的自測(cè)不是一回事”的扯皮情況。在執(zhí)行方式上我們沒有采用不勾完就鎖 merge 的硬方式因?yàn)槟菚?huì)讓團(tuán)隊(duì)為了通過而隨便勾選。我們選擇的是讓評(píng)審人承擔(dān)責(zé)任如果代碼合并后出問題評(píng)審人要和作者一起復(fù)盤。這個(gè)設(shè)計(jì)是想告訴大家完成標(biāo)準(zhǔn)不是給系統(tǒng)看的是給團(tuán)隊(duì)里的真人看的。實(shí)際跑了兩周之后明顯感覺到大家提交代碼前會(huì)多想一步因?yàn)檎l都不想在被問到“你在自測(cè)時(shí)到底測(cè)了什么”的時(shí)候無話可說。2.2 自動(dòng)化檢查怎么設(shè)置才不惹人煩完成標(biāo)準(zhǔn)定完之后第二步是把它落到自動(dòng)化檢查里。這部分的坑非常多。一開始我們天真地想把所有規(guī)范都塞進(jìn) pre-commit結(jié)果開發(fā)者每次提交都要等三分鐘各種風(fēng)格檢查還經(jīng)常報(bào)一些“對(duì)齊空格”之類的低價(jià)值錯(cuò)誤大家的逆反情緒一下就上來了。后來我們調(diào)整了策略本地只做最輕量、最相關(guān)的檢查比如格式化和基礎(chǔ)的靜態(tài)檢查把單元測(cè)試、構(gòu)建、依賴掃描、甚至性能回歸放到 CI 里執(zhí)行。本地檢查追求的是快不能超過十秒CI 檢查追求的是全面可以多跑一點(diǎn)。這樣才能讓開發(fā)者在寫代碼階段就得到反饋又不會(huì)被無關(guān)緊要的問題打斷心流。一個(gè)很關(guān)鍵的小技巧是自動(dòng)化規(guī)則要按“信號(hào)強(qiáng)度”分級(jí)。信號(hào)最強(qiáng)的檢查是“能不能編譯、能不能跑、測(cè)試通不通”這些必須阻塞合并。中等信號(hào)的是“代碼風(fēng)格、命名規(guī)范、重復(fù)代碼”可以預(yù)警但不強(qiáng)制。弱信號(hào)的是“代碼覆蓋率低于閾值、某個(gè)文件行數(shù)太長(zhǎng)”這些只做統(tǒng)計(jì)和趨勢(shì)跟蹤不阻塞任何流程。不要把強(qiáng)弱信號(hào)混在同一個(gè)門禁里否則團(tuán)隊(duì)會(huì)被弱信號(hào)煩死強(qiáng)信號(hào)反而被忽略。2.3 發(fā)布檢查單與灰度發(fā)布怎么設(shè)計(jì)才不擺設(shè)代碼合并只是中點(diǎn)上線才是真正的終點(diǎn)。Impeccable 的第二大塊工作是把發(fā)布過程變成可勾選、可驗(yàn)證的清單式操作。我們的發(fā)布檢查單不再是一張沒人看的 Excel而是根據(jù)變更類型自動(dòng)生成的一組問題提交發(fā)布單的人必須逐一確認(rèn)。普通功能變更的檢查單里包含代碼是否已合并到主分支、測(cè)試是否通過、是否需要執(zhí)行數(shù)據(jù)遷移、配置項(xiàng)是否經(jīng)過評(píng)審、發(fā)布后需要觀察哪些監(jiān)控指標(biāo)、回滾步驟是什么。涉及數(shù)據(jù)變更的發(fā)布還會(huì)額外要求提供導(dǎo)入導(dǎo)出驗(yàn)證腳本和備份確認(rèn)。每一條都要求填寫真實(shí)信息不允許只寫“是”或“確認(rèn)”必須寫清楚“在哪個(gè)環(huán)境驗(yàn)證過”“影響范圍是什么”?;叶劝l(fā)布方面我們制定的規(guī)則是分批放量不能用天數(shù)的線性時(shí)間去替代業(yè)務(wù)指標(biāo)的驗(yàn)證。我們通常的配置是第一批放 1 個(gè)實(shí)例或 5% 流量觀察十五分鐘錯(cuò)誤率第二批放 30%觀察十到二十分鐘第三批放 100%。每一批放量前都要求錯(cuò)誤率低于網(wǎng)關(guān)基準(zhǔn)值的 1.5 倍并且業(yè)務(wù)核心路徑響應(yīng)時(shí)間沒有明顯上升。如果超過閾值系統(tǒng)會(huì)自動(dòng)觸發(fā)回滾不需要值班同學(xué)糾結(jié)“再等一下會(huì)不會(huì)好”。3. 實(shí)操過程與核心環(huán)節(jié)實(shí)現(xiàn)3.1 從一次線上故障到項(xiàng)目立項(xiàng)的全過程還是繼續(xù)說那次數(shù)據(jù)修復(fù)腳本的事故。當(dāng)時(shí)的值班記錄現(xiàn)在回頭來看特別有意思問題一共分四層。第一層是業(yè)務(wù)異常用戶看到下單失敗第二層是系統(tǒng)告警數(shù)據(jù)庫連接池滿第三層是根因定位發(fā)現(xiàn)是腳本循環(huán)執(zhí)行了全量更新第四層是流程缺失發(fā)布平臺(tái)沒有任何提示說“這個(gè)操作會(huì)大規(guī)模更新線上數(shù)據(jù)”。復(fù)盤會(huì)開到一半就有人在問為什么這種代碼能上線因?yàn)闆]有性能評(píng)估環(huán)節(jié)。為什么沒有性能評(píng)估環(huán)節(jié)因?yàn)闆]有標(biāo)準(zhǔn)要求。為什么沒有標(biāo)準(zhǔn)因?yàn)榇蠹叶寄J(rèn)“腳本很小不會(huì)出事”。聽完這個(gè)因果鏈我覺得問題并不在于某個(gè)人而在于團(tuán)隊(duì)沒有一個(gè)項(xiàng)目級(jí)的兜底機(jī)制。于是當(dāng)周就在內(nèi)部立項(xiàng)代號(hào) Impeccable目標(biāo)從“降事故”擴(kuò)成了“讓交付鏈條的每個(gè)環(huán)節(jié)都有審查節(jié)點(diǎn)”。團(tuán)隊(duì)一開始只有三個(gè)人在做這件事一個(gè)后端、一個(gè)測(cè)試、一個(gè) DevOps我作為負(fù)責(zé)人。我們給自己定了兩條原則第一不搞重流程能自動(dòng)化堅(jiān)決不手填第二不追求一步到位先覆蓋最痛的發(fā)布事故場(chǎng)景再逐步往代碼提交階段延伸。3.2 落地第一套 Definition of Done 模板第一周的產(chǎn)出是一份名為 definition-of-done.md 的文件放在每個(gè)代碼倉庫的根目錄下。你可能覺得這就是個(gè)文檔沒什么技術(shù)含量但實(shí)際操作的時(shí)候細(xì)節(jié)點(diǎn)比想象中多。比如我們規(guī)定“編寫或更新單元測(cè)試”這條不只要求有測(cè)試文件還要求在 CI 上能看到新增測(cè)試用例的執(zhí)行記錄再比如“確認(rèn)回滾方案”這條要求寫出具體是回滾代碼、回滾數(shù)據(jù)還是回滾配置不能只寫“按標(biāo)準(zhǔn)流程”。這份模板后來還按變更類型做了分區(qū)。普通功能有基礎(chǔ)區(qū)涉及數(shù)據(jù)遷移或第三方接口的變更會(huì)增加對(duì)應(yīng)區(qū)塊。為了不讓模板變得冗長(zhǎng)我們規(guī)定每條標(biāo)準(zhǔn)最多十個(gè)字到二十個(gè)字詳細(xì)說明統(tǒng)一放在 wiki 里。合并請(qǐng)求的描述模板也會(huì)自動(dòng)載入這些區(qū)塊開發(fā)者直接在描述里勾選即可不需要額外打開文檔。我們給合并請(qǐng)求模板加了一行注釋“如果無法勾選某一條不要硬勾把原因?qū)懙皆擁?xiàng)后面?!边@個(gè)設(shè)計(jì)很重要因?yàn)橛行└唢L(fēng)險(xiǎn)變更確實(shí)做不到完整自測(cè)比如無法在本地模擬真實(shí)第三方回調(diào)。允許寫例外反而讓真正卡住的阻塞項(xiàng)浮出水面比一團(tuán)和氣地全勾上更有價(jià)值。3.3 接入第一層自動(dòng)化門禁的完整配置第二周我們把完成標(biāo)準(zhǔn)中能自動(dòng)化的部分接進(jìn)了 CI。這里用 GitLab CI 舉例GitHub Actions 也類似。我們的核心思路是把檢查拆成多個(gè)并行任務(wù)并且讓部署任務(wù)只依賴必須通過的任務(wù)避免某一項(xiàng)檢查掛掉之后整條流水線被連帶卡住。stages: - verify - build - deploy verify:lint: stage: verify image: registry.example.com/lint-image:latest script: - npm run lint rules: - if: $CI_PIPELINE_SOURCE ! merge_request_event when: never - when: always verify:unit-test: stage: verify image: registry.example.com/node:20 script: - npm ci - npm run test:unit coverage: /All files[^|]*\|[^|]*\s([\d.])%/ artifacts: when: always reports: junit: junit.xml verify:security: stage: verify image: registry.example.com/security-scanner:latest script: - npm audit --audit-levelhigh allow_failure: false build:image: stage: build script: - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . only: - main needs: - verify:lint - verify:unit-test - verify:security deploy:production: stage: deploy script: - ./deploy.sh production $CI_COMMIT_SHA environment: production only: - main when: manual needs: - build:image這套配置里有一個(gè)容易被忽略的地方單元測(cè)試的coverage正則捕獲覆蓋率數(shù)值是為了讓 MR 頁面可以直觀看到增量覆蓋率和總覆蓋率趨勢(shì)安全掃描audit --audit-levelhigh是阻塞級(jí)別可以直接攔截高風(fēng)險(xiǎn)依賴依賴部署任務(wù)是手動(dòng)觸發(fā)不給自動(dòng)流水線直接上生產(chǎn)的權(quán)限。這樣既做到了自動(dòng)化又把最需要人判斷的一步留給操作者。接入后第一個(gè)月的效果是MR 被阻塞的比率大約在 18%其中 70% 是單元測(cè)試掛掉20% 是安全掃描發(fā)現(xiàn)問題10% 是 lint。而我們最擔(dān)心的“developers 什么都不看就點(diǎn)合并”并沒有出現(xiàn)因?yàn)椴噬艿涝?MR 頁面上太顯眼了誰都不想和紅叉作伴。3.4 灰度發(fā)布規(guī)則的真實(shí)配置與參數(shù)計(jì)算第三周我們把灰度發(fā)布規(guī)則從口頭約定變成了平臺(tái)配置。因?yàn)橛行﹫F(tuán)隊(duì)沒有現(xiàn)成的灰度發(fā)布系統(tǒng)這里給出一個(gè)可以在部署腳本里實(shí)現(xiàn)的判斷邏輯示意用來解釋我們當(dāng)時(shí)參數(shù)是怎么算出來的。#!/usr/bin/env bash # 灰度發(fā)布判斷腳本根據(jù)當(dāng)前實(shí)例數(shù)和錯(cuò)誤率自動(dòng)決定放量比例 TOTAL_INSTANCES20 BATCH_11 # 第一批 1 個(gè)實(shí)例約 5% 流量 BATCH_26 # 第二批再加 5 個(gè)實(shí)例累計(jì) 30% 流量 BATCH_320 # 第三批全量 if [ $CURRENT_INSTANCES -lt $BATCH_1 ]; then echo 啟動(dòng)第一批灰度1 個(gè)實(shí)例 elif [ $CURRENT_INSTANCES -lt $BATCH_2 ]; then echo 待錯(cuò)誤率低于閾值后放量到 6 個(gè)實(shí)例 elif [ $CURRENT_INSTANCES -lt $BATCH_3 ]; then echo 待錯(cuò)誤率低于閾值后放量到 20 個(gè)實(shí)例 fi參數(shù)計(jì)算的核心不是“拍腦袋定百分比”而是先定義可回滾范圍。比如服務(wù)總共 20 個(gè)實(shí)例第一批只放 1 個(gè)即使這批全掛也只影響 5% 用戶第二批放到 6 個(gè)累計(jì)影響 30%第三批全量。放量間隔不是固定十五分鐘而是必須滿足“連續(xù)觀察窗口內(nèi)錯(cuò)誤率低于基準(zhǔn)的 1.5 倍且 P95 延遲低于基準(zhǔn)的 1.2 倍”。如果系統(tǒng)自動(dòng)檢測(cè)到錯(cuò)誤率飆高腳本會(huì)執(zhí)行回滾把實(shí)例數(shù)恢復(fù)到上一批并向值班群發(fā)送告警。這套手動(dòng)腳本看起來簡(jiǎn)單實(shí)際執(zhí)行時(shí)最大的價(jià)值是把決策規(guī)則寫在代碼里。以前灰度放量完全靠發(fā)版同學(xué)的感覺感覺快就快點(diǎn)感覺不對(duì)就?!,F(xiàn)在腳本會(huì)明確告訴你當(dāng)前應(yīng)該處于哪個(gè)批次閾值是多少回滾動(dòng)作是什么人和機(jī)器都清楚。4. 常見問題與排查技巧實(shí)錄4.1 自動(dòng)檢查被繞過怎么辦我們剛接入自動(dòng)門禁的時(shí)候遇到一個(gè)非常真實(shí)的場(chǎng)景開發(fā)者在本地把 pre-commit 跳過或者 CI 里故意把a(bǔ)llow_failure: true。原因無外乎覺得自己改動(dòng)很小沒必要跑完整檢查。第一次發(fā)現(xiàn)這種事我們沒有立刻處罰人而是先看是不是流程設(shè)計(jì)有問題。排查后發(fā)現(xiàn)很多人并不是想偷懶而是本地檢查確實(shí)太慢了而且報(bào)錯(cuò)信息不友好。于是我們把本地檢查精簡(jiǎn)到只剩格式化、類型檢查和最基礎(chǔ)的 smoke test同時(shí)把 CI 門禁設(shè)置為服務(wù)端強(qiáng)制。關(guān)鍵點(diǎn)是任何自動(dòng)化檢查都不能只依賴開發(fā)者本地執(zhí)行本地是可選的、快速的服務(wù)端才是必須的。只要在 MR 目標(biāo)分支上強(qiáng)制設(shè)置管道必須通過再有權(quán)限規(guī)則禁止跳過就能從機(jī)制上堵住主動(dòng)繞過的口子。如果發(fā)現(xiàn)有人通過修改 pipeline 配置來繞過那就不是流程問題而是文化問題了。我們的處理原則是公開復(fù)盤代碼不點(diǎn)名批評(píng)但要把繞過的動(dòng)作當(dāng)成事故對(duì)待。做過一兩次之后大家就明白“不要和機(jī)制作對(duì)”這個(gè)道理了。4.2 完成標(biāo)準(zhǔn)被當(dāng)成形式主義怎么辦這是最隱蔽的問題。上線兩周后我開始在 MR 里看到“全部完成標(biāo)準(zhǔn)已確認(rèn)”的勾選但代碼評(píng)審時(shí)明顯感覺作者根本沒有理解需求。為什么因?yàn)橥瓿蓸?biāo)準(zhǔn)一旦變成“只有勾選才能合并”它就會(huì)變成一種門檻而門檻會(huì)激發(fā)人的簡(jiǎn)化心理——只要不被系統(tǒng)攔下來就好。我們的調(diào)整是讓評(píng)審人不再只看勾選狀態(tài)而要追問證據(jù)。比如“已自測(cè)通過”后面必須附上自測(cè)結(jié)果截圖或日志片段“已評(píng)估性能影響”后面必須寫清楚用的是 QPS 還是響應(yīng)時(shí)間指標(biāo)。我以身作則在評(píng)審高風(fēng)險(xiǎn)的 MR 時(shí)會(huì)先要求作者在評(píng)論里回答三個(gè)問題這次變更最可能導(dǎo)致什么異常如果這個(gè)異常發(fā)生了你會(huì)怎么發(fā)現(xiàn)如果發(fā)現(xiàn)了你會(huì)怎么回滾這三個(gè)問題比任何完成標(biāo)準(zhǔn)都能逼出真實(shí)思考。團(tuán)隊(duì)也一度有人覺得這樣評(píng)審太慢。但我的經(jīng)驗(yàn)是風(fēng)險(xiǎn)高的代碼花二十分鐘問清楚比上線后整個(gè)團(tuán)隊(duì)花兩個(gè)多小時(shí)排查要?jiǎng)澦愕枚?。而且這種追問幾次之后作者自己就會(huì)提前準(zhǔn)備評(píng)審速度反而會(huì)快起來。4.3 發(fā)布檢查單漏項(xiàng)和誤報(bào)的排查發(fā)布檢查單一開始很容易變成兩極端要么太長(zhǎng)沒人仔細(xì)看要么太簡(jiǎn)單形同虛設(shè)。我們第一次跑正式發(fā)布生產(chǎn)前檢查單有 27 項(xiàng)很多人填到后面已經(jīng)不看題目了直接一路打勾結(jié)果漏掉了最關(guān)鍵的“數(shù)據(jù)庫備份恢復(fù)驗(yàn)證”。后來我們把檢查單改成智能路由按變更類型展示對(duì)應(yīng)條目。代碼發(fā)布只顯示 8 項(xiàng)常規(guī)檢查涉及數(shù)據(jù)庫變更再多顯示 5 項(xiàng)涉及第三方接口調(diào)用再顯示 3 項(xiàng)。每項(xiàng)還加了一個(gè)“自動(dòng)預(yù)填充”入口比如合并請(qǐng)求中已有的測(cè)試狀態(tài)和 lint 結(jié)果可以直接帶出來不需要人工手填。真正需要人判斷的只剩三五項(xiàng)漏項(xiàng)就少很多。還有一個(gè)經(jīng)驗(yàn)誤報(bào)比漏報(bào)的破壞力更大。如果檢查單里經(jīng)常出現(xiàn)“配置項(xiàng) A 未變更已確認(rèn)”這種永遠(yuǎn)都是默認(rèn)值的項(xiàng)它會(huì)拉低整個(gè)清單的可信度。我們養(yǎng)成了每個(gè)季度做一次檢查單瘦身的習(xí)慣把連續(xù)三個(gè)月命中率低于 10% 的項(xiàng)刪掉或者用監(jiān)控替代人工確認(rèn)。4.4 灰度環(huán)境看不出問題的現(xiàn)象與對(duì)策灰度發(fā)布最容易踩的坑是“灰度環(huán)境什么問題都測(cè)不出來”。我們的第一版灰度是基于測(cè)試環(huán)境集群做的但測(cè)試環(huán)境和生產(chǎn)環(huán)境的流量模型、數(shù)據(jù)量、緩存命中率都不一樣就算灰度集群掛掉也模擬不出真實(shí)用戶的組合場(chǎng)景。第一次灰度新版本時(shí)測(cè)試環(huán)境一切正常生產(chǎn)環(huán)境第一批流量進(jìn)去 P99 就直接漲了三倍。排查下來主要是兩個(gè)原因一是灰度流量沒有做染色無法精確識(shí)別請(qǐng)求來自灰度集群二是監(jiān)控重點(diǎn)放在了 CPU 和內(nèi)存上沒有盯事務(wù)響應(yīng)時(shí)間和錯(cuò)誤率。后來的對(duì)策是把監(jiān)控升級(jí)為“技術(shù)指標(biāo) 業(yè)務(wù)指標(biāo)”雙層結(jié)構(gòu)。技術(shù)指標(biāo)看錯(cuò)誤率、P95 延遲、線程池占用業(yè)務(wù)指標(biāo)看訂單成功率、支付回調(diào)延遲、核心接口的調(diào)用量。只有兩類指標(biāo)都正常才能繼續(xù)放量。我們還在灰度網(wǎng)關(guān)里加了流量染色標(biāo)記把灰度請(qǐng)求的 header 透?jìng)鞯饺罩局行摹_@樣出現(xiàn)問題的時(shí)候能快速判斷是灰度版本的責(zé)任還是外部依賴抖動(dòng)造成的。這個(gè)改動(dòng)帶來的收益非常直接以前灰度失敗要爭(zhēng)論半天現(xiàn)在已經(jīng)形成標(biāo)準(zhǔn)結(jié)論“看染色日志先比指標(biāo)再定責(zé)”。5. 如果現(xiàn)在重新開始我會(huì)更早做這幾件事Impeccable 走到今天已經(jīng)不再只是一個(gè)代號(hào)它變成了一套團(tuán)隊(duì)內(nèi)默認(rèn)的質(zhì)量語言。如果現(xiàn)在重新開始我會(huì)把三件事排得再靠前一點(diǎn)。第一件事先挑一個(gè)“最痛的線上事故”做全套復(fù)盤然后把對(duì)應(yīng)的檢查項(xiàng)做成門禁。不要試圖一開始覆蓋所有場(chǎng)景找到一個(gè)最近真實(shí)傷害過團(tuán)隊(duì)的坑把它堵上團(tuán)隊(duì)才會(huì)真的信這套機(jī)制。第二件事把完成標(biāo)準(zhǔn)里最容易被模糊化的部分寫得更具體。不要寫“代碼質(zhì)量好”要寫“核心路徑有測(cè)試覆蓋且評(píng)審人可以運(yùn)行測(cè)試看到覆蓋結(jié)果”。第三件事把自動(dòng)化檢查的反饋速度做快寧可少一個(gè)檢查項(xiàng)也要保證合并請(qǐng)求看到結(jié)果的時(shí)間不超過五分鐘。檢查節(jié)奏太慢人會(huì)等得不耐煩然后開始想辦法繞過。最后再分享一個(gè)我個(gè)人的體會(huì)項(xiàng)目叫 Impeccable但你要接受它永遠(yuǎn)不完美也不需要完美。這本身就是一個(gè)動(dòng)態(tài)的校準(zhǔn)過程每一次事故、每一次復(fù)盤、每一次規(guī)則優(yōu)化都是讓系統(tǒng)離理想狀態(tài)更近一步的嘗試。質(zhì)量和其他工程問題一樣不是一個(gè)終點(diǎn)而是一套持續(xù)運(yùn)轉(zhuǎn)的機(jī)制。