架構
系統: 一羣有關聯的個體 , 規則, 能力(產生了新能力)
子系統
模塊:邏輯角度 -> 組件複用
組件: 物理角度 -> 單元分離
框架: 組件規範:mvc,等
架構:結構
1. 軟件架構:指軟件系統的頂層結構。
首先,系統是一羣關聯的個體組成,這些個體可以是子系統, 模塊, 組件等;
其次, 系統中的個體需要根據某種規則運作, 架構需要明確個體運作和協作的規則;
第三,頂層結構的說法可以更好的區分系統和子系統。
2. 歷史背景
三難
機器語言 -> 彙編語言 -> 高級語言
3.目的
解決軟件系統複雜度的問題。
找複雜點
性能, 高可用, 擴展性, 存儲高可靠, 安全性,成本。
4. 複雜度來源:高性能
單機:操作系統
多機:任務分配, 任務分解
5. 複雜度來源:高可用 -> 冗餘
無中斷
計算高可用:
- 任務分配器
- 連接(與業務處理器)
- 算法(分配)
存儲高可用:
傳輸線路(故障)
狀態決策:
- 獨裁式
- 協商式
- 民主式: -> 腦裂 -> 解決:選舉超一半
6. 複雜度來源:可擴展性
- 正確預測變化
- 完美封裝變化
變化層, 接口穩定層
抽象層, 實現層
7. 複雜度來源:低成本, 安全, 規模
低成本 -> 附加約束
- 引入新事物
- 創造新事物
安全
- 功能安全:SQL注入, 代碼漏洞
- 架構安全: 防火牆
規模: -> 量變引起質變 而帶來的複雜度:
- 功能越來越多,系統複雜度指數增長
- 數據越來越多,系統複雜度發生質變
8. 架構設計原則
選擇(不確定性)
- 合適原則: 合適由於業界領先
- 簡單原則:簡單優於複雜(結構複雜, 邏輯複雜,算法複雜)
- 演化原則:演化由於一步到位
9. 案例
10. 架構設計流程:識別複雜度
列出複雜度問題,排序依次解決;
理解需求,找複雜度 -> 可用排查法,從不同角度逐一分析
每秒TPS , 平均值, 峯值(2-4倍平均值)
設計目標用峯值計算。
複雜度
- 高性能
- 高可用
- 擴展性
TPS:寫入
QPS:查詢
eg. 目前TPS 是115, QPS是1150,
則峯值 x 3, TPS是345, QPS是3450 -> 這個量級不要求高性能;
預留後續發展, 按 峯值 x 4, TPS是 1380, QPS是13800 -> 高性能
不同的公司會有不同的複雜度分析: 安全,或成本
eg. 消息隊列
前浪微博:複雜度體現在
- 高性能消息讀取
- 高性能消息寫入
- 高可用消息存儲
- 高可用消息讀取
11. 架構流程設計:設計備選方案
常見錯誤:
1.設計最優秀的方案
2. 只做一個方案
應設計3-5個備選方案。
備選方案差異要比較明顯
備選方案的技術不要只限於已熟悉的技術
備選方案不用過於詳細 -> 應關注技術選型,而不是細節
eg. 設計備選方案 - > 高性能讀取,寫入,高可用存儲,讀取。
1. 用kafka
2. 集羣 + mysql 存儲
高性能讀取,寫入 -> Java, 基於netty開發消息隊列
高可用存儲,讀取 -> 服務器的主備方案, mysql的主備複製。
3. 集羣 + 自研存儲方案
可參考kafka,自己實現一套存儲和複製方案。
12. 架構設計流程:評估和選擇備選方案
- 最簡派
- 最牛派
- 最熟派
- 領導派
分場景使用
1. 360度環評表: 列出我們需要關注的質量屬性點,分別從這些點的維度評估每個方案,再綜合選合適當時情況的最優方案。
常見的方案質量屬性點:性能,可用性, 硬件成本, 項目投入,複雜度,安全性,可擴展性,可維護性等。
2. 按優先級選擇
13. 架構設計流程: 詳細方案設計
確定方案 的關鍵細節
詳細設計方案階段,可能遇到一種極端情況,發現備選方案不可行。
一般情況下,主要原因是涉及備選方案時遺漏了關鍵技術點或關鍵質量屬性。
這種情況下,可通過以下方式避免:
- 架構師不但要備選方案的設計和選型,還要對備選方案的關鍵細節有深入理解
- 通過分步驟,分階段,分系統,降低複雜度
- 如果方案本身複雜,可採取設計團隊,博採衆長,彙集大家的智慧。
14. 高性能數據庫:讀寫分離
本質:將訪問壓力分散到集羣中的多個節點,但沒分散存儲壓力。
基本原理:將數據庫讀寫操作分散到不同的節點上。
主 + 從 集羣: 主負責讀/寫,從負責讀。
不同於 主 + 備
基本實現:
- 數據庫服務器,搭建主從集羣,一主一從或一主多從 都可以。
- 主機負責讀寫,從機負責讀
- 數據庫主機通過複製將數據同步到從機,每個數據庫都存了所有數據
- 業務服務器將寫發給主數據庫,讀發給從數據庫。
有兩個複雜點:1. 主從複製延遲; 2. 分配機制
1. 複製延遲
先假設延遲1秒,數據寫入主庫後立刻訪問從庫,讀取不到最新數,例如註冊,會有業務問題。
它的常規解決方法:
- 寫操作後的讀操作指定發主數據庫 -> 和業務強綁定,對業務侵入和影響較大
- 讀從機失敗後,再讀一次主機 -> 即二次讀取,無業務綁定,只需對底層數據庫的訪問封裝,代價小,但若有很多二次讀取,將增加主機的讀取壓力
- 關鍵業務讀寫都指向主機,非關鍵業務讀寫分離
2. 分配機制
將讀寫區分,訪問不同的數據庫,一般有兩種方式:程序代碼封裝和中間件封裝
2.1 程序代碼封裝:在代碼中抽象一個數據訪問層,實現讀寫分離和數據庫連接管理。
特點:實現簡單,可根據業務做定製化功能;
每個語言實現一次;
故障情況下,如果主從發生切換,則需要所有系統都修改配置並重啓;
eg. 淘寶的 TDDL(Taobao Distributed Data Layer)
基本原理:基於集中式的配置Jdbc datasource實現: 有主備,讀寫分離
基本架構
2.2 中間件封裝
指獨立出一套系統,實現讀寫分離和數據庫服務器連接的管理。
一般建議,程序語言封裝或用成熟中間件
Mysql Router,
Atlas : 基於Mysql Proxy
思考:
讀寫分離一般用於實現什麼場景?支撐多的業務規模?
讀多寫少,實時性要求不高
15. 高性能數據庫集羣:分庫分表
分散存儲
數據量 從千萬到億 , 就會有單臺瓶頸。
業務分庫
按業務分到不同的數據庫 -> 分庫能支撐百萬甚至千萬規模業務。
存在問題:join 問題(不同庫), 事務, 成本等。
分表
垂直拆分, 水平拆分
垂直:
水平:
複雜性
1. 路由 - 範圍路由(分佈不均勻), hash路由(分佈均勻), 配置路由(用單獨的表記錄路由信息)
2. join
3. count (count相加,記錄數表)
4. order by
實現方式:代碼封裝,中間件封裝
16. 高性能NoSQL
not only SQL, 彌補數據庫缺陷
關係數據庫缺點:
- 存儲的行記錄,無法存數據結構 - 無法直接存列表
- schema 擴展不方便: 操作不存在的列會報錯,擴充列要DDL,麻煩
- 大數據場景下, I/O較高; 單列統計 ,會讀取整行
- 全文搜索功能較弱; like 整表掃描, 性能低
NoSQL分4類
- k-v存儲:解決無法存數據結構的問題, 以Redis爲代表
- 文檔數據庫:解決強schema約束問題,以MongoDB爲代表
- 列式數據庫:解決大數據場景下I/O問題,以HBase爲代表
- 全文搜索索引:解決全文搜索性能問題,以Elasticsearch爲代表
Redis
eg, lpop 移除list第一個元素。
缺點:不支持完整的ACID, 只保證隔離性和一致性,無法保證原子性和持久性。
文檔數據庫
特點:no-schema,可存儲和讀取任意數據,大部分是Json格式存儲
優點:
- 新增字段簡單
- 歷史數據不會出錯
- 更易存儲複雜數據
適合場景:電商和遊戲
缺點:不支持事務,無法join操作。
列式數據庫
對比,關係數據庫,行式存儲:讀多列,效率高,能一次完成對一行對個列寫操作。
列式數據庫:對某個列統計,節省I/O;有更高的壓縮比(行:3:1 - 5:1; 列: 8:1, 30:1)
一般將列式存儲用在大數據分析和統計場景;主要針對部分單列操作,且寫入後無需再更新,刪除;
全文搜索引擎
基本原理:倒排索引,是一個索引方法,是建立單詞到文檔的索引。
使用方式:將關係數據轉換爲文檔數據Json。
Elasticsearch是分佈式文檔存儲方式,每個字段的所有數據默認被索引。
17.高性能緩存架構
某些複雜場景,單靠存儲系統,性能提升是不夠的。
a. 要經過複雜運算後得到的數據
b. 讀多寫少的數據
緩存-基本原理:將可能重複使用的數據放到內存,一次生成,多次使用,避免每次使用都訪問存儲系統。
mechache 單臺支持50000 TPS以上。
緩存-架構設計要點:
1. 緩存穿透:指緩存中沒數據,都查了存儲系統。
有兩種情況:
a. 存儲數據不存在 - 解決:可設置一個默認值
b. 緩存數據生成耗費大量的時間或資源
2. 緩存雪崩:指緩存失效後,引起系統性能急劇下降的情況
解決:
a. 更新鎖機制:對緩存更新操作加鎖保護,集羣時需要設置分佈式鎖。
b. 後臺更新:由後臺線程更新緩存,而不是業務線程;緩存本身設置有效期永久,後臺定時更新;
當內存不足時,會踢掉數據,可有兩種辦法:
1>. 後臺除定時更新緩存,還要頻繁讀緩存
2>. 業務線程發現緩存失效,通過消息隊列發一條消息通知後臺線程更新緩存。
還可以用後臺更新進行緩存預熱。
3. 緩存熱點
對於一些特別熱點的數據,大部分業務請求都命中同一份緩存數據,則這份數據所在的緩存服務器壓力也很大。
緩存熱點的解決方案是:複製多份緩存副本,將請求分散到多個緩存服務器,減輕緩存熱點導致後臺服務器壓力。
一個細節要注意:不同的緩存副本不要設置統一的過期時間,應設置一個過期時間範圍,不同副本過期時間是指定範圍的隨機值。
實現方式:
- 程序代碼的中間層方式實現
- 獨立中間件實現
18. 單服務器高性能模式:PPC與TPC
磁盤,操作系統,cpu,內存,網絡,編程語言,架構, 都可能影響達到高性能。
架構師要考慮:高性能架構的設計
集中兩個點
- 儘量提升單服務性能,將單服務器性能發揮到極致
- 如果單臺服務器無法支持性能,設置服務器集羣方案
還和編碼有關
架構設計決定系統性能的上限,實現細節決定下限。
單服務器性能關鍵之一:服務器採用併發模型。
關鍵設計點:
- 服務器如何管理連接
- 服務器如何處理請求
這兩個設計點都和操作系統的I/O模型及進程模型相關。
I/O模型: 阻塞, 非阻塞, 同步,異步
進程模型: 單進程, 多進程, 多線程
單服務器高性能模式:PPC, TPC
PPC
: process per connection ,指每次有新的連接就新建一個進程,去專門處理這個連接的請求。
適合連接沒那麼多的情況。
缺點:
- fork代價高
- 父子進程通信複雜
- 支持的併發連接數量有限
一般情況下,PPC最多能處理的併發連接數就幾百。
prefork -> 提前創建進程
TPC
Thread per connection. 每次有新的連接就新建一個線程,專門處理這個連接的請求。
TPC基本流程:
略
TCP雖然解決了fork代價高和進程通信複雜的問題,但有其他問題:
- 高併發性能問題
- 沒有進程僅通信,但線程間的互斥和共享,易導致死鎖
- 多線程相互影響,一個線程異常,整個進程退出
因此,幾百連接的場景,更多用PPC
prethread -> 提前創建線程
19.單服務器高性能模式:Reactor與Proactor
PPC和TPC無法支持高併發
Reactor
資源複用,進程池
read阻塞 -> 改爲了非阻塞,進程輪詢多個連接(當連接太多,輪詢效率低)
再改進 -> 只有連接上有數據,進程纔去處理 ->