1.概述
上一篇部落格,講述Hadoop V2的序列化機制,這為我們學習Hadoop V2的RPC機制奠定了基礎。RPC的內容涵蓋的資訊有點多,包含Hadoop的序列化機制,RPC,代理,NIO等。若對Hadoop序列化不瞭解的同學,可以參考《Hadoop2原始碼分析-序列化篇》。今天這篇部落格為大家介紹的內容目錄如下:
- RPC概述
- 第三方RPC
- Hadoop V2的RPC簡述
那麼,下面開始今天的學習之路。
2.RPC概述
首先,我們要弄明白,什麼是RPC?RPC能用來做什麼?
2.1什麼是RPC
RPC的全程是Remote Procedure Call,中文釋為遠端過程呼叫。也就是說,呼叫的過程程式碼(業務服務程式碼)並不在呼叫者本地執行,而是要實現呼叫著和被呼叫著之間的連線通訊,有同學可能已經發現,這個和C/S模式很像。沒錯,RPC的基礎通訊模式是基於C/S程式間相互通訊的模式來實現的,它對Client端提供遠端介面服務,其RPC原理圖如下所示:
2.2RPC的功能
我們都知道,在過去的程式設計概念中,過程是由開發人員在本地編譯完成的,並且只能侷限在本地執行的某一段程式碼,即主程式和過程程式是一種本地呼叫關係。因此,這種結構在如今網路飛速發展的情況下已無法適應實際的業務需求。而且,傳統過程呼叫模式無法充分利用網路上其他主機的資源,如CPU,記憶體等,也無法提高程式碼在Bean之間的共享,使得資源浪費較大。
而RPC的出現,正好有效的解決了傳統過程中存在的這些不足。通過RPC,我們可以充分利用非共享記憶體的機器,可以簡便的將應用分佈在多臺機器上,類似叢集分佈。這樣方便的實現過程程式碼共享,提高系統資源的利用率。減少單個叢集的壓力,實現負載均衡。
3.第三方RPC
在學習Hadoop V2的RPC機制之前,我們先來熟悉第三方的RPC機制是如何工作的,下面我以Thrift框架為例子。
Thrift是一個軟體框架,用來進行可擴充套件且跨語言的服務開發協議。它擁有強大的程式碼生成引擎,支援C++,Java,Python,PHP,Ruby等程式語言。Thrift允許定義一個簡單的定義檔案(以.thirft結尾),檔案中包含資料型別和服務介面。用以作為輸入檔案,編譯器生成程式碼用來方便的生成RPC客戶端和服務端通訊的程式語言。具體Thrift安裝過程請參考《Mac OS X 下搭建thrift環境》。
3.1Thrift原理圖
下面給出Thrift的原理圖,如下所示:
下面為大家解釋一下上面的原理圖,首先,我們編譯完thrift定義檔案後(這裡我使用的是Java語言),會生成對應的Java類檔案,該類的Iface介面定義了我們所規範的介面函式。在服務端,實現Iface介面,編寫對應函式下的業務邏輯,啟動服務。客戶端同樣需要生成的Java類檔案,以供Client端呼叫相應的介面函式,監聽服務端的IP和PORT來獲取連線物件。
3.2程式碼示例
- Server端程式碼:
package cn.rpc.main; import org.apache.thrift.TProcessorFactory; import org.apache.thrift.protocol.TCompactProtocol; import org.apache.thrift.server.THsHaServer; import org.apache.thrift.server.TServer; import org.apache.thrift.transport.TFramedTransport; import org.apache.thrift.transport.TNonblockingServerSocket; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import cn.rpc.service.StatQueryService; import cn.rpc.service.impl.StatQueryServiceImpl; /** * @Date Mar 23, 2015 * * @Author dengjie */ public class StatsServer { private static Logger logger = LoggerFactory.getLogger(StatsServer.class); private final int PORT = 9090; @SuppressWarnings({ "rawtypes", "unchecked" }) private void start() { try { TNonblockingServerSocket socket = new TNonblockingServerSocket(PORT); final StatQueryService.Processor processor = new StatQueryService.Processor(new StatQueryServiceImpl()); THsHaServer.Args arg = new THsHaServer.Args(socket); /* * Binary coded format efficient, intensive data transmission, The * use of non blocking mode of transmission, according to the size * of the block, similar to the Java of NIO */ arg.protocolFactory(new TCompactProtocol.Factory()); arg.transportFactory(new TFramedTransport.Factory()); arg.processorFactory(new TProcessorFactory(processor)); TServer server = new THsHaServer(arg); server.serve(); } catch (Exception ex) { ex.printStackTrace(); } } public static void main(String[] args) { try { logger.info("start thrift server..."); StatsServer stats = new StatsServer(); stats.start(); } catch (Exception ex) { ex.printStackTrace(); logger.error(String.format("run thrift server has error,msg is %s", ex.getMessage())); } } }
- Client端程式碼:
package cn.rpc.test; import java.util.Map; import org.apache.thrift.protocol.TCompactProtocol; import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.transport.TFramedTransport; import org.apache.thrift.transport.TSocket; import org.apache.thrift.transport.TTransport; import cn.rpc.service.StatQueryService; /** * @Date Mar 23, 2015 * * @Author dengjie * * @Note Test thrift client */ public class StatsClient { public static final String ADDRESS = "127.0.0.1"; public static final int PORT = 9090; public static final int TIMEOUT = 30000; public static void main(String[] args) { if (args.length < 4) { System.out.println("args length must >= 4,current length is " + args.length); System.out.println("<info>****************</info>"); System.out.println("ADDRESS,beginDate,endDate,kpiCode,..."); System.out.println("<info>****************</info>"); return; } TTransport transport = new TFramedTransport(new TSocket(args[0], PORT, TIMEOUT)); TProtocol protocol = new TCompactProtocol(transport); StatQueryService.Client client = new StatQueryService.Client(protocol); String beginDate = args[1]; // "20150308" String endDate = args[2]; // "20150312" String kpiCode = args[3]; // "login_times" String userName = ""; int areaId = 0; String type = ""; String fashion = ""; try { transport.open(); Map<String, String> map = client.queryConditionDayKPI(beginDate, endDate, kpiCode, userName, areaId, type, fashion); System.out.println(map.toString()); } catch (Exception e) { e.printStackTrace(); } finally { transport.close(); } } }
- StatQueryService類:
這個類的程式碼量太大,暫不貼出。需要的同學請到以下地址下載。
下載地址:git@gitlab.com:dengjie/Resource.git
- StatQueryServiceImpl類:
下面實現其中一個函式的內容,程式碼如下所示:
package cn.rpc.service.impl; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.thrift.TException; import cn.rpc.conf.ConfigureAPI; import cn.rpc.dao.KpiDao; import cn.rpc.domain.ReportParam; import cn.rpc.domain.ReportResult; import cn.rpc.service.StatQueryService; import cn.rpc.util.MapperFactory; /** * @Date Mar 23, 2015 * * @Author dengjie */ public class StatQueryServiceImpl implements StatQueryService.Iface { public Map<String, String> queryDayKPI(String beginDate, String endDate, String kpiCode) throws TException { return null; } public Map<String, String> queryConditionDayKPI(String beginDate, String endDate, String kpiCode, String userName, int areaId, String type, String fashion) throws TException { Map<String, String> res = new HashMap<String, String>(); ReportParam param = new ReportParam(); param.setBeginDate(beginDate + ""); param.setEndDate(endDate + ""); param.setKpiCode(kpiCode); param.setUserName(userName == "" ? null : userName); param.setDistrictId(areaId < 0 ? 0 : areaId); param.setProductStyle(fashion == "" ? null : fashion); param.setCustomerProperty(type == "" ? null : type); List<ReportResult> chart = ((KpiDao) MapperFactory.createMapper(KpiDao.class)).getChartAmount(param); Map<String, Integer> title = ((KpiDao) MapperFactory.createMapper(KpiDao.class)).getTitleAmount(param); List<Map<String, Integer>> tableAmount = ((KpiDao) MapperFactory.createMapper(KpiDao.class)) .getTableAmount(param); String avgTime = kpiCode.split("_")[0]; param.setKpiCode(avgTime + "_avg_time"); List<Map<String, Integer>> tableAvgTime = ((KpiDao) MapperFactory.createMapper(KpiDao.class)) .getTableAmount(param); res.put(ConfigureAPI.RESMAPKEY.CHART, chart.toString()); res.put(ConfigureAPI.RESMAPKEY.TITLE, title.toString()); res.put(ConfigureAPI.RESMAPKEY.TABLEAMOUNT, tableAmount.toString()); res.put(ConfigureAPI.RESMAPKEY.TABLEAVG, tableAvgTime.toString()); return res; } public Map<String, String> queryDetail(String beginDate, String endDate, String userName) throws TException { // TODO Auto-generated method stub return null; } }
4.Hadoop V2的RPC簡述
Hadoop V2中的RPC採用的是自己獨立開發的協議,其核心內容包含服務端,客戶端,互動協議。原始碼內容都在hadoop-common-project專案的org.apache.hadoop.ipc包下面。
- VersionedProtocol類:
package org.apache.hadoop.ipc; import java.io.IOException; /** * Superclass of all protocols that use Hadoop RPC. * Subclasses of this interface are also supposed to have * a static final long versionID field. */ public interface VersionedProtocol { /** * Return protocol version corresponding to protocol interface. * @param protocol The classname of the protocol interface * @param clientVersion The version of the protocol that the client speaks * @return the version that the server will speak * @throws IOException if any IO error occurs */ public long getProtocolVersion(String protocol, long clientVersion) throws IOException; /** * Return protocol version corresponding to protocol interface. * @param protocol The classname of the protocol interface * @param clientVersion The version of the protocol that the client speaks * @param clientMethodsHash the hashcode of client protocol methods * @return the server protocol signature containing its version and * a list of its supported methods * @see ProtocolSignature#getProtocolSignature(VersionedProtocol, String, * long, int) for a default implementation */ public ProtocolSignature getProtocolSignature(String protocol, long clientVersion, int clientMethodsHash) throws IOException; }
該類中的兩個方法一個是作為版本,另一個作為簽名用。
- RPC下的Server類:
/** An RPC Server. */ public abstract static class Server extends org.apache.hadoop.ipc.Server { boolean verbose; static String classNameBase(String className) { String[] names = className.split("\\.", -1); if (names == null || names.length == 0) { return className; } return names[names.length-1]; }
對外提供服務,處理Client端的請求,並返回處理結果。
至於Client端,監聽Server端的IP和PORT,封裝請求資料,並接受Response。
5.總結
這篇部落格贅述了RPC的相關內容,讓大家先熟悉一下RPC的相關機制和流程,並簡述了Hadoop V2的RPC機制,關於Hadoop V2的RPC詳細內容會在下一篇部落格中給大家分享。這裡只是讓大家先對Hadoop V2的RPC機制有個初步的認識。
6.結束語
這篇部落格就和大家分享到這裡,如果大家在研究學習的過程當中有什麼問題,可以加群進行討論或傳送郵件給我,我會盡我所能為您解答,與君共勉!