天天看點

RPC架構Dubbo深入分析

1,背景

随着網際網路的發展,網站應用的規模不斷擴大,正常的垂直應用架構已無法應對,分布式服務架構以及流動計算架構勢在必行,亟需一個治理系統確定架構有條不紊的演進

單一應用架構

當網站流量很小時,隻需一個應用,将所有功能都部署在一起,以減少部署節點和成本

此時,用于簡化增删改查工作量的 資料通路架構(ORM) 是關鍵

垂直應用架構 當通路量逐漸增大,單一應用增加機器帶來的加速度越來越小,将應用拆成互不相幹的幾個應用,以提升效率此時,用于加速前端頁面開發的 Web架構(MVC) 是關鍵分布式服務架構 當垂直應用越來越多,應用之間互動不可避免,将核心業務抽取出來,作為獨立的服務,逐漸形成穩定的服務中心,使前端應用能更快速的響應多變的市場需求此時,用于提高業務複用及整合的 分布式服務架構(RPC) 是關鍵分布式服務RPC架構

按業務線拆分

部署分離

每次釋出隻部署部分伺服器

每個節點可根據不同需求伸縮擴充

每個應用之間更新,部署,運作不影響

團隊分離

資料分離

停止RPC濫用,垂直業務内優先通過本地jar調用,跨業務才采用RPC調用

正确的識别業務邏輯的歸屬,讓各個子產品最大化内聚,從性能,可用性和維護性上減少耦合

流動計算架構 當服務越來越多,容量的評估,小服務資源的浪費等問題逐漸顯現,此時需增加一個排程中心基于通路壓力實時管理叢集容量,提高叢集使用率此時,用于提高機器使用率的資源排程和治理中心(SOA)是關鍵

2,需求

在大規模服務化之前,應用可能隻是通過RMI或Hessian等工具,簡單的暴露和引用遠端服務,通過配置服務的URL位址進行調用,通過F5等硬體進行負載均衡

當服務越來越多時,服務URL配置管理變得非常困難,F5硬體負載均衡器的單點壓力也越來越大

此時需要一個服務注冊中心,動态的注冊和發現服務,使服務的位置透明

并通過在消費方擷取服務提供方位址清單,實作軟負載均衡和Failover,降低對F5硬體負載均衡器的依賴,也能減少部分成本

當進一步發展,服務間依賴關系變得錯蹤複雜,甚至分不清哪個應用要在哪個應用之前啟動,架構師都不能完整的描述應用的架構關系

這時,需要自動畫出應用間的依賴關系圖,以幫助架構師理清理關系

接着,服務的調用量越來越大,服務的容量問題就暴露出來,這個服務需要多少機器支撐?什麼時候該加機器? 為了解決這些問題,第一步,要将服務現在每天的調用量,響應時間,都統計出來,作為容量規劃的參考名額其次,要可以動态調整權重,線上上,将某台機器的權重一直加大,并在加大的過程中記錄響應時間的變化,直到響應時間到達閥值,記錄此時的通路量,再以此通路量乘以機器數反推總容量

3,Dubbo架構

Dubbo分層

層次結構

Business

Service

RPC

Config

Proxy

Registry

Cluster

Monitor

Protocol

Remoting

Exchange

Transport

Serialize

層說明

config(配置層 )

對外配置接口

以ServiceConfig, ReferenceConfig為中心,可以直接new配置類,也可以通過spring解析配置生成配置類

proxy(服務代理層)

服務接口透明代理,生成服務的用戶端Stub和伺服器端Skeleton

以ServiceProxy為中心,擴充接口為ProxyFactory

選擇

Javassist ProxyFactory

Jdk ProxyFactory

registry( 注冊中心層)

封裝服務位址的注冊與發現

以服務URL為中心,擴充接口為RegistryFactory, Registry, RegistryService

Zookeeper

支援基于網絡的叢集方式,有廣泛周邊開源産品,建議使用dubbo-2.3.3以上版本(推薦使用)

依賴于Zookeeper的穩定性

Redis

支援基于用戶端雙寫的叢集方式,性能高

要求伺服器時間同步,用于檢查心跳過期髒資料

Multicast

去中心化,不需要安裝注冊中心

依賴于網絡拓普和路由,跨機房有風險

Simple

Dogfooding,注冊中心本身也是一個标準的RPC服務

沒有叢集支援,可能單點故障

cluster( 路由層)

封裝多個提供者的路由及負載均衡,并橋接注冊中心

以Invoker為中心,擴充接口為Cluster, Directory, Router, LoadBalance

Cluster選擇

Failover

失敗自動切換,當出現失敗,重試其它伺服器,通常用于讀操作(推薦使用)

重試會帶來更長延遲

Failfast

快速失敗,隻發起一次調用,失敗立即報錯,通常用于非幂等性的寫操作

如果有機器正在重新開機,可能會出現調用失敗

Failsafe

失敗安全,出現異常時,直接忽略,通常用于寫入審計日志等操作

調用資訊丢失

Failback

失敗自動恢複,背景記錄失敗請求,定時重發,通常用于消息通知操作

不可靠,重新開機丢失

Forking

并行調用多個伺服器,隻要一個成功即傳回,通常用于實時性要求較高的讀操作

需要浪費更多服務資源

Broadcast

廣播調用所有提供者,逐個調用,任意一台報錯則報錯,通常用于更新提供方本地狀态

速度慢,任意一台報錯則報錯

Router選擇

Random

随機,按權重設定随機機率(推薦使用)

在一個截面上碰撞的機率高,重試時,可能出現瞬間壓力不均

RoundRobin

輪循,按公約後的權重設定輪循比率

存在慢的機器累積請求問題,極端情況可能産生雪崩

LeastActive

最少活躍調用數,相同活躍數的随機,活躍數指調用前後計數差,使慢的機器收到更少請求

不支援權重,在容量規劃時,不能通過權重把壓力導向一台機器壓測容量

ConsistentHash

一緻性Hash,相同參數的請求總是發到同一提供者,當某一台提供者挂時,原本發往該提供者的請求,基于虛拟節點,平攤到其它提供者,不會引起劇烈變動

壓力分攤不均

路由規則

條件路由

基于條件表達式的路由規則,功能簡單易用

有些複雜多分支條件情況,規則很難描述

腳本路由

基于腳本引擎的路由規則,功能強大

沒有運作沙箱,腳本能力過于強大,可能成為後門

容器

Spring

自動加載META-INF/spring目錄下的所有Spring配置

Jetty

啟動一個内嵌Jetty,用于彙報狀态

大量通路頁面時,會影響伺服器的線程和記憶體

Log4j

自動配置log4j的配置,在多程序啟動時,自動給日志檔案按程序分目錄

使用者不能控制log4j的配置,不靈活

monitor( 監控層)

RPC調用次數和調用時間監控

以Statistics為中心,擴充接口為MonitorFactory, Monitor, MonitorService

protocol( 遠端調用層)

封裝RPC調用

以Invocation, Result為中心,擴充接口為Protocol, Invoker, Exporter

Dubbo協定

采用NIO複用單一長連接配接,并使用線程池并發處理請求,減少握手和加大并發效率,性能較好(推薦使用)

适合于小資料量大并發的服務調用,以及服務消費者機器數遠大于服務提供者機器數的情況

Dubbo預設協定不适合傳送大資料量的服務,比如傳檔案,傳視訊等,除非請求量很低

Dubbo協定預設每服務每提供者每消費者使用單一長連接配接,如果資料量較大,可以使用多個連接配接

為防止被大量連接配接撐挂,可在服務提供方限制大接收連接配接數,以實作服務提供方自我保護

在大檔案傳輸時,單一連接配接會成為瓶頸

總結

連接配接個數:單連接配接

連接配接方式:長連接配接

傳輸協定:TCP

傳輸方式:NIO異步傳輸

序列化:Hessian二進制序列化

适用範圍:傳入傳出參數資料包較小(建議小于100K),消費者比提供者個數多,單一消費者無法壓滿提供者,盡量不要用dubbo協定傳輸大檔案或超大字元串。

适用場景:正常遠端服務方法調用

Rmi協定

可與原生RMI互操作,基于TCP協定

偶爾會連接配接失敗,需重建Stub

Hessian協定

可與原生Hessian互操作,基于HTTP協定

需hessian.jar支援,http短連接配接的開銷大

Hessian協定用于內建Hessian的服務,Hessian底層采用Http通訊,采用Servlet暴露服務,Dubbo預設内嵌Jetty作為伺服器實作

可以和原生Hessian服務互操作

提供者用Dubbo的Hessian協定暴露服務,消費者直接用标準Hessian接口調用

或者提供方用标準Hessian暴露服務,消費方用Dubbo的Hessian協定調用

基于Hessian的遠端調用協定

連接配接個數:多連接配接

連接配接方式:短連接配接

傳輸協定:HTTP

傳輸方式:同步傳輸

适用範圍:傳入傳出參數資料包較大,提供者比消費者個數多,提供者壓力較大,可傳檔案

适用場景:頁面傳輸,檔案傳輸,或與原生hessian服務互操作

限制

參數及傳回值需實作Serializable接口

參數及傳回值不能自定義實作List, Map, Number, Date, Calendar等接口,隻能用JDK自帶的實作,因為hessian會做特殊處理,自定義實作類中的屬性值都會丢失

exchange( 資訊交換層)

封裝請求響應模式,同步轉異步

以Request, Response為中心,擴充接口為Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer

transport( 網絡傳輸層)

抽象mina和netty為統一接口

以Message為中心,擴充接口為Channel, Transporter, Client, Server, Codec

Netty

性能較好(推薦使用)

一次請求派發兩種事件,需屏蔽無用事件

Mina

老牌NIO架構,穩定

待發送消息隊列派發不及時,大壓力下,會出現FullGC

Grizzly

Sun的NIO架構,應用于GlassFish伺服器中

線程池不可擴充,Filter不能攔截下一Filter

serialize( 資料序列化層)

可複用的一些工具

擴充接口為Serialization, ObjectInput, ObjectOutput, ThreadPool

Hessian

性能較好,多語言支援(推薦使用)

Hessian的各版本相容性不好,可能和應用使用的Hessian沖突,Dubbo内嵌了hessian3.2.1的源碼

Dubbo

通過不傳送POJO的類元資訊,在大量POJO傳輸時,性能較好

當參數對象增加字段時,需外部檔案聲明

Json

純文字,可跨語言解析,預設采用FastJson解析

性能較差

Java

Java原生支援

關系說明

在RPC中,Protocol是核心層,也就是隻要有Protocol + Invoker + Exporter就可以完成非透明的RPC調用,然後在Invoker的主過程上Filter攔截點。

圖中的Consumer和Provider是抽象概念,隻是想讓看圖者更直覺的了解哪些類分屬于用戶端與伺服器端,不用Client和Server的原因是Dubbo在很多場景下都使用Provider, Consumer, Registry, Monitor劃分邏輯拓普節點,保持統一概念。

而Cluster是外圍概念,是以Cluster的目的是将多個Invoker僞裝成一個Invoker,這樣其它人隻要關注Protocol層Invoker即可,加上Cluster或者去掉Cluster對其它層都不會造成影響,因為隻有一個提供者時,是不需要Cluster的。

Proxy層封裝了所有接口的透明化代理,而在其它層都以Invoker為中心,隻有到了暴露給使用者使用時,才用Proxy将Invoker轉成接口,或将接口實作轉成Invoker,也就是去掉Proxy層RPC是可以Run的,隻是不那麼透明,不那麼看起來像調本地服務一樣調遠端服務。

而Remoting實作是Dubbo協定的實作,如果你選擇RMI協定,整個Remoting都不會用上,Remoting内部再劃為Transport傳輸層和Exchange資訊交換層,Transport層隻負責單向消息傳輸,是對Mina,Netty,Grizzly的抽象,它也可以擴充UDP傳輸,而Exchange層是在傳輸層之上封裝了Request-Response語義。

Registry和Monitor實際上不算一層,而是一個獨立的節點,隻是為了全局概覽,用層的方式畫在一起

Dubbo子產品分包 子產品

dubbo-common 公共邏輯子產品,包括Util類和通用模型。

dubbo-remoting 遠端通訊子產品,相當于Dubbo協定的實作,如果RPC用RMI協定則不需要使用此包。

dubbo-rpc 遠端調用子產品,抽象各種協定,以及動态代理,隻包含一對一的調用,不關心叢集的管理。

dubbo-cluster 叢集子產品,将多個服務提供方僞裝為一個提供方,包括:負載均衡, 容錯,路由等,叢集的位址清單可以是靜态配置的,也可以是由注冊中心下發。

dubbo-registry 注冊中心子產品,基于注冊中心下發位址的叢集方式,以及對各種注冊中心的抽象。

dubbo-monitor 監控子產品,統計服務調用次數,調用時間的,調用鍊跟蹤的服務。

dubbo-config 配置子產品,是Dubbo對外的API,使用者通過Config使用Dubbo,隐藏Dubbo所有細節。

dubbo-container 容器子產品,是一個Standlone的容器,以簡單的Main加載Spring啟動,因為服務通常不需要Tomcat/JBoss等Web容器的特性,沒必要用Web容器去加載服務

與分層的不同點在于

container為服務容器,用于部署運作服務,沒有在層中畫出。

protocol層和proxy層都放在rpc子產品中,這兩層是rpc的核心,在不需要叢集時(隻有一個提供者),可以隻使用這兩層完成rpc調用。

transport層和exchange層都放在remoting子產品中,為rpc調用的通訊基礎。

serialize層放在common子產品中,以便更大程度複用

模型 Protocol是服務域,它是Invoker暴露和引用的主功能入口,它負責Invoker的生命周期管理Invoker是實體域,它是Dubbo的核心模型,其它模型都向它靠擾,或轉換成它,它代表一個可執行體,可向它發起invoke調用,它有可能是一個本地的實作,也可能是一個遠端的實作,也可能一個叢集實作Invocation是會話域,它持有調用過程中的變量,比如方法名,參數等基本原則 采用Microkernel + Plugin模式,Microkernel隻負責組将Plugin,Dubbo自身的功能也是通過擴充點實作的,也就是Dubbo的所有功能點都可被使用者自定義擴充所替換采用URL作為配置資訊的統一格式,所有擴充點都通過傳遞URL攜帶配置資訊擴充點加載 Dubbo的擴充點加載從JDK标準的SPI(Service Provider Interface)擴充點發現機制加強而來在擴充類的jar包内,放置擴充點配置檔案:META-INF/dubbo/接口全限定名,内容為:配置名=擴充實作類全限定名,多個實作類用換行符分隔注意:這裡的配置檔案是放在你自己的jar包内,不是dubbo本身的jar包内,Dubbo會全ClassPath掃描所有jar包内同名的這個檔案,然後進行合并Provider暴露服務的過程 具體服務到Invoker的轉換

ServiceConfig:ref對外提供服務實際類

ProxyFactory:getInvoker()

JavassistProxyFactory

JdkProxyFactory

Invoker轉換為Exporter

Invoker:AbstractProxyInvoker的執行個體

Protocal:export()

DubboProtocol

Dubbo協定的Invoker轉為Exporter發生在DubboProtocol類的export方法,它主要是打開socket偵聽服務,并接收用戶端發來的各種請求,通訊細節由Dubbo自己實作

HessianProtocol

InjvmProtocol

它通過Spring或Dubbo或JDK來實作RMI服務,通訊細節這一塊由JDK底層來實作,這就省了不少工作量

RmiProtocol

WebServiceProtocol

Export

Consumer消費服務的過程 把遠端服務轉為Invoker

ReferenceConfig

Protocol:refer()

把Invoker轉為用戶端需要的接口

Invoker

DubboInvoker

HessianInvoker

InjvmInvoker

RmiInvoker

WebServiceInvoker

ProxyFactory:getProxy()

ref

過程:首先ReferenceConfig類的init方法調用Protocol的refer方法生成Invoker執行個體(如上圖中的紅色部分),這是服務消費的關鍵。接下來把Invoker轉換為用戶端需要的接口

無處不在的Invoker 由于Invoker是Dubbo領域模型中非常重要的一個概念,很多設計思路都是向它靠攏服務消費者Invoker

使用者代碼通過這個proxy調用其對應的Invoker(DubboInvoker、 HessianRpcInvoker、 InjvmInvoker、 RmiInvoker、 WebServiceInvoker中的任何一個),而該Invoker實作了真正的遠端服務調用

服務提供者Invoker

被封裝成為一個AbstractProxyInvoker執行個體,并新生成一個Exporter執行個體。這樣當網絡通訊層收到一個請求後,會找到對應的Exporter執行個體,并調用它所對應的AbstractProxyInvoker執行個體,進而真正調用了服務提供者的代碼

線程模型 過程

Client

Transporter

Header -> Codec

Body -> Serialization

Server

Dispatcher

ThreadPool

Implementation

all 所有消息都派發到線程池,包括請求,響應,連接配接事件,斷開事件,心跳等

direct 所有消息都不派發到線程池,全部在IO線程上直接執行

message 隻有請求響應消息派發到線程池,其它連接配接斷開事件,心跳等消息,直接在IO線程上執行

execution 隻請求消息派發到線程池,不含響應,響應和其它連接配接斷開事件,心跳等消息,直接在IO線程上執行

connection 在IO線程上,将連接配接斷開事件放入隊列,有序逐個執行,其它消息派發到線程池

fixed 固定大小線程池,啟動時建立線程,不關閉,一直持有。(預設)

cached 緩存線程池,空閑一分鐘自動删除,需要時重建

limited 可伸縮線程池,但池中的線程數隻會增長不會收縮。(為避免收縮時突然來了大流量引起的性能問題)

4,增強功能

并發控制

連接配接控制: 連接配接數控制

分組聚合: 分組聚合傳回值,用于菜單聚合等服務

泛化引用: 泛化調用,無需業務接口類進行遠端調用,用于測試平台,開放網關橋接等

異步調用

延遲暴露: 延遲暴露服務,用于等待應用加載warmup資料,或等待spring加載完成

延遲連接配接: 延遲建立連接配接,調用時建立

隐私傳參: 附加參數

5,Dubbo擴充

方法

OSGI

Equinox

Eclipse, HSF

META-INF/MANIFEST.MF

IoC

META-INF/spring/beans.xml

SPI

java.util.ServiceProvider

JDBC, MessageDigest, ScriptEngine

META-INF/services/com.xx.Xxx

Dubbo SPI Microkernel & SPIProtocol & ProxyFactory & FilterCluster & Directory & Router & LoadBalanceTransporter & Serialization & ThreadPoolTelnetHandler & StatusChecker

6,Dubbo設計原則

子產品分包原則

複用度

包中的類應該有同樣的重用可能性

緊密協作的類應該放在一個包

對于變化因子,包中的類應全改或全不改

變化應在包内終止,而不傳播到其它包

釋出的粒度和複用度相同

穩定度

被依賴的包應該總是比依賴者更穩定

不要讓一個穩定的包依賴于不穩定包

單向依賴,無環依賴

抽象度

越穩定的包應該越抽象

穩定的包不抽象将導緻擴充性極差

抽象的包不穩定将導緻其依賴包跟随變化

架構擴充原則 微核 +插件體系

平等對待第三方 

Dogfooding

架構自己的功能也要擴充點實作

甚至微核的加載方式也可以擴充

Autowire

裝配邏輯由擴充點之間互助完成

杜絕寫死的橋接和中間代碼

Cascading

層疊擴充粒度,逐級細分

由大的擴充點加載小的擴充點

Law of Demeter

隻與×××的擴充點互動,間接轉發

保持行為單一,輸入輸出明确

外置生命周期

API傳入參數,SPI擴充點執行個體

盡量引用外部對象的執行個體,而不類元

正确:userInstance.xxx()

錯誤:Class.forName(userClass).newInstance().xxx()

盡量使用IoC注入,減少靜态工廠方法調用

正确:setXxx(xxx)

錯誤:XxxFactory.getXxx(); applicationContext.getBean(“xxx”)

最少化概念模型一緻性資料模型

Dubbo統一URL模型

所有配置資訊都轉換成URL的參數

所有的元資訊傳輸都采用URL

所有接口都可以擷取到URL

領域劃分原則 服務域

指産品主要功能入口,同時負責實體域和會話域的生命周期管理。

Velocity的Engine

Spring的BeanFactory

實體域

表示你要操作的對象模型,不管什麼産品,總有一個核心概念,大家都繞圍它轉。

Velocity的Template

Spring的Bean

會話域

表示每次操作瞬時狀态,操作前建立,操作後銷毀。

Velocity的Context

Spring的Invocation

領域模型劃分優勢

結構清晰,可直接套用

充血模型,實體域帶行為

可變與不可變狀态分離,可變狀态集中

所有領域線程安全,不需要加鎖

領域模型線程安全性

服務域

通常服務域是無狀态,或者隻有啟動時初始化不變狀态,是以天生線程安全,隻需單一執行個體運作

通常設計為不變類,所有屬性隻讀,或整個類引用替換,是以是線程安全的

保持所有可變狀态,且會話域隻線上程棧内使用,即每次調用都線上程棧内建立執行個體,調用完即銷毀,沒有競争,是以線程安全

接口分離原則 API & SPI

聲明式API(Dubbo API):描述需要什麼

ServiceConfig

RpcContext

過程式SPI(Dubbo SPI):描述怎麼實作

LoadBalance

API可配置,一定可程式設計

配置用于簡化正常使用

程式設計接口用于架構內建

API區分指令與查詢

指令:無傳回值表示指令,有副作用

查詢:有傳回值表示查詢,保持幂等,無副作用

元件協作原則 管道 v.s. 派發

管道

組合行為

主功能以截面實作

比如:Servlet

派發

政策行為

主功能以事件實作

比如: Swing

分布 v.s. 共享

分布

在行為互動為主的系統是适用

狀态通過行為傳遞

共享

在以管理狀态為主的系統中适用

狀态通過倉庫共享

主過程攔截

Web架構的請求響應流

ORM架構的SQL執行

Service架構的調用過程

反例:IBatis2在SQL執行過程中沒有設攔截點,導緻添加安全或日志攔截,執行前修改分頁SQL等,不得不hack源代碼

事件派發

過程

執行前後

觸發附帶非關鍵行為

狀态

值的變化

觸發狀态觀察者行為

關鍵路徑

采用攔截鍊分離職責

保持截面功能單一,不易出問題

非關鍵路徑

采用後置事件派發

確定派發失敗,不影響主過程運作

協作防禦

可靠性分離

可靠操作

不可靠操作 (盡量縮小)

狀态分離

無狀态

有狀态 (盡量縮小)

不可變類 (盡量final)

狀态驗證

盡早失敗

前置斷言 + 後置斷言 + 不變式

異常防禦,但不忽略異常

異常資訊給出解決方案

日志資訊包含環境資訊

降低修改時的誤解性,不埋雷

避免基于異常類型的分支流程

保持null和empty語義一緻

功能演進原則 開閉原則

對擴充開放

對修改關閉

軟體品質的下降,來源于修改

替換整個實作類,而不是修改其中的某行

增量式 v.s.擴充式

Dubbo增量式擴充

Transport:

單向消息發送,抽象Mina/Netty

Exchange:

封裝Request-Respose語義

調用兩次單向消息發送完成

Portocol:

協定實作,不透明,點對點

Cluster:

将叢集中多個提供者僞裝成一個

Proxy:

透明化接口,橋接動态代理

在高階附加功能

盡可能少的依賴低階契約,用最少的抽象概念實作功能

當低階切換實作時,高階功能可以繼續複用

7,Dubbo編碼約定

異常和日志:

盡可能攜帶完整的上下文資訊,比如出錯原因,出錯的機器位址,調用對方的位址,連的注冊中心位址,使用Dubbo的版本等。

盡量将直接原因寫在最前面,所有上下文資訊,在原因後用鍵值對顯示。

抛出異常的地方不用列印日志,由最終處理異常者決定列印日志的級别,吃掉異常必需列印日志。

列印ERROR日志表示需要報警,列印WARN日志表示可以自動恢複,列印INFO表示正常資訊或完全不影響運作。

建議應用方在監控中心配置ERROR日志實時報警,WARN日志每周彙總發送通知。

RpcException是Dubbo對外的唯一異常類型,所有内部異常,如果要抛出給使用者,必須轉為RpcException。

RpcException不能有子類型,所有類型資訊用ErrorCode辨別,以便保持相容。

配置和URL: 配置對象屬性首字母小寫,多個單詞用駝峰命名(Java約定)。配置屬性全部用小寫,多個單詞用"-"号分隔(Spring約定)。URL參數全部用小寫,多個單詞用"."号分隔(Dubbo約定)。盡可能用URL傳參,不要自定義Map或其它上下文格式,配置資訊也轉成URL格式使用。盡量減少URL嵌套,保持URL的簡潔性。單元和內建測試: 單元測試統一用JUnit和EasyMock,內建測試用TestNG,資料庫測試用DBUnit。保持單元測試用例的運作速度,不要将性能和大的內建用例放在單元測試中。保持單元測試的每個用例都用try...finally或tearDown釋放資源。減少while循環等待結果的測試用例,對定時器和網絡的測試,用以将定時器中的邏輯抽為方法測試。對于容錯行為的測試,比如failsafe的測試,統一用LogUtil斷言日志輸出。擴充點基類與AOP: AOP類都命名為XxxWrapper,基類都命名為AbstractXxx。擴充點之間的組合将關系由AOP完成,ExtensionLoader隻負載加載擴充點,包括AOP擴充。盡量采用IoC注入擴充點之間的依賴,不要直接依賴ExtensionLoader的工廠方法。盡量采用AOP實作擴充點的通用行為,而不要用基類,比如負載均衡之前的isAvailable檢查,它是獨立于負載均衡之外的,不需要檢查的是URL參數關閉。對多種相似類型的抽象,用基類實作,比如RMI,Hessian等第三方協定都已生成了接口代理,隻需将将接口代理轉成Invoker即可完成橋接,它們可以用公共基類實作此邏輯。基類也是SPI的一部分,每個擴充點都應該有友善使用的基類支援。子產品與分包: 基于複用度分包,總是一起使用的放在同一包下,将接口和基類分成獨立子產品,大的實作也使用獨立子產品。所有接口都放在子產品的根包下,基類放在support子包下,不同實作用放在以擴充點名字命名的子包下。盡量保持子包依賴父包,而不要反向。

作者:歐陽海陽

連結:

https://www.jianshu.com/p/1d17c639a4d6

來源:簡書

簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權并注明出處。