分布式系統的基礎知識
- 阿姆達爾定律
-
多線程交互模式
- 互不通信,沒有交集,各自執行各自的任務和邏輯
- 基于共享容器(如隊列)協同的多線程模式->生產者-消費者->隊列
-
通過事件協同的多線程模式->如B線程需要等到某個狀態或事件發生后才能繼續工作,而這個狀態改變或者事件產生和A線程相關
- 避免死鎖
-
網絡通信基礎知識
- OSI、TCP/IP
-
網絡IO實現方式
- BIO
- NIO->Reactor模式
- AIO->Proactor模式
負載均衡
- 硬件負載均衡
- LVS等軟件的負載均衡
- 名稱服務
- 規則服務器
- Master-Worker
小結
1.一致性hash的算法思路(游戲服務器進程/線程設計思路)(擴容/縮容平滑)/環形消息隊列disruptor
2.緩存服務器擴容或者縮容要盡量平滑
3.消息中間件-MOM:Message-Oriented middleware,is software infrastructure focused on sending and receiving messages between distributed system.
4.兩個常被提及的好處:異步和解耦.
5.CountDownLatch不能循環使用,而CyclicBarrier可以循環使用(從名字上看,【循環_cyclic】屏障)
大型網站及其架構演進過程
海量數據+高并發訪問量+復雜的業務和系統
- 數據庫與應用分離
- 應用服務器走向集群(引入負載均衡器)
session問題
- Session Sticky
- Session Replication
- Session數據集中存儲
- Cookie Based
讀寫分離
- 引入讀庫->搜索引擎(站內搜索功能)->Search Cluster
- 專庫專用->數據垂直拆分
- 數據水平拆分
加快數據讀取
- 緩存->頁面緩存->Cache Cluster
分布式存儲系統
- Distributed Storage
拆分應用
- 走服務化的路
消息中間件
中間件
特定中間件是解決特定場景問題的組件,它能夠讓軟件開發人員專注于自己應用的開發
- 遠程過程調用和對象訪問中間件->解決分布式環境下應用的互相訪問問題
- 消息中間件:解決應用之間的消息傳遞、解耦、異步的問題
- 數據訪問中間件:解決應用訪問數據庫的共性問題的組件
構建Java中間件的基礎知識
- JVM/GC/內存堆布局
-
并發
- 線程池/synchronized/ReentrantLock/volatile(可見性與操作互斥是兩件事情)/Atomics/wait、nofity、nofifyAll/CountDownLatch/
- CyclicBarrier/Semaphore/Exchanger/Future、FutureTask/
- 并發容器:CopyOnWrite、Concurrent
- 動態代理
- 反射
-
網絡通信選擇
- BIO/NIO/AIO
- MINA、Netty
- 協議制定
中間件范疇
- 應用的拆分
- 服務的拆分
- 數據的拆分
- 應用的解耦
服務框架
運行期服務框架與應用和容器的關系
- 直接或者間接依賴的jar導致應用里同一個jar包有不同的版本->產生沖突
- 將服務框架自身用到的類和應用用到的類都控制在User-Defined Class Loader級別->實現相互間隔離
- web容器對于多個web應用的處理以及osgi對于不同的bundle的處理都采用了類似的方法
服務調用者與服務提供者之間通訊方式的選擇
-
調用者[服務框架]
-
-->服務注冊查找中心
- --->服務提供者[服務框架]
-
-->服務注冊查找中心
-
并不是每次調用遠程服務前都通過服務注冊查找中心來查找可用地址->而是把地址緩存在調用者本地->當有變化時主動從服務注冊查找中心發起通知->告訴調用者可用的服務提供者列表的變化
- 客戶端拿到可用的服務提供者的地址列表后->如何為當次的調用進行選擇就是路由要解決的問題->隨機、輪訓、權重(一般指動態權重)
引入基于接口、方法、參數的路由
- 因為一般情況下,一個集群會提供多個服務,每個服務又有多個方法.->需要細粒度的控制服務路由
-
如服務提供者有兩個服務-接口A和接口B->接口A中的某個方法1執行比較耗時->影響整體性能->排隊
- 隔離資源,使得快慢不同、重要級別不同的方法之間互不影響
服務調用端的流控處理
- 控制到服務提供者的請求的流量
序列化與反序列化處理
- 具體制定通信協議時->版本號、可擴展(擴展性、向后兼容性)屬性以及發起方支持能力的介紹(如是否支持壓縮等)
網絡通信實現選擇
- 同步方式進行遠程調用使用nio
-
io線程/數據隊列/通信對象隊列/定時任務
- 請求線程發送數據->進入數據隊列->生成通信對象(阻塞請求線程)->加入通信對象隊列->請求線程等待(通信對象用于喚醒請求線程)
- 數據隊列->io線程->socket連接->進行數據收發(需要發送的數據進入數據隊列后,這樣請求線程就不需要直接和socket連接打交道->復用socket連接)
- 如果遠程調用超時前有執行結果返回->io線程會通知通信對象->通信對象結束請求線程等待->結果傳送給請求線程
- 定時任務用于負責檢查通信對象隊列中的哪些通信對象已經超時->然后這些通信對象會通知請求線程已經超時
-
支持多種異步服務調用方式
- OneWay->只管發送請求而不關心結果的方式->只需要把發送的數據放入數據隊列即可
-
CallBack->請求發送方發送請求后會繼續執行自己的操作->等對方有響應時進行一個回調
- 請求方設置回調->加入回調對象隊列
- 收到服務提供者的返回后->io線程會通知回調對象->執行回調方法
- 對于超時->定時任務的方式->如果沒有返回->也需要執行回調對象的方法->告知已超時沒有結果
- 如果不引入新的線程->那么回調的執行要么是io線程中要么是在定時任務的線程中
- [建議用新的線程執行回調->不要因為回調本身的代碼執行時間久等問題影響了io線程或者定時任務].
- Future->請求線程通過future來獲取通信結果并直接控制超時->同上Future對象隊列 io線程依然是從數據隊列中得到數據再進行通信->得到結果后會把它傳給Future
- 可靠異步->要保證異步請求能夠在遠程被執行->消息中間件來完成這個保證
-
使用Future方式對遠程服務調用的優化
- 一個請求中調用多個遠程服務的情況
- 按照調用順序把服務的請求發送給服務A,服務B,服務C->請求發送過去并不直接等待執行結果
- 而是直到服務C的請求也發出去后再來統一等待服務A、服務B和服務C的執行結果->然后再接著進行本地的數據處理
- 前提:所調用服務A、B、C之間并沒有相互的依賴關系
- 即并行調用優化->因為Future方式的支持
- 反序列化線程:一般是使用io線程,不過這樣會影響io線程的工作效率;另一種方式是把反序列化工作從io線程轉移到其他線程去做
服務提供端的設計與實現
-
服務端的工作
- 本地服務的注冊管理
- 根據進來的請求定位服務并執行
-
IO線程通信處理->反序列化的工作取決于具體實現,io線程或者工作線程中進行的方式都有
- 得到反序列化的消息并定位服務后->調用服務一般在非io線程進行(工作線程)
-
執行不同服務的線程池隔離
- 服務提供端,工作線程池不止一個,而是多個,定位到服務后,根據服務名稱、方法、參數來確定具體執行服務調用的是哪個線程池.->這樣,不同線程池之間是隔離的->不會出現爭奪線程資源的情況.
- 整個服務框架的功能分為服務調用者和服務提供者兩方面,此外像序列化、協議、通信等是公用的功能.在具體實現上,是把這些功能都放在一起形成一個完成的服務框架->而不是分為服務調用者框架和服務提供者框架
- 服務框架必須做到模塊化且可配置->模塊可替換->并留有一定的擴展點來擴展原有功能
服務升級
- 接口不變->內部的服務實現有變化->比較簡單,采用灰度發布的方式驗證然后全部發布就可以了
- 接口中增加方法->也比較簡單,直接增加方法即可->需要使用新方法的調用者就使用新方法,原來的調用者繼續使用原來的方法即可
-
接口的某些方法修改調用的參數列表
- 對使用原來方法的代碼都進行修改->不太可行->因為要求我們同時發布多個系統
- 版本號解決->常用的方式->使用老方法的系統繼續調用原來版本的服務,而需要使用新方法的系統則使用新版本的服務
- 在設計方法上考慮參數的擴展性->可行,不太好->因參數列表可擴展一般就以為是采用類似map的方式來傳遞參數->不直觀,并且對參數的校驗會比較復雜
實戰中的優化
-
服務的拆分
- 要拆分的服務是需要為多方提供公共功能
-
服務的粒度
- 需要根據業務的實際情況來劃分服務
-
優雅和實用的平衡
- 多調用一次就比之前多了一次網絡->一些功能直接在服務調用者的機器上實現會更加合適、經濟
- 如服務調用者直接讀緩存->大部分對數據的請求直接走一次緩存就可以,只有少部分沒有命中緩存的數據讀取需要走服務提供者->然后再到數據庫進行讀取并插入緩存
-
分布式環境的請求合并
- 分布式鎖-->額外開銷->另外一個思路->在服務調用端不是把請求隨機分發給服務提供者,而是根據一定的規則把同樣的請求發送到同一個服務提供者上->減少復雜性.
服務治理
- 服務信息、服務質量、服務容量、服務依賴、服務分布、服務統計、服務元數據、服務查詢、服務報表、服務監視、服務上下線、服務路由、服務限流降級、服務歸組、服務線程池管理、機房規則、服務授權、
-
ESB
- Enterprise Service Bus:企業服務總線
數據訪問層
數據庫減壓思路
- 優化應用,看看是否有不必要的壓力給了數據庫(應用優化)
- 看看有沒有辦法可以降低對數據庫的壓力,例如引入緩存、加搜索引擎等
-
把數據庫的數據和訪問分到多臺數據庫上,分開支持->核心思路和邏輯
- 數據拆分->垂直拆分->水平拆分
單機變為多機后,事務如何處理
-
分布式事務->XA/DTP-->AP(Application Program)->RM(Resource Manager)->TM(Transaction Manager)
- 兩階段提交->Prepare->Commit
- Prepare階段有一個節點資源失敗則Rollback
-
大型網站一致性的基礎理論-CAP/BASE
- CAP:Consitency,一致性、Availability,可用行、Partition-Tolerance,分區容忍性
- 分布式系統中不能同時滿足上面三項,CA、AP、CP
- 分布式系統中,我們一般選擇加強可用性和分區容忍性而犧牲一致性->首先先滿足A和P,然后看如何解決C的問題.
-
BASE模型
- Basically Available,基本可用,允許分區失敗
- Soft state,軟狀態,接受一段時間的狀態不同步
- Eventually consistent,最終一致,保證最終數據的狀態是一致的
- 對于C,我們采用的方式和策略就是保持最終一致,也就是不保證數據發生變化后所有節點立刻一致,但是保證他們最終是一致的。在大型網站中,為了更好的保持擴展性和可用性,一般都不會選擇強一致性,而是采用最終一致的策略來實現.
-
比兩階段提交更輕量一些的Paxos協議
- 核心原則:少數服從多數
-
集群內數據一致性的算法實例
- Quorum、Vector Clock算法
- 從工程上說,如果能夠避免分布式事務的引入,那么還是避免為好;如果一定要引入分布式事務,那么可以考慮最終一致的方法,而不是追求強一致。而且從實現上來說,我們是通過補償的機制不斷重試,讓之前因為異常而沒有進行到底的操作繼續進行而不是回滾。如果還是不能滿足需求,那么基于Paxos的算法會是一個不錯的選擇.
多機的Sequence問題與處理
- 唯一性
-
連續性
- 提供一個實現方案:把所有id集中放到一個地方進行管理,每臺機器使用時都從這個id生成器上取
跨庫查詢
數據訪問層的設計與實現
-
對外提供數據訪問層的方式
- 為用戶提供專有API->不推薦->沒有通用性
- 通用方式->jdbc->數據層自身可以作為一個jdbc的實現,即暴露出jdbc的接口給應用
- 基于orm或者類orm接口的方式
-
按照數據層流程的順序看數據層設計
-
SQL解析
- 通過SQL解析可以得到SQL中的關鍵信息,如表明、字段、where條件等;而在數據層中,一個很重要的事情是根據執行的SQL得到被操作的表,根據參數及規則來確定目標數據庫連接
-
規則處理階段
- 用固定哈希算法作為規則_分庫分表
-
一致性 hash
- [把節點對應的哈希值變為了一個范圍],而不再是離散的.在一致性哈希中,我們會把整個哈希值的范圍定義的非常大,然后把這個范圍分配給現有的節點
-
虛擬節點對一致性hash的改進
- 解決增加或減少節點時負載不均衡的問題
-
映射表與規則自定義方式
- 通過比較復雜的函數計算來解決數據訪問的規則問題
-
為什么要改寫SQL
- 多庫多表->修改表名->跨庫計算平均值等
-
如何選擇數據源
- Master/Slave
- 根據當前SQL的特點(讀、寫)、是否在事務中以及各個庫的權重規則,計算得到這次SQL請求要訪問的數據庫
-
執行SQL和結果處理階段
- 對異常的處理和判斷
-
SQL解析
-
實戰
- 復雜的連接管理
-
三層數據源的支持和選擇
- DataSouce/AtomDataSource/groupDataSource
-
獨立部署的數據訪問層實現方式
- jar包方式
-
proxy方式
- 數據庫協議
- 私有協議
-
讀寫分離的挑戰和應對
-
數據結果相同,多從庫對應一主庫的場景
- 應用通過數據層訪問數據庫,通過消息系統就數據庫的更新送出消息通知->數據庫同步服務器獲得消息通知后會進行數據的復制工作.分庫規則配置則負責在讀數據及數據同步服務器更新分庫時讓數據層知道分庫規則->數據同步服務器和DB主庫的交互主要是根據被修改或新增的數據主鍵來獲取內容,采用的是行復制的形式
- 比較優雅的方式是基于數據庫的日志來進行數據的復制
-
主/備庫分庫方式不同的數據復制
- 非對稱復制->控制數據分發->如主庫按照買家id分庫,而備庫按照賣家id進行分庫
-
引入數據變更平臺
- 很多其他場景也會關心數據的變更,除了復制到其他數據庫,例如緩存的失效等->可以考慮構建一個通用的平臺來管理和控制數據變更->
- 引入Extractor和Applier->Extractor負責把數據源變更的信息加入到數據分發平臺中,而Applier的作用是把這些變更應用到相應的目標上->中間的數據分發平臺中是由多個管道組成->進入到數據分發平臺的變更信息就是標準化、結構化的數據了-
-
如何做到數據平滑遷移
- 最大挑戰是,在遷移的過程中又會有數據的變化(因為很多應用不能接受長時間的停機)->可以考慮的方案是在開始進行數據遷移時記錄增量的日志,在遷移結束后,再對增量的變化進行處理.->在最后,可以要把要被遷移的數據的寫暫停,保證增量日志都處理完畢后,再切換規則,放開所有的寫,完成遷移工作
-
數據結果相同,多從庫對應一主庫的場景
消息中間件
消息中間件對應用的解耦
- 如登陸系統負責向消息中間件發送消息,而其他的系統則向消息中間件來訂閱這個消息,然后完成自己的工作.
- 通過消息中間件解耦,登陸系統就不用關心到底有多少個系統需要知曉登陸成功這件事了,而不用關心如何通知它們,只需要把登陸成功這件事轉化為一個消息發送到消息中間件就可以了
- landon:和事件解耦一樣,如游戲中玩家升級拋出一個事件,其他子系統只需要監聽該事件即可,而不必升級直接調用各個子系統
- 登陸成功時需要向消息中間件發送一個消息,那么[必須保證這個消息發送到了消息中間件],否則依賴這個消息的系統就無法工作了
互聯網時代的消息中間件
- JMS:Java Message Service->規范->Hornetq,ActiveMQ等產品是這個規范的實現
-
如何解決消息發送一致性
- 消息發送一致性的定義:產生消息的業務動作與消息發送的一致,即如果業務操作成功了,那么由這個操作產生的消息一定要發送出去,否則就丟失消息了;而另一方面,如果這個業務行為沒有發生或者失敗,那么就不應該把消息發出去.
- JMS消息模型-Queue/Topic_支持XA協議(兩階段提交)->會引入分布式事務->存在一些限制且成本相對較高
-
一致性方案的正向流程
- (1) 業務處理應用首先把消息發給消息中間件,標記消息的狀態為待處理.
- (2) 消息中間件收到消息后,把消息存儲在消息存儲中,并不投遞該消息.
- (3)消息中間件返回消息處理的結果,僅是入庫的結果,結果是成功或者失敗.
-
(4)業務方收到消息中間件返回的結果并進行處理:
- a) 如果收到的結果是失敗,那么就放棄業務處理,結束
- b) 如果收到的結果是成功,則進行業務自身的操作
- (5)業務操作完成,把業務操作的結果發送給消息中間件
-
(6)消息中間件收到業務操作結果,根據結果進行處理
- a) 如果業務失敗,則刪除消息存儲中的消息,結束
- b)如果業務成功,則更新消息存儲中的消息狀態為可發送,并且進行調度,進行消息的投遞
- 需要注意各種步驟中可能出現的異常情況
-
最終一致性方案的補償流程:
- (1)消息中間件詢問狀態為待處理的消息對應業務操作結果
- (2)應用即消息發布者對業務操作檢查操作結果
- (3)發送業務處理結果給消息中間件
- 4)消息中間件更新消息狀態,業務成功,消息狀態為待發送;業務失敗則消息刪除
-
如何解決消息中間件與使用者的強依賴問題
- 把消息中間件所需要的消息表與業務數據表放到同一個業務數據庫->業務操作和寫入消息作為一個本地事務完成,然后再通知消息中間件有消息可以發送->解決一致性->也可以消息中間件定時去輪詢業務數據庫找到需要發送的消息,取出內容后進行發送
- 需要業務自己的數據庫承載消息數據/需要讓消息中間件去訪問業務數據庫/需要業務操作的對象是一個數據庫
- 消息中間件不再直接與業務數據庫打交道->將業務操作、寫入消息,輪詢消息等全部放到業務應用
- 加一個本地磁盤作為一個消息存儲
-
消息模型對消息接收的影響
-
JMS Queue模型:
- 應用1和應用2發送消息到JMS服務器,這些消息根據到達的順序形成一個隊列->應用3和應用4進行消息的消費;如果Queue里面的消息被一個應用處理了,那么連接到JMS Queue上的另一個應用是收不到這個消息的->即連接到這個JMS Queue上的應用共同消費了所有的消息->消息從發送端發送出來時不能確定最終會被哪個應用消費,但是可以明確的是只有一個應用會去消費這條消息->Peer To Peer方式(PTP)
-
JMS Topic模型:
- 和Queue模型的最大區別在于消息接收的部分,在該模型中,接收消息的應用3和應用4是可以獨立收到所有到達Topic的消息的->Pub/Sub方式
-
JMS中客戶端連接的處理和帶來的限制
- JMS中每個Connection都有一個唯一的clientId,用于標識連接的唯一性
- 應用3和JMS服務器建立了兩個連接,應用4和JMS服務器建立了一個連接->可以看到這三個連接所接收的消息是完全不同,每個連接收到的消息條數以及收到消息的順序則不是固定的.->另外每個連接都會收到所有發送到Topic的消息.
-
我們需要什么樣的消息模型
- 消息發送方和接收方都是集群/同一個消息的接收方可能有多個集群進行消息的處理/不同集群對于同一條消息的處理不能相互干擾
- 如8條消息和兩個集群,每個集群恰好有兩臺機器->那么需要這兩個集群的機器分別處理掉所有8條消息->不能遺漏也不能重復
- 引入ClusterId,用這個Id來標識不同的集群,而集群內的各個應用實例的連接使用同樣的ClusterId->把Topic模型和Queue模型的特點結合起來使用
-
JMS Queue模型:
-
消息訂閱者訂閱消息的方式
- 作為消息中間件,提供對于消息的可靠保證是非常重要的事情->一些場景中一些下游系統完全通過消息中間件進行自身任務的驅動
-
持久訂閱、非持久訂閱
- 非持久訂閱:消息接收者應用啟動時,就建立了訂閱關系->可以收到消息->如果消息接收者應用結束了,那么消息訂閱關系也就不存在了->這時的消息是不會為消息接收者保留的.
- 持久訂閱:消息訂閱關系一旦建立除非應用顯示地取消訂閱關系否則這個訂閱關系將一直存在即使消息接收者應用停止->這個消息也會保留,等待下次應用啟動后再投遞給消息接收者.
-
保證消息可靠性
- 消息從發送端應用到接收端應用,中間有三個階段需要保證可靠,分別是:[消息發送者把消息發送到消息中間件];[消息中間件把消息存入消息存儲];[消息中間件把消息投遞給消息接收者]
-
要保證這三個階段都可靠,才能保證最終消息的可靠
- 消息發送端可靠的保證->注意異對異常的處理->可能出現的問題是在不注意的情況下吃掉了異常->從而導致錯誤的判斷結果
-
消息存儲的可靠性保證
- 持久存儲部分的代碼完全自主實現
-
利用現有的存儲系統實現
- 實現基于文件的消息存儲
- 采用數據庫作為消息存儲
- 基于雙機內存的消息存儲
-
消息中間件自身擴容
- 讓消息的發送者和消息的訂閱者能夠感知到有新的消息中間件機器加入到了機器->軟負載中心
-
消息存儲的擴容處理
- 服務端主動調度安排投遞
-
消息投遞的可靠性保證
- 消息接收者在處理消息的過程中對于異常的處理->千萬不要吃掉異常后確認消息處理成功
-
投遞處理優化:
- 投遞是一定要采用多線程處理
- 單機多訂閱者共享連接->消息只發送一次
-
訂閱者視角的消息重復的產生和應對
- 分布式事務,復雜
- 冪等操作->對于消息接收端->采用同樣的輸入多次調用處理函數會得到同樣的結果
-
JMS的消息確認方式與消息重復的關系
- AUTOACKNOWLEDGE/CLIENTACKNOWLEDGE/DUPSOKACKNOWLEDGE
-
消息投遞的其他屬性支持
- 消息優先級
- 訂閱者消息處理順序和分級訂閱
- 自定義屬性
- 局部順序
-
保證順序的消息隊列設計
- 接收端的設計從原來的Push模式變為了Pull模式
軟負載中心與集中配置管理
-
軟負載中心兩個最基礎的職責
- 聚合地址信息
- 生命周期感知->需要能對服務的上下線自動感知,并且根據這個變化去更新服務地址數據
-
軟負載中心的結構
- 軟負載中心的服務端->負責感知提供服務的機器是否在線,聚合提供者的機器信息并負責把數據傳給使用數據的應用
-
軟負載中心的客戶端
- 服務提供者->把服務器提供者提供服務的具體信息主動傳給服務端->并且隨著提供服務的變化去更新數據
- 服務器使用者->向服務端告知自己所需要的數據并負責去更新數據,還要進行本地的數據緩存
- 軟負載中心三部分重要的數據->聚合數據、訂閱關系、連接數據
-
內容聚合功能的設計
- 保證數據正確性
-
高效聚合數據
- 并發下的數據正確性的保證
- 數據更新、刪除的[順序]保證
-
大量數據同時插入、更新時的性能保證
- 根據key進行分線程的處理->保證同樣key的數據是在同一個線程中處理->順序任務隊列
-
解決服務上下線的感知
-
通過客戶端與服務端的連接感知
- 長連接的心跳或數據的發布來判斷服務發布者是否還在線->如果很久沒有心跳或數據的發布,則判定為不在線;那么就取出這個發布者發布的數據->而對于新上線的發布者,通過連接建立和數據發布就實現了上線的通知
- 當負載中心的自身的負載很高時,可能產生誤判,如軟負載中心壓力很大,處理請求變慢,心跳數據來不及處理->會以為心跳超時而判斷服務不在線,認為服務不可用并且把信息通知給服務調用者,這會導致原本可用的服務被下線了
-
另外的問題,如果服務發布者到軟負載中心的網絡鏈路有問題而服務發布者到服務使用者的鏈路沒問題,也會造成感知的問題->因為軟負載中心屬于旁路
- 解決:軟負載中心客戶端增加邏輯,當收到軟負載中心通知的應用下線數據時,需要服務調用者進行驗證才能接收這個通知
-
通過對于發布數據中提供的地址端口進行連接的檢查
- 需要服務調用者進行最終確認,因為在系統中進行的實際業務調用通信是在服務調用者和服務提供者之間
-
通過客戶端與服務端的連接感知
-
軟負載中心的數據分發的特點和設計
-
數據分發與消息訂閱的區別
- 消息中間件需要保證消息不丟失->每條消息都應該送到相關訂閱者->而軟負載中心只需要保證最新數據送到相關的訂閱者->不需要保證每次的數據變化都能讓最終訂閱者感知
- 消息中間件中同一個集群中的不同機器是分享所有消息的,因為該消息只要同一集群中的一臺機器去處理了就行->而軟負載中心則不同,因為其維護的是大家都需要用的服務數據->所以需要把這數據分發給所有的機器
-
提升數據分發性能需要注意的問題
- 數據壓縮->CPU換帶寬
- 全量與增量的選擇->建議剛開始的實現中采用簡單的方式,即傳送全量數據,當全量數據很大時就需要考慮采用增量傳送的方式實現.
-
針對服務化的特性支持
-
軟負載數據分組
- 根據環境進行劃分
- 分優先級的隔離
-
提供自動感知以外的上下線開關
-
優雅的停止應用
- 我們應該先從服務列表中去掉這個機器->等待當時正在執行的服務器結束,然后再停止應用->通過指令直接從軟負載中心使機器下線
-
保持應用場景,用于排錯
- 遇到服務的問題時,可以把出問題的服務留下一臺進行故障定位和場景分析->此時需要把這臺機器從服務列表中拿下來,以免有新的請求進來造成服務的失敗,這也是需要軟負載中心直接使服務下線的一個場景.
-
優雅的停止應用
-
維護管理路由規則
- 對不同特性的數據進行拆分
-
軟負載數據分組
-
數據分發與消息訂閱的區別
-
從單機到集群
- 數據管理問題/連接管理問題
-
數據統一管理
- 數據聚合放在一個地方->軟負載中心集群,無狀態->對于數據發布者和訂閱者來說,選擇軟負載中心集群中的任何一個機器連接皆可
- 把軟負載中心集群中的機器的職責分開,即把聚合數據的任務和推送數據的任務分到專門的機器上處理->將軟負載中心集群中有一臺機器為軟負載中心數據聚合,另一臺機器為軟負載中心數據推送->發布者和訂閱者的連接是分開管理的->為了提升性能,在軟負載中心負責數據推送的機器上是可以對聚合數據做緩存
-
數據對等管理方案
-
將數據分散在各個軟負載中心的節點上并且把自己節點管理的數據分發到其他節點上,從而保證每個節點都有整個集群的全部數據并且這些節點的角色是對等的->使用軟負載中心的數據發布者和數據訂閱者只需要去連接軟負載中心集群中的任何一臺機器就可以->軟負載中心集群內部,各個節點之間會進行數據的同步
- 批量處理同步->合并變化,同步一次
- 如果節點較多,同步量會較大->對集群內的節點進行指責劃分
- 如果集群管理的總體數據很多,超過了單機限制->則需要對數據進行分組處理->讓每個節點管理一部分數據->即用UI規則對數據進行類似分庫分表的操作->則數據訂閱者可能就需要連接多個數據分發節點了
-
將數據分散在各個軟負載中心的節點上并且把自己節點管理的數據分發到其他節點上,從而保證每個節點都有整個集群的全部數據并且這些節點的角色是對等的->使用軟負載中心的數據發布者和數據訂閱者只需要去連接軟負載中心集群中的任何一臺機器就可以->軟負載中心集群內部,各個節點之間會進行數據的同步
-
集中配置管理中心
-
集中配置管理中心結構
- 準備的持久存儲來保存持久數據(Master-Slave)->一般采用關系型數據庫->通過兩個節點的主備來解決持久數據安全的問題.
- 集中配置管理中心集群這層由多個集中配置管理中心節點組成->對等->都可以提供數據給應用端等->互不依賴
- 集中配置管理中心的單個節點->部署了一個nginx和一個web應用->其中web應用主要負責完成相關的程序邏輯如數據庫的相關操作以及根據ip等的分組操作,即整個應用的邏輯放在了web應用中;單機的本地文件Local File則是為了容災和提升性能,客戶端進行數據獲取的時候,最后都是從nginx直接獲取本地文件并把數據返回給請求端
-
集中配置管理中心的使用分為了以下兩部分
- 提供給應用使用的客戶端->主要是業務應用通過客戶端去獲取配置信息和數據,用于數據的讀取
-
為控制臺或者控制腳本提供管理SDK
- 包括了對數據的讀寫,通過管理SDK可以進行配置數據的更改
-
客戶端實現和容災策略
-
客戶端通過http協議與集中配置管理中心進行通信
- 通過輪詢獲取最新數據_普通輪詢
- 改進使用長輪詢,Long Polling->如果沒有數據,長輪詢會等待;如果等待數據,立刻返回;如果一直沒有數據則等到超時后返回,繼續建立連接,而普通輪詢就直接返回了->是HTTP普通輪詢和Socket長連接方式的折中-
-
容災
- 數據緩存
- 數據快照
- 本地配置
- 文件格式->如果是二進制數據格式,那么就沒有對應的工具是無法對配置進行修改->如果客戶端容災退化到一個單機應用就會需要直接修改配置內容和數據->那么文本格式的限制就非常重要和關鍵了
-
客戶端通過http協議與集中配置管理中心進行通信
-
服務端實現和容災策略
- Nginx+Web應用->和邏輯相關的部分在Web應用上實現,Nginx用于請求的處理和最后結果的返回,而供返回的數據的都在本地文件系統中
- 和數據庫的數據同步
-
數據庫策略
- 數據庫在設計時需要支持配置的版本管理,即隨著配置內容的更改,老的版本是需要保留的,為了方便進行配置變更的對比和回滾->而數據庫本身需要主備進行數據的容災考慮
-
集中配置管理中心結構
構建大型網站的其他要素
-
加速靜態內容訪問速度的CDN
- CDN源站/CDN節點
- 網絡緩存技術
-
幾個關鍵技術
- 全局調度->需要根據用戶地域、接入運營商以及CDN機房的負載情況去調度
- 緩存技術->提升命中率
- 內容分發
- 帶寬優化
-
大型網站的存儲支持
- 基本上就是在解決存儲和計算的問題
- 關系型數據庫
-
分布式文件系統
- 圖片、大文本存儲->使用數據庫不合適
- NAS網絡存儲設備(Network Attached Storage),其本身的IO吞吐性能以及擴展性在大型網站中會出現比較明顯不足
-
分布式文件系統具體產品
- 開源的淘寶的TFS
- 不開源的Google#GFS,Goole File System->GFS Client(負責從Master獲取要操作的文件在ChunkServer中的具體地址,然后直接和ChunkServer通信,獲取數據或者進行數據的寫入、更新)/GFS Master(維護所有的文件系統元數據、控制整個系統范圍內的一些活動、與ChunkServer之間通過周期性的心跳進行通信,檢測對方是否在線)/GFS chunkserver(Data Node,文件存儲的地方)
- 主要解決了單機文件存儲容量及安全性的問題,把多臺廉價pC組成一個大的分布式的看起來像文件系統的集群
- [HDFS,采用Java的類GFS的實現]
-
NoSQL
- No SQL/Not Only SQL
- 基本上處于分布式文件系統和SQL關系型數據庫之間的系統都被歸為NOSQL的范疇
-
數據模型
- Key-Value,沒辦法進行高效的范圍查詢
- Ordered Key-Value,Key是有序的->可解決基于Key的范圍查詢的效率問題,不過在這個模型中,Value本身的內容和結構是由應用來負責解析和存儲的->如果在多個應用中去使用則并不直觀也不方便
-
BigTable
- Google的結構化數據的分布式存儲系統->Value是由多個Column Family組成
-
Document,Full-Text Search
- 可以在Value中任意自定義復雜的Scheme/對索引方面的支持
-
Graph
- 支持圖結構的數據類型
-
系統結構
-
[HBase]->借鑒Google BigTable的一個Java版本的開源實現
- 存儲到HBase的數據是通過HRegionServer來管理的,每個HRegionServer管理了多個HRegion,每個Region管理具體的數據->HMaster是管理所有HRegionServer的節點,是一個中心控制的結構
-
Amazon#Dynamo結構
- 采用了一致性哈希進行管理
- [Cassandra]是一個開源的類似Dynamo的實現
-
[HBase]->借鑒Google BigTable的一個Java版本的開源實現
-
緩存系統
- 非持久的存儲,是為了加速應用對數據的讀取
- Redis和Memcache是兩個使用很廣泛的開源緩存系統->Redis已經有了對于集群的支持,也可以做單機的應用來使用;而memcache本身還是一個單機的應用,在使用時->集群->常見的是采用一致性哈希的方式
-
使用緩存的場景
- 使用緩存來降低對底層存儲的讀壓力,需要注意緩存和數據存儲中數據一致性問題
-
應用 <---> 緩存 <---> 存儲
- 這種方式,應用是不直接操作存儲,存儲由緩存控制;對于緩存來說,需要保證數據寫入緩存后能夠存入存儲中,所以緩存本身的邏輯會復雜些,需要有很多操作日志及故障恢復等
- 另一種方式,應用直接與緩存和存儲進行交互。一般的做法是應用在寫數據時更新存儲,然后失效緩存數據;而在讀數據時首先讀緩存,如果緩存中沒有數據,那么再去讀存儲,并且把數據寫入緩存
- 第三種方式,對于全數據緩存比較合適,即當存儲的數據變化時,直接從存儲去同步數據到緩存中,以更新緩存數據
-
另一個重要場景是對于Web應用的頁面渲染內容的緩存
- 具體的實現技術為ESI(Edge Side Includes)->通過在返回的頁面中加上特殊的標簽,然后根據標簽的內容去用緩存進行填充的一個過程.
- 處理ESI標簽的具體工作可以放在Java的應用容器中做,也可以放在Java應用容器前置的服務器做
-
搜索系統
- 站內搜索->網站的數據量和訪問量很小時,一些數據的查詢可以直接用數據庫的Like操作來實現->實現效率低->不智能
- 爬蟲問題->根據數據變化來更新索引
- 倒排索引
- 查詢預處理
- 相關度計算
-
數據計算支撐
- 離線計算、實時計算
-
離線計算
- 把業務數據從在線存儲中移動到離線存儲中,然后進行數據處理的過程
-
Google MapReduce模型
- Map階段:根據設定的規則把整體數據集映射給不同的Worker來處理并且生成各自的處理結果
- Reduce階段:對前面處理過的數據進行聚合,形成最后的結果
- [Hadoop]是MapReduce的一個開源實現.Hadoop使用HDFS進行數據存儲,而[Spark]則提供了基于內存的集群計算的支持
-
在線計算
- 流式計算->Storm
-
發布系統
-
分發應用->需要提供自動高效并且容易操作的機制來把經過測試的程序包分發到線上應用->一般采用Web的操作方式
- 發布控制臺->多機房->發布服務器
-
啟動校驗
- 應用重啟啟動后,需要進行校驗從而完成這臺應用服務器上的應用發布->對應用的校驗通常是應用自身提供一個檢測腳本或者頁面,發布系統執行這個腳本或者訪問頁面后來判斷返回的結果
- 停止應用時,需要優雅的關閉->需要在關閉應用前把這個應用從負載均衡或者軟負載中心上移除
-
灰度發布
- 會對新應用進行分批發布,逐步擴大新應用在整個集群中的比例直至最后全部完成->這里講的灰度發布主要是針對新應用在用戶體驗方面完全感知不到的更新.
-
產品改版Beta
- 提供新舊應用的共存
-
分發應用->需要提供自動高效并且容易操作的機制來把經過測試的程序包分發到線上應用->一般采用Web的操作方式
-
應用監控系統
- 能夠及時了解應用的運行狀況并能夠進行相應的控制
-
監視和控制量部分
-
數據監視維度
- 系統數據和應用自身的數據->系統數據指的就是當前應用運行的系統環境的信息,如CPU使用率、內存使用情況、交換分區使用情況、當前系統負載、IO情況等;而應用自身的數據,則是不同應用有不同的數據,一般會是調用次數、成功率、響應時間、異常數量等維度的數據
-
數據記錄方式
- 系統自身的數據已經被記錄到了本地磁盤,應用的數據一般也是存放在應用自身的目錄中,便于采集->也有直接把應用日志通過網絡發送到采集服務器的情況,可以減輕本地寫日志的壓力
- 對于應用數據的記錄,會考慮用定時統計的方式記錄一些量很大的信息.如對于一個提供服務的應用,在沒有特別需求時,并不直接記錄每次調用的信息,而是會記錄一段時間如5s或者一個間隔時間內的總調用次數、總響應時間這樣寫信息,而對于異常信息則每次都會予以記錄;采用統計的方式是為了減小記錄的大小以及對本地磁盤的寫入壓力
-
數據采集的方式
- 采集方式有應用服務器主動對同給監控中心以及等待監控中心來拉取兩種方式->前者控制權在應用服務器上,可能出現的問題是應用服務器推送的壓力超過采集的中心服務器的能力,會造成重試等額外開銷并且需要應用服務器上的推送程序控制重試邏輯和當前傳送位置等信息->后者把復雜性都放在中心采集服務器上處理,使得應用服務器中支持數據采集的部分變的簡單
-
展現與告警
- 提供圖表的形式可以提供Web頁面的展示->通過手機應用來接收報警->比短信方式好
-
控制
- 應用啟動后在運行期對于應用的行為改變->對于應用的運維,最低的要求是出現問題時可以通過重啟應用解決,但是我們還是需要更加精細化的控制應用-降低和一些切換。降級是我們遇到大量請求且不能擴容的情況時所進行的功能限制的行為->而切換更多的是當依賴的下層系統出現故障并且需要手工進行切換時的一個管理,這些控制一般都是通過開關,參數設置來完成
-
數據監視維度
-
依賴管理系統
- 隨著網站功能增多,應用的個數迅速增加,應用之間的關系也會越來越復雜,理清這些依賴關系并能夠管理這些依賴會非常重要
- 一個應用在完成某個功能時到底需要依賴哪些外部系統、這些依賴中哪些是必要依賴,強依賴(登陸驗證用戶名和密碼),哪些是有了更好沒有也可以的依賴,弱依賴(如記錄登陸時間和ip等)
-
動態檢測和靜態檢測->動態檢測的主要檢查方式是模擬被調用系統不可用和響應慢的兩種情況
- Google#Dapper,A Large-Scale Distributed Systems Tracing Infrastructure->traceId,index->形成一個調用時序圖
-
多機房問題分析
-
同城機房和異地機房
- 同城多個機房中,對于重要的應用系統,會在不止一個機房中部署;而對于數據庫系統,則會把主備放在不同機房->盡量避免不必要的跨機房的內部系統調用
- 為了數據安全,把產生的業務數據都同步到異地的機房->把一些對數據延遲不敏感的系統部署到異地,如只讀系統.
-
同城機房和異地機房
-
系統容量規劃
- 我們應該知道的信息就是整個系統的容量以及運行時所處的水位->我們把某個應用系統集群能夠提供的并發能力和當前的壓力比作一個水桶的容量和水位->那么準確知道各個系統的容量和當前高峰時的水位是一件很重要的事情->因為我們還是希望優先通過擴大容量來支持更多的請求而不是首選降級的方案.
-
考慮過去的增長情況并結合人為的判斷
- 弄清楚當前系統高峰期的水位
-
弄清楚當前各個系統的容量
- 通過測試->壓力測試
- 設置警戒值,高峰水位搞過警戒值就增加容量,保持高峰的水位是低于警戒值的
- 內部私有云
總結:
可以將一些設計思路、方法等應用到游戲服務器整體架構設計當中(主要是登陸、支付等http服務).