1、RPC1.1 RPC 定義
網際網路公司的系統有成千上萬個大大小小的服務組成,服務各自部署在不同的機器上,服務間的呼叫需要用到網路通訊,服務消費方每呼叫一個服務都要寫一坨網路通訊相關的程式碼,不僅複雜而且極易出錯。還要考慮新服務依賴老服務時如何呼叫老服務,別的服務依賴新服務的時候新服務如何釋出方便他人呼叫。如何解決這個問題呢?業界一般採用RPC遠端呼叫的方式來實現。
RPC:
Remote Procedure Call Protocol 既 遠端過程呼叫,一種能讓我們像呼叫本地服務一樣呼叫遠端服務,可以讓呼叫者對網路通訊這些細節無感知,比如服務消費方在執行 helloWorldService.sayHello("sowhat") 時,實質上呼叫的是遠端的服務。這種方式其實就是RPC,RPC思想在各大網際網路公司中被廣泛使用,如阿里巴巴的dubbo、噹噹的Dubbox 、Facebook 的 thrift、Google 的grpc、Twitter的finagle等。
1.2 RPC demo說了那麼多,還是實現一個簡易版的RPC demo吧。
1.2.1 公共介面public interface SoWhatService { String sayHello(String name); }
1.2.2 服務提供者
介面類實現
public class SoWhatServiceImpl implements SoWhatService{ @Override public String sayHello(String name) { return "你好啊 " + name; }}
服務註冊對外提供者
/** * 服務註冊對外提供者 */public class ServiceFramework{ public static void export(Object service, int port) throws Exception { ServerSocket server = new ServerSocket(port); while (true) { Socket socket = server.accept(); new Thread(() -> { try { //反序列化 ObjectInputStream input = new ObjectInputStream(socket.getInputStream()); //讀取方法名 String methodName =(String) input.readObject(); //引數型別 Class<?>[] parameterTypes = (Class<?>[]) input.readObject(); //引數 Object[] arguments = (Object[]) input.readObject(); //找到方法 Method method = service.getClass().getMethod(methodName, parameterTypes); //呼叫方法 Object result = method.invoke(service, arguments); // 返回結果 ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream()); output.writeObject(result); } catch (Exception e) { e.printStackTrace(); } }).start(); } }}
服務執行
public class ServerMain{ public static void main(String[] args) { //服務提供者 暴露出介面 SoWhatService service = new SoWhatServiceImpl(); try { ServiceFramework.export(service, 1412); } catch (Exception e) { e.printStackTrace(); } }}
1.2.3 服務呼叫者
動態代理呼叫遠端服務
/** * @author sowhat * 動態代理呼叫遠端服務 */public class RpcFunction{ public static <T> T refer(Class<T> interfaceClass, String host, int port) throws Exception { return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<?>[]{interfaceClass}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable { //指定 provider 的 ip 和埠 Socket socket = new Socket(host, port); ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream()); //傳方法名 output.writeObject(method.getName()); //傳引數型別 output.writeObject(method.getParameterTypes()); //傳引數值 output.writeObject(arguments); ObjectInputStream input = new ObjectInputStream(socket.getInputStream()); //讀取結果 Object result = input.readObject(); return result; } }); }}
呼叫方法
public class RunMain{ public static void main(String[] args) { try { //服務呼叫者 需要設定依賴 SoWhatService service = RpcFunction.refer(SoWhatService.class, "127.0.0.1", 1412); System.out.println(service.sayHello(" sowhat1412")); } catch (Exception e) { e.printStackTrace(); } }}
2、Dubbo 框架設計2.1 Dubbo 簡介
Dubbo 是阿里巴巴研發開源工具,主要分為2.6.x 跟 2.7.x 版本。是一款分散式、高效能、透明化的 RPC 服務框架,提供服務自動註冊、自動發現等高效服務治理方案,可以和Spring 框架無縫整合,它提供了6大核心能力:
1. 面向介面代理的高效能RPC呼叫
2. 智慧容錯和負載均衡
3. 服務自動註冊和發現
4. 高度可擴充套件能力
5. 執行期流量排程
6. 視覺化的服務治理與運維
呼叫過程:
服務提供者 Provider 啟動然後向 Registry 註冊自己所能提供的服務。
服務消費者 Consumer 向Registry訂閱所需服務,Consumer 解析Registry提供的元資訊,從服務中透過負載均衡選擇 Provider呼叫。
服務提供方 Provider 元資料變更的話Registry會把變更推送給Consumer,以此保證Consumer獲得最新可用資訊。
注意點:
Provider 跟 Consumer 在記憶體中記錄呼叫次數跟時間,定時傳送統計資料到Monitor,傳送的時候是短連線。
Monitor 跟 Registry 是可選的,可直接在配置檔案中寫好,Provider 跟 Consumer進行直連。
Monitor 跟 Registry 掛了也沒事, Consumer 本地快取了 Provider 資訊。
Consumer 直接呼叫 Provider 不會經過 Registry。Provider、Consumer這倆到 Registry之間是長連線。
2.2 Dubbo框架分層如上圖,總的而言 Dubbo 分為三層。
Busines層:由使用者自己來提供介面和實現還有一些配置資訊。
RPC層:真正的RPC呼叫的核心層,封裝整個RPC的呼叫過程、負載均衡、叢集容錯、代理。
Remoting層:對網路傳輸協議和資料轉換的封裝。
如果每一層再細分下去,一共有十層。
介面服務層(Service):該層與業務邏輯相關,根據 provider 和 consumer 的業務設計對應的介面和實現。配置層(Config):對外配置介面,以 ServiceConfig 和 ReferenceConfig 為中心初始化配置。服務代理層(Proxy):服務介面透明代理,Provider跟Consumer都生成代理類,使得服務介面透明,代理層實現服務呼叫跟結果返回。服務註冊層(Registry):封裝服務地址的註冊和發現,以服務 URL 為中心。路由層(Cluster):封裝多個提供者的路由和負載均衡,並橋接註冊中心,以Invoker 為中心,擴充套件介面為 Cluster、Directory、Router 和 LoadBlancce。監控層(Monitor):RPC 呼叫次數和呼叫時間監控,以 Statistics 為中心,擴充套件介面為 MonitorFactory、Monitor 和 MonitorService。遠端呼叫層(Protocal):封裝 RPC 呼叫,以 Invocation 和 Result 為中心,擴充套件介面為 Protocal、Invoker 和 Exporter。資訊交換層(Exchange):封裝請求響應模式,同步轉非同步。以 Request 和Response 為中心,擴充套件介面為 Exchanger、ExchangeChannel、ExchangeClient 和 ExchangeServer。網路傳輸層(Transport):抽象 mina 和 netty 為統一介面,以 Message 為中心,擴充套件介面為 Channel、Transporter、Client、Server 和 Codec。資料序列化層(Serialize):可複用的一些工具,擴充套件介面為 Serialization、ObjectInput、ObjectOutput 和 ThreadPool。他們之間的呼叫關係直接看下面官網圖即可。
3、Dubbo SPI 機制Dubbo 採用 微核心設計 + SPI 擴充套件技術來搭好核心框架,同時滿足使用者定製化需求。這裡重點說下SPI。
3.1 微核心作業系統層面的微核心跟宏核心:
微核心Microkernel:是一種核心的設計架構,由儘可能精簡的程式所組成,以實現一個作業系統所需要的最基本功能,包括了底層的定址空間管理、執行緒管理、與程序間通訊。成功案例是QNX系統,比如黑莓手機跟車用市場。
宏核心Monolithic :把 程序管理、記憶體管理、檔案系統、程序通訊等功能全部作為核心來實現,而微核心則僅保留最基礎的功能,Linux 就是宏核心架構設計。
Dubbo中的廣義微核心:
思想是 核心系統 + 外掛,說白了就是把不變的功能抽象出來稱為核心,把變動的功能作為外掛來擴充套件,符合開閉原則,更容易擴充套件、維護。比如小霸王遊戲機中機體本身作為核心系統,遊戲片就是外掛。vscode、Idea、chrome等都是微核心的產物。
微核心架構其實是一直架構思想,可以是框架層面也可以是某個模組設計,它的本質就是將變化的部分抽象成外掛,使得可以快速簡便地滿足各種需求又不影響整體的穩定性。
3.2 SPI 含義主流的資料庫有MySQL、Oracle、DB2等,這些資料庫是不同公司開發的,它們的底層協議不大一樣,那怎麼約束呢?一般就是定製統一介面,具體實現不管,反正面向相同的介面程式設計即可。等到真正使用的時候用具體的實現類就好,問題是哪裡找用那個實現類呢?這時候就採用約定好的法則將實現類寫到指定位置即可。
SPI 全稱為 Service Provider Interface,是一種服務發現機制。它約定在ClassPath路徑下的META-INF/services資料夾查詢檔案,自動載入檔案裡所定義的類。
3.3 SPI demo介面:
package com.example.demo.spi;public interface SPIService { void execute();}
實現類1:
public class SpiImpl1 implements SPIService{ @Override public void execute() { System.out.println("SpiImpl1.execute()"); }}
實現類2:
public class SpiImpl2 implements SPIService{ @Override public void execute() { System.out.println("SpiImpl2.execute()"); }}
配置路徑
呼叫載入類
package com.example.demo.spi;import sun.misc.Service;import java.util.Iterator;import java.util.ServiceLoader;public class Test { public static void main(String[] args) { Iterator<SPIService> providers = Service.providers(SPIService.class); ServiceLoader<SPIService> load = ServiceLoader.load(SPIService.class); while(providers.hasNext()) { SPIService ser = providers.next(); ser.execute(); } System.out.println("--------------------------------"); Iterator<SPIService> iterator = load.iterator(); while(iterator.hasNext()) { SPIService ser = iterator.next(); ser.execute(); } }}
3.4 SPI原始碼追蹤ServiceLoader.load(SPIService.class) 底層呼叫大致邏輯如下:
iterator.hasNext() 跟 iterator.next()底層呼叫大致如下:
3.5 Java SPI缺點不能按需載入,Java SPI在載入擴充套件點的時候,會一次性載入所有可用的擴充套件點,很多是不需要的,會浪費系統資源。獲取某個實現類的方式不夠靈活,只能透過 Iterator 形式獲取,不能根據某個引數來獲取對應的實現類。不支援AOP與依賴注入,JAVA SPI可能會丟失載入擴充套件點異常資訊,導致追蹤問題很困難。3.6 Dubbo SPIJDK自帶的不好用Dubbo 就自己實現了一個 SPI,該SPI 可以透過名字例項化指定的實現類,並且實現了 IOC 、AOP 與 自適應擴充套件 SPI 。
key = com.sowhat.value
Dubbo 對配置檔案目錄的約定,不同於 Java SPI ,Dubbo 分為了三類目錄。
META-INF/services/ :該目錄下 SPI 配置檔案是為了用來相容 Java SPI 。
META-INF/dubbo/ :該目錄存放使用者自定義的 SPI 配置檔案。
META-INF/dubbo/internal/ :該目錄存 Dubbo 內部使用的 SPI 配置檔案。
使用的話很簡單 引入依賴,然後百度教程即可。
@Test void sowhat() { ExtensionLoader<SPIService> spiService = ExtensionLoader.getExtensionLoader(SPIService.class); //按需獲取實現類物件 SPIService demo1 = spiService.getExtension("SpiImpl1"); demo1.execute(); }
3.7 Dubbo SPI原始碼追蹤
ExtensionLoader.getExtension 方法的整個思路是 查詢快取是否存在,不存在則讀取SPI檔案,透過反射建立類,然後設定依賴注入這些東西,有包裝類就包裝下,執行流程如下圖所示:
說下重要的四個部分:
injectExtension IOC查詢 set 方法,根據引數找到依賴物件則注入。
WrapperClass AOP包裝類,Dubbo 幫你自動包裝,只需要某個擴充套件類的建構函式只有一個引數,並且是擴充套件介面型別,就會被判定為包裝類。
ActivateActive 有三個屬性,group 表示修飾在哪個端,是 provider 還是 consumer,value 表示在 URL引數中出現才會被啟用,order 表示實現類的順序。
3.8 Adaptive 自適應擴充套件需求:根據配置來進行 SPI 擴充套件的載入後不想在啟動的時候讓擴充套件被載入,想根據請求時候的引數來動態選擇對應的擴充套件。實現:Dubbo用代理機制實現了自適應擴充套件,為使用者想擴充套件的介面 透過JDK 或者 Javassist 編譯生成一個代理類,然後透過反射建立例項。例項會根據本來方法的請求引數得知需要的擴充套件類,然後透過 ExtensionLoader.getExtensionLoader(type.class).getExtension(name)來獲取真正的例項來呼叫,看個官網樣例。
public interface WheelMaker { Wheel makeWheel(URL url);}// WheelMaker 介面的自適應實現類public class AdaptiveWheelMaker implements WheelMaker { public Wheel makeWheel(URL url) { if (url == null) { throw new IllegalArgumentException("url == null"); } // 1. 呼叫 url 的 getXXX 方法獲取引數值 String wheelMakerName = url.getParameter("Wheel.maker"); if (wheelMakerName == null) { throw new IllegalArgumentException("wheelMakerName == null"); } // 2. 呼叫 ExtensionLoader 的 getExtensionLoader 獲取載入器 // 3. 呼叫 ExtensionLoader 的 getExtension 根據從url獲取的引數作為類名稱載入實現類 WheelMaker wheelMaker = ExtensionLoader.getExtensionLoader(WheelMaker.class).getExtension(wheelMakerName); // 4. 呼叫實現類的具體方法實現呼叫。 return wheelMaker.makeWheel(URL url); }}
檢視Adaptive註解原始碼可知該註解可用在類或方法上,Adaptive 註解在類上或者方法上有不同的實現邏輯。
7.8.1 Adaptive 註解在類上Adaptive 註解在類上時,Dubbo 不會為該類生成代理類,Adaptive 註解在類上的情況很少,在 Dubbo 中,僅有兩個類被 Adaptive 註解了,分別是 AdaptiveCompiler 和 AdaptiveExtensionFactory,表示拓展的載入邏輯由人工編碼完成,這不是我們關注的重點。
7.8.2 Adaptive 註解在方法上Adaptive 註解在方法上時,Dubbo 則會為該方法生成代理邏輯,表示拓展的載入邏輯需由框架自動生成,大致的實現機制如下:
載入標註有 @Adaptive 註解的介面,如果不存在,則不支援 Adaptive 機制;為目標介面按照一定的模板生成子類程式碼,並且編譯生成的程式碼,然後透過反射生成該類的物件;結合生成的物件例項,透過傳入的URL物件,獲取指定key的配置,然後載入該key對應的類物件,最終將呼叫委託給該類物件進行。@SPI("apple")public interface FruitGranter { Fruit grant(); @Adaptive String watering(URL url);}---// 蘋果種植者public class AppleGranter implements FruitGranter { @Override public Fruit grant() { return new Apple(); } @Override public String watering(URL url) { System.out.println("watering apple"); return "watering finished"; }}---// 香蕉種植者public class BananaGranter implements FruitGranter { @Override public Fruit grant() { return new Banana(); } @Override public String watering(URL url) { System.out.println("watering banana"); return "watering success"; }}
呼叫方法實現:
public class ExtensionLoaderTest { @Test public void testGetExtensionLoader() { // 首先建立一個模擬用的URL物件 URL url = URL.valueOf("dubbo://192.168.0.1:1412?fruit.granter=apple"); // 透過ExtensionLoader獲取一個FruitGranter物件 FruitGranter granter = ExtensionLoader.getExtensionLoader(FruitGranter.class) .getAdaptiveExtension(); // 使用該FruitGranter呼叫其"自適應標註的"方法,獲取呼叫結果 String result = granter.watering(url); System.out.println(result); }}
透過如上方式生成一個內部類。大致呼叫流程如下:
4、Dubbo 服務暴露流程4.1 服務暴露總覽Dubbo框架是以URL為匯流排的模式,執行過程中所有的狀態資料資訊都可以透過URL來獲取,比如當前系統採用什麼序列化,採用什麼通訊,採用什麼負載均衡等資訊,都是透過URL的引數來呈現的,所以在框架執行過程中,執行到某個階段需要相應的資料,都可以透過對應的Key從URL的引數列表中獲取。URL 具體的引數如下:
protocol:指的是 dubbo 中的各種協議,如:dubbo thrift http username/password:使用者名稱/密碼 host/port:主機/埠 path:介面的名稱 parameters:引數鍵值對
protocol://username:password@host:port/path?k=v
服務暴露從程式碼流程看分為三部分:
檢查配置,最終組裝成 URL。
暴露服務到到本地服務跟遠端服務。
服務註冊至註冊中心。
服務暴露從物件構建轉換看分為兩步:
將服務封裝成Invoker。
將Invoker透過協議轉換為Exporter。
4.2 服務暴露原始碼追蹤容器啟動,Spring IOC 重新整理完畢後呼叫 onApplicationEvent 開啟服務暴露,ServiceBean 。export 跟 doExport 來進行拼接構建URL,為遮蔽呼叫的細節,統一暴露出一個可執行體,透過ProxyFactory 獲取到 invoker。呼叫具體 Protocol 將把包裝後的 invoker 轉換成 exporter,此處用到了SPI。然後啟動伺服器server,監聽埠,使用NettyServer建立監聽伺服器。透過 RegistryProtocol 將URL註冊到註冊中心,使得consumer可獲得provider資訊。5、Dubbo 服務引用流程Dubbo中一個可執行體就是一個invoker,所以 provider 跟 consumer 都要向 invoker 靠攏。透過上面demo可知為了無感呼叫遠端介面,底層需要有個代理類包裝 invoker。
服務的引入時機有兩種:
餓漢式:透過實現 Spring 的 InitializingBean 介面中的 afterPropertiesSet 方法,容器透過呼叫 ReferenceBean的 afterPropertiesSet 方法時引入服務。
懶漢式(預設):懶漢式是隻有當服務被注入到其他類中時啟動引入流程。
服務引用的三種方式:
本地引入:服務暴露時本地暴露,避免網路呼叫開銷。
直接連線引入遠端服務:不啟動註冊中心,直接寫死遠端Provider地址 進行直連。
透過註冊中心引入遠端服務:透過註冊中心抉擇如何進行負載均衡呼叫遠端服務。
服務引用流程:
檢查配置構建map ,map 構建 URL ,透過URL上的協議利用自適應擴充套件機制呼叫對應的 protocol.refer 得到相應的 invoker ,此處
想註冊中心註冊自己,然後訂閱註冊中心相關資訊,得到provider的 ip 等資訊,再透過共享的netty客戶端進行連線。
當有多個 URL 時,先遍歷構建出 invoker 然後再由 StaticDirectory 封裝一下,然後透過 cluster 進行合併,只暴露出一個 invoker 。
然後再構建代理,封裝 invoker 返回服務引用,之後 Comsumer 呼叫的就是這個代理類。
呼叫方式:
oneway:不關心請求是否傳送成功。Async非同步呼叫:Dubbo天然非同步,客戶端呼叫請求後將返回的 ResponseFuture 存到上下文中,使用者可隨時呼叫 future.get 獲取結果。非同步呼叫透過唯一ID 標識此次請求。Sync同步呼叫:在 Dubbo 原始碼中就呼叫了 future.get,使用者感覺方法被阻塞了,必須等結果後才返回。6、Dubbo 呼叫整體流程呼叫之前你可能需要考慮這些事:
consumer 跟 provider 約定好通訊協議,dubbo支援多種協議,比如dubbo、rmi、hessian、http、webservice等。預設走dubbo協議,連線屬於單一長連線,NIO非同步通訊。適用傳輸資料量很小(單次請求在100kb以內),但是併發量很高。約定序列化模式,大致分為兩大類,一種是字元型(XML或json 人可看懂 但傳輸效率低),一種是二進位制流(資料緊湊,機器友好)。預設使用 hessian2作為序列化協議。consumer 呼叫 provider 時提供對應介面、方法名、引數型別、引數值、版本號。provider列表對外提供服務涉及到負載均衡選擇一個provider提供服務。consumer 跟 provider 定時向monitor 傳送資訊。呼叫大致流程:
客戶端發起請求來呼叫介面,介面呼叫生成的代理類。代理類生成RpcInvocation 然後呼叫invoke方法。ClusterInvoker獲得註冊中心中服務列表,透過負載均衡給出一個可用的invoker。序列化跟反序列化網路傳輸資料。透過NettyServer呼叫網路服務。服務端業務執行緒池接受解析資料,從exportMap找到invoker進行invoke。呼叫真正的Impl得到結果然後返回。呼叫方式:
oneway:不關心請求是否傳送成功,消耗最小。sync同步呼叫:在 Dubbo 原始碼中就呼叫了 future.get,使用者感覺方法被阻塞了,必須等結果後才返回。Async 非同步呼叫:Dubbo天然非同步,客戶端呼叫請求後將返回的 ResponseFuture 存到上下文中,使用者可以隨時呼叫future.get獲取結果。非同步呼叫透過唯一ID標識此次請求。7、Dubbo叢集容錯負載均衡Dubbo 引入了Cluster、Directory、Router、LoadBalance、Invoker模組來保證Dubbo系統的穩健性,它們的關係如下圖:
服務發現時會將多個多個遠端呼叫放入Directory,然後透過Cluster封裝成一個Invoker,該invoker提供容錯功能。消費者代用的時候從Directory中透過負載均衡獲得一個可用invoker,最後發起呼叫。你可以認為Dubbo中的Cluster對上面進行了大的封裝,自帶各種魯棒性功能。7.1 叢集容錯叢集容錯是在消費者端透過Cluster子類實現的,Cluster介面有10個實現類,每個Cluster實現類都會建立一個對應的ClusterInvoker物件。核心思想是讓使用者選擇性呼叫這個Cluster中間層,遮蔽後面具體實現細節。
7.2 智慧容錯之負載均衡Dubbo中一般有4種負載均衡策略。
RandomLoadBalance:加權隨機,它的演算法思想簡單。假設有一組伺服器 servers = [A, B, C],對應權重為 weights = [5, 3, 2],權重總和為10。現把這些權重值平鋪在一維座標值上,[0, 5) 區間屬於伺服器 A,[5, 8) 區間屬於伺服器 B,[8, 10) 區間屬於伺服器 C。接下來透過隨機數生成器生成一個範圍在 [0, 10) 之間的隨機數,然後計算這個隨機數會落到哪個區間上。預設實現。LeastActiveLoadBalance:最少活躍數負載均衡,選擇現在活躍呼叫數最少的提供者進行呼叫,活躍的呼叫數少說明它現在很輕鬆,而且活躍數都是從 0 加起來的,來一個請求活躍數+1,一個請求處理完成活躍數-1,所以活躍數少也能變相的體現處理的快。RoundRobinLoadBalance:加權輪詢負載均衡,比如現在有兩臺伺服器 A、B,輪詢的呼叫順序就是 A、B、A、B,如果加了權重,A 比B 的權重是2:1,那現在的呼叫順序就是 A、A、B、A、A、B。ConsistentHashLoadBalance:一致性 Hash 負載均衡,將伺服器的 IP 等資訊生成一個 hash 值,將hash 值投射到圓環上作為一個節點,然後當 key 來查詢的時候順時針查詢第一個大於等於這個 key 的 hash 值的節點。一般而言還會引入虛擬節點,使得資料更加的分散,避免資料傾斜壓垮某個節點。如下圖 Dubbo 預設搞了 160 個虛擬節點。7.3 智慧容錯之服務目錄關於 服務目錄Directory 你可以理解為是相同服務Invoker的集合,核心是RegistryDirectory類。具有三個功能。
從註冊中心獲得invoker列表。
監控著註冊中心invoker的變化,invoker的上下線。
重新整理invokers列表到服務目錄。
7.4 智慧容錯之服務路由服務路由其實就是路由規則,它規定了服務消費者可以呼叫哪些服務提供者。條件路由規則由兩個條件組成,分別用於對服務消費者和提供者進行匹配。比如有這樣一條規則:
host = 10.20.153.14 => host = 10.20.153.12
該條規則表示 IP 為 10.20.153.14 的服務消費者只可呼叫 IP 為 10.20.153.12 機器上的服務,不可呼叫其他機器上的服務。條件路由規則的格式如下:
[服務消費者匹配條件] => [服務提供者匹配條件]
如果服務消費者匹配條件為空,表示不對服務消費者進行限制。如果服務提供者匹配條件為空,表示對某些服務消費者禁用服務。
8、設計RPC通讀下Dubbo的大致實現方式後其實就可以依葫蘆畫瓢了,一個RPC框架大致需要下面這些東西:
服務的註冊跟發現的搞一個吧,你可以用ZooKeeper或者Redis來實現。接下來consumer發起請求的時候你的面向介面程式設計啊,用到動態代理來實現呼叫。多個provider提供相同服務你的用到LoadBalance啊。最終選擇一個機器後你的約定好通訊協議啊,如何進行序列化跟反序列化呢?底層就用現成的高效能Netty框架 NIO模式實現唄。服務開啟後的有monitor啊。