Hadoop2原始碼分析-RPC機制初識

哥不是小蘿莉發表於2015-04-27

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.結束語

  這篇部落格就和大家分享到這裡,如果大家在研究學習的過程當中有什麼問題,可以加群進行討論或傳送郵件給我,我會盡我所能為您解答,與君共勉!

相關文章