Skip to content

feat(basicRPC): implement basic RPC framework using dynamic proxy and invoke method #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 0 additions & 91 deletions .github/workflows/codeql.yml

This file was deleted.

99 changes: 99 additions & 0 deletions docs/BasicRPC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# BasicRPC Implementation Overview
This document provides an overview of the basic RPC (Remote Procedure Call) framework, focusing on the client-server architecture, core components, and the benefits of using a thread pool in the server for handling requests efficiently.

## Table of Contents
1. [Introduction](#introduction)
2. [Architecture Overview](#architecture-overview)
3. [Core Components](#core-components)
4. [Workflow](#workflow)
5. [Benefits of Using a Thread Pool](#benefits-of-using-a-thread-pool)
6. [Future Enhancements](#future-enhancements)

---

### 1. Introduction

This RPC framework allows a client to remotely invoke methods on a server as though they were local method calls. The client-server communication is abstracted, so the client does not need to handle the underlying network details. This document provides an in-depth look at the client and server components, and explains how a thread pool enhances the server’s ability to handle multiple client requests efficiently.

### 2. Architecture Overview

The system follows a client-server architecture:
- **Client Proxy**: Intercepts client method calls, converts them into RPC requests, and sends them to the server.
- **Server**: Listens for client requests and handles them using a thread pool to improve concurrency.
- **Transport Layer**: Manages data exchange between the client and server using sockets.

![Architecture Diagram](https://raw.githubusercontent.com/HeZephyr/NewPicGoLibrary/main/img/image-20241026223521520.png)

### 3. Core Components

#### 3.1 `ClientProxy`
Located at: `src/main/java/rpc/basic/client/proxy/ClientProxy.java`

- **Purpose**: Acts as a dynamic proxy on the client side, intercepting calls to interface methods, creating `RpcRequest` objects, and sending them to the server.
- **Key Methods**:
- `invoke`: Captures method details, packages them in an `RpcRequest`, and sends it to the server. Receives and returns the result from the server.
- `getProxy`: Creates a proxy instance for the specified interface, allowing the client to call methods as if they were local.

#### 3.2 `RpcRequest` and `RpcResponse`
Located at: `src/main/java/rpc/basic/common/message`

- **`RpcRequest`**: Encapsulates details of the method being called, including interface name, method name, parameter types, and arguments.
- **`RpcResponse`**: Encapsulates the result of the method call, including any returned data or error status.

#### 3.3 `ThreadPoolRpcServer`
Located at: `src/main/java/rpc/basic/server/impl/ThreadPoolRpcServer.java`

`ThreadPoolRpcServer` is the main server component that uses a thread pool to handle client requests concurrently:
- **Service Provider**: Holds references to server-side implementations of interfaces, allowing the server to invoke the correct service.
- **Thread Pool**: Utilizes `ThreadPoolExecutor` to manage and execute `WorkerThread` instances that handle individual client requests. This approach reduces the overhead of thread creation and destruction.
- **Server Socket**: Listens for incoming client connections and passes them to the thread pool for processing.

#### 3.4 `WorkerThread`
Located at: `src/main/java/rpc/basic/server/work/WorkerThread.java`

Each `WorkerThread`:
- Reads an `RpcRequest` from the client.
- Uses Java reflection to invoke the specified method.
- Writes the result back as an `RpcResponse` to the client.

### 4. Workflow

The following steps describe the overall workflow of the RPC system:

1. **Client-Side Method Invocation**:
- The client calls a method on a proxy object generated by `ClientProxy`. For example, `UserService proxy.getUserById(1);`.

2. **Client Proxy Interception**:
- `ClientProxy` intercepts the call, creates an `RpcRequest`, and sends it to the server via `IOClient`.

3. **Server Request Handling**:
- The `ThreadPoolRpcServer` listens for incoming connections and hands each new connection to a `WorkerThread` from the thread pool.

4. **Request Processing in `WorkerThread`**:
- `WorkerThread` deserializes the `RpcRequest`, finds the target method, and invokes it using reflection. The result is packaged in an `RpcResponse`.

5. **Response Transmission**:
- The `WorkerThread` serializes the `RpcResponse` and sends it back to the client.

6. **Client Receives Result**:
- The client proxy receives the `RpcResponse`, extracts the result, and returns it as the outcome of the original method call.

### 5. Benefits of Using a Thread Pool

The `ThreadPoolRpcServer` class utilizes a thread pool to manage resources efficiently while handling multiple client requests. The advantages include:

- **Improved Performance**: Reusing threads avoids the overhead of frequent thread creation and destruction, leading to faster response times for client requests.
- **Scalability**: Allows the server to handle many concurrent requests by limiting the number of active threads, balancing load and resource usage.
- **Controlled Resource Usage**: Configurable core and maximum pool sizes, along with a request queue, prevent resource exhaustion and stabilize the server under heavy load.
- **Enhanced Stability**: By using a bounded queue and rejecting excess tasks, the thread pool helps the server maintain performance and avoid crashes when overloaded.

### 6. Future Enhancements

To further improve this RPC framework, the following features can be added:

- **Dynamic Thread Pool Sizing**: Adjust thread pool size based on real-time load for more efficient resource use.
- **Graceful Shutdown**: Implement a process that allows active requests to complete before shutting down the server.
- **Load Balancing**: Distribute incoming requests among multiple server instances for better scalability.
- **Monitoring and Metrics**: Add metrics for active threads, queue size, and request processing time to optimize server performance.

---
23 changes: 21 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,24 @@
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

</project>
<dependencies>
<!-- Lombok dependency -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
62 changes: 62 additions & 0 deletions src/main/java/rpc/basic/client/proxy/ClientProxy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package rpc.basic.client.proxy;

import lombok.AllArgsConstructor;
import rpc.basic.client.transport.IOClient;
import rpc.basic.common.message.RpcRequest;
import rpc.basic.common.message.RpcResponse;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
* ClientProxy is a client-side proxy class based on JDK dynamic proxy.
* It implements InvocationHandler to intercept method calls and send RPC requests.
*/
@AllArgsConstructor
public class ClientProxy implements InvocationHandler {

private final String host;
private final int port;

/**
* Intercepts proxy method calls, converts them to RPC requests, sends them to the remote server,
* and returns the result of the remote invocation.
*
* @param proxy the proxy instance
* @param method the method being called
* @param args the arguments passed to the method
* @return the result of the remote method invocation
* @throws Throwable if any exception occurs
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Build the RPC request object, containing interface name, method name, parameters, and parameter types
RpcRequest request = RpcRequest.builder()
.interfaceName(method.getDeclaringClass().getName())
.methodName(method.getName())
.parameters(args)
.paramTypes(method.getParameterTypes())
.build();

// Send the request via IOClient and get the response data
RpcResponse response = IOClient.sendRequest(host, port, request);

// Return the result of the remote invocation
return response.getData();
}

/**
* Creates a proxy instance for the specified interface, allowing method calls
* to be intercepted and processed by the invoke method.
*
* @param clazz the interface class
* @param <T> the type of the interface
* @return a proxy instance of the interface
*/
@SuppressWarnings("unchecked")
public <T> T getProxy(Class<T> clazz) {
// Use JDK dynamic proxy to create a proxy instance for the interface
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, this);
}
}
51 changes: 51 additions & 0 deletions src/main/java/rpc/basic/client/transport/IOClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package rpc.basic.client.transport;

import rpc.basic.common.message.RpcRequest;
import rpc.basic.common.message.RpcResponse;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* IOClient is responsible for handling the low-level communication with the server,
* sending RPC requests, and returning RPC responses.
*/
public class IOClient {

private static final Logger logger = Logger.getLogger(IOClient.class.getName());

/**
* Sends an RPC request to the specified host and port, and returns the RPC response.
*
* @param host the host to connect to
* @param port the port to connect to
* @param request the RPC request to send
* @return the RPC response from the server
*/
public static RpcResponse sendRequest(String host, int port, RpcRequest request) {
try (Socket socket = new Socket(host, port);
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream())) {

// Send the request to the server
outputStream.writeObject(request);
outputStream.flush();

// Read the response from the server and return it
return (RpcResponse) inputStream.readObject();

} catch (IOException e) {
logger.log(Level.SEVERE, "I/O error occurred while sending RPC request", e);
// Return a failure response with a 500 status code and a message for I/O errors
return RpcResponse.fail(500, "I/O error occurred while sending RPC request");
} catch (ClassNotFoundException e) {
logger.log(Level.SEVERE, "Class not found during RPC request deserialization", e);
// Return a failure response with a 400 status code and a message for class not found errors
return RpcResponse.fail(400, "Class not found during RPC request deserialization");
}
}
}
27 changes: 27 additions & 0 deletions src/main/java/rpc/basic/common/message/RpcRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package rpc.basic.common.message;

import java.io.Serial;
import java.io.Serializable;
import lombok.Builder;
import lombok.Data;

/**
* RpcRequest represents a remote procedure call request message.
* It contains details about the method to be invoked on the remote service.
*/
@Data
@Builder
public class RpcRequest implements Serializable {

@Serial
private static final long serialVersionUID = 1L;

// The name of the interface to be invoked, which is used to find the corresponding service
private String interfaceName;
// The name of the method to be invoked
private String methodName;
// The parameters of the method to be invoked
private Object[] parameters;
// The types of the parameters of the method to be invoked
private Class<?>[] paramTypes;
}
Loading