輕量級RPC設計與實現第一版

什么是RPC

RPC (Remote Procedure Call Protocol), 遠程過程調用,通俗的解釋就是:客戶端在不知道調用細節的情況下,調用存在于遠程計算機上的某個對象,就像調用本地應用程序中的對象一樣,不需要了解底層網絡技術的協議。

簡單的整體工作流程
請求端發送一個調用的數據包,該包中包含有調用標識,參數等協議要求的參數。當響應端接收到這個數據包,對應的程序被調起,然后返回結果數據包,返回的數據包含了和請求的數據包中同樣的請求標識,結果等。

性能影響因素

  1. 利用的網絡協議。可以使用應用層協議,例如HTTP或者HTTP/2協議;也可以利用傳輸層協議,例如TCP協議,但是主流的RPC還沒有采用UDP傳輸協議。
  2. 消息封裝格式。選擇或設計一種協議來封裝信息進行組裝發送。比如,dubbo中消息體數據包含dubbo版本號、接口名稱、接口版本、方法名稱、參數類型列表、參數、附加信息等。
  3. 序列化。信息在網絡傳輸中要以二進制格式進行傳輸。序列化和反序列化,是對象到而二進制數據的轉換。常見的序列化方法有JSON、Hessian、Protostuff等。
  4. 網絡IO模型。可以采用非阻塞式同步IO,也可以在服務器上實現對多路IO模型的支持。
  5. 線程管理方式。在高并發請求下,可以使用單個線程運行服務的具體實現,但是會出現請求阻塞等待現象。也可以為每一個RPC具體服務的實現開啟一個獨立的線程運行,最大線程數有限制,可以使用線程池來管理多個線程的分配和調度。

第一版RPC

第一個版本簡單實現了RPC的最基本功能,即服務信息的發送與接收序列化方式動態代理等。
項目利用Springboot來實現依賴注入與參數配置,使用netty實現NIO方式的數據傳輸,使用Hessian來實現對象序列化。
動態代理
這里要提到代理模式,它的特征是代理類與委托類有同樣的接口,代理類主要負責為委托類預處理消息、過濾消息、把消息轉發給委托類,以及事后處理消息等。代理類與委托類之間通常會存在關聯關系。
根據創建代理類的時間點,又可以分為靜態代理和動態代理。
在以往的靜態代理中需要手動為每一個目標編寫對應的代理類。如果系統已經有了成百上千個類,工作量太大了。
靜態代理由程序員創建或特定工具自動生成源代碼,也就是在編譯時就已經將接口與被代理類,代理類等確定下來。在程序運行之前,代理類的.class文件就已經生成。
代理類在程序運行時創建的代理方式被稱為代理模式。在靜態代理中,代理類是自己定義好的,在運行之前就已經編譯完成了。而在動態代理中,可以很方便地對代理類的函數進行統一的處理,而不用修改每個代理類中的方法。可以通過InvocationHandler接口來實現。

客戶端的動態代理

public class ProxyFactory {
    public static <T> T create(Class<T> interfaceClass) throws Exception {
        return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<?>[]{interfaceClass}, new LwRpcClientDynamicProxy<T>(interfaceClass));
    }
}
@Slf4j
public class LwRpcClientDynamicProxy<T> implements InvocationHandler {
    private Class<T> clazz;
    public LwRpcClientDynamicProxy(Class<T> clazz) throws Exception {
        this.clazz = clazz;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        LwRequest lwRequest = new LwRequest();
        String requestId = UUID.randomUUID().toString();
        String className = method.getDeclaringClass().getName();
        String methodName = method.getName();
        Class<?>[] parameterTypes = method.getParameterTypes();

        lwRequest.setRequestId(requestId);
        lwRequest.setClassName(className);
        lwRequest.setMethodName(methodName);
        lwRequest.setParameterTypes(parameterTypes);
        lwRequest.setParameters(args);
        NettyClient nettyClient = new NettyClient("127.0.0.1", 8888);
        log.info("開始連接服務器端:{}", new Date());
        LwResponse send = nettyClient.send(lwRequest);
        log.info("請求后返回的結果:{}", send.getResult());
        return send.getResult();
    }
}

在服務端會利用在客戶端獲取到的類名。參數等信息利用反射機制進行調用。

Class<?>[] parameterTypes = request.getParameterTypes();
        Object[] paramethers = request.getParameters();
        // 使用CGLIB 反射
        FastClass fastClass = FastClass.create(serviceClass);
        FastMethod fastMethod = fastClass.getMethod(methodName, parameterTypes);
        return fastMethod.invoke(serviceBean, paramethers);

Netty客戶端

@Slf4j
public class NettyClient  {
    private String host;
    private Integer port;
    private LwResponse response;
    private EventLoopGroup group;
    private ChannelFuture future = null;
    private Object obj = new Object();
    private NettyClientHandler nettyClientHandler;
    public NettyClient(String host, Integer port) {
        this.host = host;
        this.port = port;
    }


    public LwResponse send(LwRequest request) throws Exception{
        nettyClientHandler = new NettyClientHandler(request);
        group = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.SO_KEEPALIVE, true)
                .option(ChannelOption.TCP_NODELAY, true)
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        pipeline.addLast(new LengthFieldBasedFrameDecoder(65535, 0, 4));
                        pipeline.addLast(new LwRpcEncoder(LwRequest.class, new HessianSerializer()));
                        pipeline.addLast(new LwRpcDecoder(LwResponse.class, new HessianSerializer()));
                        pipeline.addLast(nettyClientHandler);
                    }
                });
        future = bootstrap.connect(host, port).sync();
        nettyClientHandler.getCountDownLatch().await();
        this.response = nettyClientHandler.getLwResponse();
        return this.response;
    }

    @PreDestroy
    public void close() {
        group.shutdownGracefully();
        future.channel().closeFuture().syncUninterruptibly();
    }

}
@Slf4j
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
    private final CountDownLatch countDownLatch = new CountDownLatch(1);
    private LwResponse response = null;
    private LwRequest request;

    public NettyClientHandler(LwRequest request) {
        this.request = request;
    }


    public CountDownLatch getCountDownLatch() {
        return countDownLatch;
    }

    public LwResponse getLwResponse() {
        return this.response;
    }
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        log.info("客戶端向客戶端發送消息");
        ctx.writeAndFlush(request);
        log.info("客戶端請求成功");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        LwResponse lwResponse = (LwResponse) msg;
        log.info("收到服務端的信息:{}", lwResponse.getResult());
        this.response = lwResponse;
        this.countDownLatch.countDown();
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.close();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx,
                                Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }

}

在客戶端發送服務信息時,用LwQuest類進行封裝,返回的結果用LwResponse進行封裝,當客戶端讀取到服務器端返回的響應時,在NettyClientHandler中進行處理,并利用CountDownLatch進行線程的阻塞和運行。
Netty服務端

@Component
@Slf4j
public class NettyServer {
    private EventLoopGroup boss = null;
    private EventLoopGroup worker = null;
    @Autowired
    private ServerHandler serverHandler;
    @Value("${server.address}")
    private String address;
    public void start() throws Exception {
        log.info("成功");
        boss = new NioEventLoopGroup();
        worker = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(boss, worker)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            pipeline.addLast(new LengthFieldBasedFrameDecoder(65535, 0, 4));
                            pipeline.addLast(new LwRpcEncoder(LwResponse.class, new HessianSerializer()));
                            pipeline.addLast(new LwRpcDecoder(LwRequest.class, new HessianSerializer()));
                            pipeline.addLast(serverHandler);
                        }
                    });
            String[] strs = address.split(":");
            String addr = strs[0];
            int port = Integer.valueOf(strs[1]);
            ChannelFuture future = serverBootstrap.bind(addr, port).sync();
            future.channel().closeFuture().sync();
        } finally {
            worker.shutdownGracefully();
            boss.shutdownGracefully();
        }
    }

    @PreDestroy
    public void destory() throws InterruptedException {
        boss.shutdownGracefully().sync();
        worker.shutdownGracefully().sync();
        log.info("關閉netty");
    }
}

@Component
@Slf4j
@ChannelHandler.Sharable
public class ServerHandler extends SimpleChannelInboundHandler<LwRequest> implements ApplicationContextAware {
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, LwRequest msg) throws Exception {
        LwResponse lwResponse = new LwResponse();
        lwResponse.setRequestId(msg.getRequestId());
        log.info("從客戶端接收到請求信息:{}", msg);
        try {
            Object result = handler(msg);
            lwResponse.setResult(result);
        } catch (Throwable throwable) {
            lwResponse.setCause(throwable);
            throwable.printStackTrace();

        }
        channelHandlerContext.writeAndFlush(lwResponse);
    }

    private Object handler(LwRequest request) throws ClassNotFoundException, InvocationTargetException {

        Class<?> clazz = Class.forName(request.getClassName());
        Object serviceBean = applicationContext.getBean(clazz);
        Class<?> serviceClass = serviceBean.getClass();
        String methodName = request.getMethodName();
        log.info("獲取到的服務類:{}", serviceBean);
        Class<?>[] parameterTypes = request.getParameterTypes();
        Object[] paramethers = request.getParameters();
        // 使用CGLIB 反射
        FastClass fastClass = FastClass.create(serviceClass);
        FastMethod fastMethod = fastClass.getMethod(methodName, parameterTypes);
        return fastMethod.invoke(serviceBean, paramethers);
    }
}

在Netty服務端中,會利用``serverHandler`來處理從客戶端中接收的信息,并利用反射的思想調用本地的方法,并將處理的結構封裝在LwResponse中。

LwRequestLwRespnse要想在網絡中進行傳輸,需要轉化為二進制轉換。具體方法如下:

public class HessianSerializer implements Serializer {
    @Override
    public byte[] serialize(Object object) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        Hessian2Output output = new Hessian2Output(byteArrayOutputStream);
        output.writeObject(object);
        output.flush();
        return byteArrayOutputStream.toByteArray();
    }

    public <T> T deserialize(Class<T> clazz, byte[] bytes) throws IOException {
        Hessian2Input input = new Hessian2Input(new ByteArrayInputStream(bytes));
        return (T) input.readObject(clazz);
    }
}

public class LwRpcDecoder extends ByteToMessageDecoder {

    private Class<?> clazz;
    private Serializer serializer;

    public LwRpcDecoder(Class<?> clazz, Serializer serializer) {
        this.clazz = clazz;
        this.serializer = serializer;
    }


    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
        if (byteBuf.readableBytes() < 4)
            return;
        byteBuf.markReaderIndex();
        int dataLength = byteBuf.readInt();
        if (dataLength < 0) {
            channelHandlerContext.close();
        }
        if (byteBuf.readableBytes() < dataLength) {
            byteBuf.resetReaderIndex();
        }
        byte[] data = new byte[dataLength];
        byteBuf.readBytes(data);

        Object obj = serializer.deserialize(clazz, data);
        list.add(obj);
    }
}

public class LwRpcEncoder extends MessageToByteEncoder<Object> {
    private Class<?> clazz;
    private Serializer serializer;

    public LwRpcEncoder(Class<?> clazz, Serializer serializer) {
        this.clazz = clazz;
        this.serializer = serializer;
    }

    @Override
    protected void encode(ChannelHandlerContext ctx, Object in, ByteBuf out) throws Exception {
        if (clazz.isInstance(in)) {
            byte[] data = serializer.serialize(in);
            out.writeInt(data.length);
            out.writeBytes(data);
        }

    }

}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370