基于netty的rpc框架

基于netty的rpc框架

[TOC]

如果你已經(jīng)對以下東東有所了解,那么你就可以完成一個(gè)rpc框架了

  • Java的反射技術(shù)
  • java的動(dòng)態(tài)代理機(jī)制
  • 基于nio的框架netty
  • 全世界最好的框架-spring
  • java的序列化

神馬是rpc?

  • 在這個(gè)大數(shù)據(jù)時(shí)代,很多公司的服務(wù)器都是以集群的方式存在的。在我們傳統(tǒng)的mvc后臺開發(fā)中,我們就需要把不同層的服務(wù)部署到不同的服務(wù)器上面,這個(gè)每個(gè)服務(wù)器的的壓力就會比較小了。

    但是這樣也會帶來一個(gè)問題——我在這臺機(jī)器上如何才能調(diào)用到另一臺機(jī)器的代碼呢?這是個(gè)問題。

    我們先來舉個(gè)栗子:

    這里寫圖片描述

    比如我們在一個(gè)傳統(tǒng)mvc項(xiàng)目中,我們有一個(gè)UserController處理用戶的請求,假如它長的這個(gè)樣子:

    @RestController
    @RequestMapping(value = "/user")
    public class UserController {
    
        @Autowired
        UserService userService;
    
        @RequestMapping(value = "/current")
        public AjaxResponse login(String name,String password) {
            return AjaxResponse.success(userService.login(name,password));
        }
    }
    

    正常情況下,這個(gè)UserService的實(shí)現(xiàn)肯定是在同一個(gè)項(xiàng)目或者是本地的,早就已經(jīng)被加入到spring容器中了,不過加入我們?yōu)榱藴p少服務(wù)器的壓力,我們將UserService的實(shí)現(xiàn)放到另一臺服務(wù)器上,加入我們有一個(gè)膜法,可以在本地的Controller像調(diào)用本地方法一樣調(diào)用另一臺的userServiceImpl就好了,Rpc就是這樣一種技術(shù)。

實(shí)現(xiàn)思路

  • 表面上看這是一個(gè)很難完成的任務(wù),本機(jī)怎么可能可以調(diào)用到遠(yuǎn)程的方法呢?不過如果我們這個(gè)任務(wù)拆分開來,就會發(fā)現(xiàn)只要一步一步來,其實(shí)還是挺簡單的。

    我們可以換一種思路,既然直接調(diào)用不行,我們可以曲線救國呀,我們只要把調(diào)用方法的對象的名稱,方法的名字,方法的參數(shù)與方法的類型都通過網(wǎng)絡(luò)發(fā)送到另一臺機(jī)器上,另一條機(jī)器接收到之后根據(jù)請求信息調(diào)用該對象的方法,然后在把執(zhí)行結(jié)果通過網(wǎng)路直接返回回來不就ok了。其實(shí),rpc框架的大體思路就是如此。

  • 大體實(shí)現(xiàn)流程如下:

    1. 通過java的動(dòng)態(tài)代理機(jī)制為我們UserService創(chuàng)建代理對象,在代理對象執(zhí)行方法的時(shí)候?qū)嶋H上已經(jīng)被我們定制的方法攔截。
    2. 在攔截的邏輯里面,我們在獲取到調(diào)用的方法的所有接口,方法名,參數(shù)集合,參數(shù)類型集合后封裝到一個(gè)JavaBean——request中去,然后我們將這個(gè)對象序列化之后通過網(wǎng)絡(luò)傳輸?shù)搅硪慌_機(jī)器上。
    3. 另一臺機(jī)器接受到這個(gè)網(wǎng)絡(luò)請求后,將數(shù)據(jù)反序列化為Request對象,從而了解我們請求的是具體是什么對象的什么方法,然后服務(wù)器通過反射的方式調(diào)用,并將執(zhí)行結(jié)果通過另一個(gè)JavaBean——Response返回。
    4. 本機(jī)收到服務(wù)端的返回。整個(gè)rpc調(diào)用就完成了。
  • 如下圖所示,由于畫圖水平有限,不過大致就是這個(gè)意思:

    這里寫圖片描述

代碼具體實(shí)現(xiàn)

  1. 首先我們需要為我們的網(wǎng)絡(luò)請求分裝兩個(gè)JavaBean,分別為Request與Response.。

    //在Request中應(yīng)有的屬性
    private String requestId;
    private String className;
    private String methodName;
    private Class<?>[] parameterTypes;
    private Object[] parameters;
    //在response應(yīng)該有的屬性
    private String requestId;
    private Throwable error;
    private Object result;
    
  2. 創(chuàng)建RpcClient,封裝我們的網(wǎng)絡(luò)請求流程。其中最重要的是這個(gè)方法:

    public Response send(Request request) throws InterruptedException {
            ClientBootstrap bootstrap = new ClientBootstrap();
            ExecutorService boss = Executors.newCachedThreadPool();
            ExecutorService work = Executors.newCachedThreadPool();
    
            bootstrap.setFactory(new NioClientSocketChannelFactory(boss,work));
            bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
                @Override
                public ChannelPipeline getPipeline() throws Exception {
                    ChannelPipeline pipeline = Channels.pipeline();
                    pipeline.addLast("decoder",new ResponseDecoder());
                    pipeline.addLast("encoder",new RequestEncoder());
                    pipeline.addLast("handler",RpcClient.this);
                    return pipeline;
                }
            });
            ChannelFuture connect = bootstrap.connect(new InetSocketAddress(address, port)).sync();
            connect.getChannel().write(request).sync();
             //阻塞線程直到完成請求或者請求失敗
            synchronized (obj){
                obj.wait();
            }
            connect.getChannel().close().sync();
    
            return this.response;
        }
    

    這里用netty3進(jìn)行的網(wǎng)咯請求,這里ResponseDecoderRequestEncoder是對Response與Request進(jìn)行的序列化與反序列化,采用的谷歌的Protostuff序列化框架實(shí)現(xiàn)(為啥不用java自帶的序列化工具呢?因?yàn)閖ava自定的序列化附帶了很多其他信息,序列化的字節(jié)長度比谷歌的長好幾倍,所以是為了節(jié)約帶寬,同時(shí)Protostuff的序列化支持多種編程語言)

  3. 創(chuàng)建代理的工具類,返回代理對象。

    public <T>T proxy(Class<?> clazz){
            return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[] { clazz }, new InvocationHandler() {
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    Request request = new Request();
                    request.setClassName(method.getDeclaringClass().getName());
                    request.setMethodName(method.getName());
                    request.setParameters(args);
                    request.setRequestId(UUID.randomUUID().toString());
                    request.setParameterTypes(method.getParameterTypes());
                    RpcClient client =new RpcClient(address,port);
                     //通過封裝的網(wǎng)絡(luò)框架進(jìn)行網(wǎng)絡(luò)請求
                    Response response = client.send(request);
                    if (response.getError()!=null){
                        throw response.getError();
                    }
                    else{
                        return response;
                    }
                }
            });
        }
    
  4. 服務(wù)端在開啟服務(wù)的時(shí)候就需要通過spring掃描所有的service實(shí)現(xiàn)類,將其裝進(jìn)spring的容器中。

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(RPCService.class);
            for(Map.Entry<String,Object> entry :beansWithAnnotation.entrySet()){
                String interfaceName = entry.getValue().getClass()
                        .getAnnotation(RPCService.class).value().getName();
                serviceMap.put(interfaceName,entry.getValue());
            }
            startServer();
        }
    

    需要發(fā)布的服務(wù)類都需要使用@RPCService注解,這是一個(gè)自定義的注解。

  5. 在服務(wù)端收到客戶端的網(wǎng)絡(luò)請求之后,我們就需要從spring容器中找到請求的服務(wù)類完成調(diào)用并返回執(zhí)行結(jié)果。

    @Override
        public void messageReceived(ChannelHandlerContext ctx, MessageEvent event) throws Exception {
            Request request = (Request) event.getMessage();
            Response response = new Response();
            //調(diào)用請求類的請求方法執(zhí)行并返回執(zhí)行結(jié)果
            Object invoke = null;
            try {
                Object requestBean = serviceMap.get(request.getClassName());
                Class<?> requestClazz = Class.forName(request.getClassName());
                Method method = requestClazz.getMethod(request.getMethodName(), request.getParameterTypes());
                invoke = method.invoke(requestBean, request.getParameters());
                response.setRequestId(UUID.randomUUID().toString());
                response.setResult(invoke);
            } catch (Exception e) {
                response.setError(e);
                response.setRequestId(UUID.randomUUID().toString());
            }
            System.out.println(request+""+response);
            //返回執(zhí)行結(jié)果
            ctx.getChannel().write(response);
    
        }
    

總結(jié)

  • 整體的流程還是比較簡單的,就是具體實(shí)現(xiàn)的時(shí)候會有一些細(xì)節(jié)問題需要好好處理。雖然是第一次寫這種輪子程序,不過感覺還是不錯(cuò)的。完整代碼已上傳到我的GitHub倉庫里面,有興趣的小伙伴可以去看看。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,837評論 18 139
  • RPC框架遠(yuǎn)程調(diào)用的實(shí)現(xiàn)方式在原理上是比較簡單的,即將調(diào)用的方法(接口名、方法名、參數(shù)類型、參數(shù))序列化之后發(fā)送到...
    謎碌小孩閱讀 3,146評論 0 13
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,739評論 18 399
  • 一. Java基礎(chǔ)部分.................................................
    wy_sure閱讀 3,832評論 0 11
  • 文/趙曉璃 去年夏天,我無意中看到一篇演講稿,演講者是《赫芬頓郵報(bào)》創(chuàng)始人阿里安娜·赫芬頓,她的演講主題是《成功的...
    趙曉璃閱讀 2,576評論 15 67