研讀Hadoop源碼,不得不說其中的RPC機制。
實現RPC的幾個基本步驟:
1)客戶端需要有一個負責與遠程服務端對象通信的對象,稱爲A;
2)服務端需要有一個負責與遠程客戶端對象通信的對象,稱爲B;
3)A負責將客戶端請求的Java類型方法、參數,序列化成字節流,通過網絡傳遞給B;
4)B負責將通過網絡收到的請求字節流,反序列化成Java類型方法、參數,傳遞給真正的服務端對象,並調用方法;
5)B收到服務端對象返回的結果,再序列化傳遞給A;
6)A反序列化後,將結果返回給客戶端調用者。
如果採用RMI實現上述RPC過程的話,A即爲存根對象,B即爲骨架對象;實際上CORBA架構也離不開上述的過程。
下面從RPC調用過程的先後出場順序,闡述hadoop的RPC方案:
1、客戶端使用Java 反射,獲取遠程服務端對象的代理對象,可以視爲A,同時解決了類型安全的問題;
public static Object getProxy(Class protocol, InetSocketAddress addr, Configuration conf) {
return Proxy.newProxyInstance(protocol.getClassLoader(),
new Class[] { protocol },
new Invoker(addr, conf));
}
通過new 一個Invoker,來實現invocationHandler,實現調用過程,具體調用又是通過New一個Client對象的call方法實現;
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
ObjectWritable value = (ObjectWritable)
CLIENT.call(new Invocation(method, args), address);
return value.get();
}
2、上述的call調用與後續的調用,都是通過實現Writable接口的對象與ObjectWritable對象,來實現序列化與反序列化的,其中還包括所有Java基本類型在內都實現了Writable接口;
public interface Writable {
void write(DataOutput out) throws IOException; //序列化接口
void readFields(DataInput in) throws IOException; //反序列化接口
}
3、CLIENT在向服務端發送請求時,進行了同步,解決了線程安全的問題,並通過新建一個connection線程將調用序列化輸出;
public Writable call(Writable param, InetSocketAddress address)
throws IOException {
Connection connection = getConnection(address); //新啓一個connection線程
Call call = new Call(param); //封裝成call
synchronized (call) {
connection.sendParam(call); // connection線程發送請求
//以下省略
}
connection線程中,對call對象進行序列化輸出;
public void sendParam(Call call) throws IOException {
calls.put(new Integer(call.id), call);
synchronized (out) {
if (LOG.isLoggable(Level.FINE))
LOG.fine(getName() + " sending #" + call.id);
try {
writingCall = call;
out.writeInt(call.id);
call.param.write(out); //參數序列化,並寫出
out.flush();
} finally {
writingCall = null;
}
}
4、服務端socket監聽線程監聽到請求時,會開啓一個connection線程,構造參數對象,進行反序列化,構造call對象,喚醒call隊列;
while (running) {
int id;
try {
id = in.readInt(); // try to read an id
} catch (SocketTimeoutException e) {
continue;
}
if (LOG.isLoggable(Level.FINE))
LOG.fine(getName() + " got #" + id);
Writable param = makeParam(); // 構造參數對象
param.readFields(in); // 反序列化
Call call = new Call(id, param, this); // 構造call對象
synchronized (callQueue) {
callQueue.addLast(call); // queue the call
callQueue.notify(); // 喚醒call隊列
}
5、服務端Handler線程被喚醒後,調用服務端對象,獲取結果後,又序列化輸出;
Writable value = null;
try {
value = call(call.param); // 調用
} catch (IOException e) {
LOG.log(Level.INFO, getName() + " call error: " + e, e);
error = getStackTrace(e);
} catch (Exception e) {
LOG.log(Level.INFO, getName() + " call error: " + e, e);
error = getStackTrace(e);
}
DataOutputStream out = call.connection.out;
synchronized (out) {
out.writeInt(call.id); // write call id
out.writeBoolean(error!=null); // write error flag
if (error != null)
value = new UTF8(error);
value.write(out); // write value
out.flush();
}
調用過程如下:
public Writable call(Writable param) throws IOException {
Invocation call = (Invocation)param;
if (verbose) log("Call: " + call);
Method method =
implementation.getMethod(call.getMethodName(),
call.getParameterClasses());
Object value = method.invoke(instance, call.getParameters()); //真正調用服務端對象
if (verbose) log("Return: "+value);
return new ObjectWritable(method.getReturnType(), value);
}
6、以DataNode客戶端爲例,DataXceiveServer線程監聽到服務端返回的數據後,就新開啓一個DataXceiver線程,DataXceiver將返回的數據進行反序列化後再進行相應處理;
if (op == OP_WRITE_BLOCK) {
//
// Read in the header
//
DataOutputStream reply = new DataOutputStream(new BufferedOutputStream(s.getOutputStream()));
try {
boolean shouldReportBlock = in.readBoolean();
Block b = new Block();
b.readFields(in); //反序列化
這樣就完成了一個完整的RPC調用過程。