什么是分層設計?它有何好處?
關注我
1. 什么是分層設計?它有何好處? 2. 計算機語言的發(fā)展 3. Linux 內核 4. TCP/IP 網絡協議堆棧 5. Netty 6. 微服務分層 7. Rails On Rack 8. 總結
在日常開發(fā)中,經常聽到大家說一句話“任何需求都可以通過一個間接的的中間層來解決”。今天,通過幾個 case 就“分層”話題梳理下自己的思考,其中,有些 case 比較直觀,而有些不那么直觀,甚至有些微妙,需要我們自己多品味。這意味著學習過程需要我們不斷將新知識與舊知識進行關聯,形成自己的知識體系,而非一個個知識孤島。
1. 什么是分層設計?它有何好處?

分層設計將軟件劃分成若干層,每一層只解決一部分問題,通過所有層的協作來完成整體目標。一個復雜問題通過分解成一個個系統(tǒng)子問題,這樣就有效的降低了每個子問題的規(guī)模與復雜度。
分層設計帶來的好處:
降低了系統(tǒng)軟件的復雜度,將一個復雜問題通過分解,分而治之 功能的復用和封裝
2. 計算機語言的發(fā)展

機器語言
早期,軟件開發(fā)是機器語言,直接用二進制 0 和 1 表示機器可以識別的指令和數據,看起來像這樣:
0010000100100011
這就是計算機 CPU 唯一可以理解的語言。對人類為說,二進制的程序是不可讀的。
匯編語言
為了解決語言可讀性的問題,匯編程序誕生了。匯編程序是人類可讀的機器代碼。它又被稱為“符號語言”,使用助記符來代替機器的操作碼。
匯編語言是二進制的文本形式,與 CPU 的指令是一一對應的關系。而我們不同的 CPU 體系結構(比如 PC 的 X86、嵌入式的 ARM) 是不同的,面向機器的語言帶來的問題就是:對于不同的 CPU 體系架構,就需要不同的匯編語言。
高級語言
為了解決語言對機器的無關性,高級語言誕生了。一條高級語言通常由若干條機器語言實現的,并且不具有對應性。
高級語言讓開發(fā)者不需要關注底層 CPU 體系結構與指令,只關注業(yè)務即可。
計算機語言的發(fā)展就是不斷的抽象,只有通過抽象,將一個復雜的的系統(tǒng)變成一層層的接口集合,讓我們每次只需要考慮關注當前層集合內的邏輯,而不用去考慮當前層次以上或者以下的復雜度,才有可能讓我們從復雜系統(tǒng)中解放出來,逐步理解以及構造一個復雜系統(tǒng)。
3. Linux 內核
內核功能層與內核硬件層

操作系統(tǒng)內核,可以簡化理解成三大層:
內核接口層?:向上對用戶態(tài)應用程序提供一套接口子集,開發(fā)者使用的系統(tǒng)調用 APIs。 內核功能層?:這一層完成各種實際的功能,我們知道 OS 主要負責資源管理、內存、進程這些資源,物理內存如何申請、釋放,進程如何調度。具體來說進程管理、內存管理、中斷管理、設備管理。 內核硬件層?:分離硬件的相關性,我們知道一個 OS 可以運行不同的指令集,也就是運行在不同的硬件平臺。
不管是 ARM 體系結構,還是 X86,選擇一個進程調度的算法是可以相同的,需要改變的進程切換相關代碼,因為不同的硬件平臺的上下文是不同的,CPU 的寄存器也不同。這時候最好的設計是分層,當操作系統(tǒng)運行在不同的硬件平臺時,就只需要修改硬件平臺相關層代碼,實現操作系統(tǒng)的高可移植性。
操作系統(tǒng)有兩個關鍵設計:
內核接口層區(qū)分用戶態(tài)與內核態(tài),來保護硬件資源受限訪問。 內核硬件層分離多種硬件平臺相關性。這種分層的架構,極大提升了系統(tǒng)的穩(wěn)定性和擴展性。
MMU 抽象層
操作系統(tǒng)負責管理物理內存,而用戶進程使用虛擬內存。操作系統(tǒng)呈現給用戶進程的是連續(xù)的虛擬空間,但不一定是連續(xù)的物理空間。因為物理內存被整個 OS 共享。
什么是 MMU 呢?它是硬件,即內存管理單元,它對 CPU 發(fā)出的訪存地址進行映射與檢查,可以讓處理器發(fā)出的訪存地址訪問不同的物理內存單元。
如果將計算機上有限的物理內存分配給多個應用程序使用,如果讓應用程序直接訪問物理內存,如果沒有 MMU 這層抽象呢?帶來的問題是每個應用程序地址空間不隔離,內存使用率低,程序運行地址也無法固定。

解決的問題:虛擬內存 VA 與物理內存 PA 的映射——通過在 CPU 與內存之間加入 MMU 抽象層,讓 CPU 在運行指令時發(fā)出的 VA 虛擬地址通過 MMU 轉換后變成 PA 物理地址,然后再去訪問物理內存。

MMU 引入帶來的好處:
權限控制??梢詫σ恍┨摂M地址進行訪問控制,比較代碼段為只讀,用戶程序代可寫。 提升內存使用率:物理內存按需申請。fork 子進程的對應的物理空間是能過寫時復制才進行真正的物理內存分配。 不同進程之間可以使用相同的虛擬內存地址空間,而進程的物理內存又可以隔離。 系統(tǒng)運行多個進程,所分配的內存之和可以大于實際物理內存大小。
這是我認為最經典、最本質、最受啟發(fā)的中間抽象層的設計。
CPU 與外設的通信
CPU 訪問外設有兩種方法;
IO 與內存統(tǒng)一編址 IO 與內存的獨立編址

外設接口中的 IO 寄存器(即 IO 端口)與主存單元一樣看待,每個端口占用一個存儲單元的地址,將主存的一部分劃分出來用作 IO 的地址空間。
把外設的寄存器當做是一個內存地址,從而 CPU 以類似訪問內存相同的方式來操作外設。
對 IO 外設的端口映射到一個物理內存單元地址,在 CPU 與外設之間的“內存”抽象層,帶來好處是訪問內存一樣去訪問外設。
小結
Linux 中的內核硬件層設計、MMU、CPU 與 IO 外設通信設計處處體現了分層 / 中間層的設計思想。
4. TCP/IP 網絡協議堆棧
從最底層的物理鏈路層層層向上封裝抽象,解決了復雜的網絡通信的問題。同樣的,任何復雜的問題,通過分層最終總能夠回歸最本質、最簡單。這個分層架構,對所有開發(fā)者而言,再熟悉不過,它的引入是想與后續(xù)介紹的 Netty 形成對比。這里先賣個關子,后面解開謎底。

舉例說明::
來自杭州西湖區(qū)某個小區(qū)的商務人士來京出差后,被確診新冠肺炎,實施在京隔離措施,同時北京將此報告先發(fā)給浙江省,接著浙江省發(fā)給杭州市政府,然后市政府再向西湖區(qū)發(fā)送,最后到達某小區(qū)。這個發(fā)送報告過程也是分層報告思想。
DNS 中間層

DNS (domain name system) 是域名系統(tǒng),是用來將主機轉換為 IP 地址的服務。我們有至少三種方式在互聯網上標識一臺主機、主機名、IP 地址以及 MAC 地址。為什么有引入 DNS 中間抽象層呢? 主要是主機名便于記憶,而 IP 地址方便于在計算機網絡設備的處理,因此需要設計出一個 DNS 協議 (中間層) 來做主機名到 IP 地址的轉換。
ARP 中間層

ARP(address resolution protocol) 是地址解析協議,它根據 IP 地址來獲取物理地址。上面也談到,MAC 與 IP 都可以用來標識一臺主機。那二者區(qū)別是什么?
同一個局域網中的一臺主機和另一臺主機通信的時候,需要通過 MAC 地址進行定位,之后才能進行數據包的傳送。
而在網絡層和傳輸層中,主機之間是通過 IP 地址來定位的,對應的數據包中必須攜帶目標主機的 IP 地址, 而沒有 MAC 地址。
因此,ARP 協議 (中間層) 用來實現從 IP 到 MAC 地址的轉換。
5. Netty
Netty 提供了異步的,基于事件驅動的網絡應用程序框架。目前分布式搜索引擎,Spark 框架底層是擴展使用 Netty 框架。Netty 本身的架構理解有些曲線,為了講清楚,我還是希望循序漸進方式,通過它的發(fā)展歷史來一步步介紹。先鋪墊再介紹,大家需要一些耐心。
傳統(tǒng)阻塞 IO 服務模型

思路:
采用阻塞 IO 模式獲取輸入數據 每個連接都需要獨立的線程完成數據的輸入,業(yè)務的處理和數據返回
問題:
當并發(fā)數很大時,就會創(chuàng)建大量的線程,占用了很大的系統(tǒng)資源。 連接創(chuàng)建后,如果當前線程沒有數據可讀,這個線程會阻塞在 read 方法上,造成資源浪費。
單 Reactor 單線程

思路:
通過引入 selector 事件選擇器來監(jiān)聽多路連接的請求。 Reactor 對象通過 selector 監(jiān)控客戶端請求事件后,通過 Dispatch 進行分發(fā)。 如果建立連接請求事件,則由 Acceptor 負責建立一個連接,然后創(chuàng)建一個 Handler 對象處理連接完成后的業(yè)務處理。
問題:
模型簡單,沒有多線程,資源競爭的問題。所以工作在一個線程完成。 性能問題,一個線程,無法發(fā)揮多核 CPU 的性能。 可靠性問題,線程 crash,會導致整個系統(tǒng)不可用。
主從 Reactor 多線程
主 React 處理所有 socket 連接事件的監(jiān)聽和響應,而從 React 處理所有 socket 的讀寫事件的監(jiān)聽與響應。主從 React 都在多線程中運行。

Netty 模型
Netty 主要基于主從 Reactor 多線程模型發(fā)展出來的。

Netty 邏輯架構
前面 Netty 的發(fā)展階段都是鋪墊,Nettty 邏輯架構為典型網絡分層架構設計,從下到上分別為網絡通信層、事件調度層、服務編排層。

網絡通信層?:它執(zhí)行網絡 I/O 操作,核心組件包含 BootStrap、ServerBootStrap、Channel?!狢hannel 通道,提供了基礎的 API 用于操作網絡 IO,比如 bind、connect、read、write、flush 等等。它以 JDK NIO Channel 為基礎,提供了更高層次的抽象,同時屏蔽了底層 Socket 的復雜性。Channel 有多種狀態(tài),比如連接建立、數據讀寫、連接斷開。隨著狀態(tài)的變化,Channel 處于不同的生命周期,背后綁定相應的事件回調函數。
事件調度層?:它的核心組件包含 EventLoopGroup、EventLoop。——EventLoop 本質是一個線程池,主要負責接收 Socket I/O 請求,并分配事件循環(huán)器來處理連接生命周期中所發(fā)生的各種事件。
服務編排層?:它的職責實現網絡事件的動態(tài)編排和有序傳播——ChannelPipeline 基于責任鏈模式,方便業(yè)務邏輯的攔截和擴展;本質上它是一個雙向鏈表將不同的 ChannelHandler 鏈接在一塊,當 I/O 讀寫事件發(fā)生時, 會依次調用 ChannelHandler 對 Channel(Socket) 讀取的數據進行處理。
ChannelPipeline 私有協議棧 vs. TCP/IP 協議棧

前面鋪墊這么久,就是為了自然過渡到上面的圖,請務必與 TCP/IP 協議棧進行對比。
socket。read 經過 TCP/IP 協議棧后,進入 netty 的網絡通信層,事件調度層,最后來到服務編排層。而服務編排層的 channelPipeline 的設計也是一個 upstream/downstream 的 stack,一進一出的二個 pipeline。負責處理流入 / 流出的數據包。
上面的 stack 就非常類似 TCP/IP 協議棧。根據公司組織的需要可以定制分層的私有協議棧,比如從 authentication-handler、message-validation-handler、message-encode-handler、message-decoder-handler。
6. 微服務分層

grpc-gateway?——它是一個開源框架, 讀取 protobuf 接口定義并生成一個反向代理服務器, 此服務器時一步將 restful http API 轉換成 grpc 服務.
middleware?——實現鑒權功能, 比如哪些 URL 需要權限檢驗
handler 通用處理層?——參數檢驗: handler 層負責執(zhí)行與客戶端約定參數的檢驗, 檢驗通過后再組裝成后端服務需要的數據結構發(fā)往后端;接口聚合 / 組合服務: handler 層可以根據業(yè)務需要, 調用多個后端服務的 endpoint 來組合實現一個新的接口, 同時將下層返回的數據進行聚合處理.
service/model 業(yè)務邏輯層?——對業(yè)務邏輯的封裝, 負責將多個 DAO 數據結構轉換和封裝成一個有邏輯意義的模型;可以引入緩存策略, 優(yōu)化數據存取效率.
DAO 層?——數據訪問層, 主要負責操作 DB 中某張表并映射到內存中某個 DAO 模型;與數據表結構一一對應, 通過 DAO 內存模型向上層傳遞數據源的對象.
數據訪問層 DAL?——對底層的數據源做統(tǒng)一的抽象, 屏蔽數據庫. 如果沒有 DAL 的存在, 那么向乎所有的業(yè)務邏輯層都會去與具體的數據庫存儲強挷定. 耦合性就很高.
還有一個補充點:
業(yè)務邏輯層中的服務在實際場景中不可避免的會出現互相調用的場景,這種情況往往需要將耦合 / 公共的功能進行下沉,比如數據請求下沉為數據訪問層服務,而業(yè)務下沉為穩(wěn)定的通用業(yè)務服務,被其它服務穩(wěn)定依賴。
7. Rails On Rack
熟悉 Ruby On Rails Web 應用框架的開發(fā)者,肯定知道 Rack 是如何成為應用容器 (webserver) 和應用框架之間的橋梁的。

Rack 在 webserver 和應用框架之間提供了一套最小的 API 接口,如果 webserver 都遵循 Rack 提供的這套規(guī)則,那么所有的框架都能通過協議任意地改變底層使用 webserver。

Rack 分層設計非常類似 Decorate Pattern 或者 Chain of Responsibility Pattern。
8. 總結
本文作者結合自身工作經驗, 總結一些典型分層設計案例
計算機語言的發(fā)展 Linux 內核設計 (內核功能層與內核硬件層,MMU 抽象層,CPU 與外設的通信) TCP/IP 網絡協議堆棧 (DNS 和 ARP 協議) Netty 框架發(fā)展以及分層私有協議棧分析 微服務分層 應用框架 Rails On Rack
這些案例充分說明了計算機系統(tǒng)本身就是通過一層一層抽象構造出來的。
硬件方面是從一個個小的晶體管,抽象成一個個門電路,再到 CPU 器件,最后抽象組成計算機。 軟件設計也是一個層次一個層次功能完善疊加的,無論是自頂向下還是自底向上。
楊敏,Freewheel 首席工程師,負責 SFX 團隊的整體工作。目前從事服務化框架、容器化平臺相關。關注與感興趣的技術主要有 Python/Java 虛擬機、Golang、K8s、分布式數據庫、分布式搜索引擎 ElasticSearch。
END
若覺得文章對你有幫助,隨手轉發(fā)分享,也是我們繼續(xù)更新的動力。
長按二維碼,掃掃關注哦
?「C語言中文網」官方公眾號,關注手機閱讀教程??

