引言
很久以前就想結(jié)合網(wǎng)上一些資源以及自己的實(shí)踐總結(jié)一篇屬于自己的RPC筆記了,因?yàn)閼?時(shí)間關(guān)系遂唧,直到現(xiàn)在才完成一個(gè)簡(jiǎn)要的記錄芙代。本文只是一個(gè)簡(jiǎn)單粗糙的筆記記錄,將別人的東西結(jié)合自己的理解整理了一下盖彭,思路主要來(lái)自于dubbo作者之一梁飛的博客:RPC框架幾行代碼就夠了纹烹。
概述
RPC(Remote Procedure Call)遠(yuǎn)程過(guò)程調(diào)用,概念就不再贅述了召边。個(gè)人的理解铺呵,不管是什么樣的RPC框架,總體思路都是服務(wù)提供方暴露服務(wù)掌实,消費(fèi)方通過(guò)服務(wù)方提供的接口使用動(dòng)態(tài)代理獲取代理對(duì)象陪蜻,然后調(diào)用代理對(duì)象的方法,代理對(duì)象在內(nèi)部進(jìn)行遠(yuǎn)程調(diào)用贱鼻,獲得計(jì)算結(jié)果宴卖。簡(jiǎn)要示意圖如下圖所示:
在這個(gè)過(guò)程中滋将,有兩處關(guān)鍵點(diǎn)。第一是獲取代理對(duì)象症昏,第二是代理對(duì)象與服務(wù)提供方建立連接随闽。對(duì)于獲取代理對(duì)象的方式,需要了解Java的動(dòng)態(tài)代理肝谭,可參考Java的三種代理模式掘宪。總的來(lái)說(shuō)攘烛,Java的動(dòng)態(tài)代理(此處只指JDK中生成代理對(duì)象的API魏滚,不包括cglib)把建立遠(yuǎn)程連接的細(xì)節(jié)封裝起來(lái),使服務(wù)消費(fèi)方可以在僅已知服務(wù)提供方的接口的情況下坟漱,可以像調(diào)用本地對(duì)象的方法一樣去調(diào)用遠(yuǎn)程服務(wù)鼠次。
簡(jiǎn)單實(shí)例
本實(shí)例使用socket建立連接,JDK的API做動(dòng)態(tài)代理芋齿,主要有服務(wù)提供方暴露服務(wù)腥寇、服務(wù)消費(fèi)方獲取代理對(duì)象、代理對(duì)象與服務(wù)提供方建立遠(yuǎn)程連接并調(diào)用三個(gè)方面觅捆。忽略所有的參數(shù)校驗(yàn)赦役、異常。
暴露服務(wù)
已知的某個(gè)服務(wù)的實(shí)例對(duì)象service栅炒,建立ServerSocket服務(wù)掂摔,并監(jiān)聽(tīng)指定端口,當(dāng)有遠(yuǎn)程連接建立時(shí)职辅,創(chuàng)建一個(gè)線程棒呛,在線程中從輸入流中依次讀取方法名、參數(shù)類(lèi)型域携、參數(shù)值等信息簇秒,并根據(jù)方法名和參數(shù)執(zhí)行實(shí)例對(duì)象service中對(duì)應(yīng)的方法,獲得返回結(jié)果秀鞭。
public class RpcExport {
private static int port = 1234;
/**
* 暴露服務(wù)
*
* @param service
* 服務(wù)的實(shí)現(xiàn)
* @param port
* 服務(wù)的端口
* @throws Exception
*/
public static void export(final Object service) throws Exception {
System.out.println("Export service " + service.getClass().getName() + " on port " + port);
// 創(chuàng)建socket趋观,開(kāi)始監(jiān)聽(tīng)
ServerSocket server = new ServerSocket(port);
while (true) {
final Socket socket = server.accept();
new Thread(new Runnable() {
@Override
public void run() {
ObjectInputStream input = null;
ObjectOutputStream output = null;
try {
// 從監(jiān)聽(tīng)的socket中獲得輸入流
input = new ObjectInputStream(socket.getInputStream());
String methodName = input.readUTF();
Class<?>[] parameterTypes = (Class<?>[]) input.readObject();
Object[] arguments = (Object[]) input.readObject();
// 從監(jiān)聽(tīng)的socket中獲得輸出流
output = new ObjectOutputStream(socket.getOutputStream());
Method method = service.getClass().getMethod(methodName, parameterTypes);
Object result = method.invoke(service, arguments);
output.writeObject(result);
} catch (Exception e) {
} finally {
try {
if (output != null) {
output.close();
}
if (input != null) {
input.close();
}
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
}
代理工廠
消費(fèi)方獲取代理對(duì)象,使用JDK的動(dòng)態(tài)代理API锋边,傳入接口類(lèi)名皱坛。生成代理對(duì)象的時(shí)候,需要傳入一個(gè)實(shí)現(xiàn)了InvocationHandler的對(duì)象豆巨,也就是下面的RpcHandler剩辟,
import java.lang.reflect.Proxy;
public class RpcProxyFactory {
public static <T> T getService(Class<T> interfaceClass) {
return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[] { interfaceClass },
new RpcHandler());
}
}
代理對(duì)象建立遠(yuǎn)程連接
這個(gè)RpcHandler中重寫(xiě)了invoke方法,就是代理對(duì)象的方法執(zhí)行的時(shí)候真正與服務(wù)提供方建立連接并獲得返回結(jié)果的地方。
public class RpcHandler implements InvocationHandler {
private String host = "127.0.0.1";
private int port = 1234;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//與服務(wù)提供方建立連接
Socket socket = new Socket(host, port);
try {
//獲取輸出流贩猎,并寫(xiě)出方法名熊户、參數(shù)名、參數(shù)值
ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
try {
output.writeUTF(method.getName());
output.writeObject(method.getParameterTypes());
output.writeObject(args);
ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
try {
//獲得返回結(jié)果
Object result = input.readObject();
if (result instanceof Throwable) {
throw (Throwable) result;
}
return result;
} finally {
input.close();
}
} finally {
output.close();
}
} finally {
socket.close();
}
}
}
使用實(shí)例
服務(wù)接口
public interface HelloService {
public String sayHello(String name);
}
服務(wù)實(shí)現(xiàn)類(lèi)
public class HelloServiceImpl implements HelloService{
@Override
public String sayHello(String name) {
return "hello "+name;
}
}
暴露服務(wù)
public class RpcProvider {
public static void main(String[] args) throws Exception {
HelloService service = new HelloServiceImpl();
RpcExport.export(service);
}
}
消費(fèi)者獲取代理對(duì)象并調(diào)用
public class RpcConsumer {
public static void main(String[] args) throws Exception {
HelloService proxy = RpcProxyFactory.getService(HelloService.class);
String result = proxy.sayHello("world");
System.out.println(result);
}
}
結(jié)果
輸出:hello world
總結(jié)
這個(gè)簡(jiǎn)易實(shí)例中吭服,整個(gè)RPC原理很清晰嚷堡,上面用例中最核心的一點(diǎn),就是當(dāng)proxy.sayHello執(zhí)行的時(shí)候艇棕,實(shí)際是在執(zhí)行RpcHandler的invoke方法蝌戒,也就是遠(yuǎn)程調(diào)用。
如果要真正實(shí)現(xiàn)一個(gè)企業(yè)級(jí)RPC框架沼琉,僅僅有這個(gè)原理還是不夠的北苟。還需要考慮很多東西,例如建立連接的時(shí)候打瘪,使用NIO從而使得IO效率更高粹淋;或者在集群中,暴露服務(wù)的ip和端口都是動(dòng)態(tài)的瑟慈,而消費(fèi)者此時(shí)也不能將要調(diào)用的服務(wù)提供方的ip和端口寫(xiě)死,于是需要一個(gè)注冊(cè)中心的角色屋匕,產(chǎn)生注冊(cè)服務(wù)葛碧、訂閱服務(wù)等事件。