拓展技術邊界,提高業務創新能力。html
不患不知,而患不精。精益求精,技術過硬是必要的。理解互聯網技術的底層原理與實現,對提高設計能力很是有益處,亦能爲業務創新提供重要的技術保障。技術深度不夠會限制業務創新能力。java
本文旨在聚合互聯網應用的服務端所用到的主要且重要的技術思想、原理、機制、技巧。
node
綜述
軟件的使命是處理數據。人賦予數據以意義。mysql
數據處理的要求:靈活、高性能、高可靠、高可用、一致、海量、安全、自動化、智能化。redis
- 靈活:數據建模與存儲、架構與編程、微服務、框架、代理、組件與配置化。
- 高性能:數據結構與算法、併發、緩存、精簡開銷、參數調優、編譯優化、服務器性能、服務指標測量。
- 高可用:限流、降級、冗餘。架構上要支持水平擴展,運維上多節點部署、負載均衡、彈性伸縮容(容器化)。
- 高可靠:分佈式 + 容錯。
- 一致: 複製、狀態機、一致協議、重試補償對帳。
- 海量:分佈式 + 大數據存儲 + 實時計算 + 離線分析
- 安全:機密性、完整性、可用性。機密性 - 數據內容不泄露;完整性 - 數據內容不被篡改;可用性 - 保護資源隨需所得。
- 智能化:讓機器從大量已有數據中自動學習和分析模式,增長反饋環節,來改善產品、業務的前置環節。
視角
解決問題視角算法
- 解決什麼問題,有哪些情形,要考慮什麼錯誤和異常 ?What - Problem - Circumstances - Errors - Exceptions
- 爲何要解決這個問題 ? 形成了什麼麻煩,有哪些應用場景 ? Why - Troublesome - Applications
- 如何解決:理念、方法、實踐如何 ? How - Thoughts - Methods - Practices
- 方案對比:優勢和缺點,適用場合 ? Evaluation - Benefits - Drawbacks
系統設計視角sql
- 核心抽象、概念及類。 Abstraction - Conception
- 主流程、重要環節、核心類的串聯。 Flow - Segments - Links
- 問題分解、技術難點與方案、可行性論證。Decomposition - Keypoints - Solutions - Feasibility
- 關鍵數據集及算法。 Core Dateset - Algorithm
技術決策視角數據庫
- 場景考量:通用場景,仍是特定場景? 場景是否有特殊性,能夠用更高效的方式有針對性地解決?場景下的語境上下文是怎樣的 ? Scene - Context
- 方案權衡:要達成的目標是怎樣的? 哪些質量因素是尤其重要的?要兼顧考慮哪些質量因素?可用資源如何? Goal - Quality Requirements - Available Resources
思想
縱觀各類技術:基本思想是在不一樣語境裏反覆使用着的。apache
- 功能與靈活: 標識、標記/狀態位、元數據;分離與解耦。
- 時間複雜度: 二分、映射、分治與組合、分攤、緩存、緩衝;動態規劃;預處理;空間換時間。
- 空間複雜度: 位圖、冗餘編碼、虛擬化;時間換空間。
- 高效讀:緩存優先於主存,主存優先於磁盤,緩衝優先於直接讀寫;減小磁盤讀次數,減小更慢存儲介質的讀次數。
- 可靠寫:主存優先於緩存、磁盤優先於主存;提交/確認/回滾機制。
- 提高響應:批量、異步、併發。
- 可維護性:抽象(問題本質)、封裝(實現隱藏)、複用(提高效率)、多態(加強靈活性)、組合(功能集成)。
靈活
靈活是指可以低成本、高質量地添加新功能和優化現有功能。
編程
數據組織與存儲
- 問題分析 -> 抽象 -> 提取出關鍵數據集 -> 數據範式與結構化 -> 數據 Shema -> 一致性與完整性 -> 組織與存取。
- 數據特色:結構化、半結構化、非結構化;記錄、文本、二進制流;
- 數據關聯:聚合關係(有意義的實體);對應關係(一對1、一對多、多對多);映射關係;泛化與特化(類似與相異);衍生關係(計算與推理)。
- 數據變化:靜態不可變、只變動一次、不多變動、頻繁變動、瞬時變動不少次。
- 數據操做:插入、查詢、更新、刪除(CRUD);過濾、排序、分組、聚合(FSGA)。
- 業務驅動:業務 -> 問題 -> 視角 -> 數據 Schema -> 採集數據 -> 處理數據。
事務
事務是數據處理的基本保證之一。 ACID (原子性、一致性、隔離性、持久性)是事務保證的四個重要特性。
- 事務類型:(帶保存點)扁平事務、鏈事務、嵌套事務、分佈式事務。
- 事務的實現機制: redo 和 undo 日誌、鎖技術、MVCC 機制。
- 緩衝池:爲了保證讀寫數據性能,MySQL 讀寫提供了緩衝池(Buffer Pool,BP)。 事務開啓後,數據更新數據先寫入 BP 裏,在提交事務後再經過後臺線程按期同步到磁盤文件中。當事務執行中發生異常時, BP 的數據可能未能刷入磁盤,或者刷入了一部分,所以存在數據丟失或不一致的風險。這樣,就須要在事務提交前記錄 redo 日誌和 undo 日誌,這樣就能在斷電後從新執行事務保證數據不丟失,或者回滾事務保證數據原子性。在事務提交後將 redo 日誌寫入磁盤。
- 原子性和持久性:原子性是指一個事務中的全部操做要麼所有成功,要麼什麼都不作。持久性是指一個事務一旦提交即便斷電也不會丟失。redo -- 記錄事務執行過程當中對頁的物理修改的日誌(512 字節、塊形式),用於保證事務的持久性,undo -- 記錄事務更新操做的逆向邏輯的日誌,用來保證事務的原子性。開啓事務後,每次更新執行都會分別記錄 redo 日誌到 redo log buffer, 記錄 undo 日誌到 undo log buffer ,並將數據更新改動保存到緩衝池 BP ;提交事務後,經過後臺線程同步到磁盤的 redo 日誌文件和 undo 日誌文件,並將緩衝池 BP 中的更新改動寫入到磁盤中。若是事務提交以前斷電或失敗,則什麼都不會發生;若是事務提交以後斷電,則能夠經過讀取 redo 日誌來重作事務;若是事務執行失敗,經過 undo 日誌中的回滾語句,完成回滾操做。
- 隔離性:隔離性是指一個事務不能看到未提交事務的數據。隔離性經過鎖技術和 MVCC 來實現。鎖技術 -- 共享鎖和排他鎖,讀讀並行,讀寫或寫寫串行;MVCC -- 根據每行記錄的隱藏列(建立時間版本號和修改時間版本號)來實現讀寫控制。
- 一致性:一致性是指數據從事務前的一致狀態轉換爲事務後的一致狀態。事務完成後仍然知足數據的完整性約束。
- Savepoint:通知系統記錄指定執行點的狀態集。當事務執行出錯時,能夠回滾到指定 Savepoint 的狀態,而沒必要回滾到事務前。好比旅行計劃,是不能所有回滾的。扁平事務能夠看作是隻有一個 Savepoint (事務執行開始前記錄)。 Savepoint 的編號是單調遞增的。
- 嵌套事務: 一棵事務樹。只有頂層事務提交時,全部子事務才能提交;當父事務回滾時,其下的全部子事務都會回滾。子事務能夠繼承父事務的部分鎖。嵌套事務可用於實現相關事務的並行性。
架構與編程
對於單個微服務而言,能夠經過以下手段來加強靈活性:
- 建模存儲:數據模型、數據 Schema 、完整性和一致性約束定義。
- 領域驅動:DDD 設計,穩定的精煉的可持續演進的領域模型,六邊形架構,聚合根,充血模型。
- 架構模式:分層、微內核+插件、PipeLine 、事件驅動、訂閱-推送、Actor 等。
- 柔性編程:應用設計模式、設計原則(SOLID, KISS)、組件化、持續小幅重構。
微服務
-
單體應用的問題:直接經過訪問 DB 來共用數據庫,數據管理容易缺少明確 owner 而混亂,DB 設計變動難度高;缺少明確的域的劃分,功能邊界不清晰,重複功能實現和重複代碼難以維護,業務耦合,依賴關係混亂;協做成本高,系統總體穩定性差。
-
微服務:將單體應用分解爲多個具備明確領域定義的業務子域,將每一個相對獨立的業務子域實現成單獨的微服務,微服務獨立管理各自子域的問題,採用不一樣的架構和方案來適配自身領域的問題,最終全部微服務集成起來完成總體應用功能。實現獨立自治和發展、模塊化、分工協做等。
-
微服務要解決的問題是服務治理。主要包括:限流/熔斷降級、配置管理、日誌中心、監控預警、鏈路跟蹤、故障隔離、動態擴容、分流發佈、全鏈路壓測、中間件支撐、團隊組織架構適配與管理。基本解決方案: RPC 框架和統一應用框架接入。
-
一個較爲棘手的問題:應用一致性。解決方案有:分佈式事務、消息補償、對帳和自動修復。
-
分佈式事務解決方案:2PC,TCC,本地消息表(消息解耦)、消息事務。
RPC:Dubbo
- Dubbo 官方文檔已經給出了詳細的解釋和引讀,推薦閱讀。Dubbo 官網地址在文末參考文獻中給出。
- 核心抽象是服務。Dubbo 服務是經過 URL, ServiceConfig 來表示的。 URL 是服務的表示,做爲配置信息的統一格式,全部擴展點都經過傳遞 URL 攜帶配置信息;而 ServiceConfig 是基於 URL 的服務的配置,包括與服務導出有關的全部信息。服務的導出入口在 ServiceBean.onApplicationEvent 方法裏,即 Spring 容器初始化完成以後。
- Dubbo 十層架構理解:整體可分爲 RPC 服務和 RPC 通訊兩部分。在 RPC 服務部分,首先是最接近於業務方的服務接口和實現層(Service),有了服務就須要服務的生產者和消費者配置(Config - XML 或註解),還須要服務的註冊與訂閱(Register - Dubbo, ZK, redis), 與之相配套的服務監控(Monitor)。一般裸服務是不夠的,每每會有代理(Proxy - javassist,JDK)、負載均衡(LoadBalance - ConstentHash, LeastActive, Random, RoundRobin)和容錯(Cluster)。在 RPC 通訊部分,須要協議層(Protocol - Dubbo, HTTP, Rest, Hessian, InJVM, Tether, Thrift Nova, RMI, WebService, Redis, 直連)、信息交換層(Exchange - 封裝 Request-Response 語義)、傳輸層(Transport - netty, mina), 以及數據在網絡和內存中的序列化和反序列化(Serialize - Hession2, Dubbo, FastJson, JDK)。
- 擴展機制:Dubbo SPI ,核心類是 ExtensionLoader , 核心成員是 EXTENSION_LOADERS[Class, ExtensionLoader], EXTENSION_INSTANCES[Class, Object] 。帶緩存的實現。
- 序列化與反序列化:考慮跨平臺、性能(佔用空間及網絡帶寬傳輸)和兼容性。Hession2 -- 自描述序列化,跨語言、佔用空間小,要注意子類和父類的同名變量在反序列化時有覆蓋問題。 FastJson -- 序列化行爲可定製、序列化後可讀性好、容易調試、丟失類型信息。反序列化時容易有安全漏洞,敏感信息要麼不序列化,要麼加密後再序列化。
統一應用框架:Spring
- IoC: Bean 生命週期定義和操做定義。定義一個包含多個階段的生命週期,在每一個階段能夠設置一些鉤子方法做爲擴展點。
- AOP: Pointcut - Advice - Joint - Aspect 。 接口用 JDK 代理,其餘用 CGLIB 代理。JDK 代理將方法委託給目標對象,CGLIB 代理則經過加載並修改目標對象的字節碼、繼承並覆寫目標對象的方法而實現。
分佈式事務
- 核心問題:1. 如何將分佈式事務拆分爲多個本地事務; 2. 如何組合多個本地事務,並儘量保證一致性。
- 解決方案:2PC、TCC、本地消息表、消息事務、Sagas 事務模型。
- 2PC:經過一個全局協調者來組合本地事務。分準備和提交兩個階段。準備階段,協調者向全部參與者發送投票消息,詢問參與者事務是否能夠提交事務,參與者向協調者回復 OK ;提交階段,協調者通知全部參與者提交事務並執行。若是有任一執行失敗,則所有回滾。優勢:儘量保證強一致性,犧牲可用性。問題:同步阻塞;協調者的單點問題;不一致問題,好比協調者只通知了部分參與者提交;任一節點失敗致使所有回滾,代價巨大。
- TCC:Try-Confirm-Cancel。對每一個操做都註冊一個確認和撤銷操做。Try -- 主要是對業務系統作檢測及資源預留; Confirm -- 業務確認提交;Cancel -- 業務執行出錯時執行撤銷操做。TCC 自己實現簡單、代價稍小,不過業務層要寫不少補償代碼(有些場景可能難以作補償),數據一致性弱於 2PC。
- 本地消息事務:經過本地消息表來組合事務。將分佈式事務拆分紅多個本地事務,本地事務與本地消息表關聯。當一個事務執行後,在本地消息表存儲一條消息。業務操做和消息存儲都放在一個事務裏。另外一個本地事務消費這條消息,執行相應的事務操做。若執行成功,則修改這條消息狀態爲成功,事務均執行成功;若執行失敗,要麼重試直至成功(可支持冪等),要麼修改消息狀態爲失敗,回滾事務,並通知消息生產方進行回滾或補償。須要處理事務級聯問題、事務與本地消息表的耦合問題,須要封裝好。
- 事務消息:經過消息系統來組合事務。消息系統支持事務。
- Sagas 事務模型:經過工做流引擎來組合事務。將長時運行事務拆分爲多個短的本地事務,並經過工做流引擎來進行管理和執行。
分佈式Trace
- TraceId + Span + javaagent + 字節碼修改。
消息系統
服務解耦一般採用消息隊列。
- 設計考量:及時性(消費延遲、準實時或批量處理)、可靠性保證、高吞吐量和動態吞吐量、數據格式、數據轉換(ETL,ELT)、安全性(認證機制和加密傳輸,審計和追蹤)、故障處理、耦合性和靈活性(臨時數據管道、缺失元數據、末端處理)、Collection API 的開箱即用。
- 設計要點:RPC協議、存儲;消息投遞、防丟,防重、堆積、廣播、事務;最終一致性。
- 能力特性:順序、延遲、流控;批量、異步。
可靠性保證
可靠性保證:「至多一次」,「至少一次」,「僅有一次」。消息的可靠性保證,不只僅取決於消息系統,還取決於與消息系統集成的上下游一整套的可靠性措施。
- 使用者角度:生產者和消費者的消息鏈接配置,消息鏈接組件可靠性(適配業務端的生產和消費速率);消息體儘量精簡,保持在小體積(避免大致積消息在高併發下的發送 RT 上漲);避免消息裏有大 JSON,防止 JSON 過長截斷致使髒數據( 避免消息隊列堵塞);消息消費後要 ACK(避免消息堆積);數據格式的一致性(避免消息生產者和消費者之間的升級耦合);異常重試消息發送或消費(避免數據丟失);冪等處理消息(避免資損問題);生產者寫入消息的速度控制;消費者的消費速率保證。
- 設計者角度:可靠生產,可靠存儲,可靠消費。可靠生產 -- 消息生產確認;可靠存儲 -- 複製、持久化,多副本,落盤後發送;可靠消費 -- 最少一次,確認機制、重試、冪等處理。
- 網絡角度:可靠傳輸協議 TCP,見高可靠部分
KafKa
- 基本概念:Topic, Partition ; Producer, Consumer ;Broker ; Leader/Follower 。
- 一個主題 Topic 能夠寫入多個分區 Partition,實現 Kafka 的數據冗餘和伸縮性。生產者 Producer 能夠爲某種消息指定消息鍵,經過 Kafka 分區器爲消息鍵生成散列值,從而寫入指定分區,這樣,一樣的消息鍵能夠寫入同一個分區。寫入分區時,消息會生成一個遞增的偏移量。 消費者 Consumer 能夠根據偏移量按消息生成的順序進行消費。多個消費者能夠共同訂閱同一個主題,造成消費者羣組。羣組保證每一個分區只能被一個消費者使用。訂閱同一個主題的不一樣消費羣組的消費是互不影響的。消費者經過輪詢的方式處理從分區獲取的數據。
- Kafka 不須要消費者進行確認。消費者能夠跟蹤消費的偏移量(經過往主題爲 _consumer_offset 的特殊主題發送消息)。當消費者崩潰或有新消費者加入羣組時(相似服務器擴容或縮容),會致使分區的再均衡。消費者須要讀取最後一次提交的偏移量並從這裏開始從新工做。這樣可能會有重複消息或丟失消息的機率。消費者須要自行處理偏移量。
- Broker。一個獨立的 Kafka 服務器稱爲 Broker 。Broker 處理生產者的消息,並提供給消費者服務。同時也承擔集羣控制器的角色。Kafka 經過 ZK 來管理 Broker 的關聯。Broker 會註冊到 ZK 的一個臨時節點 /kafka/brokers/ids/ 上,當 Broker 崩潰、退出集羣、長時間垃圾回收致使停頓時,Broker 會從 ZK 上斷開鏈接,Kafka 就會獲得通知,臨時節點會被移除。控制器 Broker 會註冊到 臨時節點 /controller 上,其餘 Broker 會建立該臨時節點的 Watch 監聽對象。
- 消息投遞能力:多生產者/多消費者、分批處理(批量投遞消息)、順序保證(按消息生產順序投遞)、保留期限(保留天數或消息量大小)和非實時讀取、分區多副本。
- 可靠性保證:分區多副本。複製係數(可用性與硬件存儲的權衡)、分區副本在不一樣的 Broker 上、Broker 分佈在不一樣機架、首領副本崩潰的權衡、生產者發送確認副本配置(acks=0,1,all, 丟數據、不一致與吞吐量的權衡)、生產者的消息錯誤重試(風險是消息重複)、消費者檢測並讀取消息偏移量。Kafka 只容許首領副本讀取生產者數據,跟隨者副本只負責保持與首領副本一致。不可用或不一致的情形 -- 跟隨者副本不一樣步而首領副本又崩潰的狀況下。若是不容許不一致(好比銀行),則不能容許不一樣步的副本提高爲首領副本。若是丟失數據可接受而不可用不能接受,則可提高不一樣步副本爲首領副本。
代理
- 代理的目標是性能、路由、安全、透明、遲加載、隱藏複雜實現細節。
- 代理技術:四層代理(IP+Port,LVS)、七層代理(IP+Port+Application,Ngnix)。四層代理性能更高,七層代理更靈活。
- 代理模式:靜態代理、動態代理。
代理模式
- 參與者: 目標實例、代理實例、代理邏輯。 目標實例是已知的,須要代理的邏輯須要指定,代理實例須要生成。
- 靜態代理: 編寫和目標實例具備相同行爲的代理實例,並將對目標實例的請求轉發給這個代理實例上。因爲老是須要爲目標對象手動編寫代理實例,所以稱爲靜態代理。靜態代理容易理解,但不夠靈活。
- 動態代理:動態生成和目標實例具備相同行爲的代理實例,並將對目標實例的請求轉發給這個代理實例上。動態體如今能夠爲不一樣行爲的目標對象生成相應的代理實例,而不是手動去編寫代理實現。經常使用動態代理有 JDK 代理和 CGBLIB 代理。
- JDK 代理:經過 java.lang.reflect.Proxy.newProxyInstance + 反射機制實現。適合對接口代理。代理邏輯經過 InvocationHandler 接口定義,Proxy 將實現 InvocationHandler 的實例傳入構造器,生成動態代理實例。代理實例的類繼承自 Proxy 。Proxy 經過proxyClassCache 來管理 ProxyClass 和 ProxyFactory ,並在 getProxyClass0 的時候去緩存 ProxyClass 的信息。
HTTP代理
- HTTP 代理就像客戶端與服務器之間的攔截器。既充當客戶端的角色,又充當服務器的角色。代理能夠級聯,組合使用。能夠經過 Trace 方法和 響應頭的 Via 首部來追蹤報文途徑的網關和代理(Via 有安全與隱私問題)。
- HTTP 代理的做用:過濾(不宜內容)、訪問控制與審計追蹤(安全)、安全防火牆(安全)、流量監控(安全)、緩存(性能,下降網絡開銷和擁塞)、反向代理(性能)、內容路由器(增值服務)、轉碼與壓縮(國際化與性能)、匿名(安全與隱私)、路由與負載均衡(穩定性)。
- HTTP 代理的部署: 出口(LAN 出網點,過濾、安全)、入口(緩存與性能)、邊緣(反向代理)、對等交換點(緩存與安全)。
- 使用 HTTP 代理的方式: 瀏覽器配置、交換或路由設備攔截、修改 DNS 、重定向。客戶端代理配置 -- PAC 文件(提供一個URI, 指向用 JS 寫的代理自動配置文件,會動態計算適合的代理配置);自動代理髮現 -- WPAD ,按順序嘗試 DHCP(動態主機配置協議)、 SLP(服務定位協議)、DNS Known Hosts、DNS SRV 等技術,自動發現合適的 PAC 文件。
- HTTP 代理的問題及方案:客戶端發給代理的 HTTP 請求報文裏應當是包含主機名的完整 URI。但客戶端並不老是知道對方是代理,或者並不知道代理的存在。所以通用代理須要進行「缺失主機名的部分 URI 補全」,拿到主機名拼成完整的 URI(沒有代理時瀏覽器也會作相似的事情)。某些代理會對 URI 作細微修改,影響互操做性。代理的容錯機制(解析到的主機是已停用服務器時)。
- 代理認證:客戶端發送請求,代理髮現沒有認證,會返回 407 響應碼,客戶端拿到 407 後搜索和拿到證書,重發請求,代理認證經過。
組件和配置化
- 組件化: 配置化的基本前提。組件須要定義良好的行爲規範和接口規範。
- 流程編排:須要將整個流程劃分爲若干階段,定義每一個階段的行爲和目標、階段之間的鏈接。
- 動態語言腳本。好比訂單導出使用 Groovy 腳本配置報表字段邏輯。 腳本注意作成緩存對象,避免可能的內存泄漏。
- 選項參數。選項參數的原型是命令行參數。用戶能夠經過選項參數來選擇策略、調節性能等。
- 規則引擎。 將業務邏輯表達爲若干條規則,而後用工做流將規則集合串聯起來。
發佈
- 分流發佈:灰度發佈、藍綠髮布。小批量驗證。分流係數可動態配置和生效。
- 容器化部署。
高性能
提高性能,便是用更少的資源作更多的事情。資源類型包括:CPU 時間片、內存空間、磁盤空間、網絡帶寬、鏈接池、緩存等。弄清楚應用程序依賴哪些資源類型以及依賴程度如何。
性能一般用 RT 和 吞吐量來衡量。 RT 是單個請求的處理時間, 吞吐量是指定時間內處理的請求數。應用也分爲響應敏感型和吞吐量敏感型。好比訂單詳情,就是響應敏感型,RT 太高會致使超時,表現爲服務不穩定; 而訂單導出,則是吞吐量敏感型,單個請求處理長一點不影響體驗,單位時間的吞吐量越高越好。
數據結構和算法的設計是性能提高的基本層面。併發是經過多個 Woker 併發或並行處理的思路。緩存是不一樣讀寫速度的存儲介質及淘汰算法的權衡。精簡開銷和參數調優更可能是針對熱點和耗時進行細節上的調優。編譯優化是語言層面的優化。高配服務器能直接提高性能,但成本較高。最後,性能測量必不可少。只有理論值是不夠的。
高性能的核心:更少的 CPU 週期(數據結構、算法、內存計算),更少的等待時間(IO 讀寫、加鎖與解鎖、內核與用戶態的切換)。
常見硬件性能:L1 cache (0.5ns) > 分支預測失敗(5ns) OR L2 cache (7ns) > Mutex 加鎖與解鎖 (25ns) > 內存訪問 (100ns) > 固態盤 SSD 訪問延遲(0.1ms) > 機房內網絡來回 (0.5ms) > 千兆網絡發送 1MB (10ms) OR SATA 磁盤尋道 (10ms) OR SATA 順序讀取 1MB 數據 (20ms) > 異地機房網絡來回 (30-100ms) 【來源:《大規模分佈式存儲系統》2.1.4 】
數據結構與算法
時間複雜度和空間複雜度。 在空間存儲充足的狀況下,一般考慮時間複雜度。固然,在移動端以及大數據存儲方面,空間複雜度也須要仔細考慮。
- 基本數據結構:數組、位圖、鏈表、棧、隊列、堆、表、哈希散列、二叉查找樹、紅黑樹、DAG、JSON 。
- 基本算法:分治、遞歸、動態規劃、貪心算法;排序、查找;深度優先遍歷、廣度優先遍歷。
- 預處理思想:經過預排序、預索引、拓撲結構等構造特定的數據結構,以支持高效查找,好比 KMP, RETE ,倒排索引,都運用了這種思想。
- 高效查找:有序查找、哈希查找。有序查找 -- 構建有序結構,好比有序鏈表、跳錶、二叉查找樹、紅黑樹,B+ 樹,從而使用二分查找來提高查找效率,減小比較次數,查找時間複雜度 O(logn) ; 哈希查找 -- 構建哈希 key-value 映射結構,解決哈希衝突,查找時間複雜度 O(1) 。哈希查找須要仔細選擇哈希函數(經過參數調優來提高性能),不支持 rank 和 select 操做(第 K 大元素),一般用於 K-V 結構的存儲系統; 順序查找的查找效率與哈希接近,支持 rank 和 select 操做,一般用於有序表、關係型數據庫等。能夠結合兩種查找結構使用。好比 java8 的 HashMap 是哈希查找與順序查找的結合。
- 最優排序:快速排序、合併排序,時間複雜度 O(nlogn) , 根據特殊情形能夠時間換空間或空間換時間策略得到更好性能
- 空間效率:壓縮算法(RLE、增量編碼、哈夫曼編碼、Rice編碼、LZ77編碼、位表示、Trie前綴樹); snappy, gzip, lz4; 圖像視頻壓縮(MLP、CNN、GAN)。
- 生成全局惟一ID: snowflake 算法,生成 64 bit 的 long 型數值做爲惟一ID。41bit (毫秒數) + 10 bit (5bit 數據中心 + 5bit 機器號) + 12bit (毫秒內的偏移量) + 1bit (=0)。
- 判斷 key 存在性 : 位圖(稠密、不重複)、布隆過濾器,O(logn)。位圖可用於稠密不重複數組的排序。
- 動態變化的查找: 一致性哈希。環狀隊列 + 多哈希 + 虛擬節點。構建一個 serverMap = TreeMap[VirtualNodeHash, Server] 的有序映射。對數據進行哈希 h 後,在 serverMap 找到第一個不小於 h 的鍵 S,將該數據分佈到服務器 serverMap[S] 上。哈希算法可採用 Fowler-Noll-Vo 哈希算法。一致性哈希的性質:平衡性、單調性、分散性、負載、平滑性。
- 全文檢索: 倒排索引。關鍵詞分割、關鍵詞所在的文檔及位置的存儲、詞典索引構建與優化。
- 動態規劃:子問題求解、計算結果緩存與複用。要學會劃分可複用的子問題,並理清楚子問題與原問題之間的關聯。
- 堆:排序 O(nlogn)、前 K 最大或最小值 O(n) 。可用於實現優先級隊列。
- 字符串匹配:樸素字符串匹配 - 雙重循環,簡單,低頻場景;有限自動機 - 根據模式字符串構造有限自動機,再匹配文本,適用模式串的不一樣字符數不多的情形; KMP - 根據模式字符串及構造後綴匹配數組(PMT,Partial Match Table,前綴集合與後綴集合的交集字符串的最長長度),避免無用位移測試,適用生產環境。
- SkipList: 有序鏈表,空間換時間,經過在每一個節點上新增多級索引指向後繼節點,實現跳躍查找,平均 O(logN) ,最壞 O(N) 查找效率;批量順序操做;比平衡樹實現簡單。Redis 使用 SkipList 實現有序集合鍵。
- 布隆過濾器: 使用多個哈希函數將一個值映射成多個哈希值,並投影到位圖上,並置爲 1。若是指定 key 經過哈希函數映射到位圖上,有一個位爲 0, 則必定不存在這個 key 。利用布隆過濾器減小磁盤 IO 或者網絡請求。
- 紅黑樹:平衡二叉樹。最壞狀況下查找效率 O(logn)。 2-3 樹的變體。指向左孩子的指針爲紅色的節點標識 3-節點。左旋與右旋。
- 哈希計算:主要基於整數或二進制位來計算。若是是浮點、字符串、IP地址、對象,先將其轉換成整數型,再進行計算。最好能用到 key 裏面全部相異的部分。常見的哈希函數有取模(模最好是素數)、MurmurHash2 算法(源碼能夠在 GitHub 上搜到)。能夠編寫一個程序,來檢測哈希值的分佈均勻度。若是哈希計算代價比較昂貴,須要作哈希值緩存。
- 位操做技巧:通常用於求哈希值、高效實現特殊運算(好比 m mod 2^n == m & (n-1) )、狀態位設置、節省空間等。
- 字符串處理技巧:從尾部開始編輯,能夠避免覆蓋問題。
- 鏈表技巧:快指針與慢指針。
容器
- HashMap: 數組 + 鏈表(在必要時會變成紅黑樹)。 核心操做是 put, resize, get。線程不安全。併發下會發生數據丟失( put 更新 value 時)、死循環( resize 複製數據時)。
- LinkedHashMap: 保持插入序的 Map 。節點採用雙向鏈表。新插入節點或已訪問節點移至鏈表尾部。線程不安全。
- TreeMap: 紅黑樹實現。可順序查找的 Map 。線程不安全。
分區
「【總結系列】互聯網服務端技術體系:可擴展之數據分區」
數據庫索引
「【總結系列】互聯網服務端技術體系:高性能之數據庫索引」
併發
「【總結系列】互聯網服務端技術體系:高性能之併發」
緩存
精簡開銷
- 熱點分析,定位開銷大的地方。好比訂單導出的熱點耗時區域在批量獲取訂單的詳情內容上。
- 移除沒必要要:去掉沒必要要的訪問、去掉重複開銷。好比獲取訂單詳情時不須要的字段就不去訪問相應的 API。
- 精簡鏈路:去掉重複調用,合併調用,調用結果緩存與傳遞。好比交易能夠拿到商品信息並緩存、傳給營銷中心。
- 無鎖化:CAS 機制。去掉加鎖和釋放鎖的操做耗時,增長了 CPU 輪詢開銷(兩害權衡取其輕)。
- 非阻塞IO:Select 和 Epoll 機制。減小沒必要要的線程切換和數據拷貝。
非阻塞IO
- 阻塞IO:須要爲每一個 Socket 創建線程(線程數量有限),建立大量線程(空間開銷大),線程容易被IO阻塞等待(利用率低),大量的線程切換開銷(時間耗費在與業務無關的事情上)。
- 非阻塞IO:IO 多路複用思想;Select機制、Epoll 機制。
- Select 機制 : Buffer - Channel - Selector 。 一個線程能夠管理多個 selector ,每一個 selector 能夠輪詢監聽多個 Channel 的事件並讀取或寫入數據,每一個 channel 與一個 Buffer 相連,提高 IO 讀寫的效率。select 相對於阻塞IO有進步,但仍然有缺點:1. 每次 select 都須要將 fd 集合從用戶態拷貝到內核態,並在內核態遍歷全部的 fd ;fd 越多開銷越大; 2. 一個進程支持的 fd 集合大小受限,默認是 1024 ; 3. 若是沒有 fd 處於就緒狀態,select 會阻塞。
- Epoll 機制:三個函數 --- epoll_create ,epoll_ctl,epoll_wait 。epoll_create 會建立一個 epoll 實例, 其中包含一棵紅黑樹 rbr 存儲須要監控的 fd 集合,雙鏈表 rdlist 存儲返回給用戶的知足條件的事件; epoll_ctl 向 epoll 實例註冊給 fd 要監聽的事件;epoll_wait 等待內核返回監聽 fd 的事件發生,返回已就緒的事件及數量。Epoll 有 LT 和 ET 兩種工做模式。LT --- 檢測到 fd 的事件就緒通知應用程序後,能夠暫時不處理,事件放回就緒鏈表中,待下次調用 epoll_wait 時再通知;ET --- 必須當即處理。 Epoll 的優勢: fd是共享在用戶態和內核態之間的(mmap技術),不須要將 fd 在內核態和用戶態之間拷貝,不須要遍歷就能夠得到就緒的 IO 事件。一個進程支持的 fd 集合大小隻受操做系統限制。
- Netty: ByteBuf - Channel - ChannelEvent - EventLoop - ChannelPipeline - Channel Handler - Callback - Channel Handler Context - ChannelFuture 。Channel 是數據流 ByteBuf 的出入通道,網絡鏈接和數據事件發生的起點。當 Channel 的某個事件 ChannelEvent 發生時,EventLoop 會輪詢 ChannelEvent 已經註冊的 Channel Handler ,並執行 Channel Handler 對應的回調函數 Callback。多個 Channel Handler 能夠構成一個 Channel Pipeline。 Future 提供了異步返回結果和通知的方式。Future 能夠註冊 FutureListener ,從而在 Future 執行完成時回調 FutureListener 的方法 operationComplete。一個 Channel 在其整個生命週期裏只關聯一個 EventLoop 且它的全部 ChannelEvent 都由這個 EventLoop 處理。EventLoop 是 netty 的事件併發執行模型。
- Redis 多路複用: evport, epoll , kqueue , select
HTTP2優化
- HPack 壓縮首部算法,經常使用首部作成靜態表映射獲取, 哈夫曼編碼。
- 借鑑 TCP ,數據劃分爲更小的二進制幀進行傳輸;
- 多路複用,單鏈接多流並行;
- 服務器主動推送相關資源,而非瀏覽器發送屢次請求,減小 TCP 鏈接耗時;
- 應用層重置鏈接和請求優先級;
- 流量控制。
參數調優
- JVM 調優
- 應用配置參數調優。首先提取影響系統功能、性能、穩定性、可用性等的重要因子,而後經過配置平臺來管理。參數配置能夠包括策略選擇、超時設置、重試次數、批次處理數、限流因子等。好比訂單導出的策略選擇有報表維度(商品/訂單/商品訂單均要)、文件上傳下載維度(本地服務器或雲存儲); 訂單導出併發批量拉取訂單詳情,能夠配置分批處理的訂單數,併發拉取的訂單數,每批次訂單處理的時間間隔、超時重試次數等。
服務指標測量
在工程上,測量是尤其重要的。理論只是給出了定性值,而測量將給出更精確的量化值。
- 基本指標: 單機 RT 和 QPS。運行屢次單個請求,取請求響應時間的平均、峯值、最小值、百分位數。平均值即爲 RT 值。QPS = 1 / RT(ms) 。
- 併發指標: 併發數。經過壓測來測試併發負載能力。
高可靠
分佈式
分佈式經過異構、冗餘實現容錯和擴展能力。
- CAP:一致性、可用性、分區容錯。 一致性要求不高的,一般採用 AP; 資金敏感的,一般採用 CP。
- BASE: 基本可用,軟狀態、最終一致性。犧牲強一致性來得到可用性。
- 協議:Paxos,Raft
選舉算法
選舉算法是分佈式系統的基礎。許多分佈式都是基於 Master-Slaves 或 Leader - Followers 模式。通常 Leader 負責寫數據,而 Followers 負責同步寫入的數據到本身的副本上保持與主一致。
- 選舉算法:Bully 算法、FastLeader 算法(ZK)。Bully -- ID 最大的做爲 Leader ,有頻繁換主的風險。
選舉機制的組成部分:
- 選舉輪次標識:時間戳或編號,防止過時選舉消息干擾;
- 選舉檢查項:好比服務器狀態;
- 選舉依據:根據什麼來比較,判斷誰應當成爲 Leader ;
- 投票統計:每一個服務器都投出本身的一票,並接收別人的投票作某種處理,和本身的投票來作統計;
- 選出 Leader : 根據統計結果,大部分服務器都能達到共識,Leader 從共識中產出。
ZK
- 實現分佈式協同服務。CP 模型、ZAB 協議、ZNode、樹形結構、Watch 通知。 ZK 只能存少許元數據,不適合作分佈式存儲系統。
- ZK 的數據結構:節點以樹的形式組織起來。節點分 持久/ 臨時節點,有序/無序節點。
- ZK 服務器數最好爲奇數個:1. 偶數個只容許更少服務器崩潰,更脆弱; 2. 防止腦裂現象。
- ZK 以事務的方式執行全部操做,並確保全部事務以原子方式執行,不受其它事務的干擾。ZK 事務具備原子性和冪等性。zxId 做爲事務的標識。zxId 是 64 位的值,分爲 32 位時間戳和32位的計數器。32位時間戳能夠用於識別當前時段的 Leader 和事務時間戳。
- 選舉算法: 每一個服務擁有一個 Eo (sId, zxId) ,並向其它服務器發送本身的 Eo (sId, zxId) 。 收到 Er 的每一個服務器將 Er 與 Eo 進行比較。若是 Er.zxId > Eo.zxId 或者 Er.zxId = Eo.zxId AND Er.sId > Eo.sId ,則用 Er 替換 Eo 。最終大部分服務器都會擁有相同的 Er。從而選舉出具備 Er 的那個服務做爲 Leader 。
- ZAB:S1- Leader 向全部 Follower 發送一個事務的 Proposal 消息 P; S2- 每一個 Follower 收到消息 P 後響應一個 ACK ,表示贊成該事務; S3- 當仲裁數量的服務器發送 ACK 消息後,Leader 將通知 Follower 進行提交事務。
容錯
- 異常:磁盤故障、服務器宕機、網絡丟包、網絡分區、RPC超時、髒數據、鏈接池滿、內存耗盡。
- 發生頻率:磁盤故障 > 服務器單機故障 > DNS 故障 > 機架故障 > 路由器重啓
- 可預見錯誤處理、異常捕獲、規範定義的錯誤碼和錯誤消息。
- 快速失敗(無冪等的寫操做)、切換服務器重試(有冪等的讀)、失敗忽略並記錄日誌(不影響流程和總體讀)、補償重試(非關鍵數據寫入、後臺任務)、並行調用(一個成功便可)、廣播調用(只要一個失敗就失敗)。
- 冪等方案:惟一索引、樂觀鎖、Token 機制(防頁面重複提交)、分佈式鎖、select+insert、狀態機冪等、查詢/刪除自然冪等。
- 重試:指定重試次數、定時任務重試
- 補償:失敗隊列、補償任務。
健壯性
健壯性是極爲重要的程序質量屬性。分爲代碼健壯性和業務健壯性。健壯性體如今代碼和業務上的錯誤和異常處理上,避免總體失敗,避免數據泄露、不一致、資損等故障。要作出健壯性好的設計和程序,就要預先思考清楚:流程中有哪些可能的錯誤和異常,每一種對應的處理措施是什麼 ? 這樣,才能讓邏輯思惟更加縝密,也是鍛鍊邏輯思惟的一種有效之法。
- 代碼健壯性體如今避免局部失敗致使總體失敗。常見考慮:參數校驗以攔截不合法請求、越界異常捕獲、JSON 髒數據異常捕獲、類型轉換異常捕獲、底層異常捕獲(鏈接異常、DB 異常、網絡超時異常等)。
- 業務健壯性體如今業務的閉合環。在整個業務過程當中會發生什麼異常,致使什麼問題(體驗或資損問題),如何處理。好比同城異常檢測要考慮商家又快遞發貨的情形。
TCP可靠傳輸
- 網絡的不可靠性: IP 數據包的丟失、重複、失序。
- TCP 可靠機制:全雙工鏈接、字節流、數據包有序編號、數據校驗和、確認、定時器與超時重傳、重複丟棄、流量控制(滑動窗口機制)、擁塞控制。
- TIME_WAIT :TCP 鏈接關閉的過程當中,客戶端在發送服務端 FIN 包的 ACK 以後,進入 TIME_WAIT 狀態,須要等待 2MSL 才能關閉。這是爲了確保 ACK 可靠到達服務端。
- TCP 重傳機制: 超時重傳和快速重傳。超時重傳依賴於超時時間的設定,快速重傳依賴確認包的接收。能夠說,選擇不一樣的依賴因子決定了不一樣的解決途徑,也就有不一樣的優勢和缺點。
- 超時重傳機制:TCP 發送數據包後,會啓動相應的定時器,若是超時 RTO 尚未收到數據確認包,就會從新發送該數據包。超時 RTO 的選擇很是重要。若 RTO > RTT, 則可能網絡利用率低,若 RTO < RTT 則可能擁塞。RTO 的選取基於 RTT 的統計及 BEB (二進制指數退避算法),重傳次數及重傳最大超時時間(RFC1122)。 RTO 估計: EWMA RTT 權重估計法(RTO = min(ubound, max(lbound,(SRTT)β)), RTO = (1-2)*SRTT, SRTT ← α(SRTT) + (1 − α) RTTs, α 取 80%-90%, β 取 1.3-2.0),標準估計法(查閱相關文獻)。TCP 能夠記錄每一次的鏈接和傳輸,做爲性能調優的依據(srtt 和 rttvar),能夠作成自適應的。 TCP 重傳時,能夠將多個數據包重組起來一塊兒發送(總大小不能超過 MSS 和 MTU),以提高網絡傳輸效率,也能夠減輕重傳數據包的二義性。因爲 RTO 一般至少是 RTT 的 2 倍,所以超時重傳機制容易致使網絡利用率不高。
- 快速重傳機制: TCP 收到失序數據包後會當即發送確認包,意在告訴發送方迅速發送須要的數據包以填補「包漏洞」(可能存在包丟失)。若是有 dupthresh 個包從新確認了,則極可能存在包丟失,將啓動快速重傳機制,而不是依賴超時。也就是說,依賴確認包的快速發送和重複確認包來決定是否重傳。SACK (選擇性確認)用來解決數據包重複和失序的問題,老是發送最近收到確認的序列號的範圍值。發送者能夠推理出最須要發送的未確認的包,並優先發送這些數據包。可使用位圖來檢測沒有收到的確認序號。快速重傳更高效,不過會產生誤重傳問題。
故障檢測與恢復
- 故障檢測主要靠監控。服務器監控(CPU、內存利用率、Load、IO RW、Net RW)、服務監控(RT、QPS、消費速率
、延遲、網絡鏈接;來源、TOPN)、Java 監控(ThreadPool、Heap、GC 等)、異常監控(失敗次數、失敗比例;超時,消費堆積或不均)、業務監控(瞬時峯值、瞬時下跌、同比上漲或下跌、大數據對象)、對帳監控(數據一致性檢測,尤爲資金相關)。
- 心跳機制:心跳機制用於故障檢測。每臺工做機每隔指定時間將本身的CPU、內存、磁盤、網絡、IO讀寫、負載等狀況上報給 Master, Master 會判斷服務器是否正常,以決定是否分發流量給該服務器。每臺服務器與 Master 有個超時設置,若是達到超時沒有收到該服務器的心跳信息,則會判斷服務器出現問題。心跳機制須要 KeepAlive (鏈接最大空閒時間) 屬性,使用 MQTT 協議。
- 租約機制:每臺工做機向 Master 申請具備必定租約期限的服務時間。當服務時間快到時,再向 Master 繼續申請延長租約的有效期。若是由於網絡或工做機故障致使租約沒法響應,則會將該工做機隔離,再也不提供服務。
- WAL(Write Ahead Log): 預寫日誌,恢復日誌。基本機理是複製狀態機、備忘錄、備份。關係數據庫系統中用於提供原子性和持久性。in-place 和 shadow paging 。提高磁盤寫性能:隨機讀寫改順序讀寫、緩衝單條讀寫改批量讀寫、單線程讀寫改併發讀寫。同步 WAL 文件和數據庫文件的行爲被稱爲 checkpoint(檢查點)。實現方法:DB - undo, redo 機制;ES - fsync 機制;ZK - 先寫 WAL,再更新內存,最後通知客戶端;按期將內存中的目錄樹進行 Snapshot,落磁盤; ETCD - wal 和 snap 目錄; HBase - 更新數據前寫 WAL, 且寫 WAL 與數據更新在同一事務。
- 檢查點(CheckPoint):系統按期將內存狀態以檢查點文件的形式dump到磁盤中,並記錄檢查點時刻對應的操做日誌回放點。 檢查點能夠快速提高故障恢復的速度。
- 備份。備份是應對數據故障(丟失、不一致)的重要保障。備份只是實現數據的恢復,並不能徹底實現故障恢復。故障恢復是指服務或服務器回到正常可用狀態。
MySQL備份
- 邏輯備份和物理備份。邏輯備份是可讀文件、恢復簡單靈活(恢復工具及選項)、在不一樣機器上運行、與存儲引擎無關、避免物理環境致使的數據損壞,但恢復時間較長,須要 MySQL 來完成、須要測試恢復;物理備份一般是不可讀的二進制文件,恢復更簡單(只要拷貝文件到目的路徑)、恢復時間很短,但 InnoDB 的原始文件一般比邏輯備份的文件更大。兩種方式可混合。
高可用
可用性策略
- 可用性評估:平均失效時間(MTBF)和平均恢復時間(MTTR)。不可用時間 -- 99%-5256m,99.9%-526m,99.99%-53m, 99.999%-5m。得到可用性與投入成本是非線性的。越高的可用性須要越高的成本,所以評估應用所需的可用性是必要的。提煉應用的核心模塊,對更小的系統提高可用性成本會更小。使用風險敞口來評估優先考慮的可用性需求。風險敞口 = 失效機率 * 失效損失。
- 宕機緣由分析: 1. 運行環境,磁盤故障居多; 2. 性能問題,大流量殺手;3. 軟件 bug; 4. 變動管理操做出錯。
- 高可用策略:思路 - 避免致使宕機的主要緣由、快速恢復故障。三大措施 - 限流、降級、冗餘。方法層面 - 避免單點失效、冗餘和故障轉移。執行層面 - 主備、對稱佈局;評估組件切換時間和故障恢復時長(估算和演練);備庫演練(記錄切換時的工做負載);虛擬IP、代理、端口轉發、NAT;運維操做安全準則、故障演練。
限流
大流量殺手是形成故障的一大主因。限流措施是針對大流量的。
- 計數器算法:實現簡單。有流量不均衡、臨界大併發問題。
- 滑動窗口算法:切分時間窗格,分而治之。可解決臨界大併發問題。切分窗口足夠細,才能作到精確控制限流值。
- 漏桶算法: 桶的容量恆定,以固定速率流出。若是流入數量超過桶的容量,則丟棄請求。桶能夠用有界隊列實現。控制出口,能夠用於削鋒和平滑流量。如有短期的突發流量,因爲處理速率恆定,可能致使大量請求被丟棄。能夠用「漏桶+緩衝區」的組合,多出來的就放在緩衝區裏,待漏桶處理完後再放入。
- 令牌桶算法:得到令牌做爲業務可執行的許可,桶做爲限流的容器。以固定速率增長令牌值,直到令牌桶的最大值。獲取令牌則可執行,獲取不到則阻塞或直接返回。使用一個有界阻塞隊列保存令牌,一個定時任務用等速率生成令牌放入隊列,訪問量進入系統時,從隊列獲取令牌再進入系統。令牌桶能夠作到最大流量控制,不過不能作到固定流速輸出。
RateLimiter
RateLimiter 是一個限流器,通常用於瞬時大流量場景下對請求流量進行指定速率的限制,將流量速率控制在應用可以處理的範圍內,保持應用的穩定性。RateLimiter 接受每秒 N 個請求,N 是可配置的。RateLimiter 的思路是:
- 因爲要知足在指定時間內的速率限制要求,請求來臨時間點及請求申請許可數的不肯定性, RateLimiter 要作的是對過去請求及所消耗時間的建模。
- 基本邏輯:若是 QPS = 5, 那麼申請一個許可的每兩個相鄰的請求相隔 200ms ( stableIntervalMicros )。若是已經夠了,則要當即處理;若是不夠,則要等待知足 200ms 再處理。申請 N 個許可的當前請求與最近一個請求應該相隔 N * 200ms 。
- 「Past underutilization」 :空閒時段的許可建模,使用 storedPermits 來表達。Past underutilization 意味着過去一段時間請求量很是少,利用率低,且服務器可能須要預熱(好比創建鏈接、加載緩存)處理。storedPermits 用來表示在過去一段空閒時間內儲存的令牌數(能夠當即使用)。對於每一次申請許可的請求,RateLimiter 從兩個地方獲取許可:1. storedPermits ; 2. fresh permits 。
- 對於每一次申請 permits 的請求,老是須要根據上一次計算獲得的 nextFreeTicketMicros 來計算這次須要等待的時間 microsToNextFreeTicket (delay of current request) ,若大於 0 則要休眠。
RateLimiter 支持兩種方式:帶熱身的 warmUp 方式和支持突發流量的 Bursty 方式。默認 Bursty。
- RateLimiter 的計算是以微秒 toMicros 爲單位的。限流計算主要是關於時間和數量的。RateLimiter 在初始化時會啓動一個計時器,全部的時間都是相對於啓動時刻點的相對納秒數。
- resync: 若是當前請求姍姍來遲(當前時間點遲於上一個請求計算獲得的 nextFreeTicketMicros ),則計算能夠儲存的令牌數 storedPermits。這個方法完成了空閒時間建模。
- reserveNextTicket:核心方法。根據上一個請求計算的 nextFreeTicketMicros 來計算當前請求須要延遲的時間以及下一個請求的必須等待的時間點 nextFreeTicketMicros。因爲 RateLimiter 採用的是預消費模式,所以,每個當前請求會受到上一個請求的執行時間的影響。
- storedPermitsToWaitTime:從 storedPermits 獲取所須要的 permitsToTake 須要等待的時間。對於 Bursty 來講是 0, 對於 warmUp 來講是經過函數計算的(從 halfMaxPermits 到 maxPermits 的一條傾斜直線,斜率 slope = 2*stableIntervalMicros/halfMaxPermits )。這是兩種限流方式的最主要區別。
- maxPermits: Bursty 經過指定秒數來指定最大 maxPermits, maxPermits = secs * permitsPerSecond; 而 warmUp 經過熱身時間來指定最大 maxPermits, maxPermits = warmUpTime * permitsPerSecond.
- RateLimiter 支持併發。使用了 synchronized(object) 的方式,經過減小持鎖邏輯增大併發度。
熔斷降級
某個基礎服務的不可用,可能致使雪崩效應(整個基礎設施的級聯性故障)。降級的目的:1. 防止雪崩效應; 2. 防止局部次要失敗影響總體可用性。
- 思路:快速失敗和 Fallback 機制。
- 斷路器設計模式:檢查故障並封裝邏輯來阻止在系統維護期間、外部系統暫時出現故障期間和意外系統故障期間,錯誤反覆不斷地產生。斷路狀態機:CLOSE - HALF OPEN - OPEN。斷路器使用稱爲 HALF-OPEN 狀態的監視和反饋機制來了解 Supplier Mircoservice 是否以及什麼時候恢復。 它使用這種機制按期對 Supplier Mircoservice 進行嘗試調用,以檢查它是否已經恢復。 若是對Supplier Mircoservice 的調用超時,則斷路器保持在 OPEN 狀態。 若是調用返回成功,則電路切換到CLOSED狀態。 而後,斷路器在 HALF-OPEN 狀態期間將全部外部調用返回到服務併發生錯誤。與命令模式結合使用。斷路狀況:失敗百分比、失敗絕對次數、強制直接熔斷。
- 請求攔截(AOP)、消息隊列、特徵識別、實時計算與分析、反饋式熔斷和快速返回
- 熔斷實現原理:STEP1- 解析熔斷配置,構造請求(方法簽名)與 fallback 的映射關係 FallbackMethodMap[Method, FallbackConfig],其中 FallbackConfig 包含 fallbackMethod 及調用該方法的失敗閾值配置;STEP2- 作一個 AOP,使用 ConcurrentHashMap[Method, FailureCount] 收集請求(方法簽名)及請求失敗結果,當該請求的失敗統計 FailureStat 匹配指定配置閾值 FallbackConfig 時,就觸發熔斷,調用 fallbackMethod 。
- 斷路器的負載問題:1. 斷路器可看作是一個高可用的微服務,其做用是統計應用成功或失敗的情形,並給出合適的策略;2. 能夠作成自適應的。記錄最近若干次調用狀況,若是均是成功,則不調用斷路器;只有當調用出現異常時,纔開啓斷路器模式;3. 斷路器是錦上添加的做用,即便斷路器失效,也不該當影響服務的正常調用;4. 斷路器能夠持續監聽服務的狀態,在業務應用中加一個依賴服務的標誌位 Map,當某個服務出現問題時,斷路器能夠推送消息給該應用,設置標誌位。這樣,能夠異步去設置斷路,而沒必要每次都調用一次斷路器。
- 滑動窗口機制:將統計時長切分爲多個桶,多個桶能夠聚合成一個滑動窗口。
冗餘
冗餘是保證可用性的基礎。
- 冗餘是指一份數據有多個副本存儲在不一樣的節點上。冗餘是應對容錯和提高可用性的重要機制,同時又會帶來一致性問題。
- 副本協議:中心化副本協議,primary-backup 協議、去中心化副本協議。主節點寫入成功後,發送操做日誌給指定節點或所有節點的副本,指定節點或所有節點根據操做日誌寫入成功後回覆主節點,主節點回複寫入成功。
- 一致與可用:強一致複製 -- 若是全部副本寫入成功,主節點才返回客戶端成功,則是強一致同步複製。但若是某個副本失敗,則總體寫入失敗,此時沒法達成可用性。弱一致複製 -- 只要主副本寫入成功,就返回客戶端成功,經過線程異步發送給其餘節點去更新副本,則是弱一致同步複製。某個副本寫入失敗,不影響總體寫入的結果。此時,能夠達成可用性,但沒法達成強一致性。
- 多個副本不能所有放在同一個節點或機架上。不然,節點或機架故障時,數據會丟失,沒法達成可用性。
數據拷貝
- 數據拷貝是指數據在兩個端點之間進行傳輸。複製是帶有策略和目標的大規模數據集的可靠拷貝。互聯網從拷貝起航,通往復制。
- 賦值:賦值是數據在內存中的兩個變量之間進行拷貝。對於嵌套結構,賦值可分爲深拷貝和淺拷貝。淺拷貝只拷貝數據的引用(引用兩份,數據一份),引用的數據是共享的;深拷貝則是建立了徹底相同的數據,即存在兩份一樣的數據,引用的數據是隔離互不影響的。
- IO:數據在網絡/磁盤和內存之間傳輸。Linux 將磁盤、網絡等設備抽象爲文件,從而可以用統一的方式來處理 IO。 要訪問一個設備時,內核返回一個非負整數的文件描述符(進程中當前沒有打開的最小描述符),用來操做這個對應於設備的文件。內核中保持一個偏移量,用來定位讀寫到的文件位置。一個打開的文件包括三部分:描述符(文件的引用,進程專享)、文件表項(文件位置、引用計數等,全部進程共享)、v_node 引用項(指向全部進程共享的 v_node 表,存放文件元數據)。IO 要考慮效率,使用緩衝機制、 IO中斷和 DMA 技術來實現。
- 緩衝機制。數據先從網絡/磁盤拷貝到內核的數據緩衝區,再拷貝到用戶程序空間的數據區。反之亦然。用戶程序只與操做系統的數據緩衝區打交道,而與磁盤傳輸解耦。還有一種直接 IO 的方式,應用程序不通過內核緩衝區來傳輸數據,不過也須要本身去設計一套緩存機制,好比 DBMS 。
- I/O 中斷與 DMA:PIO -- CPU 發出 IO 請求,磁盤將數據放入磁盤緩衝區,而後向 CPU 發出中斷信號,CPU 將數據從磁盤緩衝區拷貝到內核緩衝區再拷貝到用戶緩衝區,全程須要耗費 CPU 資源,可能致使系統在 IO 讀寫時無響應。DMA -- 磁盤將數據放入磁盤緩衝區後,系統主內存與磁盤或網絡之間的數據傳輸能夠繞開 CPU 的全程調度,DMA 會與外部設備交互,完成數據在外部設備和內核緩衝區之間的傳輸。
- 零拷貝技術:即便是 DMA ,也會多一步內核緩衝區與用戶緩衝區之間的傳輸開銷,耗費 CPU。零拷貝技術是爲了不這層開銷。主要經過 mmap 來實現。 mmap 是基於虛擬內存管理實現的。mmap 的目的是將內核中讀緩衝區(read buffer)的地址與用戶空間的緩衝區(user buffer)進行映射,從而實現內核緩衝區與應用程序內存的共享,省去了數據在內核讀緩衝區(read buffer)到用戶緩衝區(user buffer)之間的傳輸過程。
- 健壯性。 IO 讀寫會遇到不足值(傳輸字節數比所須要的少)的問題。磁盤 IO 遇到不足值一般是 EOF,而網絡傳輸考慮緩衝約束和延遲,要頻繁處理不足值問題。這就須要一個健壯讀寫 IO 的系統函數 RIO。
- IO SDK。磁盤讀寫首選標準 IO, 網絡讀寫首選 RIO。標準 IO 和 RIO 都難以知足需求時,使用系統 IO。
複製機制
- 分佈式冗餘機制:數據分片存儲、冗餘副本、宕機檢測、自動副本遷移、多餘副本刪除。
- 複製拓撲:主從 Leader - Followers 或 Master - Slave ; 多主 Multi-Leaders ; 無主 Leaderless 。大多數採用的是主從拓撲。
- 複製依據:複製一般要根據某個相似日誌文件來進行。MySQL 基於 binlog 日誌,Redis 基於 redis 命令的快照文件。 通常是依據數據變動文件。能夠基於語句、二進制日誌、邏輯日誌、WAL。
- 複製方式:同步或異步。同步能夠保證至少有一個 backup 可用,但會阻塞寫操做。因爲複製節點的不可靠、網絡延遲等可能致使複製節點回復時間長,所以,複製操做一般採用異步機制。或者採用半同步機制(指定部分複製節點回復 OK 就返回給用戶)。複製能夠自動配置、基於條件觸發。基於應用代碼的複製能夠更加靈活可控。
- 複製步驟:歷史數據複製和增量複製。STEP1 -- 首先 Leader 生成一份某個時間點的快照文件,發送給 Follower , Follower 執行快照文件裏的全部操做變動; STEP2 -- Follower 鏈接 Leader ,請求自快照生成時間點以後的全部數據變動請求,並以此處理後,就與 Leader 的數據保持一致了。
複製延遲
複製延遲會致使主從節點的數據在一段時間內的不一致,這種不一致會給用戶帶來困擾。在實現最終一致性時,須要評估複製延遲對系統的影響,並判斷須要做出哪些一致性的保證。
- Read-After-Write :寫數據後要求當即能讀到。方案:1. 能夠檢測數據是否被修改過,若修改過讀主,不然讀從。能夠經過 profile 信息來記錄和判斷是否修改過; 2. 監控主從延遲。 在延遲內讀主,延遲外讀從; 3. 客戶端記錄最近更新的時間戳,服務端能夠根據這個時間戳決定由主仍是由某個已更新的歷來響應讀。 跨設備或跨數據中心的 Read-After-Write 一致性須要考慮更多的因素,好比節點的時鐘不一樣步。
- Monitonic Reads:複製延遲可能致使在不一樣的 Followers 上讀到的數據不一致。好比用戶反覆刷新頁面時,在一個已更新的 Follower 讀到最新數據,隨後在一個沒有更新的 Follower 讀不到數據,就像時間回退了同樣(Moving Backward in time)。方案:保證同一個請求被轉發到同一個 Follower,好比使用 ID 的哈希映射到特定節點上。
- Consistent Prefix Reads: 複製延遲可能致使果在因以前看到。
複製容錯
複製容錯主要是基於分佈式容錯機制。
- Follow Failure : Follow 會持續保存一份讀取從 Leader 接收的 replica log ,請求自 Failed 的時間點以後的數據變動,並執行到所在節點。
- Leader Failure: 首先要基於一致性協議選擇一個新的 Leader ,並配置使用新的 Leader 。客戶端的寫請求會發給新的 Leader 。Leader Failure 須要考慮的問題 -- 判斷原來 Leader 宕機的超時時間選擇; 新 Leader 不必定跟上了原來 Leader 的數據更新;有多個新 Leader(腦裂)。
MySQL複製機制
- 複製用途:多數據中心的備份、密集讀操做的負載均衡、避免單點失敗、故障切換、升級測試。
- 複製機制:基於語句的複製和基於行的複製。MySQL 複製的基礎是 binlog 日誌。在主庫記錄 binlog 日誌( SQL 或 被更新的數據記錄),在備庫重放日誌的方式實現異步的數據複製。能夠將讀操做指向備庫,從而加強讀操做的可擴展性。複製對主庫的開銷:讀取二進制日誌、鎖競爭、主庫處於高吞吐量時。
- 複製步驟:STEP1-主庫在每次準備提交事務完成數據更新前將數據更新的事件記錄到 binlog 中。按照事務提交的順序來記錄。記錄 binlog 後,通知引擎提交事務;STEP2-備庫 IO 線程將主庫的 binlog 複製到本地的 relaylog 裏(備庫起IO線程-與主庫創建客戶端鏈接-主庫起特殊的二進制轉儲線程讀取主庫的二進制日誌,備庫IO線程將二進制日誌讀取到 relaylog 裏);STEP3-備庫 SQL 線程從 relaylog 裏讀取事件並在備庫執行。
- 複製方式:基於語句的複製 --- 實現簡單、二進制日誌緊湊,佔用存儲和網絡帶寬不多、操做靈活、容易定位問題。缺點是:語句執行是否成功及快慢、結果可能依賴環境因素、使用觸發器或存儲過程的語句的複製可能失敗、執行代價很高但只選擇或更新不多行的語句複製成本高、只能串行須要加更多鎖。mysqlbinlog 工具是基於語句複製的好工具。基於行的複製 --- 將實際數據記錄在二進制日誌裏。適用場景普遍、能夠正確複製每一行、執行代價高但更新少數行的語句更高效複製。缺點:有些簡單語句的更新行數多,二進制日誌不少,增長主庫的負載;沒法判斷執行了哪些 SQL,難以排查問題。默認基於語句的複製。
- 複製拓撲:一個主庫能夠對應多個備庫,一個備庫只能對應一個主庫且必須有惟一的服務器ID(惟一ID用於打破一些複製無限循環)。備庫能夠用於 --- 不一樣角色使用、待用主庫、容災、數據恢復、培訓測試等。經常使用推薦拓撲:一主多備、主動-被動模式下的主-主複製、主庫-分發主庫-備庫、樹形。
- 複製方案:選擇性複製-分庫分表複製-水平數據劃分、OLTP 與 OLAP 功能分離、備庫上實現數據歸檔、日誌服務器。
- 主從複製延遲: 1. 主庫併發,從庫單線程,經過 sharding 打散或升級 Mysql 5.7 開啓基於邏輯時鐘的並行複製; 2. 主庫大事務拆分爲小事務,大語句拆分爲小語句; 3. 對大表執行 DDL,找到阻塞的 DDL 並幹掉; 4. 缺少主鍵或惟一索引,檢查表設計,確保有自增主鍵和合適索引; 5. 主從配置不一致,儘可能統一配置; 6. 從庫壓力過大,分解爲多個從庫。
Redis複製機制
- 持久化:快照和 AOF 。
- 快照機制:使用 BGSAVE 建立快照。 BGSAVE 是異步的,會 fork 子進程來寫入硬盤,父進程仍然處理請求。SAVE 是同步的,沒法在建立快照的同時響應命令。快照存在丟數據的機率,適合於非核心數據場景,丟失數據也無損失的。配置 save timeInSecs writingTimes 表示從最近一次建立快照開始,在 timeInSecs 秒內若是有 writingTimes 次寫入,則會使用 BGSAVE 建立快照。若是有多個 save 配置,則任一個知足都會建立快照。
- AOF機制。 Redis 將執行的命令寫入到 AOF 的末尾,記錄數據發生的變化。只要自某個起始點從新執行 AOF 文件中包含的全部寫命令就能夠恢復AOF 所包含的數據變化。選項有 appendonly (yes or no) 和 appendsync (always, everysec, no)。always 會最小化數據丟失的機率,但會對 Redis 處理命令的性能有很大影響。通常推薦 everysec 選項。AOF 機制要解決 AOF 文件膨脹的問題。能夠發送 BGREWRITEAOF 命令來合併 AOF 中的冗餘命令,減小 AOF 文件大小。BGREWRITEAOF 也是起一個子進程來異步執行。BGREWRITEAOF 的配置選項:auto-aof-rewrite-percentage (大小膨脹到原來的百分比) 和 auto-aof-rewrite-min-size (大小膨脹到最低多少 MB)。
- 一主多從。從服務器用來減輕讀請求對主服務器的負載壓力。主從複製過程:STEP1: 從服務器發送 SLAVEOF host port 鏈接主服務器,發送 SYNC 命令;STEP2: 主服務器執行 BGSAVE 生成快照文件,並往緩衝區記錄增量寫命令;STEP3: 主服務器的快照文件生成以後,會發送給從服務器,快照文件發送完畢以後,會發送緩衝區寫入的增量寫命令;STEP4:從服務器解析快照文件,覆蓋原有數據;STEP5:從服務器接受主服務器發送來的緩衝區增量寫命令,執行命令實現增量同步。STEP6:主服務器發送完緩衝區的增量寫命令後,每接收一條寫命令,執行完後就向從服務器發送相同的寫命令,從服務器接收到寫命令並執行,從而與主服務器保持一致。NOTE: 從服務器在同步時,會清空原有的數據。Redis 不支持主主複製。
- 主從鏈。從服務器也能夠擁有從服務器,從而構成樹狀的主從鏈。當從服務器開始解釋快照時,會斷開從從之間的鏈接。
一致性
數據的多副本在必定時刻點以後保持一致性的要求。
分類
要保證數據一致性,根據應用實際需求,有多種一致性的定義。
- 時間點一致性:若是全部相關的數據組件在任意時刻都是一致的,那麼能夠稱做爲時間點一致性。
- 事務一致性:事務的一致性指的是數據庫的數據從事務執行以前的有效狀態變動爲事務執行以後的另外一種有效狀態。數據有效狀態是指必須知足數據的約束與完整性(事先在數據庫裏定義的數據規則集合)。若是事務成功地完成,那麼系統中全部變化將正確地應用,系統處於新的有效狀態。若是在事務中出現錯誤,那麼系統中的全部變化將自動地回滾,系統返回到原始有效狀態。
- 應用一致性:在應用程序中涉及多個不一樣的單機事務,在全部的單機事務完成以前和完成以後,多個應用的數據老是符合指定的規則集合。
- 區別與聯繫:事務一致性和應用一致性,能夠當作數據約束和完整的有效性,這些數據不必定是同一個數據項的定義(好比支付成功後送優惠與送積分,退款後退券與退積分);時間點的一致性,主要是多副本的數據內容要相同,一般屬於同一個數據項定義(好比DB、ES、HBase 上的訂單狀態是同一個值)。
程度
一致性程度的觀察角度:從全部的複製節點來看。
- 一致性程度: 線性一致性 > 順序一致性 > 因果一致性 > 最終一致性
- 線性一致性:亦稱原子一致性。任何進程的寫操做,若是一個節點進程的讀操做讀到了最新值,則全部後續的讀操做都應當讀到最新值。反之,則不知足線性一致性。難點:分佈式下的統一的全局時鐘。
- 順序一致性:順序一致性是指全部的進程以相同的順序看到全部的修改。讀操做未必能及時獲得此前其餘進程對同一數據的寫更新。可是每一個進程讀到的該數據的不一樣值的順序是一致的。
- 因果一致性: 有因果關係的操做知足順序一致性。好比一個線程先寫一個變量,再讀一個變量,那麼它讀到的必定是寫以後的值。就知足因果一致性。
- 最終一致性:不須要關注中間變化的順序,只須要保證在某個時間點一致便可。
手段
Raft協議
- Raft 是一個基於複製狀態機的一致性算法,用於分佈式存儲系統。Raft 將一致性保證分解以下子問題:Leader election, Log replication, safety, and membership changes。Raft 保證一個強有力的 Leader ,只容許 Leader 發送複製日誌 ;複製狀態機一般使用複製日誌實現,每一個服務器存儲一個包含一系列命令的日誌,其狀態機按順序執行日誌中的命令。 每一個日誌中命令都相同而且順序也同樣,所以每一個狀態機處理相同的命令序列。 這樣就能獲得相同的狀態和相同的輸出序列;Safety 機制用來保證在 Leader, Follower 崩潰的狀況下,依然可以保證 Leader 總有全部已提交的日誌。一致性算法的工做就是保證複製日誌的一致性。
- Leader election:時間被隨機劃分爲多個時間片。每一個時間片做爲一個「任期」,在任期內進行選舉,選舉出來的 Leader 負責管理 log replication。Leader 週期性地向全部 follower 發送心跳(不包含日誌條目的 AppendEntries RPC)來維持本身的地位。每一個服務器都有一個任期號。若是 RequestVote RPC 中的任期號比本身小,那麼 candidate 就會拒絕此次的 RPC 而且繼續保持 candidate 狀態。Raft 算法使用隨機選舉超時時間的方法來確保不多發生選票瓜分的狀況,就算髮生也能很快地解決。
- Log replication:Leader 一旦被選舉出來,就開始爲客戶端請求提供服務。客戶端的每個請求都包含一條將被複制狀態機執行的指令。Leader 把該指令做爲一個新的條目追加到日誌中去,而後並行的發起 AppendEntries RPC 給其餘的服務器,讓它們複製該條目。當該條目被安全地複製,leader 會應用該條目到它的狀態機中(狀態機執行該指令)而後把執行的結果返回給客戶端。若是 follower 崩潰或者運行緩慢,或者網絡丟包,leader 會不斷地重試 AppendEntries RPC(即便已經回覆了客戶端)直到全部的 follower 最終都存儲了全部的日誌條目。複製狀態機用於解決分佈式系統中的各類容錯問題。
- Safety:Raft 使用投票的方式來阻止 candidate 贏得選舉除非該 candidate 包含了全部已經提交的日誌條目。候選人爲了贏得選舉必須與集羣中的過半節點通訊,這意味着至少其中一個服務器節點包含了全部已提交的日誌條目。若是 candidate 的日誌至少和過半的服務器節點同樣新(接下來會精確地定義「新」),那麼他必定包含了全部已經提交的日誌條目。RequestVote RPC 執行了這樣的限制: RPC 中包含了 candidate 的日誌信息,若是投票者本身的日誌比 candidate 的還新,它會拒絕掉該投票請求。Raft 經過比較兩份日誌中最後一條日誌條目的索引值和任期號來定義誰的日誌比較新。若是兩份日誌最後條目的任期號不一樣,那麼任期號大的日誌更新。若是兩份日誌最後條目的任期號相同,那麼日誌較長的那個更新。Safety 五法則:1. 至多隻有一個 Leader 被選舉; 2. Leader 只能添加新日誌條目,不能修改或刪除日誌條目; 3. 兩條日誌條目具備相同的 index 和 任期,則視爲等同; 4. 若是一個日誌條目被提交,那麼它必定出如今最高任期的 Leader 的提交日誌條目裏; 5. 若是一個節點服務器應用了某個 index 在狀態機上,那麼沒有任何一個其餘節點服務器會在相同的 index 具備不一樣的日誌條目。
海量
搜索
ES
- 存儲模型:JSON 串,沒法部分刷新。只能寫入新的文檔,而後刪除舊的文檔,按期刷新清理。
- 實現原理:倒排索引 - trie 前綴樹 - Skip List - 增量編碼。倒排索引 - 構建關鍵字詞典,存儲關鍵字及其文檔編號、文檔位置; trie 前綴樹 - 爲關鍵字詞典構建詞典索引(term index)以 FST 放在內存裏,減小磁盤 random access 次數,節省空間;SkipList - 聯合搜索的有序 DOC ID 列表的求交集;增量編碼 - 獲取到 DOC ID 列表後,對 DOC ID 進行增量編碼存儲,求出每一個 DOC ID 針對前一個 DOC ID 的增量,只存儲增量值。
- 四種查詢: query and fetch, query then fetch, DFS query and fetch, DFS query then fetch。 ES 查詢數據實際上包括兩個步驟:查詢到符合搜索條件的 DOC ID 列表(query),根據 DOC ID 列表獲取 DOC 數據(fetch)。 and 就是把兩個步驟合成一個步驟執行,能夠節省網絡請求開銷(若是數據量不大),then 則是先查詢後取數據(減小拿到沒必要要的數據量)。DFS 是爲了讓結果更加精確。
- 深分頁問題:[ from,size ] 須要從 N 個分片中各取出 (from+size) 條數據,總共 N*(from+size) 條數據。from 很大時,要取出很是多的數據,才能返回指定的少許數據。 深分頁方案:scroll 與 search_after 及對比。search_after 無狀態,指定惟一標識及排序,能夠實時查下一頁,不支持跳頁查詢,集羣資源消耗小;scroll 有狀態,取一段時間的快照,新寫入數據查不到,scroll context 開銷大,集羣資源消耗大。
- 多表同步問題:1. 順序隊列方案,使用業務 ID 做爲消息投遞順序依據; 2. 非順序隊列方案,全局中間存儲、全局版本號遞增、加鎖。全局版本號更新加鎖。順序隊列方案的問題:吞吐量擴展困難(取決於消息中間件實現)、容易堵塞形成消息堆積。非順序隊列方案:解決了順序隊列方案的難擴展、易堵塞問題,代價是:方案更復雜,在高併發下加鎖 RT 會上漲。
存儲
存儲引擎
- 哈希存儲引擎:K-V 存儲 。 Append-Only , 老文件只讀不寫,只寫一個活躍文件;內存哈希表存儲 key 和 value 的位置;老文件會作合併並創建索引文件(可用於快速故障恢復)。
- B+ 樹存儲引擎:數據存於葉子節點,內節點存儲更多的 key 值,減小鍵值比較時的磁盤讀。
- LSM 存儲引擎(LogStructuredMergeTree): 數據修改增量存儲在內存裏,達到必定大小時批量寫入磁盤。讀取時須要合併歷史數據和新的增量修改。優點是:磁盤順序寫。 LevelDB實現:操做日誌 Commit Log(寫以前記錄操做,故障恢復)、可變 MemTable(增量寫)、不可變 MemTable(合併刷入磁盤)、SSTable(刷入磁盤持久化後的文件)。
DB
- 適用場景:適合固定 Schema 的結構化方式來存儲大量結構化數據。大規模結構化數據存取的優先選擇。
- 主要優點:數據存儲的規範性、完整性;事務與持久化。插入和查詢的效率高,但更新的代價較高。
- 開發要點:數據模型與表設計(規範性和完整性,場景走查)、索引設計與 SQL 語句(性能考量)、併發控制與事務(數據一致性保障)、鏈接配置優化(穩定性)。
- 注意事項:須要在數據規範範式與冗餘之間達成一個平衡點;避免死鎖。
Redis
- 適用場景:適合小規模數據子集的頻繁快速讀寫。好比應用數據緩存、分佈式鎖、數據聚合/排名、關聯關係、任務隊列等。
- 主要優點:內存讀寫,性能高、集中化,更新效率高。
- 開發要點:須要可以靈活使用數據結構(字符串、列表、集合、散列、有序集合)及組合來實現業務功能。
- 注意事項:須要限制數據集的最大值及遏制快速膨脹,處理好過時清除問題。選擇適當的緩存策略和緩存讀寫策略,避免緩存雪崩/擊穿/穿透問題/一致性問題。
HBase
- 適用場景:適合大規模非結構化數據的存儲讀寫。
- 部署架構:Master/Slave,LSM, [ Master, RegionServer, HRegion, HDFS,Zookeeper ] 。
- 數據存儲模型 :[ Rowkey, ColumnFamily, ColumnName, Value, Timestamp ] (邏輯) ; [ Store, MemStore, StoreFile, HFile, HLog ] (物理)。一個 HRegion 包含 一個 HLog, 多個 Store 。 一個 Store 包含 一個 MemStore ,多個 StoreFile, 多個 HFile 。
- 列寫入:動態列寫入,無需事先聲明;列寫入的過濾機制:對於新的列值 ,若其 Timestamp 比現有的小,則數據不會寫入。
- 查詢及優化:1. 根據 rowkey 查詢 HRegion 地址 (zk 緩存以及 hbase:meta 表); 2. RegionServer 處理讀請求。Scanner 對象,最小堆。客戶端讀優化: get 請求 - 啓動時表預熱,rowkey 均衡,適量批量 get ,指定列族; scan 請求 - 改造爲 batchGet ,設置合適的 startKey 和 endKey ,設置 caching 數量。 服務端優化:讀緩存配置、本地化率,SSD 盤,短路讀等。
- 開發要點: Rowkey 設計及 region 分佈平衡;啓動預熱表、超時設置、主備設置; BatchGet 替代 Scan 。
- 注意事項: 避免數據分佈不均勻。
文件系統
分析
實時計算
安全
- 常見安全問題:緩衝區溢出、XSS、SQL注入、DDOS
- 安全評估:資產(數據)等級分析、威脅分析、風險分析、解決方案確認
- 機密性保證:加密(對稱加密和非對稱加密)、脫敏(敏感信息避免模糊化處理)、代理隱藏、權限管控
- 完整性保證:數字簽名
- 可用性保證:黑名單、限流、防火牆、清洗流量
- 威脅建模:STRIDE 模型。假裝、篡改、抵賴、信息泄露、拒絕服務、提高權限。
- 風險分析:DREAD 模型。Damage Potential(完整驗證權限、執行管理員操做、非法上傳文件)、Reproducibility (隨意再次攻擊)、Exploitability(易學性,初學者能夠很快學會)、Affected Users(全部用戶、默認配置、關鍵用戶)、Discoverability(漏洞很顯眼,攻擊條件易得)
- Secure by Default: 白名單原則 , 最小權限原則。 採用黑名單容易繞過或遺漏。白名單要避免範圍過大的通配符。
- 縱深防護原則:從不一樣層次、不一樣方面實施安全方案,構成防護總體。避免薄弱環節與短板。
- 數據與代碼分離原則:防止 緩衝區溢出、SQL 注入、XSS。
- 不可預測原則:讓系統的關鍵運行區域顯示出隨機性,從而大幅提高惡意構造請求來攻擊的難度和成本。請求 token 機制。
- 源頭堵截原則:在源頭上堵截漏洞,而不要依靠不少人經過學習來掌握防護技術。讓防護變得易學易用。
智能化
平臺
操做系統
內存管理
- 虛擬內存。創建從虛擬地址空間到物理內存地址的映射。由 MMU 中的地址翻譯硬件、頁表(PT,常駐物理內存,進程專享)、操做系統共同實現。虛擬內存的用途:1. 磁盤空間的緩存工具; 2. 內存管理工具,爲進程提供獨立的地址空間,互不影響; 3. 靈活的內存分配,虛擬地址能夠連續,而對應的物理地址能夠不連續;4. 保護內存不受進程破壞,不容許修改只讀代碼段和內核的代碼與數據,不容許修改其餘進程的地址空間的數據,不容許修改共享頁面。能夠爲 PT 添加一些許可位來控制進程的讀寫權限。
- 內存佈局。指各類元數據、數據、代碼段在內存中的有序佈置。設計良好的內存佈局,更有利於訪問數據和代碼。具體示例可參考 ELF 可重定向目標文件格式。JVM 也有本身的內存佈局。
- 內存分配。內存分配策略 - 首次適配(簡單)、最佳適配(碎片小,但很難再次分配,須要合併)、最壞適配(分配後仍然可再次分配,大塊內存難以知足)。算法 - Buddy, CMA, slab。Bump the Pointer(若是未使用內存與已使用內存分離了,Serial, ParNew)、Free List(找適配大小的空閒塊,Mark-Sweep, CMS)。分配內存的併發問題解決方案:CAS 機制和 預先分配的 TLAB(本地線程緩衝分配)。指向對象的指針或引用在虛擬機棧上分配。
程序執行
- 連接與加載。將各類代碼片斷、數據片斷進行收集並組合起來,構成完整的可執行目標文件,並加載到內存中執行的過程。連接與加載涉及模塊和庫依賴、符號引用、做用域、動態共享庫之類的話題。連接器的主要任務:符號引用和重定向。每一個可重定向目標模塊都有一個符號表,用來作符號解析。
JVM
內存管理
- 內存佈局:PC寄存器(指令地址)、虛擬機棧(方法執行)、本地方法棧(Native方法執行)、堆(對象內存分配)、方法區(類加載信息、運行時常量池、靜態變量、即時編譯信息等)、直接內存(NewIO)。每一個線程都有專屬的【虛擬機棧、PC寄存器 、本地方法棧】。
- 對象建立和初始化:new 參數 -> 定位類引用 -> 檢查類是否加載、解析、初始化 -> 若是沒有則執行類加載過程 -> 爲對象分配內存 -> 分配空間清零 -> 對象頭內容設置 -> 執行 _init 方法初始化對象。
- 對象內存佈局:對象頭(MarkWord)、實例數據、對齊填充。 對象頭 -- 對象哈希碼、分代年齡、輕量鎖、重量鎖、偏向、類元數據指針。實例數據 -- 虛擬機字段分配策略和程序指定順序。字節數相同的類型的字段放在一塊兒分配。對齊填充 -- 對象起始地址必須是 8 的整數倍。訪問對象的方式:對象句柄池和直接指針。使用對象句柄的好處是引用的地址是穩定的,只要改變句柄的值(對象移動時),使用直接指針的好處是效率更高,減小一次指向過程。
- 對象引用分析:對象可達性遍歷判斷對象是否活躍。強、軟、弱、虛引用。強 - 只要有引用就不回收;軟 - 在拋出 OOM 以前進行回收;弱 - 下一次 GC 時回收;虛 - 持有虛引用的對象在GC時能夠收到系統通知。
- GC算法:複製算法(新生代,優化:迭代避免溢出、多空間提高空間利用率);標記-清除算法(老年代,優化:多空閒鏈表、位圖標記、延遲清除)。 GC 收集器:三個維度,單線程與多線程,Client 與 Server,新生代和老年代。Serial- 單線程,Client, Par- 多線程。CMS 更好的響應,Parallel Scavenge 更好的吞吐。G1 :可預測停頓時間。 GC 日誌分析。
- 內存問題:OOM, Memory Leak , Memory Overflow,Stack Overflow ,Method Area Overflow。 OOM -- 線程轉儲,定位緣由;Memory Overflow -- 堆內存空間不足,死循環; Memory Leak 一般是資源沒有正確釋放,沒法及時 GC ,使用 MAT 等工具分析從 GC ROOT 的引用鏈;Stack Overflow -- 遞歸過深,分配數組大小過大;Method Area Overflow -- 字節碼技術動態生成或加強的大量類載入方法區可能致使方法區溢出(CGLib, JSP, OSGi)。
- FullGC 緣由及方案:1. 大對象列表,很大的列表,拆分小對象/小列表,及時釋放; 2. Old 空間小,能夠調大 Old 區; 3. 不要輕易手動調用 System.gc 方法。
類加載
- 平臺無關性和語言無關性:字節碼與虛擬機。將高級語言編譯成符合虛擬機規範的字節碼,從而可以在符合規範的虛擬機上執行字節碼,最終映射到本地機器指令集。操做系統平臺和編程語言對應用程序是透明的。
- 重要元素:Class 類文件 - 字節碼指令集 - 類加載器 - 加載機制。Class 類文件是 Java 類或接口的字節碼錶示,字節碼指令集是字節碼的主要元素。類加載器根據 Class 類文件在虛擬機中建立類對應的 Class 對象 。Java 類加載器包含 Bootstrap 加載器、Ext 加載器、App 加載器、Customized 加載器。類加載的順序是先用前面的加載器進行加載,找不到則用下一個。類或接口的惟一標識:類或接口的名稱 + 定義類加載器。不一樣類加載器加載同一個類或接口在 JVM 是不一樣的。
- 類加載過程:加載-連接-與初始化:加載 -- 查找類或接口的二進制表示,並據此來建立類或接口的 Class 對象; 連接 -- 將類或接口連接到虛擬機運行時狀態,連接包括驗證、準備、解析; 初始化 -- 執行初始化方法
。
- 加載:運行時常量池存儲着類、類的成員和方法的各類信息的二進制表示,符號引用來自於 CONSTANT_XXX_info 結構。
- 初始化:調用 new, getstatic, putstatic, invokestatic 指令時觸發。初始化須要保證多線程環境的安全性,由虛擬機負責實現。每一個類或接口 C 都有一個對應的惟一初始化鎖 LC。初始化以前須要先獲取這個鎖 LC ,分狀況進行處理並釋放鎖。
安全機制
- 安全模型:沙箱模型、限定於 JVM 範圍。主要是對本地系統資源的訪問限制。類加載驗證。安全策略。域、代理、權限組。doPrivileged。
小結
數據結構與算法是構建互聯網的基本材料,網絡、存儲、操做系統、分佈式是現代互聯網應用的服務端的基石,而語言、平臺、框架、中間件則是在基石之上的基礎設施,它們共同構建了穩固的互聯網大廈。
體系結構是一種強大的方法論。打造一個知識體系,構建底層邏輯,理解上層變化,自下而上打通,持續融入所學。
PS:之前,我常以爲業界寫具體技術的書比較多,而總結軟件設計思想和案例的書太少,不利於開發者的設計能力的快速提高。如今看來,業界到處都有優秀的框架、中間件、平臺設計,簡直是寶山在眼前,而我卻視若無睹。不過,要能從這寶山裏挖掘財富,其實也不容易。
參考文獻
- 《算法導論》:第 3,6,7,9,10,11,12,13,18 章
- 《算法》:第 3 章
- 《TCP/IP 協議詳解》(英文版): 第 12-17 章
- 《HTTP權威指南》 : 第 2-4,6-7 章
- 《深刻理解計算機系統》: 第 6, 9-12 章,有時間推薦讀徹底書
- 《Designing Data-Intensive Applications》: 第 3,5-9 章
- 《Java虛擬機規範(Java SE 8版)》:第 2,4,5 章
- 《Java併發編程實戰》:有時間推薦讀徹底書
- 《Netty實戰》:第 1-7,10 章
- 《MySQL技術內幕:InnoDB存儲引擎》 :第 5-7 章
- 《高性能MySQL》:第 4,5,6,7,10 章
- 《Redis實戰》:第 1-6 章
- 《白帽子講Web安全》:第 1,2,3,7,8,9,10,13 章
- 《大規模分佈式存儲系統》
- 《Kafka權威指南》:第 3-7 章
- 《深刻理解Java虛擬機》:第 2,3 部分
- 「Dubbo官方文檔」
- 「帶你走進神同樣的Elasticsearch索引機制」
- 「支撐百萬併發的 ‘零拷貝’ 技術」
- 「分佈式一致性算法:Raft 算法(論文翻譯)」
- 「HTTP2 詳解」
- 微信公衆號:「石杉的架構筆記」