沒有完美的方案。全部方案都有利弊,在於適用場景以及權衡取捨。Think Deeper, Design Better.前端
代碼即設計。編程
在編程實現中,實際上融合了設計的思考結果。不管需求大小,在設計上考慮充分一些,實現質量上就能更容易理解和維護。 那麼,在設計方案時須要考慮哪些因素呢? 如何作出一個比較適合的設計方案 ?後端
一般有以下步驟: 明確痛點 -> 有序思考 -> 根據質量要求尋找合適的設計準則 -> 制定可選方案 -> 設計溝通 -> 尋找合適的細則來提高實現質量。緩存
每個需求/優化/重構,總能追溯到某個痛點。痛點主要有以下:安全
功能訴求: 競爭對手有拼團功能,賺了好多錢好多粉絲,我也要有!【新功能】服務器
穩定性優化: 時不時出現xx報錯,真是使人煩躁!同時一波大流量來襲,系統波動有點大啊!【穩定性】併發
性能提高:怎麼這麼慢啊 ! 這麼多訂單,得處理到何時?【響應速度與吞吐量】異步
維護成本: 這方案得佔雙倍的存儲資源,還有兩個同步,理解起來多費勁!這個報錯,無法看出問題在哪裏,還得再打個日誌看看。商家在等着修復問題,真急人!【資源/時間】ide
彈性: 明年訂單量要增長3倍,如今這個方案貌似扛不住啊!【容量擴展】函數
數據: 這待發貨訂單數顯示爲2,怎麼點進去沒訂單?【對比分析有困惑】
體驗: 要作完一個批量操做,要好多步驟,還容易出錯,真耗費時間啊!【步驟繁瑣,易錯】
及時: 更新一個內容,要立刻生效,而不須要從新修改代碼部署系統。【即時更新】
安全: 啊啊,不當心把DB/重要文件目錄數據刪除了!【安全性提醒】
擴展:實現一個需求,要改這麼多代碼?
重構: 這麼多新的業務需求,真無法改了!非得動大手術了!
痛點是否足夠痛? 避免爲了解決/優化問題而解決/優化問題,避免爲了嘗試新技術而引入新技術。必定是爲了解決痛點。由於事情是作不完的,顧此則失彼,要對作的事情進行仔細規劃。
明確痛點,才能真正對症下藥,藥到病除。
拿到一個需求/優化/重構,如何有序地思考設計方案呢 ?
STEP0: 弄清楚問題的背景及前因後果。
STEP1: 明確功能或服務目標,肯定硬性質量要求(一般是性能),或軟性質量要求(一般是健壯性、可擴展性、可維護性);
STEP2: 肯定重點關注者,數據的存儲和分佈;
STEP3: 思考最終方案形態應該是怎樣,現狀是怎樣,可以作怎樣的權衡取捨;
STEP4: 根據設計準則,思考處理對策。
STEP5: 設計溝通,尋求更有經驗的幫助。
STEP6:肯定設計方案,分離關注點,具體設計和實現。
有清晰、直觀、易懂的心智模型/領域模型;
沒有拐彎抹角的地方。
域的劃分清晰。
分層清晰。
語義一致。
所見即所得與形式的一致性。
案例:
訂單同步,使用 Input-Filters-Output 過濾器-管道模式,輔以基礎組件的配置和組合, 清晰地表達了各類具體任務的實現流程。
訂單詳情,使用 Providers-Plugins 兩層結構,清晰地表達瞭如何從數據存儲獲取源數據並經過插件格式化成最終數據的過程。
訂單搜索,ES 查詢提供了索引與 JSON 查詢語句的簡潔抽象。 每一個搜索字段相互獨立,且聯合搜索 DSL 直觀易懂。
訂單導出,使用多個詳情收集器的順序組合來獲取所須要的訂單詳情,每一個收集器相互獨立變動;使用策略模式分離標準報表和自定義報表。
反例:
API 入參中的繼承致使 API 使用很迷暈,容易使用錯誤; 不要爲了追求一點點複用效果在 API 使用繼承。
要導出零售總店的全部訂單,須要傳 head_shop_id, 並將 shop_id 置爲空。Workaround 方案。
將大量邏輯放在一個類裏。代碼分層不清晰。
獲取覈銷人信息,須要覈銷人所在的店鋪ID,但交易只能拿到訂單所在的店鋪ID,這二者的語義在連鎖形態下不必定一致,致使有時拿不到覈銷人信息。
減小了錯誤發生的可能性。
錯誤發生時,更安全友好地處理。
不會由於次要局部影響總體。
減小了故障可能性,或可能故障級別。
故障發生時,可以更快更安全地處理和恢復正常。
案例:
使用切面實現業務異常的捕獲和轉譯封裝。【健壯性】
對每一個訂單的處理進行異常捕獲打日誌,且對每一個訂單的每一個字段的處理進行異常捕獲打日誌。避免某個訂單的某個字段錯誤影響該訂單的其餘字段的導出,或者避免某個訂單的數據錯誤,影響了其餘訂單的導出。【隔離】
捕獲 API 或 IO 訪問異常,並進行轉譯處理。 避免因局部影響總體,或提供更友好的上層提示。【容錯】
重試機制。適合於「在極端狀況下暫時不可用,而在正常狀況下自動恢復」的防護機制。【容錯】
針對可能致使故障的點,進行重點防禦。【防護】
IO 訪問設置超時。【隔離】
反例:
對於 API 返回結果不作任何校驗而直接使用,致使 NPE 。
因爲單個接口調用失敗致使總體信息加載失敗。
快速的響應時間和吞吐量;
大幅減小任務運行時長。
系統在大流量情形下的穩定運行。
對外部依賴進行降級或熔斷。
案例:
採用多個線程,批量、併發地拉取訂單詳情。
備份的過程,使用異步來完成,提高響應速度。
減小沒必要要的IO訪問;僅在真正須要的時候去訪問 IO 或 API 。
限流處理。 在連續多個大流量導出的情形下,進行限流,只容許指定數目的大流量導出。
熔斷降級。 HBase 主集羣訪問失敗時,自動切換到備集羣訪問。
底層模型統一。
核心簡潔而穩定,外圍可擴展。
適當地分離關注點,組合和組織關注點。
組件化、配置化,經過增減插件來支持需求。
案例:
不是按照前端頁面所需功能,而是按照所須要提供的能力模型,來設計訂單搜索服務。底層具備強大而通用的能力,上層進行適配,提供受限的能力。
梳理整個流程,將總體流程中可變的子流程進行抽象,容許配置不一樣的子流程實現,來實現多變的具體流程。
將導出構建成「查詢-詳情-過濾-排序-格式化-生成報表」的插件化流程。只須要新增或編排插件,就能實現各類形態的導出。
當業務量增加時,能夠天然應對而無需額外改動。
能夠即時加機器,解決臨時高併發吞吐量問題。
能夠即時減機器,去掉沒必要要的資源空閒。
案例:
爲熱狀態訂單搜索創建熱索引。不管訂單總量及增加量如何,熱狀態訂單量始終維持在漲幅不大的程度。
應用對等,無狀態設計,能夠隨時增減應用服務器而無影響。
減小了存儲資源佔用。
減小了多處同步。
能更快速地定位問題,大幅減小了排查和解決問題的時間(秒/分鐘/小時/天)。
分離出了變化的部分,更容易識別變化和擴展。
案例:
去掉了對老訂單同步的依賴,訂單導出的總體理解和維護更加簡單。
更明顯的錯誤緣由指明和建議措施,利於快速定位問題和解決。
以小見大,從一個需求點看到一類需求。
創建可重複使用的方法、機制和流程,更容易地解決類似問題。
案例:
創建一個可複用的 HBase 詳情獲取插件,來解決導出商品編碼的問題;同時又能爲其餘字段導出需求所使用。
使用模板方法模式,將導出的「入參校驗-保存導出任務記錄-上傳導出結果-更新導出任務記錄」基本流程實現爲可複用的模板流程。具體導出只要關心如何生成報表便可。
案例:
案例:
案例:
訂單詳情接口去除對某個外部接口的依賴。
訂單導出任務完成後,直接更新DB裏的任務記錄,再也不依賴消息中間件。
反例:
老是首先尋找簡單、改動最小、比較完全的方案。
複雜度衡量: 少許順序代碼 < 一些條件分支代碼 < 增長少許apollo配置 < 增長DB < 增長DB和緩存 < 增長一個模塊。
案例:
沒有完美的方案,只有合適的權衡取捨。
針對不一樣的場景,衡量收益和代價。
要綜合思考,避免線性思考;避免爲了解決一個次要的問題引入更大的問題。
優先級:穩定性 > 清晰性 > 靈活性。
一般能夠認爲:
若是可以達到創建新功能、避免故障、彈性擴展、大幅下降維護成本、可複用, 其收益將是很是高的,此時,增長少量依賴、複雜度,實際上是能夠接受的。
爲擴展留下實現空間,但能夠暫時不實現。
一勞常逸/觸類旁通,相比只是解決當前問題,更有價值;多往前走一步。
天然清晰,是很是不容易達到的;但很值得爲之一步步接近。
設計細則是在準則的基礎上,針對具體事項、場景而建議採用的方案。
文檔說明: 當設計變動涉及較大變更時,創建文檔說明改動點及原因。
API :繼承不可超過兩層,避免嵌套;避免將不相關的東西混雜在 API 參數中;避免將底層實現細節暴漏在API 參數與傳參中。
分層: 提煉出一系列關注點,分離到不一樣的語義層次,分離到多個類的單一職責中。
組件化: 將工程裏的代碼與功能實現抽象爲組件接口與實現。
策略模式: 使用策略模式分離同一個接口的不一樣實現,並根據場景選擇適宜的實現。
插件流程: 若是流程是可變的,那麼將單個流程節點變成可配置的插件,並進行編排。
啓動檢查: 當應用啓動時,加載全部必要組件,任一不知足時及時報錯退出,避免錯上加錯。
使用切面: 當多個功能要複用同一個前置或後置邏輯時,使用切面來實現這些前置或後置邏輯。
受控線程池: 切忌在應用裏動態建立單個線程或線程池;使用全局受控的線程池來執行任務。
重載函數: 使用重載函數創建適合的工具類。
無狀態: 除非必要,不要在實例間共享狀態;不要讓請求的處理結果依賴於某個狀態。
快速失敗: 當前置要件不知足時,快速失敗勝於自覺得的智能容錯處理。
事務: 多個關聯操做的原子性和一致性保障。
冪等: 處理多個徹底相同的請求時,與處理一次的效果相同。
範式: 在關係型數據設計中,要遵循基本的規範範式。
日誌: 在開發時,打印合適級別的必要的日誌(關鍵路徑和關鍵狀態),方便快速排查錯誤。
來源監控: 若是有多個來源或類型,創建監控瞭解每一個來源或類型的業務量及佔比。
設計溝通也是很是重要的。一我的不免由於經驗不足,沒有想到某些關鍵點,須要別人提醒。 如何可以讓別人更好地參與進來,幫助一塊兒完善設計方案呢(同時也能夠幫助感興趣的小夥伴增加知識和經驗面) ?一般,問題的發起者應該作到以下三點:
創建文檔說明場景、痛點及前因後果;
多種可選方案,各自的優勢與弊端,利弊權衡。
提出本身的疑問。
這樣,別人才能更好地提出好的建議。
【未完待續】