开始尝试搭建一个RPC框架,先从最简单的写起。
RPC(Remote Procedure Call)远程过程调用,是一个节点请求另一个节点提供的服务。
如何实现呢,很简单,创建服务端和客户端,客户端通过网络传输,告诉服务端需要调用的接口,服务端接收到客户端发送来的请求后,执行这个接口的实现类,然后返回结果。这里直接假设客户端知道服务端的地址了,后面再进行更复杂的实现。
后面的代码主要放关键部分。
照着这个原理,首先要有客户端和服务端都能访问的接口,这个接口主要就是返回一些信息。
public interface HelloService {
String hello(HelloObject object);
}
这个对象要通过网络传输,所以要序列化
import lombok.AllArgsConstructor;
import lombok.Data;
import java.io.Serializable;
@Data
@AllArgsConstructor
public class HelloObject implements Serializable {
private Integer id;
private String message;
}
它的实现类
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloServiceImpl implements HelloService {
private static final Logger logger = LoggerFactory.getLogger(HelloServiceImpl.class);
public String hello(HelloObject object) {
logger.info("服务器接收到客户端信息:{}", object.getMessage());
return "客户端接收的返回值,id=" + object.getId();
}
}
对于客户端发送的请求,服务端需要知道一些基本的信息才能正确调用接口
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
@Data
@Builder
public class RPCRequest implements Serializable {
//待调用接口名称
private String interfaceName;
//待调用方法名称
private String methodName;
//调用方法的参数
private Object[] parameters;
//调用方法的参数类型
private Class<?>[] paramTypes;
}
然后服务端执行完毕后,要给客户端返回信息
import lombok.Data;
import java.io.Serializable;
@Data
//服务端调用后返回给客户端的信息
public class RPCResponse<T> implements Serializable {
//响应状态码
private Integer statusCode;
//响应状态补充信息
private String message;
//响应数据
private T data;
构建一个客户端对象,用来处理发送请求
使用try-with-resource来关闭资源
public class RPCClient {
private static final Logger logger = LoggerFactory.getLogger(RPCClient.class);
public Object sendRequest(RPCRequest rpcRequest,String host,int port){
try(Socket socket = new Socket(host,port)){
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(rpcRequest);
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
objectOutputStream.flush();
RPCResponse rpcResponse = (RPCResponse)objectInputStream.readObject();
return rpcResponse.getData();
}
catch (IOException | ClassNotFoundException e) {
logger.error("调用时有错误发生:", e);
throw new RPCException("服务调用失败: ", e);
}
}
}
但通用接口的实现类只有服务端有,客户端并没有。所以客户端没有办法直接生成接口实现类的实例来发送给服务端。这时就可以采用动态代理的方式。
public class RPCClientProxy implements InvocationHandler {
private static final Logger logger = LoggerFactory.getLogger(RPCClientProxy.class);
private String host;
private int port;
public RPCClientProxy(String host, int port) {
this.host = host;
this.port = port;
}
public <T> T getProxy(Class<T> clazz) {
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[]{clazz}, this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
logger.info("调用方法: {}#{}", method.getDeclaringClass().getName(), method.getName());
RPCRequest rpcRequest = RPCRequest.builder()
.interfaceName(method.getDeclaringClass().getName())
.methodName(method.getName())
.parameters(args)
.paramTypes(method.getParameterTypes())
.build();
RPCClient rpcClient = new RPCClient();
return rpcClient.sendRequest(rpcRequest, host, port);
}
}
重写invoke()方法,它在客户端需要调用接口的时候生成一个包含所需参数的请求对象,通过之前所写的发送请求方法来将请求发送出去,然后得到客户端返回的消息。
客户端就基本写好了。
对于服务端,它需要接受服务,保存相关信息。
public interface ServiceRegistry {
<T> void register(T service);
Object getService(String serviceName);
}
其功能为接收到服务时,将服务加入Set集合中,同时构建一个ConcurrentHashMap来保存服务实现的接口名称。(集合方面的知识有些不熟悉,后面巩固一下)
public class ServiceRegistryImpl implements ServiceRegistry{
private static final Logger logger = LoggerFactory.getLogger(ServiceRegistry.class);
private final Map<String,Object> serviceMap = new ConcurrentHashMap<>();
private final Set<String> registeredService = ConcurrentHashMap.newKeySet();
@Override
public synchronized <T> void register(T service) {
String serviceName = service.getClass().getCanonicalName();
if(registeredService.contains(serviceName))
return;
registeredService.add(serviceName);
Class<?>[] interfaces = service.getClass().getInterfaces();
if(interfaces.length == 0){
throw new RPCException(RPCError.SERVICE_NOT_IMPLEMENT_ANY_INTERFACE);
}
for(Class<?> i:interfaces){
serviceMap.put(i.getCanonicalName(),service);
}
logger.info("向接口 {} 注册服务 {}",interfaces,serviceName);
}
@Override
public synchronized Object getService(String serviceName) {
Object service = serviceMap.get(serviceName);
if(service == null)
throw new RPCException(RPCError.SERVICE_NOT_FOUND);
return service;
}
}
之后创建一个线程,这个线程的作用是接收需要提供服务的对象,并将其发送出去
public class RequestHandlerThread implements Runnable{
private static final Logger logger = LoggerFactory.getLogger(RequestHandlerThread.class);
private Socket socket;
private RequestHandler requestHandler;
private ServiceRegistry serviceRegistry;
public RequestHandlerThread(Socket socket, RequestHandler requestHandler, ServiceRegistry serviceRegistry) {
this.socket = socket;
this.requestHandler = requestHandler;
this.serviceRegistry = serviceRegistry;
}
@Override
public void run() {
try (ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream())) {
RPCRequest rpcRequest = (RPCRequest) objectInputStream.readObject();
String interfaceName = rpcRequest.getInterfaceName();
Object service = serviceRegistry.getService(interfaceName);
Object result = requestHandler.handle(rpcRequest, service);
objectOutputStream.writeObject(RPCResponse.success(result));
objectOutputStream.flush();
} catch (IOException | ClassNotFoundException e) {
logger.error("调用或发送时有错误发生:", e);
}
}
}
之后有专门处理的类,通过反射,获取服务需要执行的方法并调用
public class RequestHandler {
private static final Logger logger = LoggerFactory.getLogger(RequestHandler.class);
public Object handle(RPCRequest rpcRequest,Object service){
Object result = null;
try{
result = invokeTargetMethod(rpcRequest, service);
logger.info("服务:{} 成功调用方法:{}", rpcRequest.getInterfaceName(), rpcRequest.getMethodName());
} catch (IllegalAccessException | InvocationTargetException e) {
logger.error("调用或发送时有错误发生:", e);
} return result;
}
private Object invokeTargetMethod(RPCRequest rpcRequest,Object service) throws InvocationTargetException, IllegalAccessException {
Method method;
try {
method = service.getClass().getMethod(rpcRequest.getMethodName(), rpcRequest.getParamTypes());
} catch (NoSuchMethodException e) {
return RPCResponse.fail(ResponseCode.METHOD_NOT_FOUND);
}
return method.invoke(service, rpcRequest.getParameters());
}
}
最后便是服务端的构建,以线程池的方式构建服务端。
前面的准备工作已做好,在创建服务端时,传入一个已注册的服务,便可直接启动。
public class RPCServer {
private static final Logger logger = LoggerFactory.getLogger(RPCServer.class);
private static final int CORE_POOL_SIZE = 5;
private static final int MAXIMUM_POOL_SIZE = 50;
private static final int KEEP_ALIVE_TIME = 60;
private static final int BLOCKING_QUEUE_CAPACITY = 100;
private final ExecutorService threadPool;
private RequestHandler requestHandler = new RequestHandler();
private final ServiceRegistry serviceRegistry;
public RPCServer(ServiceRegistry serviceRegistry){
this.serviceRegistry = serviceRegistry;
BlockingQueue<Runnable> workingQueue = new ArrayBlockingQueue<>(BLOCKING_QUEUE_CAPACITY);
ThreadFactory threadFactory = Executors.defaultThreadFactory();
threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,KEEP_ALIVE_TIME,TimeUnit.SECONDS,workingQueue,threadFactory);
}
public void start(int port) {
try (ServerSocket serverSocket = new ServerSocket(port)) {
logger.info("服务器启动……");
Socket socket;
while((socket = serverSocket.accept()) != null) {
logger.info("消费者连接: {}:{}", socket.getInetAddress(), socket.getPort());
threadPool.execute(new RequestHandlerThread(socket, requestHandler, serviceRegistry));
}
threadPool.shutdown();
} catch (IOException e) {
logger.error("服务器启动时有错误发生:", e);
}
}
}
先启动服务端
public class ServerTest {
public static void main(String[] args) {
HelloService helloService = new HelloServiceImpl();
ServiceRegistry serviceRegistry = new ServiceRegistryImpl();
serviceRegistry.register(helloService);
RPCServer rpcServer = new RPCServer(serviceRegistry);
rpcServer.start(9000);
}
}
然后启动客户端
public class ClientTest {
public static void main(String[] args) {
RPCClientProxy proxy = new RPCClientProxy("127.0.0.1", 9000);
HelloService helloService = proxy.getProxy(HelloService.class);
HelloObject object = new HelloObject(12, "这是客户端传来的消息");
String res = helloService.hello(object);
System.out.println(res);
}
}
简单的实现就完成了。
这一个小项目对反射,线程的知识运用得较多,我也感觉到自己这方面还有很大的空白,需要马上学习。
参考资料
https://blog.csdn.net/qq_40856284/article/details/106972652
https://github.com/Snailclimb/guide-rpc-framework