手寫實現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

/**
 * 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

/**
 * 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

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<T> {

    public static <T> T getInstance(Class<T> 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<String,Object> map = new HashMap<String, Object>();

    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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章