# 手写RPC框架:极简版

# 思路分析
前面的文章我们详细分析了如何实现一个RPC框架,后面几个章节,我们就自己来写一个RPC框架。框架先实现一个最基本的功能,然后不断迭代,完善这些功能。
首先我们需要封装一个请求对象,包含要调用方法的基本信息,producer端收到这些请求后,找到对应的服务和方法,执行方法调用,将结果返回consumer端。
consumer端组装请求和网络调用的过程交给动态代理对象来完成,这样就可以接耦方法调用和网络请求

因为是极简版,所以本节我们只实现点对点调用的过程,不涉及注册中心的内容
# 封装网络请求对象
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RpcRequest implements Serializable {
private String interfaceName;
private String methodName;
private Class<?>[] paramTypes;
private Object[] parameters;
}
根据interfaceName可以确定需要调用的接口,methodName和paramTypes则可以确定要调用接口的方法名,定位到具体的方法,传入参数即可调用方法
# 封装调用接口
封装接口到api模块,producer端写实现逻辑,consumer端写调用逻辑
public interface HelloService {
String sayHello(String content);
}
public interface UpperCaseService {
String toUpperCase(String content);
}
# 开发producer端
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String content) {
return "hello " + content;
}
}
public class UpperCaseServiceImpl implements UpperCaseService {
@Override
public String toUpperCase(String content) {
return content.toUpperCase();
}
}
ServiceMap保存了producer端接口名和接口实现类的映射关系,这样可以根据请求对象的接口名,找到对应的实现类
public class ServiceMap {
// 接口名 -> 接口实现类
private static Map<String, Object> serviceMap = new HashMap<>();
public static void registerService(String serviceKey, Object service) {
serviceMap.put(serviceKey, service);
}
public static Object lookupService(String serviceKey) {
return serviceMap.get(serviceKey);
}
}
为了提高服务端的并发度,我们将每一个请求的处理过程放到线程池中
@Slf4j
public class RequestHandler implements Runnable {
private Socket socket;
public RequestHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try (ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream())) {
RpcRequest rpcRequest = (RpcRequest) inputStream.readObject();
Object service = ServiceMap.lookupService(rpcRequest.getInterfaceName());
Method method = service.getClass().getMethod(rpcRequest.getMethodName(), rpcRequest.getParamTypes());
Object result = method.invoke(service, rpcRequest.getParameters());
outputStream.writeObject(result);
} catch (Exception e) {
log.error("invoke method error", e);
throw new RuntimeException("invoke method error");
}
}
}
启动服务端
public class RpcProviderMain {
private static final ExecutorService executorService = Executors.newCachedThreadPool();
public static void main(String[] args) throws Exception {
HelloService helloService = new HelloServiceImpl();
UpperCaseService upperCaseService = new UpperCaseServiceImpl();
// 将需要暴露的接口注册到serviceMap中
ServiceMap.registerService(HelloService.class.getName(), helloService);
ServiceMap.registerService(UpperCaseService.class.getName(), upperCaseService);
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
// 获取一个套接字(阻塞)。所以为了并行,来一个请求,开一个线程处理
// 为了复用线程,用了threadPool
final Socket socket = serverSocket.accept();
executorService.execute(new RequestHandler(socket));
}
}
}
# 开发consumer端
前面说过,我们要通过动态代理对象接耦方法调用和网络调用,所以接下来我们就写一下动态代理对象的实现逻辑
生成一个代理对象的过程很简单
- 实现InvocationHandler接口,在invoke方法中增加代理逻辑
- 调用Proxy.newProxyInstance方法生成代理对象,3个参数分别是ClassLoader,代理对象需要实现的接口数组,InvocationHandler接口实现类
- 当执行代理执行实现的接口方法时,会调用到InvocationHandler#invoke,这个方法中增加了代理逻辑哈。
public class ConsumerProxy {
public static <T> T getProxy(final Class<T> interfaceClass, final String host, final int port) {
return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),
new Class<?>[]{interfaceClass}, new ConsumerInvocationHandler(host, port));
}
}
可以看到代理对象的主要功能就是组装请求参数,然后发起网络调用
@Slf4j
public class ConsumerInvocationHandler implements InvocationHandler {
private String host;
private Integer port;
public ConsumerInvocationHandler(String host, Integer port) {
this.host = host;
this.port = port;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try (Socket socket = new Socket(host, port);
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream())) {
RpcRequest rpcRequest = RpcRequest.builder()
.interfaceName(method.getDeclaringClass().getName())
.methodName(method.getName())
.paramTypes(method.getParameterTypes())
.parameters(args).build();
outputStream.writeObject(rpcRequest);
Object result = inputStream.readObject();
return result;
} catch (Exception e) {
log.error("consumer invoke error", e);
throw new RuntimeException("consumer invoke error");
}
}
}
此时我们只需要通过ConsumerProxy#getProxy方法,就能很方便的获取到代理对象。通过代理对象调用远程方法和调用本地方法一样方便
public class RpcConsumerMain {
public static void main(String[] args) {
// 因为这是一个小demo,就不拆分多模块了
// 这个HelloService是通过网络调用的HelloServiceImpl,而不是本地调用
HelloService helloService = ConsumerProxy.getProxy(HelloService.class, "127.0.0.1", 8080);
// hello world
System.out.println(helloService.sayHello("world"));
UpperCaseService upperCaseService = ConsumerProxy.getProxy(UpperCaseService.class, "127.0.0.1", 8080);
// THIS IS CONTENT
System.out.println(upperCaseService.toUpperCase("this is content"));
}
}