手写实现RPC框架基础功能

随着微服务、分布式的流行,基本复杂点的项目都会涉及到远程调用,最基础的可以使用http来实现调用,也可以通过一些RPC框架来实现,比如Ailiaba 的dubbo,Thrift等。那么作为一个rpc框架,需要具备哪些基本的元素呢?

  • 网络通讯 在动态代理的实现过程中肯定是需要进行远程调用来获取相应的执行结果的,可以使用Java中的socket或者使用Netty。
  • 序列化和反序列化  数据需要通过网络传输,所以对象都要求可以被序列化和反序列化,常用的有java的Serializable,json(Gson,fastjson等),Protobuf等 这里就直接使用java自带的序列化接口
    不了解序列化的可以参考这篇博客 Java中的序列化和反序列化

当然,如果功能更加完善,一个优秀的rpc框架往往还会具备注册中心,负载均衡等功能,这里就先不做要求了

调用方式 : 在客户端引入服务端接口的jar包,直接调用接口就可以实现远程调用

分析:
因为客户端调用服务端api后,最后需要调用到服务端具体接口的具体方法,那么
1)需要通过动态代理来创建接口的代理类(接口无法实例化直接调用);
2)请求参数里就要求有类信息,方法名,参数集合,这样服务端才能通过反射来调用
3)InvocationHandler的实现类里的invoke具体实现应该是socket调用,这样才能在执行接口方法的时候进行socket通讯

我把这个rpc实现分为两部分,一部分是rpc-server服务端实现,另一部分是rpc-client实现
rpc-server又分为两部分 rpc-server-api(提供jar包给rpc-client调用)和rpc-server-provider

rpc-server-api
手写实现RPC框架基础功能_第1张图片

/**
 * 2020/3/7
 * created by chenpp
 * 定义接口,将rpc-server-api打包提供给client端使用,
 * client只要调用对应的接口方法就可以远程调用服务端的具体实现
 * 
 */
public interface IUserService {

    public void saveUser(User user);

    public User getUserById(Integer id);
}
/**
 * 2020/3/7
 * created by chenpp
 * 请求rpc时需要的参数
 */
public class RpcRequest implements Serializable {

    private String className;
    private String methodName;
    private Object[] args;

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }
    ...
}

rpc-server-provider

手写实现RPC框架基础功能_第2张图片

/**
 * 2020/3/7
 * created by chenpp
 * 远程调用的服务端入口,使用socket监听
 */
public class RpcServer {

    private static final ExecutorService executor = Executors.newCachedThreadPool();

    /**
     * 注册服务实例,服务注册后,其他客户端通过接口调用就可以调用服务端的实现
     * */
    public void register(Object service ,int port)  {
        ServerSocket serverSocket = null;
        try {
            //创建socket
            serverSocket = new ServerSocket(port);
            while(true){
                //监听端口,是个阻塞的方法
                Socket socket = serverSocket.accept();
                //处理rpc请求,这里使用线程池来处理
                executor.submit(new HandleThread(service,socket));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if(serverSocket != null){
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}
package com.chenpp.server;

import com.chenpp.request.RpcRequest;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.Socket;

/**
 * 2020/3/7
 * created by chenpp
 * 处理RPC请求的线程
 */
public class HandleThread implements Runnable {

    private Socket socket;

    private Object serviceInstance;

    public HandleThread(Object serviceInstance, Socket socket) {
        this.socket  = socket;
        this.serviceInstance = serviceInstance;
    }

    public void run() {
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        try {
            //从socket中获取RPC请求
            ois = new ObjectInputStream(socket.getInputStream());
            RpcRequest rpcRequest = (RpcRequest) ois.readObject();
            Object result = invoke(rpcRequest);
            oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(result);
            oos.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (oos != null) {
                try {
                    ois.close();
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private Object invoke(RpcRequest rpcRequest) {
        String className = rpcRequest.getClassName();
        Object result = null;
        try {
            Class clazz = Class.forName(className);
            //这里无法实例化,因为传入的是接口类型,接口无法实力哈
            Object[] parameters = rpcRequest.getArgs();
            if (parameters == null) {
                Method method = clazz.getMethod(rpcRequest.getMethodName());
                result = method.invoke(serviceInstance);
            } else {
                Class[] types = new Class[parameters.length];
                for (int i = 0; i < parameters.length; i++) {
                    types[i] = parameters[i].getClass();
                }
                Method method = clazz.getMethod(rpcRequest.getMethodName(), types);
                result = method.invoke(serviceInstance, parameters);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
}
/**
 * 2020/3/7
 * created by chenpp
 * 启动类,启动rpc服务端
 */
public class StartServer {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        RpcServer rpcServer = new RpcServer();
        rpcServer.register(userService,8080);
    }
}

rpc-client

手写实现RPC框架基础功能_第3张图片

public class StartClient {

    public static void main(String[] args) {

        //由于rpc-server-api里只有实体类和接口类,想要实例化只能通过代理来实现
        IUserService userService = RpcProxy.getInstance(IUserService.class,"localhost",8080);
        User user = new User();
        user.setAge(12);
        user.setName("chenpp");
        userService.saveUser(user);
        User user1 = userService.getUserById(1);
        System.out.println(user1.getName()+","+user1.getAge());

    }
}
/**
 * 2020/3/7
 * created by chenpp
 * 创建代理对象
 */
public class RpcProxy {

    public static  T getInstance(Class classInterface, String host, int port) {
        return (T) Proxy.newProxyInstance(classInterface.getClassLoader(), new Class[]{classInterface}, new RpcInvocationHandler(host, port));
    }
}
/**
 * 2020/3/7
 * created by chenpp
 */
public class RpcInvocationHandler implements InvocationHandler {

    private String host;
    private int port;

    public RpcInvocationHandler(String host,int port){
        this.host = host;
        this.port = port;
    }

    /**
     * 增强的InvocationHandler,接口调用方法的时候实际是调用socket进行传输
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        
        //将远程调用需要的接口类、方法名、参数信息封装成RPCRequest
        RpcRequest rpcRequest = new RpcRequest();
        rpcRequest.setArgs(args);
        rpcRequest.setClassName(method.getDeclaringClass().getName());
        rpcRequest.setMethodName(method.getName());

        //通过socket发送RPCRequest给服务端并获取结果返回
        Socket socket= new Socket(host,port);
        ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
        oos.writeObject(rpcRequest);
        ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
        Object result = ois.readObject();
        return result;
    }
}

因为接口无法实例化,所以对于每个实现类我都自己手动创建了一个实例对象并发布,可以考虑引入spring来优化对bean的管理,并通过注解来实现服务的发布

/**
 * 2020/3/7
 * created by chenpp
 * 引入Component注解,加了RegisterService注解的类都会被Spring容器管理
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface RegisterService {
    String interfaceClass() ;
}
/**
 * 实现了InitializingBean接口,那么会在对应的AutoRpcServer实例化之后调用afterPropertiesSet方法
 * 而实现了ApplicationContextAware接口,当spring容器初始化的时候,会自动的将ApplicationContext注入进来,
 * 使得当前bean可以获得ApplicationContext上下文
 * */
@Component
public class AutoRpcServer implements ApplicationContextAware, InitializingBean {

    private int port;

    public AutoRpcServer(int port){
        this.port = port;
    }

    private static final ExecutorService executor = Executors.newCachedThreadPool();

    //key 为对应的接口类名,valeu 为具体的实例
    private Map map = new HashMap();

    public void afterPropertiesSet() throws Exception {
        ServerSocket serverSocket = null;
        try {
            //创建socket
            serverSocket = new ServerSocket(port);
            while(true){
                //监听端口,是个阻塞的方法
                Socket socket = serverSocket.accept();
                //处理rpc请求,这里使用线程池来处理
                executor.submit(new HandleThread(map,socket));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if(serverSocket != null){
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    public void setApplicationContext(ApplicationContext context) throws BeansException {
        //从spring上下文中获取添加了RegisterService的注解的bean
        String[] beanNames = context.getBeanNamesForAnnotation(RegisterService.class);
        for(String beanName : beanNames){
           Object bean =  context.getBean(beanName);
            RegisterService annotation = bean.getClass().getAnnotation(RegisterService.class);
            Class interfaceClass = annotation.interfaceClass();
            //将接口的类名和对应的实例bean对应起来
            map.put(interfaceClass.getName(),bean);
        }
    }
}
/**
 * 2020/3/7
 * created by chenpp
 * 扫描com.chenpp.impl包下的类,注入spring容器
 */
@Component
@ComponentScan("com.chenpp.impl")
public class SpringConfig {
     
  
    //把autoRpcServer bean注入spring容器,初始化完成后会触发InitializingBean接口的afterPropertiesSet调用
    @Bean
    public AutoRpcServer autoRpcServer(){
        //这里直接写死启动的端口 : 8080
        return new AutoRpcServer(8080);
    }
}
/**
 * 2020/3/7
 * created by chenpp
 * 启动类,通过注解方式启动rpc服务端
 */
public class StartServer {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        ((AnnotationConfigApplicationContext) applicationContext).start();
    }
}

GitHub源码地址:https://github.com/dearfulan/cp-rpc

参考:https://www.jianshu.com/p/775d49b30567

你可能感兴趣的:(分布式)