在軟件開發中,一般的作法是將一些基礎,簡單的服務組合在一塊兒而造成一個具備某一功能的特定服務。這種搭積木的結構,或者說自下而上的組合更有利於程序的資源隔離以及維護與拓展。高層的服務依賴底層服務提供業務計算,低層的服務提供諸如數據存儲,網絡傳輸等基礎操做。java
這些低層的服務就如現實世界中的城市基礎設施,沒有道路車輛就無處行駛,沒有發電廠提供的電力全部的電力設備就沒法工做。因此,在程序世界構建的時候,這些基礎服務必須預先完成加載(初始化)。當低層接口與高層接口變得比較多時,它們的關係錯綜複雜。因此,合理地管理它們變得很是有必要!下面咱們將討論幾種常見的模式:git
一種很是簡單的方式是:將全部的服務按照依賴層級逐個地加載它們。以下圖, C 分別依賴了 A 與 B 。在程序啓動的時候能夠按照 A -> B -> C 的方式依次加載它們。github
這種方式可以有效地解決服務依賴,在服務不是特別複雜,依賴的鏈不是很長的狀況下它會是一個很不錯的選擇。可是這種方式會將整個服務啓動的時候變得很長,由於它們的加載是逐個進行的。所以在大型的服務中這種方式是不被推薦的。網絡
另外一種方式是按需加載,在服務被使用(調用)時,再去加載它所依賴的服務。以下圖,有 2 個高層服務 C 與 E,它們分別依賴了基礎服務 A,B 與 B,D。app
當訪問 C 服務的時候,會按需加載 C <-> B <-> A。C 去嘗試找到並加載 B,B 也會嘗試找到並加載 A。這是一個有返回的過程,全部使用符號 <->
表示。同理,E 服務的加載過程也是按照 E <-> B <-> D。maven
這種模式最大的優勢是能作到按需加載,當高層服務被使用的時候纔會去加載依賴的服務。在容器技術中也稱之爲 依賴注入
。爲避免重複加載的過程,實際上咱們會把這些服務放在一個服務容器裏面,服務的加載會有容器處理,咱們要作的僅僅是從一個容器裏面找到它們。例如上圖中 C, E 都依賴了 B。ide
大多的應用服務都會選擇按需加載的模式,它不只能避免有序加載的不能同時加載多個服務的問題,按需加載實際上由於不會加載那些無訪問的服務,因此能有效地節省一部分資源。svg
但這就是終極方案嗎?顯然不是!下面咱們來了解第三種模式。性能
分組加載是將服務按照依賴的層級劃分稱不一樣的 ServiceGroup
。ServiceGroup 之間是按照順序加載的,但 ServiceGroup 內的服務是並行加載的。這種方式可以快速地將全部的服務一次性加載。與按需加載不一樣,分組加載能夠在應用啓動成功之時就能夠當即服務;同時對於按序加載能過作到更加快速地啓動服務。以下圖,ServiceGroup 1
中的 A
, B
, 'C' 是並行加載的,ServiceGroup 2
在 ServiceGroup 1
加載完成以後纔開始加載。this
服務 A, B, D 之間沒有依賴,因此它們的加載能夠是無序的。當 ServiceGroup 1
加載完成以後,ServiceGroup 2
在加載以前就已經加載了必要的服務。這時候不會出現 ServiceNotFound
的問題。下面的代碼使用了 DSL 來描述整個加載的過程:
GroupedServiceBus serviceBus = ... serviceBus.start(serviceA, serviceB, serviceD) .then(serviceC, serviceE) .awaitStarted();
銷燬的順序必須是加載順序的反序。這樣你才能保證不回出現必要服務的丟失。
使用 Gauva 的 Service 做爲服務的基礎接口,下面給出了一個分組加載的簡單實現。
import com.google.common.util.concurrent.Service; public interface GroupedServiceBus { void awaitStarted(); void awaitStopped(); GroupedServiceBus start(Service... services); GroupedServiceBus then(Service... services); GroupedServiceBus awaitServiceGroupStarted(Service... services); }
使用 DSL 的方式設計 API 能過讓它變得更加容易使用跟理解。
public interface KernelService { }
可使用 KernelService
來指定哪些服務是必須正確加載的,當 KernelService 加載失敗應用並不能提供一個正確的服務,這時你也許能夠將整個程序退出。
public class GroupedServiceBusImpl implements GroupedServiceBus { private final List<ServiceManager> serviceCluster = new ArrayList<>(); private final ServiceManager.Listener listener = new ServiceManager.Listener() { @Override public void failure(Service service) { if (service instanceof KernelService) { logger.error("KernelService [{}] start failure, system will be exit 1.", service.getClass()); System.exit(1); } else { logger.error("Service [{}] start failure.", service.getClass()); } } }; @Override public void awaitStarted() { if (!serviceCluster.isEmpty()) { for (final ServiceManager serviceManager : serviceCluster) { awaitStartedServiceManager(serviceManager); } } } @Override public void awaitStopped() { if (!serviceCluster.isEmpty()) { for (int i = serviceCluster.size() - 1; i > -1; i--) { final ServiceManager serviceManager = serviceCluster.get(i); serviceManager.stopAsync(); serviceManager.awaitStopped(); } serviceCluster.clear(); } } @Override public GroupedServiceBus start(final Service... services) { then(services); return this; } @Override public GroupedServiceBus then(Service... services) { final ServiceManager serviceManager = new ServiceManager(ImmutableList.copyOf(services)); serviceManager.addListener(listener); serviceCluster.add(serviceManager); return this; } private void awaitStartedServiceManager(final ServiceManager serviceManager) { if (!serviceManager.isHealthy()) { serviceManager.startAsync(); serviceManager.awaitHealthy(); } } }
上面的 serviceCluster
是一個 ServiceManager
的集合,在這裏 ServiceManager
就是咱們上面講到的 ServiceGroup
。在 ServiceGroup
的全部服務均可以並行加載。
這三種模式沒有哪種是絕對正確跟優秀的。在程序設計中,除了要考慮性能,穩定性等同時還要避免陷入過分設計的陷阱。你的選擇須要適應你的環境!