歡迎光臨
每天分享高質量文章

大型網站系統與 Java 中間件實踐

(點擊上方公眾號,可快速關註)


來源:wanglizhi ,

wanglizhi.github.io/2016/07/27/JavaWeb-And-MiddleWare/

第一章 分佈式系統介紹

分佈式系統的定義:組件分佈在網絡計算機上,組件間僅僅通過訊息傳遞來通信並協調行動。

分佈式系統的意義:

  • 升級單機處理能力的性價比越來越低

  • 單機處理能力存在瓶頸

  • 處於穩定性和可用性的考慮

摩爾定律:當價格不變時,每隔18個月,集成電路上可容納的晶體管數目會增加一倍,性能也將提升一倍。

執行緒與行程的執行樣式

馮諾依曼結構:輸入設備、輸入設備、運算器、控制器、儲存器。

基於共享容器協同的多執行緒樣式:經典如生產者消費者問題,對於儲存資料的容器或物件,有執行緒安全和不安全之分,對於不安全的容器或物件,一般可以通過加鎖或者通過Copy On Write的方式控制併發。

通過事件協同的多執行緒樣式:避免死鎖

多行程樣式:

  • 執行緒是屬於行程的,一個行程內的多個執行緒共享了行程的記憶體空間;而多個行程間的記憶體空間是獨立的,因此多個行程間通過記憶體共享、交換資料的方式與多個執行緒間就有所不同

  • 此外,行程間通信、協調,以及通過一些事件通知或者等待一些互斥鎖的釋放方面也不一樣

  • 多行程相對於單行程多執行緒來說,資源控制會更容易實現;多行程中單個行程出現問題,不會造成整體的不可用

  • 多行程之間可以共享資料,但其代價較大,會涉及序列化和反序列化的開銷

網絡通信基礎知識

OSI七層模型與TCP/IP模型:

Socket套接字進行網絡通信開發時,用到的三種方式:BIO、NIO和AIO

BIO:Blocking IO,採用阻塞的方式實現,一個執行緒處理一個Socket,發生建立連接、讀資料、寫資料的操作時,都可能會阻塞。

NIO:Nonblocking IO,基於時間驅動思想,採用Reactor樣式,可以在一個執行緒中處理多個Socket套接字

AIO:AsynchronousIO,異步IO,採用Proactor樣式,與NIO的差別是,AIO在進行讀寫操作時,只需要呼叫響應的read/write方法,並且需要傳入CompletionHandler,在動作完成後會呼叫。

如何把應用從單機擴展到分佈式

  • 輸入設備的變化

  • 輸出設備的變化

  • 控制器的變化

方式1和2,透明代理:對發起方和處理方都是透明的

  • 使用硬體負載均衡

  • 使用LVS(或其他軟體負載均衡系統)

缺點:

  • 會增加網絡的開銷,一方面指流量,另一方面指延遲

  • 這個透明代理處於請求的必經之路,如果代理出現問題,所有請求都會受到影響。我們需要考慮代理服務器的熱備份

方式3,採用名稱服務器直連的方式:

請求發起方和處理方直接沒有代理服務器,而是直接連接。外部多了一個“名稱服務”的角色,作用有:

  • 收集提供請求處理的服務器的地址信息

  • 提供這些地址信息給請求發起方

名稱服務只是起到一個地址交換的作用,在發起請求的機器上,需要根據從名稱服務得到的地址進行負載均衡的工作。

優點如下:

  • 名稱服務器出現問題,有辦法可以保證處理正常

  • 發起方和處理方直連,減少中間路徑和帶寬小號

缺點就是代碼升級較複雜

方式4,採用規則服務器控制路由的請求直連呼叫

與名稱服務器不同的是,規則服務器並不和請求處理的機器交互,只負責把規則提供給請求發起的機器。

方式5,Master+Worker的方式

存在一個Master節點來管理任務,由Master把任務分配給不同的Worker進行處理。

運算器的變化

通過DNS服務器進行調度和控制

增加負載均衡設備,DNS傳回的永遠是負載均衡地址

儲存器的變化

同控制器的變化,加代理服務器、or名稱服務器、or規則服務器

分佈式系統的難點

  • 缺乏全域性時鐘

  • 面對故障獨立性

  • 處理單點故障,如果不能把單點變為集群,則需要給單點做好備份,降低單點故障影響範圍

  • 事務的挑戰:2PC、最終一致、BASE、CAP、Paxos等

第二章 大型網站及其架構演進過程

大型網站:訪問量(PV)、資料量、業務複雜度

單機負載告警,資料庫與應用分離

應用服務器負載告警,走向集群

  • 服務器選擇問題:DNS、集群前加負載均衡設備

  • Session的問題

Session儲存會話狀態,在Web服務器上,各個會話獨立儲存,多台服務器不能保證每次請求都落在同一邊的服務器上。解決方案如下:

1、Session Sticky:負載均衡根據會話標識進行轉發,讓同樣的Session請求每次都發送到同一個服務器端處理

缺點:

  • 如果一臺Web服務器宕機或重啟,會話資料會丟失,用戶要重新登錄

  • 會話標識是應用層信息,則負載均衡要在應用層進行解析,開銷比在第四層大

  • 負載均衡變為了有狀態的節點,要將會話儲存到具體Web服務器的映射。記憶體消耗會變大,容災更麻煩

2、Session Replication:會話在多型服務器上複製同步

缺點:

  • 同步Session資料造成了網絡帶寬的開銷

  • 每台Web服務器都要儲存所有的Session資料,資料量容易很大

3、Session資料集中儲存

Session資料不再Web服務器上,而是放在另一個集中儲存的地方。

缺點:

  • 讀寫Session資料引入了網絡操作,存在時延和不穩定性

  • 如果集中儲存Session的機器或者集群有問題,就會影響我們的應用

4、Cookie Based:把Session資料放在Cookie中

缺點:

  • Cookie長度的限制

  • 安全性:外部訪問和修改

  • 帶寬消耗

  • 性能影響:每次HTTP請求都帶有Session資料

資料讀壓力變大,讀寫分離

1、採用資料庫作為讀庫

缺點:

  • 資料複製問題;

  • 應用對於資料源的選擇問題:寫操作和事務走主庫,考慮從庫相對主庫的延遲

2、搜索引擎其實是一個讀庫

3、加速資料讀取的利器——快取

  • 資料快取,Key-Value,“熱資料”,容量不夠時清除快取

  • 頁面快取,ESI標簽頁面快取

彌補關係型資料庫的不足,引入分佈式儲存系統

分佈式檔案系統,解決小檔案和大檔案的儲存問題

分佈式key-value系統,提供高性能的半結構化支持

分佈式資料庫提供一個支持大資料、高併發的資料庫系統

讀寫分離後,資料庫又遇到瓶頸

儘管讀寫分離以及分佈式儲存系統,能夠降低主庫的壓力,但是交易、商品、用戶的資料都還在一個資料庫中,壓力還在繼續增加,我們有資料垂直拆分和水平拆分兩種選擇;

1、專庫專用,資料垂直拆分

垂直拆分即把不同的業務資料分到不同的資料庫中。

問題:

  • 應用需要多個資料源,帶來的是每個資料庫連接池的隔離

  • 單機跨業務事務,一種方法是使用分佈式事務,性能較低;另一種辦法就是去掉事務

2、單表達到瓶頸,資料水平拆分

水平拆分就是把同一個表的資料拆到兩個資料庫中。

問題:

  • SQL路由問題,選擇哪個資料表

  • 主鍵處理等機制不同,如自增主鍵

  • 一些查詢需要從兩個資料庫中取資料,加上分頁操作,比較難處理

資料庫問題解決後,應用面對的新挑戰

拆分應用

  • 根據業務特性,還可以根據用戶註冊、登陸、用戶信息維護等再拆分。

  • 走服務化的路,共享代碼放在各個服務中心,如商品中心、用戶中心、交易中心

初識訊息中間件

訊息中間件是在分佈式系統中完成訊息發送和接收的基礎軟體。兩個明顯好處:異步、解耦。

第三章 構建Java中間件

三個領域的中間件:

  • 遠程過程呼叫和物件訪問中間件:主要解決分佈式環境下應用的互相訪問問題。是支撐應用服務化的基礎

  • 訊息中間件:解決應用之間的訊息傳遞、解耦、異步的問題

  • 資料訪問中間件:解決應用訪問資料庫的共性問題

構建Java中間件的基礎知識

JVM中堆分為三塊:Young/Tenured/Perm,新生代/年老代/持久代

一般來說,新物件分配在新生代的Eden區,也可能直接分配在年老代,在進行新生代垃圾回收時,Eden區存活的物件被覆制到空的Survivor區,在下次新生代回收時,Eden區存活的物件和這個Survivor存活的物件被覆制到另外那個Survivor區,並且清空當前Survivor區,經過多次新生代垃圾回收,還存活的物件會被移動到年老代。

執行緒池

ThreadPoolExecutor tp = new ThreadPoolExecutor(1, 1, 60, TimeUnit, SECONDS, new LinkedBlockingQueue(count));

tp.execute(new Runnable(){

  public void run(){}

});

使用執行緒池的方式是復用執行緒的,不用每次都創建執行緒。而創建執行緒的開銷占比較大。

synchronized

synchronized修飾靜態方法、物件方法、代碼塊

ReetrantLock

  • **提供tryLock方法,嘗試呼叫,如果鎖被其他執行緒持有,則tryLock立即傳回

  • 可以實現公平鎖

  • ReentrantReadWriteLock:讀寫鎖,用於讀多寫少並且讀不需要互斥的場景

  • 可以有多個Condition

volatile

可見性指一個執行緒修改變數值後,其他執行緒中能夠看到這個值。volatile雖然解決了可見性問題,但是不能控制併發

Atomics

原子操作,如AtomicInteger內部通過JNI的方式使用了硬體支持的CAS指令

wait、notify和notifyAll

wait是等待執行緒,notify是喚醒一個等待執行緒(並不能指定,隨機),notifyAll是喚醒所有的等待執行緒。

CountDownLatch

java.util.concurrent包中的一個類,主要提供的機制是當多個執行緒都到達了預期狀態或完成預期工作時觸發事件,其他執行緒可以等待這個事件來觸發自己後續的工作。

CyclicBarrier

迴圈屏障,可以協同多個執行緒,讓多個執行緒在這個屏障前等待,知道所有執行緒都到達了這個屏障時,再一起繼續執行後面的動作。

Semaphore

Semaphore是用於管理信號量的,構造時傳入可供管理的信號量的數值。如果信號量只有一個,就退化到互斥鎖了,如果多於一個,則主要用於控制併發數。

Exchanger

用於兩個執行緒之間進行資料交換,執行緒會阻塞在exchange方法上,知道另外一個執行緒也到了同一個Exchanger的exchange方法時,二者進行交換。

Future和FutureTask

Future是一個接口,FutureTask是一個具體實現類

Future future = getDataFromRemote2();

……

HashMap data = (HashMap) future.get();

 

public Future getDataFromRemote2(){

  return threadPool.submit(new Callable(){

    public HashMap call()throws Exception{

      return getDataFromRemote();

    }

  });

}

getDataFromRemote2還是使用率getDataFromRemote完成操作,並且用到了執行緒池:把任務加入執行緒池中,把Future物件傳回出去。

併發容器

CopyOnWrite:更改容器時,把容器複製一份進行修改,用於讀多寫少

Concurrent:儘量保證讀不加鎖,並且修改時不影響讀,所以比讀寫鎖更高的併發性能

動態代理

繼承InvocationHandler

反射

Java反射機制是指在運行狀態,對於任意一個類,都能知道這個類所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性。

Class clazz = Object.getClass();

String className = clazz.getName();

Method[] methods = clazz.getDeclaredMethods();

Field[] fields = clazz.getDeclaredFields();

// 構建物件

Class.forName(“ClassName”).newInstance();

// 動態執行方法

Method method = clazz.getDeclaredMethod(“add”, int.class, int.class);

method.invoke(this, 1, 1);

// 動態操作方法

Field field = clazz.getDeclaredField(“name”);

field.set(this, “test”);

網絡通信的選擇

BIO、NIO、AIO

第三方框架,MINA,Netty

第四章 服務框架

服務呼叫端的設計與實現

呼叫發起==>尋址路由==>協議適配和序列化==>網絡傳輸

==>反序列化 協議解析==>得到結果傳回給呼叫方

1、確定服務框架的使用方式

2、服務呼叫者與服務提供者之間通信方式的選擇

3、引入基於接口、方法、引數的路由

4、多機房場景,避免跨機房呼叫,一是在服務註冊中心甄別,二是地址過濾

5、服務呼叫端的流控處理

6、序列化與反序列化處理,Java本身的序列化性能問題、跨語言問題、序列化後語言長度

7、網絡通信實現選擇:BIO、NIO、AIO

8、支持多種異步服務呼叫方式:Oneway,Callback,Future,可靠異步

服務提供端的設計與實現

1、如何暴露遠程服務

2、服務端對請求處理的流程

3、執行不同服務的執行緒池隔離

4、服務提供端的流控處理

第五章 資料訪問層

分佈式事務

  • 兩階段提交:2PC

  • 一致性理論:CAP、BASE

  • Paxos協議

多機自增主鍵問題

考慮唯一性和連續性,UUID生成方式(IP、MAC、時間等)連續性不好

實現方案1:把ID集中放在一個地方進行管理,對每個Id序列獨立管理,每台機器使用Id時都從這個Id生成器上取。

缺點:

  • 性能問題:每次都去遠程取Id會有資源損耗

  • 生成器的穩定性問題,作為一個無狀態的集群,保證可用性

  • 儲存的問題

實現方案2:舍掉Id生成器,把相關的邏輯放到需要生成Id的應用本身。每個生成器讀取可用的Id,然後給應用使用,但是資料的Id並不是嚴格按照進入資料庫順序而增大的。

應對多機的資料查詢

跨庫Join

  • 在應用層把原來資料庫的Join操作分成多次的資料庫操作

  • 資料冗餘,對常用信息進行冗餘

  • 借助外部系統,如搜索引擎

外鍵約束

外鍵約束比較難解決,不能完全依賴資料庫本身來完成之前的功能了。

跨庫查詢的問題及解決

一張邏輯表,對應多個資料庫的多張資料表,在一些場景下比較複雜,如排序、最大最小求和等函式處理、求平均值、非排序分頁、排序後分頁。

如何對外提供資料訪問層的功能

1、為用戶提供專有API

2、通用的方式,資料層JDBC

3、基於ORM或類ORM接口的方式

直接基於JDBC驅動方式較好~

資料層的整體流程

SQL解析==>規則處理==>SQL改寫==>資料源選擇==>SQL執行==>結果集傳回合併處理

1、SQL解析階段

  • SQL解析並不完備

  • SQL中不帶有分庫條件,但實際上是可以明確指定分庫的

2、規則處理階段

  • 採用固定哈希演算法作為規則,如根據用戶id取模,id mod 2分庫,再id mod 4分表。實現簡單,但是如果擴容的話比較複雜!

  • 一致性哈希,節點對應的哈希值為一個範圍,分配給現有節點。如果有節點加入,會從原有節點分管一部分範圍的哈希值;如果有節點退出,會把哈希值交給下一個節點管理

  • 虛擬節點對一致性哈希的改進,引入虛擬節點,如4個物理節點可以變為多個虛擬節點,每個虛擬節點支持連續的哈希環上的一段。

  • 映射表與規則自定義計算方式,映射表是根據分庫分表欄位的值的查表法來確定資料源的方法,一般用於對熱點資料的特殊處理。

3、為什麼要改寫SQL

分庫分表後,同一個賣家的商品可能會分在多個庫中,查詢就要跨庫。分佈的不同資料庫中的表的結構雖然一樣,但是表的名字、索引名字未必一樣,所以要修改SQL。

還有需要修改SQL的地方,如跨庫計算平均值,必須修改SQL獲取數量、總數後再進行計算。

4、如何選擇資料源,讀寫分析

5、執行SQL和結果處理階段,異常處理和判斷

第六章 訊息中間件

JMS,Java Message Service是Java EE中關於訊息的規範,ActiveMQ等是對這個規範的實現。如果是小型系統直接使用JMS是一個經濟的選擇,在大型系統中不適合使用JMS。

如何解決訊息發送一致性

訊息發送一致性是指產生訊息的業務動作與訊息發送一致,即如果業務操作成功了,那麼由這個操作產生的訊息一定要發送出去。

1、發送訊息給訊息中間件

2、訊息中間件入庫訊息

3、訊息中間件傳回結果

4、業務操作

5、發送業務操作結果給訊息中間件

6、更改儲存中訊息狀態

……

註:後面內容略,不方便摘要

參考

  • 《大型網站系統與Java中間件實踐》

看完本文有收穫?請轉發分享給更多人

關註「ImportNew」,提升Java技能

赞(0)

分享創造快樂