首頁 Java java教程 Java 手寫一個RPC框架

Java 手寫一個RPC框架

Jun 17, 2020 pm 05:30 PM
java

Java 手寫一個RPC框架

RPC框架稱為遠端呼叫框架,其實現的核心原理就是消費者端使用動態代理來代理一個介面的方法(基於JDK的動態代理,當然如果使用CGLib可以直接使用無介面類別的方法),透過加入網路傳輸編程,傳輸呼叫介面方法名稱,方法參數來給提供者獲取,再透過反射,來執行該介面的方法,再將反射執行的結果透過網路程式傳回消費者端。

現在我們來依序實作這些概念。這裡我們做最簡單的實作,網路程式設計使用的是BIO,大家可以使用Reactor模式的Netty來改寫效能更好的方式。而網路傳輸所使用的序列化和反序列化也是Java自帶的,當然這樣的傳輸位元組比較大,可以用google的protoBuffer或是kryo來處理。 這裡只為了方便說明原理。

pom

<?xml version="1.0" encoding="UTF-8"?>
<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>com.guanjian</groupId>
    <artifactId>rpc-framework</artifactId>
    <version>1.0-SNAPSHOT</version>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

首先當然是我們要進行遠端呼叫的介面以及介面的方法。

public interface HelloService {
    String sayHello(String content);}

介面實作類別

public class HelloServiceImpl implements HelloService {    public String sayHello(String content) {        return "hello," + content;    }
}

消費者端的動態代理,如果你是把提供者和消費者寫在兩個工程中,則提供者端需要上面的接口和實作類,而消費者端只需要上面的介面。

public class ConsumerProxy {
    /**
     * 消费者端的动态代理
     * @param interfaceClass 代理的接口类
     * @param host 远程主机IP
     * @param port 远程主机端口
     * @param <T>
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T consume(final Class<T> interfaceClass,final String host,final int port) {
        return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),
                new Class[]{interfaceClass}, (proxy,method,args) -> {
                    //创建一个客户端套接字
                    Socket socket = new Socket(host, port);
                    try {
                        //创建一个对外传输的对象流,绑定套接字
                        ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
                        try {
                            //将动态代理的方法名写入对外传输的对象流中
                            output.writeUTF(method.getName());
                            //将动态代理的方法的参数写入对外传输的对象流中
                            output.writeObject(args);
                            //创建一个对内传输的对象流,绑定套接字
                            //这里是为了获取提供者端传回的结果
                            ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
                            try {
                                //从对内传输的对象流中获取结果
                                Object result = input.readObject();
                                if (result instanceof Throwable) {
                                    throw (Throwable) result;
                                }
                                return result;
                            } finally {
                                input.close();
                            }
                        } finally {
                            output.close();
                        }
                    } finally {
                        socket.close();
                    }
                }
        );
    }
}

有關JDK動態代理的內容可以參考AOP原理與自實現,BIO的部分可以參考傳統IO與NIO比較

提供者端的網路傳輸與遠端方式呼叫服務

public class ProviderReflect {
    private static final ExecutorService executorService = Executors.newCachedThreadPool();

    /**
     * RPC监听和远程方法调用
     * @param service RPC远程方法调用的接口实例
     * @param port 监听的端口
     * @throws Exception
     */
    public static void provider(final Object service,int port) throws Exception {
        //创建服务端的套接字,绑定端口port
        ServerSocket serverSocket = new ServerSocket(port);
        while (true) {
            //开始接收客户端的消息,并以此创建套接字
            final Socket socket = serverSocket.accept();
            //多线程执行,这里的问题是连接数过大,线程池的线程数会耗尽
            executorService.execute(() -> {
                try {
                    //创建呢一个对内传输的对象流,并绑定套接字
                    ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
                    try {
                        try {
                            //从对象流中读取接口方法的方法名
                            String methodName = input.readUTF();
                            //从对象流中读取接口方法的所有参数
                            Object[] args = (Object[]) input.readObject();
                            Class[] argsTypes = new Class[args.length];
                            for (int i = 0;i < args.length;i++) {
                                argsTypes[i] = args[i].getClass();

                            }
                            //创建一个对外传输的对象流,并绑定套接字
                            //这里是为了将反射执行结果传递回消费者端
                            ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
                            try {
                                Class<?>[] interfaces = service.getClass().getInterfaces();
                                Method method = null;
                                for (int i = 0;i < interfaces.length;i++) {
                                    method = interfaces[i].getDeclaredMethod(methodName,argsTypes);
                                    if (method != null) {
                                        break;
                                    }
                                }
                                Object result = method.invoke(service, args);
                                //将反射执行结果写入对外传输的对象流中
                                output.writeObject(result);
                            } catch (Throwable t) {
                                output.writeObject(t);
                            } finally {
                                output.close();
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        } finally {
                            input.close();
                        }
                    } finally {
                        socket.close();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
    }
}

啟動提供者端的網路偵聽和遠端呼叫

public class RPCProviderMain {
    public static void main(String[] args) throws Exception {
        HelloService service = new HelloServiceImpl();
        ProviderReflect.provider(service,8083);
    }
}

啟動消費者的動態代理呼叫

public class RPCConsumerMain {
    public static void main(String[] args) throws InterruptedException {
        HelloService service = ConsumerProxy.consume(HelloService.class,"127.0.0.1",8083);
        for (int i = 0;i < 1000;i++) {
            String hello = service.sayHello("你好_" + i);
            System.out.println(hello);
            Thread.sleep(1000);
        }
    }
}

運行結果

hello,你好_0
hello,你好_1
hello,你好_2
hello,你好_3
hello,你好_4
hello,你好_5

.....

如果你要擴充成一個Netty ProtoBuffer的高效能RPC框架可以參考Netty整合Protobuffer 的相關寫法。

推薦教學:《PHP

以上是Java 手寫一個RPC框架的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Stock Market GPT

Stock Market GPT

人工智慧支援投資研究,做出更明智的決策

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

UC瀏覽器如何強制縮放網頁_UC瀏覽器網頁強制縮放功能使用技巧 UC瀏覽器如何強制縮放網頁_UC瀏覽器網頁強制縮放功能使用技巧 Sep 24, 2025 pm 04:54 PM

首先啟用UC瀏覽器內置縮放功能,進入設置→瀏覽設置→字體與排版或頁面縮放,選擇預設比例或自定義百分比;其次可通過雙指張開或捏合手勢強制調整頁面顯示大小;對於限制縮放的網頁,可請求桌面版網站以解除限制;高級用戶還可通過在地址欄執行JavaScript代碼修改viewport屬性,實現更靈活的強制縮放效果。

如何在Java中獲取通話方法的名稱? 如何在Java中獲取通話方法的名稱? Sep 24, 2025 am 06:41 AM

答案是使用Thread.currentThread().getStackTrace()獲取調用方法名,通過索引2得到調用anotherMethod的someMethod名稱,因索引0為getStackTrace、1為當前方法、2為調用者,示例輸出“Calledbymethod:someMethod”,也可用Throwable實現,但需注意性能、混淆、安全及內聯影響。

如何使用可選類避免Java中的NullPoInterException? 如何使用可選類避免Java中的NullPoInterException? Sep 25, 2025 am 06:04 AM

Optional類用於安全地處理可能為null的值,避免空指針異常。 1.使用Optional.ofNullable創建實例,可處理null值。 2.通過isPresent或ifPresent安全檢查和訪問值,避免直接調用get導致異常。 3.利用orElse、orElseGet提供默認值,或使用orElseThrow拋出自定義異常。 4.通過map和filter鍊式操作轉換或過濾值,提升代碼可讀性和健壯性。

如何在Java中獲得對象的類? 如何在Java中獲得對象的類? Sep 26, 2025 am 04:58 AM

使用getClass()方法可獲取對象的運行時類,如str.getClass()返回Class對象;對於類型可直接使用String.class語法。 Class類提供getName()、getSimpleName()等方法獲取類信息,例如num.getClass().getSimpleName()輸出Integer。

如何在Java中使用字符串上的替換方法? 如何在Java中使用字符串上的替換方法? Sep 24, 2025 am 02:53 AM

ThereplacemethodinJavareturnsanewstringwithalloccurrencesofspecifiedcharactersorsequencesreplaced.Ithastwoforms:oneforreplacingsinglecharactersandanotherforsubstrings.Sincestringsareimmutable,theoriginalremainsunchanged.Forexample,"helloworld&qu

如何在Java中創建多維數組? 如何在Java中創建多維數組? Sep 25, 2025 am 05:37 AM

atwo-dimensionalarayinjavaisanarrayofarrays,宣布Withtwobrackets,例如[] [] [] [] m atrix,and canbeinitializedwithvaluesorusisionnew; forexample,int [] [] [] [] [] [] matrix = {{1,2},{1,2},{3,4}}}}; createSa3x2matrix。

如何在Java中使用揮發性關鍵字進行線程安全性? 如何在Java中使用揮發性關鍵字進行線程安全性? Sep 24, 2025 am 05:15 AM

volatile確保多線程環境下變量的可見性,適用於簡單標誌位如boolean開關,不適用複合操作;其通過禁止指令重排序並建立happens-before關係,保證寫操作對其他線程立即可見,但不提供原子性,需配合Atomic類或鎖處理複雜並發。

如何在Java中獲取當前的工作目錄? 如何在Java中獲取當前的工作目錄? Sep 26, 2025 am 05:51 AM

thecurrentworkingdirectoryinjavacanbeobtainedusystem.getProperty(“ user.dir”),whoturnsthearsthearstheasthearstheabsolutepathwherethetheretheprogramwaslaunched; or of paths.gets.gets.get(“”)。 toabsolutepath(“)

See all articles