天天看點

【遠端調用架構】如何實作一個簡單的RPC架構(二)實作與使用1. 服務注冊查找中心2. LCRPC服務架構核心部分3. LCRPC服務架構使用

【如何實作一個簡單的RPC架構】系列文章:

【遠端調用架構】如何實作一個簡單的RPC架構(一)想法與設計

【遠端調用架構】如何實作一個簡單的RPC架構(二)實作與使用

【遠端調用架構】如何實作一個簡單的RPC架構(三)優化一:利用動态代理改變使用者服務調用方式

【遠端調用架構】如何實作一個簡單的RPC架構(四)優化二:改變底層通信架構

【遠端調用架構】如何實作一個簡單的RPC架構(五)優化三:軟負載中心設計與實作

第一個優化以及第二個優化修改後的工程代碼可下載下傳資源 如何實作一個簡單的RPC架構

  • 服務注冊查找中心
    • 1 接口設計
      • 11 服務注冊接口
      • 12 服務ip位址清單查詢接口
      • 13 服務資訊查詢接口
    • 2 類設計
      • 21 UML類圖
      • 22 核心代碼
  • LCRPC服務架構核心部分
    • 1 服務架構UML類圖
    • 2 服務調用部分
    • 3 服務釋出部分
  • LCRPC服務架構使用
    • 1 服務釋出
    • 2 服務調用
    • 3 效果

參考【遠端調用架構】如何實作一個簡單的RPC架構(一)想法與設計,對應四個子產品一共建立了四個Java工程,他們分别是:

  • 【ServiceAccessCenter】:一個Java Web應用,服務注冊查找中心
  • 【LCRPC】:LCRPC服務架構的核心部分,最終利用Maven生成一個jar包提供服務釋出以及遠端調用功能
  • 【LCRPCTest】:服務端的測試部分,釋出一個電腦服務
  • 【LCRPCClientTest】:用戶端的測試部分,利用服務釋出者提供的二方包,遠端調用該電腦服務

1. 服務注冊查找中心

一個Java Web應用,服務注冊查找中心

1.1 接口設計

1.1.1 服務注冊接口

  • (1)url:localhost:8080/ServiceAccessCenter/serviceRegistry.do POST請求
  • (2)參數:JSON格式的字元串,如下:
{
"interfaceName":"interfaceName",
"version":"version",
"implClassName":"imlClassName",
"ip":"ip"
}
           

(3)響應結果:true(代表注冊成功)false(代表注冊失敗)

1.1.2 服務ip位址清單查詢接口

  • (1)url:

    localhost:8080/ServiceAccessCenter/queryServiceIPsByID.do?serviceID=interfaceName_version GET請求

  • (2)參數:serviceID 服務的唯一辨別
  • (3)響應結果:該服務對應的ip清單數組/空字元串,如下:
["ip","ip"]
           

1.1.3 服務資訊查詢接口

  • (1)url:

    localhost:8080/ServiceAccessCenter/queryServiceInfoByID.do?serviceID=interfaceName_version GET請求

  • (2)參數:serviceID 服務的唯一辨別
  • (3)響應結果:該服務的所有資訊,如下:
{"interfaceName":"interfaceName","version":"version","implClassName":"imlClassName","ips":["ip","ip"],"ip":"ip"}
           

1.2 類設計

1.2.1 UML類圖

【遠端調用架構】如何實作一個簡單的RPC架構(二)實作與使用1. 服務注冊查找中心2. LCRPC服務架構核心部分3. LCRPC服務架構使用

1.2.2 核心代碼

核心代碼主要是對注冊服務集合的管理,包括增加以及查詢。注意多線程操作的問題。

- (1)描述服務資訊的DO:ServiceInfoDO

package whu.edu.lcrpc.servicecenter.entity;

/**
 * Created by apple on 17/3/26.
 */

import lombok.Data;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 服務的描述資訊,包括:
 * 服務唯一辨別\實作類全限定名\ip位址清單等
 */
@Data
public class ServiceInfoDO {

    private String interfaceName;//服務對應接口名稱
    private String version;//版本号
    private String implClassName;//實作該接口的類
    private Set<String> ips = new HashSet<>();//該服務的位址
    private String ip;//某一次注冊服務的位址
}
           
  • (2)以一個單例類儲存服務資訊的集合:
public class ServicesSingle {
    private static ServicesSingle servicesSingle = null;
    private Map<String,ServiceInfoDO> services = null;

    private ServicesSingle(){
        services = new ConcurrentHashMap<>();
    }

    public static ServicesSingle getServiceSingle(){
        synchronized (ServicesSingle.class){
            if (servicesSingle == null){
                servicesSingle = new ServicesSingle();
            }
        }
        return servicesSingle;
    }


}
           
  • (3)對服務資訊集合操作的接口:
public interface IServiceAccess {
    /**
     * 根據使用者提供的服務資訊,進行服務的注冊
     * @param serviceInfo  要注冊的服務資訊
     * @return
     */
    public boolean serviceRegistry(ServiceInfoDO serviceInfo);

    /**
     * 根據服務的唯一辨別ID查詢服務的位址清單
     * @param serviceID
     * @return
     */
    public Set<String> queryServiceIPsByID(String serviceID);

    /**
     * 根據服務的唯一辨別ID查詢服務的資訊
     * @param serviceID
     * @return
     */
    public ServiceInfoDO queryServiceInfoByID(String serviceID);

}
           
  • (4)接口的實作類:
/**
 * 完成服務的管理操作:注冊\查詢
 */
public class ServiceAccessImpl implements IServiceAccess{
    @Override
    public boolean serviceRegistry(ServiceInfoDO serviceInfo) {
        if (serviceInfo.getInterfaceName() == null || serviceInfo.getInterfaceName().length() == ||
                serviceInfo.getImplClassName() == null || serviceInfo.getImplClassName().length() == ||
                serviceInfo.getVersion() == null || serviceInfo.getVersion().length() == ||
                serviceInfo.getIp() == null || serviceInfo.getIp().length() ==)
            return false;
        String serviceID = serviceInfo.getInterfaceName() + "_" + serviceInfo.getVersion();
        if (ServicesSingle.getServiceSingle().getServices().containsKey(serviceID)){
            ServicesSingle.getServiceSingle().getServices().get(serviceID).getIps().add(serviceInfo.getIp());
        }else {
            serviceInfo.getIps().add(serviceInfo.getIp());
            ServicesSingle.getServiceSingle().getServices().put(serviceID,serviceInfo);
        }
        return true;
    }

    @Override
    public Set<String> queryServiceIPsByID(String serviceID) {
        if (!ServicesSingle.getServiceSingle().getServices().containsKey(serviceID))
            return null;


        return ServicesSingle.getServiceSingle().getServices().get(serviceID).getIps();
    }

    @Override
    public ServiceInfoDO queryServiceInfoByID(String serviceID) {
        if (!ServicesSingle.getServiceSingle().getServices().containsKey(serviceID))
            return null;


        return ServicesSingle.getServiceSingle().getServices().get(serviceID);
    }
}
           

2. LCRPC服務架構核心部分

LCRPC服務架構的核心部分,最終利用Maven生成一個jar包提供服務釋出以及遠端調用功能

整個工程是由maven以及spring開發建構的,是一個Java工程,最終利用maven建構jar包提供使用者使用。

pom依賴配置如下:

<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>whu.edu.lcrpc.</groupId>
  <artifactId>lcrpc-core</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>lcrpc-core</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spring.version>4.3.3.RELEASE</spring.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <!--spring依賴-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <!--gson依賴-->
    <dependency>
      <groupId>com.google.code.gson</groupId>
      <artifactId>gson</artifactId>
      <version>2.2.4</version>
    </dependency>

    <!--lombok依賴-->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.16.10</version>
    </dependency>

    <!--json解析依賴-->
    <dependency>
      <groupId>net.sf.json-lib</groupId>
      <artifactId>json-lib</artifactId>
      <version>2.4</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
           

2.1 服務架構UML類圖

【遠端調用架構】如何實作一個簡單的RPC架構(二)實作與使用1. 服務注冊查找中心2. LCRPC服務架構核心部分3. LCRPC服務架構使用

(看不清可通路服務架構UML類圖)

2.2 服務調用部分

服務調用端提供LCRPCConsumerImpl類給使用者使用,該類中包含兩個必要的屬性變量分别是interfaceNeme(接口名)、version(版本号),用來辨別某一個LCRPCConsumer執行個體對象是對哪一個服務的調用,同時提供方法serviceConsumer,使用者通過傳入要調用的方法名稱以及參數資訊,實作對該接口某一個方法的遠端調用。LCRPCConsumer類使用一個幫助類ConsumerServiceImpl來實作整個調用過程。同時,設計了一些自定義異常,用于在程式出錯時抛出給使用者進行捕獲。

核心代碼

  • (1)類LCRPCConsumerImpl提供使用者使用,主要實作方法的遠端調用
@Data
public class LCRPCConsumerImpl implements ILCRPCConsumer {

    /**
     * 以下兩個變量共同組成服務的唯一辨別
     */
    private String interfaceName;//服務對應接口的全限定名
    private String version;//服務版本号
    private IConsumerService consumerService;//初始化用戶端輔助類

    public LCRPCConsumerImpl(){
        consumerService = new ConsumerServiceImpl();
    }



    @Override
    public Object serviceConsumer(String methodName, List<Object> params) throws LCRPCServiceIDIsIllegal,LCRPCServiceNotFound,LCRPCRemoteCallException {

        //若服務唯一辨別沒有提供,則抛出異常
        if (interfaceName == null || interfaceName.length() == 
                || version == null || version.length() == )
            throw new LCRPCServiceIDIsIllegal();
        //step1. 根據服務的唯一辨別擷取該服務的ip位址清單
        String serviceID = interfaceName + "_" + version;
        Set<String> ips = consumerService.getServiceIPsByID(serviceID);
        if (ips == null || ips.size() == )
            throw new LCRPCServiceNotFound();

        //step2. 路由,擷取該服務的位址,路由的結果會傳回至少一個位址,是以這裡不需要抛出異常
        String serviceAddress = consumerService.getIP(serviceID,methodName,params,ips);

        //step3. 根據傳入的參數,拼裝Request對象,這裡一定會傳回一個合法的request對象,是以不需要抛出異常
        LCRPCRequestDO requestDO = consumerService.getRequestDO(interfaceName,version,methodName,params);

        //step3. 傳入Request對象,序列化并傳入服務端,拿到響應後,反序列化為object對象
        Object result = null;
        try {
            result = consumerService.sendData(serviceAddress,requestDO);
        }catch (Exception e){
            //在服務調用的過程種出現問題
            throw new LCRPCRemoteCallException(e.getMessage());
        }
        if (result == null)throw new LCRPCRemoteCallException(Constant.SERVICEUNKNOWNEXCEPTION);
        //step4. 傳回object對象
        return result;
    }
}
           
  • (2)幫助類 ConsumerServiceImpl,主要實作一些輔助函數,包括服務ip清單的查詢,路由,資料的發送等。
public class ConsumerServiceImpl implements IConsumerService {
    @Override
    public Set<String> getServiceIPsByID(String serviceID) {

        //調用服務注冊查找中心的服務,擷取ip清單
        Set<String> ips = new HashSet<>();
        String url = "http://" + Constant.SERVICEACCESSCENTER_IP + ":" + Constant.SERVICEACCESSCENTER_PORT + "/"
                + Constant.QUERYSERVICEIPSBYID + "?serviceID=" + serviceID;

        Set status = new HashSet<Integer>();
        status.add();
        StringBuilder response = new StringBuilder();
        GetRemoteInfo.getRemoteInfo(url,"GET",null,null,response,status);
        if (response.length() == )return ips;
        ips = new Gson().fromJson(response.toString(),ips.getClass());
        return ips;
    }

    @Override
    public String getIP(String serviceID, String methodName,List<Object> params, Set<String> ips) {
        //可以根據接口\方法\參數進行路由,這裡我們先簡單實作,選出清單的第一個,模拟路由的過程
        String[] temparr = new String[ips.size()];
        ips.toArray(temparr);
        return temparr[];
    }

    @Override
    public LCRPCRequestDO getRequestDO(String interfaceName, String version, String methodName, List<Object> params) {
        LCRPCRequestDO requestDO = new LCRPCRequestDO();
        requestDO.setInterfaceName(interfaceName);
        requestDO.setMethodName(methodName);
        requestDO.setParams(params);
        requestDO.setVersion(version);
        return requestDO;
    }

    @Override
    public Object sendData(String ip,LCRPCRequestDO requestDO) throws IOException, ClassNotFoundException {
        ObjectOutputStream objectOutputStream = null;
        Socket socket = null;
        ObjectInputStream objectInputStream = null;
        try {
            socket = new Socket(ip, Constant.PORT);//向遠端服務端建立連接配接
            objectOutputStream = new ObjectOutputStream(socket.getOutputStream());//獲得輸出流
            objectOutputStream.writeObject(requestDO);//發送序列化結果
            objectOutputStream.flush();
            socket.shutdownOutput();
            //等待響應
            objectInputStream = new ObjectInputStream(socket.getInputStream());//獲得輸入流
            Object result = objectInputStream.readObject();//序列化為Object對象
            objectInputStream.close();
            objectOutputStream.close();
            socket.close();
            return result;
        }catch (Exception e){
            throw e;
        }finally {
            try {
                if(objectInputStream != null)objectInputStream.close();
                if (objectOutputStream != null)objectOutputStream.close();
                if (socket !=null )socket.close();
            } catch (IOException e) {
                e.printStackTrace();
                throw e;
            }

        }


    }
}
           

2.3 服務釋出部分

服務釋出部分,主要包括兩部分,第一是服務的監聽,第二是服務的注冊與處理。服務的監聽,通過開啟一個socket監聽線程ServerThread來實作。當有用戶端請求後,開啟新的處理線程ServerProcessThread,來進行資料的解析、服務的調用、資料響應等操作。提供給使用者使用的是類LCRPCProviderImpl,主要包含四個必要屬性變量(interfaceName、version、implClassName、ip),對要釋出的服務進行描述。該類主要提供的方法是服務的注冊。同時還設計了自定義異常類,用于在出錯時抛出,他們分别是:LCRPCServiceInfoNotComplete(要釋出的服務的資訊不完整)、LCRPCServiceListenFailed(服務監聽失敗)、LCRPCServiceRegistryFailed(服務注冊失敗)。

核心代碼

  • (1)LCRPCProviderImpl提供給使用者使用,用于服務的釋出與注冊
@Data
public class LCRPCProviderImpl implements ILCRPCProvider{

    /**
     * 以下變量為釋出一個服務的必要變量
     */
    private String interfaceName;//服務對應接口的全限定名
    private String version;//服務版本号
    private String implClassName;//實作該服務接口的類
    private String ip;//釋出該服務的位址

    private static boolean isListened = false;//是否已經開啟監聽

    private IProviderService providerService;
    public LCRPCProviderImpl(){
        providerService = new ProviderServiceImpl();


        //開啟服務監聽端口
        if (!isListened ){
            if (providerService.startListen())
            isListened = true;
            else throw new LCRPCServiceListenFailed();
        }
    }

    public void checkInfo(){
        //先判斷服務參數資訊是否完整
        if (interfaceName == null || interfaceName.length() ==  ||
                version == null || version.length() ==  ||
                implClassName == null || implClassName.length() == ||
                ip == null || ip.length() ==)
            throw new LCRPCServiceInfoNotComplete();
    }

    @Override
    public boolean servicePublish() {
        checkInfo();
        //step1. 注冊服務.注冊服務之前先判斷服務是否開啟,若沒有開啟,則首先開啟服務
        synchronized (LCRPCProviderImpl.class){
            if (!isListened){
                if (providerService.startListen())
                    isListened = true;
                else throw new LCRPCServiceListenFailed();
            }
            providerService.serviceregistry(interfaceName,version,implClassName,ip);
        }
        return true;
    }
}
           
  • (2)線程類ServerThread開啟socket監聽,等待用戶端的請求,開啟處理線程進行處理
public class ServerThread implements Runnable{

    @Override
    public void run() {
        try {
            ServerSocket serverSocket = new ServerSocket(Constant.PORT);
            System.out.println("已經開始監聽,可以注冊服務了");
            while (true){
                Socket socket = serverSocket.accept();
                new Thread(new ServerProcessThread(socket)).start();//開啟新的線程進行連接配接請求的處理
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
           
  • (3)服務端連接配接處理線程ServerProcessThread,負責解析用戶端的請求,利用反射對響應服務進行方法調用,将調用結果序列化發送給調用端
public class ServerProcessThread implements Runnable {

    private Socket socket;
    public ServerProcessThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {

        //擷取用戶端的資料,并寫回
        //等待響應
        ObjectInputStream objectInputStream = null;
        ObjectOutputStream objectOutputStream = null;
        try {

            //step1. 将請求資料反序列化為request對象
            objectInputStream = new ObjectInputStream(socket.getInputStream());//獲得輸入流
            LCRPCRequestDO requestDO = (LCRPCRequestDO) objectInputStream.readObject();//序列化為Object對象

            //step2. 擷取服務接口實作類的資訊
            ServiceInfoDO serviceInfoDO = ServicesSingle.getServiceSingle().getServices().get(requestDO.getInterfaceName() + "_" + requestDO.getVersion());

            //step3.利用反射建立對象,調用方法,得到結果
            Class clz = Class.forName(serviceInfoDO.getImplClassName());
            Method methodethod = null;
            Object result = null;
            if (requestDO.getParams() != null && requestDO.getParams().size() > ){
                Class []classes = new Class[requestDO.getParams().size()];
                Object []obj = requestDO.getParams().toArray();
                int i = ;
                for (Object object:requestDO.getParams()){
                    if(object instanceof Integer){
                        classes[i] = Integer.TYPE;
                    }else if(object instanceof Byte){
                        classes[i] = Byte.TYPE;
                    }else if(object instanceof Short){
                        classes[i] = Short.TYPE;
                    }else if(object instanceof Float){
                        classes[i] = Float.TYPE;
                    }else if(object instanceof Double){
                        classes[i] = Double.TYPE;
                    }else if(object instanceof Character){
                        classes[i] = Character.TYPE;
                    }else if(object instanceof Long){
                        classes[i] = Long.TYPE;
                    }else if(object instanceof Boolean){
                        classes[i] = Boolean.TYPE;
                    }else {
                        classes[i] = object.getClass();
                    }

                    i++;
                }
                methodethod = clz.getDeclaredMethod(requestDO.getMethodName(),classes);
                result = methodethod.invoke(clz.newInstance(),obj);
            }else {
                methodethod = clz.getDeclaredMethod(requestDO.getMethodName());
                result = methodethod.invoke(clz.newInstance());
            }

            //step4.将結果序列化,寫回調用端

            objectOutputStream = new ObjectOutputStream(socket.getOutputStream());//獲得輸出流
            objectOutputStream.writeObject(result);//發送序列化結果
            objectOutputStream.flush();
            socket.shutdownOutput();
            socket.close();

        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                if(objectInputStream != null)objectInputStream.close();
                if (objectOutputStream != null)objectOutputStream.close();
                if (socket !=null )socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }
}
           
  • (4)服務釋出的幫助類ProviderServiceImpl
public class ProviderServiceImpl implements IProviderService {
    @Override
    public boolean startListen() {
        new Thread(new ServerThread()).start();
        return true;
    }

    @Override
    public boolean serviceregistry(String interfaceName, String version, String implClassName, String ip) {
        //注冊到服務注冊查找中心的同時也要緩存到記憶體services
        //step1. 注冊到服務中心

        String url = "http://" + Constant.SERVICEACCESSCENTER_IP + ":" + Constant.SERVICEACCESSCENTER_PORT + "/"
                + Constant.SERVICEREGISTRY;
        Map<String,String> headers = new HashMap();
        headers.put("Content-Type","application/json");
        JSONObject param = new JSONObject();
        param.put("interfaceName",interfaceName);
        param.put("version",version);
        param.put("implClassName",implClassName);
        param.put("ip",ip);
        Set status = new HashSet<Integer>();
        status.add();
        StringBuilder response = new StringBuilder();
        GetRemoteInfo.getRemoteInfo(url,"POST",headers,param.toString(),response,status);

        if (response.equals("false")) throw new LCRPCServiceRegistryFailed();

        //step2. 存入到緩存
        if (ServicesSingle.getServiceSingle().getServices().containsKey(interfaceName + "_" + version)){
            ServicesSingle.getServiceSingle().getServices().get(interfaceName + "_" + version).getIps().add(ip);
        }
        else {
            ServiceInfoDO serviceInfoDO = new ServiceInfoDO();
            serviceInfoDO.setInterfaceName(interfaceName);
            serviceInfoDO.setVersion(version);
            serviceInfoDO.setImplClassName(implClassName);
            serviceInfoDO.getIps().add(ip);
            ServicesSingle.getServiceSingle().getServices().put(interfaceName + "_" + version,serviceInfoDO);
        }
        System.out.println("成功注冊服務:[" + interfaceName  + "]");

        return true;
    }


}
           

3. LCRPC服務架構使用

3.1 服務釋出

服務端的測試部分,釋出一個電腦服務;

該部分為一個基本Java應用,采用spring+maven的開發方式。

  • (1)pom檔案如下,包括對LCRPC服務架構提供jar包的依賴、對spring的依賴等
<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>whu.edu.lcrpc</groupId>
  <artifactId>lcrpc-test</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>lcrpc-test</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spring.version>4.3.3.RELEASE</spring.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <!--spring依賴-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
    </dependency>

    <!--lombok依賴-->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.16.10</version>
    </dependency>
    <!--對LCRPC服務架構的依賴-->
    <dependency>
      <groupId>whu.edu.lcrpc</groupId>
      <artifactId>lcrpc-core</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
           
  • (2)spring的配置檔案:

    主要是配置LCRPC的服務釋出端的bean,通過該bean來進行服務的釋出,配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans default-autowire="byName" xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--ILCRPCProvider的bean 進行配置後 自動釋出服務-->
    <bean id="lcrpcProvider" class="whu.edu.lcrpc.server.impl.LCRPCProviderImpl" init-method="servicePublish">
        <property name="implClassName" value="whu.edu.lcrpc.service.impl.CaculatorImpl"></property>
        <property name="version" value="0.1"></property>
        <property name="ip" value="127.0.0.1"></property>
        <property name="interfaceName" value="whu.edu.lcrpc.service.ICaculator"></property>
    </bean>
</beans>
           

其中四個屬性均是必要的。同時init-method的配置也是必要的,該函數通過使用者配置為屬性資訊進行服務的釋出。

如果是在web容器中進行服務的釋出,例如tomcat,則隻需要在web.xml中配置spring的監聽,就可以進行服務的釋出。或者在main函數中利用ApplicationContext進行配置檔案的讀取。

在本例中,為了簡單,采用main函數的方式。代碼如下:

  • (3)main函數
package whu.edu.lcrpc.app;

/**
 * Created by apple on 17/3/26.
 */

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 服務端測試類,調用LCRPC接口,進行服務的釋出
 */
public class ProviderTest {

    public static void main(String[] args) {
        //直接讀取spring的配置檔案就好
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    }
}
           
  • (4)下面就是需要釋出的服務對應的接口,以及接口的實作類。我們為了進行不同情況的測試,是以寫了四個函數,參數和傳回值分别是Java中的基本類型、引用類型。

    接口代碼如下:

package whu.edu.lcrpc.service;

/**
 * Created by apple on 17/3/26.
 */

import whu.edu.lcrpc.entity.MyParamDO;
import whu.edu.lcrpc.entity.MyResultDO;

/**
 * 服務對應接口
 * 該服務實作一個簡單的電腦服務,實作加減乘除四個基本功能
 */
public interface ICaculator {

    /**
     * 加
     * @param n1
     * @param n2
     * @return
     */
    public double add(double n1,double n2);

    /**
     * 減
     * @param n1
     * @param n2
     * @return
     */
    public MyResultDO minus(double n1, double n2);


    /**
     * 乘
     * @param myParamDO
     * @return
     */
    public MyResultDO multiply(MyParamDO myParamDO);

    /**
     * 除
     * @param myParamDO
     * @return
     */
    public double divide(MyParamDO myParamDO);
}
           

接口實作類的代碼如下:

package whu.edu.lcrpc.service.impl;

import whu.edu.lcrpc.entity.MyParamDO;
import whu.edu.lcrpc.entity.MyResultDO;
import whu.edu.lcrpc.service.ICaculator;

/**
 * Created by apple on 17/3/26.
 */

/**
 * 服務對應接口的實作類
 */
public class CaculatorImpl implements ICaculator {
    @Override
    public double add(double n1, double n2) {
        try {
            Thread.sleep();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return n1 + n2;
    }

    @Override
    public MyResultDO minus(double n1, double n2) {
        try {
            Thread.sleep();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        MyResultDO myResultDO = new MyResultDO();
        myResultDO.setResult(n1 - n2);
        return myResultDO;
    }

    @Override
    public MyResultDO multiply(MyParamDO myParamDO) {
        try {
            Thread.sleep();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        MyResultDO myResultDO = new MyResultDO();
        myResultDO.setResult(myParamDO.getN1() * myParamDO.getN2());
        return myResultDO;
    }

    @Override
    public double divide(MyParamDO myParamDO) {
        try {
            Thread.sleep();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return myParamDO.getN2() / myParamDO.getN1();
    }
}
           

最終該工程會生成一個二方包提供給服務調用端使用。

3.2 服務調用

用戶端的測試部分,利用LCRPC提供的包以及服務釋出者提供的二方包,遠端調用該電腦服務。

  • (1)pom檔案
<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>whu.edu.lcrpc</groupId>
  <artifactId>lcrpc-clienttest</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>lcrpc-clienttest</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spring.version>4.3.3.RELEASE</spring.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <!--spring依賴-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
    </dependency>

    <!--lombok依賴-->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.16.10</version>
    </dependency>

    <!--對服務釋出者二方包的依賴-->
    <dependency>
      <groupId>whu.edu.lcrpc</groupId>
      <artifactId>lcrpc-test</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    <!--對LCRPC服務架構的依賴-->
    <dependency>
      <groupId>whu.edu.lcrpc</groupId>
      <artifactId>lcrpc-core</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>

  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
           
  • (2)spring配置檔案,主要是配置LCRPC提供的用戶端接口,利用該bean,進行方法的調用,每一個consumer的bean代表對一個服務接口的調用
<?xml version="1.0" encoding="UTF-8"?>
<beans default-autowire="byName" xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="rpcConsumer" class="whu.edu.lcrpc.server.impl.LCRPCConsumerImpl">
        <property name="interfaceName" value="whu.edu.lcrpc.service.ICaculator" ></property>
        <property name="version" value="0.1"></property>
    </bean>
    <bean id="ConsumerTest" class="whu.edu.lcrpc.app.ConsumerTest"></bean>


</beans>
           

屬性interfaceName以及version是必要的,辨別該bean是對哪一個遠端服務的調用。

  • (3)服務調用幫助類,在該類中我們利用LCRPC提供的接口,對電腦服務進行調用,分别調用該服務的加減乘除四個方法
package whu.edu.lcrpc.app;

/**
 * Created by apple on 17/3/26.
 */

import lombok.Data;
import whu.edu.lcrpc.entity.MyParamDO;
import whu.edu.lcrpc.entity.MyResultDO;
import whu.edu.lcrpc.server.ILCRPCConsumer;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * 用戶端測試類,調用LCRPC接口,進行服務的調用
 */
@Data
public class ConsumerTest {

    @Resource
    ILCRPCConsumer rpcConsumer;
    public void add() {
        //調用接口 進行加減乘除
        List<Object> params = new ArrayList<>();
        params.add();
        params.add();
        Double result = (Double) rpcConsumer.serviceConsumer("add",params);
        System.out.println("add: " + result);

    }

    public void minus(){
        List<Object> params = new ArrayList<>();
        params.add();
        params.add();
        MyResultDO result = (MyResultDO) rpcConsumer.serviceConsumer("minus",params);
        System.out.println("minus: " + result.getResult());
    }

    public void multiply(){
        List<Object> params = new ArrayList<>();
        MyParamDO myParamDO = new MyParamDO();
        myParamDO.setN1();
        myParamDO.setN2();
        params.add(myParamDO);
        MyResultDO result = (MyResultDO) rpcConsumer.serviceConsumer("multiply",params);
        System.out.println("multiply: " + result.getResult());
    }

    public void divide(){
        List<Object> params = new ArrayList<>();
        MyParamDO myParamDO = new MyParamDO();
        myParamDO.setN1();
        myParamDO.setN2();
        params.add(myParamDO);
        Double result = (Double) rpcConsumer.serviceConsumer("divide",params);
        System.out.println("divide: " + result);
    }
}
           
  • (4)運作主類,開啟四個線程,同時對四個方法進行遠端調用
package whu.edu.lcrpc.app;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created by apple on 17/3/27.
 */
public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        ConsumerTest consumerTest = (ConsumerTest) context.getBean("ConsumerTest");
        new Thread(()->{
            consumerTest.add();
        }).start();
        new Thread(()->{
            consumerTest.minus();
        }).start();
        new Thread(()->{
            consumerTest.multiply();
        }).start();
        new Thread(()->{
            consumerTest.divide();
        }).start();
    }
}
           

3.3 效果

  • (1)首先開啟服務注冊查找中心
  • (2)服務測試端,運作main函數進行服務的釋出,運作後,服務釋出成功,如下圖所示:
    【遠端調用架構】如何實作一個簡單的RPC架構(二)實作與使用1. 服務注冊查找中心2. LCRPC服務架構核心部分3. LCRPC服務架構使用
  • (3)服務調用端測試,運作main函數進行服務遠端調用,運作後,服務調用成功,如下圖所示,對1,2兩個數字開啟四個線程分别進行了加減乘除運算,四個結果同時輸出:
【遠端調用架構】如何實作一個簡單的RPC架構(二)實作與使用1. 服務注冊查找中心2. LCRPC服務架構核心部分3. LCRPC服務架構使用

注意:所有工程代碼可通路資源:實作自己的遠端調用架構,進行下載下傳

未完待續。。。。這隻是服務架構的初步實作版本,期待優化提升後的第二個版本~

值得期待的是:

(1)怎樣利用動态代理,使得使用者與通路本地接口一樣調用遠端服務

(2)當連接配接增多,服務是否可以支撐?需要改變IO模式。