筆者初識Dubbo的時候微服,只知道它是一個RPC框架,那么什么是RPC呢猾普?
1. RPC是什么
維基百科是這么定義RPC的:
在分布式計算皮获,遠(yuǎn)程過程調(diào)?(英語:Remote Procedure Call舞骆,縮寫為 RPC)是?個計算機(jī)通信協(xié) 議。該協(xié)議允許運?于?臺計算機(jī)的程序調(diào)?另?個地址空間(通常為?個開放?絡(luò)的?臺計算機(jī))的?程序糊饱,?程序員就像調(diào)?本地程序?樣垂寥,?需額外地為這個交互作?編程(?需關(guān)注細(xì)節(jié))。RPC是 ?種服務(wù)器-客戶端(Client/Server)模式,經(jīng)典實現(xiàn)是?個通過發(fā)送請求-接受回應(yīng)進(jìn)?信息交互的系統(tǒng)滞项。如果涉及的軟件采??向?qū)ο缶幊滔凉椋敲催h(yuǎn)程過程調(diào)?亦可稱作遠(yuǎn)程調(diào)?或遠(yuǎn)程?法調(diào)?,例:Java RMI文判。
所以對于Java程序而言过椎,RPC就是遠(yuǎn)程方法調(diào)用。
1.1 相對于本地方法調(diào)用
遠(yuǎn)程?法調(diào)?和本地?法調(diào)?是相對的兩個概念戏仓,本地?法調(diào)?指的是進(jìn)程內(nèi)部的?法調(diào)?疚宇,?遠(yuǎn)程?法調(diào)?指的是兩個進(jìn)程內(nèi)的?法相互調(diào)?。
1.2 基于網(wǎng)絡(luò)傳輸
從上述表述中可以看到赏殃,遠(yuǎn)程方法調(diào)用指的是兩個進(jìn)程內(nèi)的方法調(diào)用敷待,如果要實現(xiàn)遠(yuǎn)程方法調(diào)用,基本就是通過網(wǎng)絡(luò)傳輸數(shù)據(jù)進(jìn)行調(diào)用仁热。
所以就有了
- RPC over Http:基于Http協(xié)議來傳輸數(shù)據(jù)
- PRC over Tcp:基于Tcp協(xié)議來傳輸數(shù)據(jù)
1.3 傳輸數(shù)據(jù)
對于所傳輸?shù)臄?shù)據(jù)榜揖,可以交由RPC的雙?來協(xié)商定義,但基本都會包括:
- 調(diào)?的是哪個類或接?
- 調(diào)?的是哪個?法抗蠢,?法名和?法參數(shù)類型(考慮?法重載)
- 調(diào)??法的?參
2. 什么是Dubbo
2.1 定義
?前举哟,官?上是這么介紹的:Apache Dubbo 是?款?性能、輕量級的開源 Java 服務(wù)框架迅矛。
在?個?前妨猩,官?的介紹是:Apache Dubbo 是?款?性能、輕量級的開源 Java RPC框架秽褒。
Dubbo?開始的定位就是RPC壶硅,專注于兩個服務(wù)之間的調(diào)?。但隨著微服務(wù)的盛?震嫉,除開服務(wù)調(diào)?之外森瘪, Dubbo也在逐步的涉獵服務(wù)治理牡属、服務(wù)監(jiān)控票堵、服務(wù)?關(guān)等等,所以現(xiàn)在的Dubbo?標(biāo)已經(jīng)不?是RPC框架了逮栅,?是和Spring Cloud類似想成為?個服務(wù)框架悴势。
2.2 基本組成
Dubbo有以下幾個模塊:注冊中心、服務(wù)提供者措伐、容器特纤、服務(wù)消費者和監(jiān)控中心組成。
2.2.1 注冊中心(Registry)
使用過SpringCloud或者Zookeeper的同學(xué)應(yīng)該有印象侥加,對于一個分布式服務(wù)捧存,最重要的組成之一就是注冊中心,它可以管理系統(tǒng)的整個服務(wù),包括服務(wù)發(fā)現(xiàn)與服務(wù)注冊昔穴。
2.2.2 服務(wù)提供者(Provider)
前面提到RPC框架需要在網(wǎng)絡(luò)傳輸數(shù)據(jù)镰官,這個數(shù)據(jù)在Java中可以表示為一個接口,而服務(wù)提供者的作用就是把這個接口提供出去吗货,并向注冊中心注冊自己泳唠,這樣才可以把服務(wù)暴露出去,供服務(wù)消費者調(diào)用宙搬。服務(wù)提供者和服務(wù)消費者雙方需要約定一個協(xié)議來處理服務(wù)消費者的請求笨腥。
2.2.3 容器(Container)
Dubbo的Container模塊,是一個獨立的容器勇垛,因為服務(wù)通常不需要Tomcat/JBoss等Web容器的特性脖母,沒必要用Web容器去加載服務(wù)。Dubbo內(nèi)置了jetty窥摄、spring來啟動服務(wù)镶奉,但是也提供了容器擴(kuò)展,用戶可以自定義容器啟動服務(wù)崭放,所以也可以使用Tomcat啟動服務(wù)哨苛,但是官方不推薦這么使用。
2.2.4 服務(wù)消費者(Consumer)
服務(wù)消費者需要向服務(wù)提供者發(fā)送請求币砂,來獲取最終數(shù)據(jù)建峭。
2.2.5 監(jiān)控中心
一個分布式服務(wù)要想穩(wěn)定運行,需要在全鏈路上監(jiān)控服務(wù)的運行狀態(tài)决摧,那種無監(jiān)控的服務(wù)實在是太可怕了亿蒸,筆者就遇到過無監(jiān)控的服務(wù),發(fā)現(xiàn)問題掌桩、排查問題的過程實在是太痛苦了边锁。
2.2.6 基本調(diào)用流程
- 服務(wù)容器負(fù)責(zé)啟動,加載波岛,運行服務(wù)提供者茅坛。
- 服務(wù)提供者在啟動時,向注冊中心注冊自己提供的服務(wù)则拷。
- 服務(wù)消費者在啟動時贡蓖,向注冊中心訂閱自己所需的服務(wù)。
- 注冊中心返回服務(wù)提供者地址列表給消費者煌茬,如果有變更斥铺,注冊中心將基于長連接推送變更數(shù)據(jù)給消費者。
- 服務(wù)消費者坛善,從提供者地址列表中晾蜘,基于軟負(fù)載均衡算法邻眷,選一臺提供者進(jìn)行調(diào)用,如果調(diào)用失敗剔交,再選另一臺調(diào)用耗溜。
- 服務(wù)消費者和提供者,在內(nèi)存中累計調(diào)用次數(shù)和調(diào)用時間省容,定時每分鐘發(fā)送一次統(tǒng)計數(shù)據(jù)到監(jiān)控中心抖拴。
3. 自己實現(xiàn)一個簡單的Dubbo
介紹了這么多,是時候展現(xiàn)真正的技術(shù)了腥椒,我們先自己實現(xiàn)一個簡單版本的Dubbo阿宅。
3.1 需求分析
首先進(jìn)行需求分析,我們設(shè)計一個框架笼蛛,希望框架有哪些模塊洒放、每個模塊的功能是什么,這里可以參考Dubbo的架構(gòu)來設(shè)計滨砍。
由于是簡易的版本往湿,我們只需要注冊中心、服務(wù)提供者惋戏、服務(wù)消費者以及容器這四個模塊领追。
3.2 模塊功能分析
3.2.1 注冊中心
- 提供注冊服務(wù)功能
- 提供獲取已注冊服務(wù)功能
3.2.2 服務(wù)提供者
- 提供調(diào)用接口供服務(wù)消費者調(diào)用
- 啟動時向注冊中心注冊服務(wù)
3.2.3 容器
- 服務(wù)容器負(fù)責(zé)啟動,加載响逢,運行服務(wù)提供者
3.2.4 服務(wù)消費者
- 啟動時向注冊中心訂閱服務(wù)
- 調(diào)用服務(wù)提供者提供的接口绒窑,調(diào)用該接口
3.3 項目整體結(jié)構(gòu)
整個項目分三個包,consumer舔亭、provider以及framework些膨。
- consumer:包含一個消費者啟動類Consumer
- provider:包含提供的接口UserService、接口實現(xiàn)類UserServiceImpl以及提供方啟動類Provider
- framework:包含protocol包钦铺、proxy包以及register包
- protocol:協(xié)議層订雾,本次用Http協(xié)議,包含HttpClient(Http客戶端)矛洞、HttpServer(服務(wù))洼哎、HttpServerHandler(服務(wù)處理)、DispatcherServlet(轉(zhuǎn)發(fā))以及Invocation(傳輸實體)
- proxy:包含一個代理工廠ProxyFactory
- register:包含一個LocalRegister
3.4 調(diào)用流程
- Provider類啟動缚甩,注冊本服務(wù)谱净,啟動容器
- Consumer類啟動窑邦,通過動態(tài)代理擅威,調(diào)用暴露的接口
- ProxyFactory通過HttpClient發(fā)送請求,調(diào)用服務(wù)提供方提供的接口
- 服務(wù)提供方接到該接口請求冈钦,通過HttpServerHandler處理請求
- HttpServerHandler解析請求參數(shù)郊丛,獲取傳輸實體Invocation
- 通過傳輸實體中的參數(shù),用反射獲取UserServiceImpl的實例
- 調(diào)用UserServiceImpl中的方法,獲得返回數(shù)據(jù)
- 將返回數(shù)據(jù)寫到Response中厉熟,返回給服務(wù)消費者
- 消費者接到返回數(shù)據(jù)导盅,呈現(xiàn)出來
3.5 具體實現(xiàn)
整體依賴文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>RPCDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.16.Final</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.12</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-client</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.13</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.2</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.14</version>
</dependency>
</dependencies>
</project>
3.5.1 provider層
Provider類
package com.rpc.provider;
import com.rpc.framework.protocol.http.HttpServer;
import com.rpc.framework.register.LocalRegister;
public class Provider {
public static void main(String[] args) {
String interfaceName = UserService.class.getName();
LocalRegister.register(interfaceName,UserServiceImpl.class);
//啟動服務(wù)
HttpServer httpServer = new HttpServer();
httpServer.start("localhost",8080);
}
}
接口UserService
package com.rpc.provider;
public interface UserService {
String hello(String name);
}
實現(xiàn)類UserServiceImpl
package com.rpc.provider;
public class UserServiceImpl implements UserService {
@Override
public String hello(String name) {
return "hello" + name;
}
}
3.5.2 consumer層
Cousumer類
package com.rpc.consumer;
import com.rpc.framework.proxy.ProxyFactory;
import com.rpc.provider.UserService;
public class Consumer {
public static void main(String[] args) {
UserService userService = ProxyFactory.gerProxy(UserService.class);
String result = userService.hello("PRC");
System.out.println(result);
}
}
3.5.3 framework層
3.5.3.1 protocol層
http層里的四個類
package com.rpc.framework.protocol.http;
import lombok.SneakyThrows;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class DispatcherServlet extends HttpServlet {
@SneakyThrows
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
new HttpServerHandler().handler(req,resp);
}
}
package com.rpc.framework.protocol.http;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.rpc.framework.protocol.Invocation;
import org.apache.http.HttpEntity;
import org.apache.http.ParseException;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
public class HttpClient {
public static SSLContext createSSL() {
try {
return new SSLContextBuilder().loadTrustMaterial(null, (certificate, authType) -> true).build();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
}
return null;
}
/**
* 構(gòu)建httpclient客戶端,設(shè)置SSL證書揍瑟,并設(shè)置請求失敗的重試次數(shù)
*/
private static final CloseableHttpClient httpclient = HttpClients
.custom().setSSLContext(createSSL())
.setRetryHandler(new HttpRequestRetryHandler() {
@Override
public boolean retryRequest(IOException e, int retryCount, HttpContext httpContext) {
if (retryCount <= 3) {
return true;
}
return false;
}
}).build();
/**
* 發(fā)送HttpPost請求白翻,參數(shù)為map
*
* @param url
* @param invocation
* @return
*/
public static String sendPost(String url, Invocation invocation) {
String jsonString = JSON.toJSONString(invocation);
StringEntity entity = new StringEntity(jsonString, Charset.forName("UTF-8"));
/*entity.setContentEncoding("UTF-8");
entity.setContentType("application/json;charset=UTF-8");*/
HttpPost httppost = new HttpPost(url);
/*
設(shè)置請求頭 請求體
*/
httppost.setHeader("Content-Type", "application/json;charset=UTF-8");
httppost.setHeader("Accept", "application/json");
httppost.setEntity(entity);
CloseableHttpResponse response = null;
try {
response = httpclient.execute(httppost);
} catch (IOException e) {
e.printStackTrace();
}
HttpEntity httpEntity = response.getEntity();
String result = null;
try {
result = EntityUtils.toString(httpEntity);
} catch (ParseException | IOException e) {
e.printStackTrace();
}
return result;
}
}
package com.rpc.framework.protocol.http;
import org.apache.catalina.*;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.startup.Tomcat;
/**
* 封裝Tomcat啟動服務(wù)
*
*/
public class HttpServer {
public void start(String hostName, Integer port){
Tomcat tomcat = new Tomcat();
Server server = tomcat.getServer();
Service service = server.findService("Tomcat");
Connector connector = new Connector();
connector.setPort(port);
Engine engine = new StandardEngine();
engine.setDefaultHost(hostName);
Host host = new StandardHost();
host.setName(hostName);
String contextPath = "";
Context context = new StandardContext();
context.setPath(contextPath);
context.addLifecycleListener(new Tomcat.FixContextListener());
host.addChild(context);
engine.addChild(host);
service.setContainer(engine);
service.addConnector(connector);
tomcat.addServlet(contextPath,"dispatcher", new DispatcherServlet());
context.addServletMappingDecoded("/*", "dispatcher");
try {
tomcat.start();
tomcat.getServer().await();
} catch (LifecycleException e) {
e.printStackTrace();
}
}
}
package com.rpc.framework.protocol.http;
import com.alibaba.fastjson.JSONObject;
import com.rpc.framework.protocol.Invocation;
import com.rpc.framework.register.LocalRegister;
import com.rpc.provider.UserService;
import com.rpc.provider.UserServiceImpl;
import org.apache.commons.io.IOUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class HttpServerHandler {
public void handler(HttpServletRequest req, HttpServletResponse resp) throws IOException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException {
//處理請求
Invocation invocation = JSONObject.parseObject(req.getInputStream(), Invocation.class);
Class aClass = LocalRegister.get(invocation.getInterfaceName());
try {
Method method = aClass.getMethod(invocation.getMethodName(),invocation.getParamTypes());
String result = (String) method.invoke(aClass.newInstance(), invocation.getParams());
IOUtils.write(result,resp.getOutputStream());
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
傳輸實體類
package com.rpc.framework.protocol;
import java.io.Serializable;
public class Invocation implements Serializable {
private String interfaceName;
private String methodName;
private Class[] paramTypes;
private Object[] params;
public Invocation(String interfaceName, String methodName, Class[] paramTypes, Object[] params) {
this.interfaceName = interfaceName;
this.methodName = methodName;
this.paramTypes = paramTypes;
this.params = params;
}
public String getInterfaceName() {
return interfaceName;
}
public void setInterfaceName(String interfaceName) {
this.interfaceName = interfaceName;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public Class[] getParamTypes() {
return paramTypes;
}
public void setParamTypes(Class[] paramTypes) {
this.paramTypes = paramTypes;
}
public Object[] getParams() {
return params;
}
public void setParams(Object[] params) {
this.params = params;
}
}
3.5.3.2 proxy層
ProxyFactory類
package com.rpc.framework.proxy;
import com.rpc.framework.protocol.Invocation;
import com.rpc.framework.protocol.http.HttpClient;
import java.lang.reflect.Proxy;
public class ProxyFactory {
public static <T> T gerProxy(final Class interfaceClass){
return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass},
(proxy, method, args) -> {
Invocation invocation = new Invocation(interfaceClass.getName(),
method.getName(),
method.getParameterTypes(),args);
//發(fā)送請求
String result = HttpClient.sendPost("http://localhost:8080", invocation);
return result;
});
}
}
3.5.3.3 register層
LocalRegister類
package com.rpc.framework.register;
import java.util.HashMap;
import java.util.Map;
public class LocalRegister {
private static Map<String, Class> map = new HashMap<>();
public static void register(String interfaceName, Class implClass){
map.put(interfaceName, implClass);
}
public static Class get(String interfaceName){
return map.get(interfaceName);
}
}
3.6 驗證
啟動Provider類,再啟動Consumer類
輸出
helloPRC
大功告成>钇B蒜伞!