目錄
一、原生JDK的IO模型BIO
服務端提供IP和監聽端口,客戶端通過連接操作想服務端監聽的地址發起連接請求,通過三次握手連接,如果連接成功建立,雙方就可以通過套接字進行通信。
傳統的同步阻塞模型開發中,ServerSocket負責綁定IP地址,啓動監聽端口;Socket負責發起連接操作。連接成功後,雙方通過輸入和輸出流進行同步阻塞式通信。
傳統BIO通信模型:採用BIO通信模型的服務端,通常由一個獨立的Acceptor線程負責監聽客戶端的連接,它接收到客戶端連接請求之後爲每個客戶端創建一個新的線程進行鏈路處理沒處理完成後,通過輸出流返回應答給客戶端,線程銷燬。即典型的一請求一應答模型。
代碼示例:
/**
*@author Darkking
*
*類說明:Bio通信的服務端
*/
public class Server {
public static void main(String[] args) throws IOException {
/*服務器必備*/
ServerSocket serverSocket = new ServerSocket();
/*綁定監聽端口*/
serverSocket.bind(new InetSocketAddress(10001));
System.out.println("Server start.......");
while(true){
//每監聽到一個客戶端請求創建一個線程
new Thread(new ServerTask(serverSocket.accept())).start();
}
}
private static class ServerTask implements Runnable{
private Socket socket = null;
public ServerTask(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
/*拿和客戶端通訊的輸入輸出流*/
try(ObjectInputStream inputStream
= new ObjectInputStream(socket.getInputStream());
ObjectOutputStream outputStream
= new ObjectOutputStream(socket.getOutputStream())){
/*服務器的輸入*/
String userName = inputStream.readUTF();
System.out.println("Accept clinet message:"+userName);
outputStream.writeUTF("Hello,"+userName);
outputStream.flush();
}catch (Exception e){
e.printStackTrace();
}
finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
/**
*@author Darkking
*
*類說明:Bio通信的服務端
*/
public class Client {
public static void main(String[] args) throws IOException {
//客戶端啓動必備
Socket socket = null;
//實例化與服務端通信的輸入輸出流
ObjectOutputStream output = null;
ObjectInputStream input = null;
//服務器的通信地址
InetSocketAddress addr
= new InetSocketAddress("127.0.0.1",10001);
try{
socket = new Socket();
/*連接服務器*/
socket.connect(addr);
output = new ObjectOutputStream(socket.getOutputStream());
input = new ObjectInputStream(socket.getInputStream());
/*向服務器輸出請求*/
output.writeUTF("Darkking");
output.flush();
//接收服務器的輸出
System.out.println(input.readUTF());
}finally{
if (socket!=null) socket.close();
if (output!=null) output.close();
if (input!=null) input.close();
}
}
}
該模型最大的問題就是缺乏彈性伸縮能力,當客戶端併發訪問量增加後,服務端的線程個數和客戶端併發訪問數呈1:1的正比關係,Java中的線程也是比較寶貴的系統資源,線程數量快速膨脹後,系統的性能將急劇下降,隨着訪問量的繼續增大,系統最終就死-掉-了。
爲了改進這種一連接一線程的模型,我們可以使用線程池來管理這些線程,實現1個或多個線程處理N個客戶端的模型(但是底層還是使用的同步阻塞I/O),通常被稱爲“僞異步I/O模型“。
我們知道,如果使用CachedThreadPool線程池(如果不太瞭解線程池,可以查看之前的併發專題線程池的使用),其實除了能自動幫我們管理線程(複用),看起來也就像是1:1的客戶端:線程數模型,而使用FixedThreadPool我們就有效的控制了線程的最大數量,保證了系統有限的資源的控制,實現了N:M的僞異步I/O模型。
但是,正因爲限制了線程數量,如果發生讀取數據較慢時(比如數據量大、網絡傳輸慢等),大量併發的情況下,其他接入的消息,只能一直等待,這就是最大的弊端。
服務端改進代碼如下:
mport java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
*@author Darkking
*
*類說明:Bio通信的服務端
*/
public class ServerPool {
//創建指定線程數的線程池,線程數量爲cpu數
private static ExecutorService executorService
= Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors());
public static void main(String[] args) throws IOException {
//服務端啓動必備
ServerSocket serverSocket = new ServerSocket();
//表示服務端在哪個端口上監聽
serverSocket.bind(new InetSocketAddress(10001));
System.out.println("Start Server ....");
try{
while(true){
executorService.execute(new ServerTask(serverSocket.accept()));
}
}finally {
serverSocket.close();
}
}
//每個和客戶端的通信都會打包成一個任務,交個一個線程來執行
private static class ServerTask implements Runnable{
private Socket socket = null;
public ServerTask(Socket socket){
this.socket = socket;
}
@Override
public void run() {
//實例化與客戶端通信的輸入輸出流
try(ObjectInputStream inputStream =
new ObjectInputStream(socket.getInputStream());
ObjectOutputStream outputStream =
new ObjectOutputStream(socket.getOutputStream())){
//接收客戶端的輸出,也就是服務器的輸入
String userName = inputStream.readUTF();
System.out.println("Accept client message:"+userName);
//服務器的輸出,也就是客戶端的輸入
outputStream.writeUTF("Hello,"+userName);
outputStream.flush();
}catch(Exception e){
e.printStackTrace();
}finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
二、BIO應用-RPC框架
爲什麼要有RPC?
我們最開始開發的時候,一個應用一臺機器,將所有功能都寫在一起,比如說比較常見的電商場景。
隨着我們業務的發展,我們需要提示性能了,我們會怎麼做?將不同的業務功能放到線程裏來實現異步和提升性能。
但是業務越來越複雜,業務量越來越大,單個應用或者一臺機器的資源是肯定揹負不起的,這個時候,我們會怎麼做?將核心業務抽取出來,作爲獨立的服務,放到其他服務器上或者形成集羣。這個時候就會請出RPC,系統變爲分佈式的架構。
爲什麼說千萬級流量分佈式、微服務架構必備的RPC框架?和LocalCall的代碼進行比較,因爲引入rpc框架對我們現有的代碼影響最小,同時又可以幫我們實現架構上的擴展。現在的開源rpc框架,有什麼?dubbo,grpc等等
當服務越來越多,各種rpc之間的調用會越來越複雜,這個時候我們會引入中間件,比如說MQ、緩存,同時架構上整體往微服務去遷移,引入了各種比如容器技術docker,DevOps等等。最終會變爲如圖所示來應付千萬級流量,但是不管怎樣,rpc總是會佔有一席之地。
什麼是RPC?
RPC(Remote Procedure Call ——遠程過程調用),它是一種通過網絡從遠程計算機程序上請求服務,而不需要了解底層網絡的技術。
一次完整的RPC同步調用流程:
1)服務消費方(client)以本地調用方式調用客戶端存根;
2)什麼叫客戶端存根?就是遠程方法在本地的模擬對象,一樣的也有方法名,也有方法參數,client stub接收到調用後負責將方法名、方法的參數等包裝,並將包裝後的信息通過網絡發送到服務端;
3)服務端收到消息後,交給代理存根在服務器的部分後進行解碼爲實際的方法名和參數
4) server stub根據解碼結果調用服務器上本地的實際服務;
5)本地服務執行並將結果返回給server stub;
6)server stub將返回結果打包成消息併發送至消費方;
7)client stub接收到消息,並進行解碼;
8)服務消費方得到最終結果。
RPC框架的目標就是要中間步驟都封裝起來,讓我們進行遠程方法調用的時候感覺到就像在本地調用一樣。
RPC和HTTP
rpc字面意思就是遠程過程調用,只是對不同應用間相互調用的一種描述,一種思想。具體怎麼調用?實現方式可以是最直接的tcp通信,也可以是http方式,在很多的消息中間件的技術書籍裏,甚至還有使用消息中間件來實現RPC調用的,我們知道的dubbo是基於tcp通信的,gRPC是Google公佈的開源軟件,基於最新的HTTP2.0協議,底層使用到了Netty框架的支持。所以總結來說,rpc和http是完全兩個不同層級的東西,他們之間並沒有什麼可比性。
實現RPC框架
實現RPC框架需要解決的那些問題呢?
代理問題
代理本質上是要解決什麼問題?要解決的是被調用的服務本質上是遠程的服務,但是調用者不知道也不關心,調用者只要結果,具體的事情由代理的那個對象來負責這件事。既然是遠程代理,當然是要用代理模式了。
代理(Proxy)是一種設計模式,即通過代理對象訪問目標對象.這樣做的好處是:可以在目標對象實現的基礎上,增強額外的功能操作,即擴展目標對象的功能。那我們這裏額外的功能操作是幹什麼,通過網絡訪問遠程服務。
jdk的代理有兩種實現方式:靜態代理和動態代理。
序列化問題
序列化問題在計算機裏具體是什麼呢?我們的方法調用,有方法名,方法參數,這些可能是字符串,可能是我們自己定義的java的類,但是在網絡上傳輸或者保存在硬盤的時候,網絡或者硬盤並不認得什麼字符串或者javabean,它只認得二進制的01串,怎麼辦?要進行序列化,網絡傳輸後要進行實際調用,就要把二進制的01串變回我們實際的java的類,這個叫反序列化。java裏已經爲我們提供了相關的機制Serializable。
通信問題
我們在用序列化把東西變成了可以在網絡上傳輸的二進制的01串,但具體如何通過網絡傳輸?那就要使用JDK爲我們提供的BIO或者其他IO。
登記的服務實例化
登記的服務有可能在我們的系統中就是一個名字,那他怎麼變成實際執行的對象實例,當然是使用JAVA的反射機制。
反射機制是什麼?
反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。
反射機制主要提供了以下功能:
•在運行時判斷任意一個對象所屬的類;
•在運行時構造任意一個類的對象;
•在運行時判斷任意一個類所具有的成員變量和方法;
•在運行時調用任意一個對象的方法;
•生成動態代理。
手寫RPC框架
瞭解原理之後接下來我們就手寫一個簡單地RPC框架,包括服務註冊發現(服務治理)。服務端提供發送短信接口服務,註冊到註冊中心,供客戶端調用
注:RPC只是指遠程過程調用,能滿足遠程服務調用的都可以說是支持RPC。服務治理是一種實現模式。是爲了對遠程服務更好地進行管理而產生的。
1、服務註冊中心類
/**
*@author Darkking
* 類說明:註冊中心註冊服務的實體類
*/
public class RegisterServiceVo implements Serializable {
private final String host;//服務提供者的ip地址
private final int port;//服務提供者的端口
public RegisterServiceVo(String host,int port) {
this.host = host;
this.port = port;
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
}
/**
*@author Darkking
* 類說明:服務註冊中心,服務提供者在啓動時需要在註冊中心登記自己的信息
*/
public class RegisterCenter {
//key表示服務名,value代表服務提供者地址的集合
private static final Map<String,Set<RegisterServiceVo>> serviceHolder
= new HashMap<>();
//註冊服務的端口號
private int port;
public RegisterCenter(int port) {
this.port = port;
}
//服務註冊,考慮到可能有多個提供者同時註冊,進行加鎖
private static synchronized void registerSerive(String serviceName,
String host,int port){
//獲得當前服務的已有地址集合
Set<RegisterServiceVo> serviceVoSet = serviceHolder.get(serviceName);
if(serviceVoSet==null){
//已有地址集合爲空,新增集合
serviceVoSet = new HashSet<>();
serviceHolder.put(serviceName,serviceVoSet);
}
//將新的服務提供者加入集合
serviceVoSet.add(new RegisterServiceVo(host,port));
System.out.println("服務已註冊["+serviceName+"]," +
"地址["+host+"],端口["+port+"]");
}
//取出服務提供者
private static Set<RegisterServiceVo> getService(String serviceName){
return serviceHolder.get(serviceName);
}
//處理服務請求的任務
private static class ServerTask implements Runnable{
private Socket client = null;
public ServerTask(Socket client){
this.client = client;
}
public void run() {
try(ObjectInputStream inputStream =
new ObjectInputStream(client.getInputStream());
ObjectOutputStream outputStream =
new ObjectOutputStream(client.getOutputStream())){
//檢查當前請求是註冊服務還是獲得服務
boolean isGetService = inputStream.readBoolean();
/*獲得服務提供者*/
if(isGetService){
String serviceName = inputStream.readUTF();
//取出服務提供者集合
Set<RegisterServiceVo> result = getService(serviceName);
//返回給客戶端
outputStream.writeObject(result);
outputStream.flush();
System.out.println("將已註冊的服務["+serviceName+"提供給客戶端");
}
/*註冊服務*/
else{
//取得新服務提供方的ip和端口
String serviceName = inputStream.readUTF();
String host = inputStream.readUTF();
int port = inputStream.readInt();
//在註冊中心保存
registerSerive(serviceName,host,port);
outputStream.writeBoolean(true);
outputStream.flush();
}
}catch(Exception e){
e.printStackTrace();
}finally {
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//啓動註冊服務
public void startService() throws IOException {
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(port));
System.out.println("RegisterCenter server on:"+port+":運行");
try{
while(true){
new Thread(new ServerTask(serverSocket.accept())).start();
}
}finally {
serverSocket.close();
}
}
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
try{
RegisterCenter serviceServer = new RegisterCenter(9999);
serviceServer.startService();
}catch(IOException e){
e.printStackTrace();
}
}
}).start();
}
}
2、服務端
對象實體類
/**
*@author Darkking
* 類說明:註冊中心註冊服務的實體類
*/
public class RegisterServiceVo implements Serializable {
private final String host;//服務提供者的ip地址
private final int port;//服務提供者的端口
public RegisterServiceVo(String host, int port) {
this.host = host;
this.port = port;
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
}
/**
*@author Darkking
*
*類說明:用戶的實體類,已實現序列化
*/
public class UserInfo implements Serializable {
private final String name;
private final String phone;
public UserInfo(String name, String phone) {
this.name = name;
this.phone = phone;
}
public String getName() {
return name;
}
public String getPhone() {
return phone;
}
}
服務提供類
/**
*@author Darkking
*
*
*類說明:短信息發送接口
*/
public interface SendSms {
boolean sendMail(UserInfo user);
}
/**
*@author Darkking
*
*類說明:短信息發送服務的實現
*/
public class SendSmsImpl implements SendSms {
@Override
public boolean sendMail(UserInfo user) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("已發送短信息給:"+user.getName()+"到【"+user.getPhone()+"】");
return true;
}
}
服務註冊類
**
*@author Darkking
*
*類說明:rpc框架的服務端部分
*/
public class RpcServerFrameReg {
private static ExecutorService executorService
= Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors());
//服務在本地的註冊中心,主要是接口名和實現類的對照
private static final Map<String,Class> serviceHolder
= new HashMap<>();
//服務的端口號
private int port;
public RpcServerFrameReg(int port) {
this.port = port;
}
//服務註冊
public void registerSerive(Class<?> serviceInterface,Class impl) throws IOException {
Socket socket = null;
ObjectOutputStream output = null;
ObjectInputStream input = null;
/*向註冊中心註冊服務*/
try{
socket = new Socket();
socket.connect(new InetSocketAddress("127.0.0.1",9999));
output = new ObjectOutputStream(socket.getOutputStream());
output.writeBoolean(false);
output.writeUTF(serviceInterface.getName());
output.writeUTF("127.0.0.1");
output.writeInt(port);
output.flush();
input = new ObjectInputStream(socket.getInputStream());
if(input.readBoolean()){
serviceHolder.put(serviceInterface.getName(),impl);
System.out.println(serviceInterface.getName()+"服務註冊成功");
}else{
System.out.println(serviceInterface.getName()+"服務註冊失敗");
};
}finally {
if (socket!=null) socket.close();
if (output!=null) output.close();
if (input!=null) input.close();
}
}
//處理服務請求任務
private static class ServerTask implements Runnable{
private Socket client = null;
public ServerTask(Socket client){
this.client = client;
}
public void run() {
try(ObjectInputStream inputStream =
new ObjectInputStream(client.getInputStream());
ObjectOutputStream outputStream =
new ObjectOutputStream(client.getOutputStream())){
//方法所在類名接口名
String serviceName = inputStream.readUTF();
//方法的名字
String methodName = inputStream.readUTF();
//方法的入參類型
Class<?>[] parmTypes = (Class<?>[]) inputStream.readObject();
//方法入參的值
Object[] args = (Object[]) inputStream.readObject();
Class serviceClass = serviceHolder.get(serviceName);
if (serviceClass == null){
throw new ClassNotFoundException(serviceName+" Not Found");
}
Method method = serviceClass.getMethod(methodName,parmTypes);
Object result = method.invoke(serviceClass.newInstance(),args);
outputStream.writeObject(result);
outputStream.flush();
}catch(Exception e){
e.printStackTrace();
}finally {
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//啓動RPC服務
public void startService() throws IOException{
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(port));
System.out.println("RPC server on:"+port+":運行");
try{
while(true){
executorService.execute(new ServerTask(serverSocket.accept()));
}
}finally {
serverSocket.close();
}
}
服務註冊啓動類
/**
*@author Darkking
*
*類說明:rpc的服務端,提供服務
*/
public class SmsRpcServerReg {
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
try{
RpcServerFrameReg serviceServer = new RpcServerFrameReg(9189);
serviceServer.registerSerive(SendSms.class, SendSmsImpl.class);
serviceServer.startService();
}catch(Exception e){
e.printStackTrace();
}
}
}).start();
}
}
3、客戶端代理類
/**
*@author Darkking
*
*類說明:rpc框架的客戶端代理部分
*/
public class RpcClientFrameReg {
//遠程代理對象
public static <T> T getRemoteProxyObj(final Class<?> serviceInterface){
final InetSocketAddress addr
= new InetSocketAddress("127.0.0.1",9999);
return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(),
new Class<?>[]{serviceInterface}
,new DynProxy(serviceInterface,addr));
}
//動態代理類
private static class DynProxy implements InvocationHandler {
private final Class<?> serviceInterface;
private final InetSocketAddress addr;
private RegisterServiceVo[] serviceArray;/*遠程服務在本地的緩存列表*/
public DynProxy(Class<?> serviceInterface, InetSocketAddress addr) {
this.serviceInterface = serviceInterface;
this.addr = addr;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Socket socket = null;
ObjectOutputStream output = null;
ObjectInputStream input = null;
/*檢索遠程服務並填充本地的緩存列表*/
if(serviceArray==null){
try{
socket = new Socket();
socket.connect(addr);
output = new ObjectOutputStream(socket.getOutputStream());
output.writeBoolean(true);
output.writeUTF(serviceInterface.getName());
output.flush();
input = new ObjectInputStream(socket.getInputStream());
Set<RegisterServiceVo> result = (Set<RegisterServiceVo>)input.readObject();
serviceArray = new RegisterServiceVo[result.size()];
result.toArray(serviceArray);
}finally {
if (socket!=null) socket.close();
if (output!=null) output.close();
if (input!=null) input.close();
}
}
/*本地的緩存列表取得一個遠端服務器的地址端口
* 可以考慮使用更復雜的算法,以實現服務器的負載均衡
* 這裏簡單化處理,用隨機數挑選*/
Random r = new Random();
int index = r.nextInt(serviceArray.length);
InetSocketAddress serviceAddr
= new InetSocketAddress(serviceArray[index].getHost(),serviceArray[index].getPort());
try{
socket = new Socket();
socket.connect(serviceAddr);
output = new ObjectOutputStream(socket.getOutputStream());
output.writeUTF(serviceInterface.getName());//方法所在的類
output.writeUTF(method.getName());//方法的名
output.writeObject(method.getParameterTypes());//方法的入參類型
output.writeObject(args);
output.flush();
input = new ObjectInputStream(socket.getInputStream());
return input.readObject();
}finally{
if (socket!=null) socket.close();
if (output!=null) output.close();
if (input!=null) input.close();
}
}
}
}
客戶端遠程調用類
/**
*@author Darkking
*類說明:rpc的客戶端,調用遠端服務
*/
public class RpcClientReg {
public static void main(String[] args) {
UserInfo userInfo
= new UserInfo("你好,Drakking","88888888");
SendSms sendSms = RpcClientFrameReg.getRemoteProxyObj(SendSms.class);
System.out.println("Send mail: "+ sendSms.sendMail(userInfo));
}
}
執行過程
1、啓動服務註冊中心
2、服務註冊
服務端打印服務註冊成功,註冊中心打印服務已註冊
3、客戶端進行服務調用,可以正常調用服務端接口,服務端打印發送信息。
代碼資源可以見附件,或者去我的資源裏下載。
三、主流的RPC框架
Dubbo
Dubbo作爲阿里開源的RPC框架,現在已經成爲apache的頂級項目,官網地址如下:http://dubbo.apache.org/en-us/
這裏簡單介紹下,後續會有專題進行框架搭建以及源碼解析。
在Dubbo裏,底層採用的是Netty框架,下節會將java中的NIO。支持BIO,BIO等多種線程模型,dubbo的運行機制如下
1、服務容器負責啓動,加載,運行服務提供者。
2、服務提供者在啓動時,向註冊中心註冊自己提供的服務。
3、服務消費者在啓動時,向註冊中心訂閱自己所需的服務。
4、註冊中心返回服務提供者地址列表給消費者,如果有變更,註冊中心將基於長連接推送變更數據給消費者。
5、服務消費者,從提供者地址列表中,基於軟負載均衡算法,選一臺提供者進行調用,如果調用失敗,再選另一臺調用。
6、服務消費者和提供者,在內存中累計調用次數和調用時間,定時每分鐘發送一次統計數據到監控中心。
Dubbo除了支持RPC,還支持微服務化的服務治理。
SpringCloud
Spring Cloud是一系列框架的集合。它利用SpringBoot的開發便利性巧妙地簡化了分佈式系統基礎設施的開發,如服務發現註冊、配置中心、消息總線、負載均衡、斷路器、數據監控等,都可以用Spring Boot的開發風格做到一鍵啓動和部署。SpringCloud的RPC實現主要是基於HTTP的RESTful接口。有着跨語言,輕量,易用等特點。並且所屬Spring全家桶,包含很多組件,不需要重複造輪子。
gRPC
gRPC是Google開源的通用高性能RPC框架,它支持的是使用Protocol Buffers來編寫Service定義,支持較多語言擴平臺並且擁有強大的二進制序列化工具集。是一個純粹的RPC框架
微服務化Dubbo和SpringCloud選型
協議上比較:http相對更規範,更標準,更通用,無論哪種語言都支持http協議。如果你是對外開放API,例如開放平臺,外部的編程語言多種多樣,你無法拒絕對每種語言的支持,相應的,如果採用http,無疑在你實現SDK之前,支持了所有語言,所以,現在開源中間件,基本最先支持的幾個協議都包含RESTful。
RPC協議性能要高的多,例如Protobuf、Thrift、Kyro等,(如果算上序列化)吞吐量大概能達到http的二倍。響應時間也更爲出色。千萬不要小看這點性能損耗,公認的,微服務做的比較好的,例如,netflix、阿里,曾經都傳出過爲了提升性能而合併服務。
服務全面上比較:當然是springloud更勝一籌,但也就意味着在使用springloud上其實更重量級一點,dubbo目前版本專注於服務治理,使用上更輕量一點。
就國內的熱度來說,如果我們看百度指數的查詢結果,springloud和dubbo幾乎是半斤八兩,dubbo相比起來還略勝一籌
總的來說對外開放的服務推薦採用RESTful,內部調用推薦採用RPC方式。當然不能一概而論,還要看具體的業務場景。