Netty(九)之手寫rpc小案例

想法的來源

學了netty框架以及看了一下一小部分的netty框架的源碼,聽說dubbo是基於netty框架的一個優秀的落地實現,所以看了一小部分dubbo的源碼,感覺學習netty總要有一個方式證明自己曾經學過,所以寫下這一篇小筆記,寫給自己看。

前提

zookeeper知識點

zookeeper有四種節點

1:PERSISTENT                                //  持久化節點

2:PERSISTENT_SEQUENTIAL       //  持久化排序節點

3:EPHEMERAL                                 //  臨時節點

4:EPHEMERAL_SEQUENTIAL        //  臨時排序節點

netty知識點

https://blog.csdn.net/qq_37171353/category_9328166.html

其它參考百度。。。

Spring的生命週期

https://blog.csdn.net/qq_37171353/article/details/103165108

其它參考百度。。。

動手實踐

注意

1)下面只貼了部分代碼,全部代碼在  https://github.com/cbeann/Nettyy  中

2)項目寫的很爛,很爛,很爛

目標

1)zk爲註冊中心

2)服務消費者可以發送請求給服務提供者,但是方法的返回值我沒辦法在服務消費端獲取到,我只能打印,因爲dubbo重寫了future方法,我的目標是可以在服務消費者的netty中的handler中打印即可。

3)

服務註冊中心zookeeper

1)在zookeeper的/myrpc下創建兩個子節點/myrpc/provider  和 /myrpc/consumer,分別存儲消息提供者和消息消費者的註冊信息

2)ZkClient對providerBean和consumerBean按照一定的格式存到zk中,其中key是服務提供者和服務消費者的名稱

基本功能就是對zk

package com.zk;

import com.entity.rpc.ConsumerBean;
import com.entity.rpc.ProviderBean;
import com.untils.JsonUtils;
import org.apache.zookeeper.*;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

/**
 * @author CBeann
 * @create 2020-03-06 23:06
 */
public class ZKClient {

    //根目錄
    private static final String path = "/myrpc/";
    //服務提供者
    private static final String path_provider = "/myrpc/provider";
    //服務消費者
    private static final String path_consumer = "/myrpc/consumer";


    /**
     * zookeeper地址
     */
    String CONNECT_ADDR = "39.105.30.146:2181";
    /**
     * session超時時間
     */
    static final int SESSION_OUTTIME = 2000;// ms
    /**
     * 信號量,阻塞程序執行,用於等待zookeeper連接成功,發送成功信號
     */
    static final CountDownLatch connectedSemaphore = new CountDownLatch(1);

    ZooKeeper zk;


    public ZKClient(String url) {
        this.CONNECT_ADDR = url;
        try {
            zk = new ZooKeeper(url, 5000, new Watcher() {
                @Override
                public void process(WatchedEvent event) {
                    // 獲取事件的狀態
                    Event.KeeperState keeperState = event.getState();
                    Event.EventType eventType = event.getType();
                    // 如果是建立連接
                    if (Event.KeeperState.SyncConnected == keeperState) {
                        if (Event.EventType.None == eventType) {
                            // 如果建立連接成功,則發送信號量,讓後續阻塞程序向下執行
                            System.out.println("zk 建立連接");
                            connectedSemaphore.countDown();
                        }
                    }
                }
            });

            // 進行阻塞
            connectedSemaphore.await();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    //向zk註冊服務提供者
    public void sendProviderBeanMsg(ProviderBean providerBean) {


        String s = JsonUtils.objectToJson(providerBean);
        try {
            String path1 = zk.create(path_provider + "/" + providerBean.getProviderName(), s.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE,
                    CreateMode.EPHEMERAL);
            System.out.println("Success create path: " + path1);
        } catch (Exception e) {
            e.printStackTrace();
        }


    }

    //獲取服務提供者集合
    public List<ProviderBean> getProviderBeanList() {

        List<ProviderBean> providerBeanList = new ArrayList<>();

        try {
            List<String> children = zk.getChildren(path_provider, null);
            for (String child : children) {
                byte[] data = zk.getData(path_provider + "/" + child, false, null);
                String json = new String(data);
                ProviderBean providerBean = JsonUtils.jsonToPojo(json, ProviderBean.class);
                providerBeanList.add(providerBean);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return providerBeanList;
    }

    //獲取服務消費者集合
    public List<ConsumerBean> getConsumerBeanList() {
        List<ConsumerBean> consumerBeanList = new ArrayList<>();

        try {
            List<String> children = zk.getChildren(path_consumer, null);
            for (String child : children) {

                byte[] data = zk.getData(path_consumer + "/" + child, false, null);
                String json = new String(data);
                ConsumerBean providerBean = JsonUtils.jsonToPojo(json, ConsumerBean.class);
                consumerBeanList.add(providerBean);


            }
        } catch (Exception e) {
            e.printStackTrace();
        }


        return consumerBeanList;
    }

    //向zk註冊服務消費者
    public void sendConsumerBeannMsg(ConsumerBean consumerBean) {

        String s = JsonUtils.objectToJson(consumerBean);
        try {
            String path1 = zk.create(path_consumer + "/" + consumerBean.getConsumerName(), s.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE,
                    CreateMode.EPHEMERAL);
            System.out.println("Success create path: " + path1);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    //關閉zk
    public void closeZk() {
        try {
            zk.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}

 

 

服務提供者(思路)

因爲要整合Spring,所以我們要按照Bean的方式進行寫一些內容。

首先想到的是創建一個NettyServer,如下所示,大衆的想法


@Component
public class NettyServer  {
    EventLoopGroup bossGroup = new NioEventLoopGroup();
    EventLoopGroup workerGroup = new NioEventLoopGroup();
    ServerBootstrap b = new ServerBootstrap();
    public void bind(int port) {}
}

但是NettyServer中ServerBootstrap中的port在是怎麼獲取的呢???

dubbo是配置在properties文件中,我們也是放在myrpc_provider.properties中,並且把信息映射到一個類ProviderConfigBean 中

provider.name=user-provider
provider.port=8888
provider.intername=com.service.StudentService
zk.url=39.105.30.146:2181
@Component
@PropertySource(value = "classpath:myrpc_provider.properties",ignoreResourceNotFound = true)
@Data
public class ProviderConfigBean {


    @Value("${provider.name}")
    private String providerName;

    @Value("${provider.port}")
    private Integer providerport;

    @Value("${provider.intername}")
    private String providerIntername;


    @Value("${zk.url}")
    private String zkUrl;

}

我們可以獲取properties中的信息,但是怎麼從 ProviderConfigBean 中的port怎麼傳到NettyServer中呢???

我們可以給NettyServer實現ApplicationContextAware接口,把ioc容器當做NettyServer的一個變量,然後就可以獲得ProviderConfigBean裏的port信息。

MyServerHandler是NettyServer中ServerBootstrap的hander,如果客戶端給服務端發送數據,那hander怎麼調用方法返回數據呢???

我們可以把applicationContext當做構造方法的參數傳入到hander中,這樣,我們就可以從客戶端發送的信息中獲取class、method和args,然後調用applicationContext根據class獲取類,然後有method和args可以通過反射執行方法,並且把結果寫回到channel中。

NettyServer中ServerBootstrap的bind方法什麼時候調用呢???

什麼時候調用都行,我是在ioc容器中加載完畢後調用bind方法,這裏讓NettyServer實現ApplicationListener<ApplicationEvent>接口,是在ioc最後的時候調用的。

ServerBootStrap.bind阻塞怎麼辦?

  new Thread(() -> {
            this.bind(providerConfigBean.getProviderport());
        }).start();

 服務提供者有自己專屬的類NettyServer和ProviderConfigBean,服務消費者也有,服務提供者需要排除一些類,怎麼又更好的重用性和可讀性???

自定義註解

@ComponentScan(value = "com",
        excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {ConsumerConfigBean.class, NettyClient.class})})
@Target(ElementType.TYPE)//方法註解
@Retention(RetentionPolicy.RUNTIME)//運行時註解
public @interface EnableProvider {
}

服務端的hander知道class、method、args怎麼執行,沒有實現類怎麼進行代理???

https://blog.csdn.net/qq_37171353/article/details/104757500

服務消費者

首先貼一下myrpc_consumer.properties和與其綁定的ProviderConfigBean 

consumer.name=order-consumer
consumerref.intername=com.service.StudentService
zk.url=39.105.30.146:2181
@Component
@PropertySource(value = "classpath:myrpc_provider.properties",ignoreResourceNotFound = true)
@Data
public class ProviderConfigBean {


    @Value("${provider.name}")
    private String providerName;

    @Value("${provider.port}")
    private Integer providerport;

    @Value("${provider.intername}")
    private String providerIntername;


    @Value("${zk.url}")
    private String zkUrl;

}

 

 Bean創建的時候是不能添加BeanDefination,也就是說先有全部的BeanDefination,然後有Bean。常情況下ioc容器會創建一個引用name爲studentService的類型爲StudentService的BeanDefination,但是在rpc中服務消費者只有一個接口,是不可能是上面那種中規中矩的方式。dubbo是怎麼實現的呢???

consumerref.intername在dubbo中其實是xml中ref標籤,然後通過解析xml向Spring中添加一個BeanDefination,其中name爲studentService。我沒有解析xml,說要我直接寫一個類@Component("studentService"),然後實現FactoryBean接口,然後重新getObject方法就能返回各種各樣的類。

package com.entity;

import com.consumer.ConsumerConfigBean;
import com.consumer.NettyClient;
import com.proxy.RpcFactoryProxy;
import com.service.StudentService;
import lombok.Data;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * @author CBeann
 * @create 2020-03-09 19:27
 */
@Data
@Component("studentService")
public class RefRpcBean implements FactoryBean, ApplicationContextAware {

    private String classname;

    private ApplicationContext applicationContext = null;

    private Object ref = null;

    @Autowired
    private NettyClient nettyClient;


    @Override
    public Object getObject() throws Exception {
        Object proxy = null;
        try {
            RpcFactoryProxy factoryProxy = new RpcFactoryProxy(Class.forName(classname), nettyClient);
            proxy = factoryProxy.getProxy();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return proxy;

//        Student student = new Student();
//        student.setName("refRpcBean");
//        student.setId(1);
//        return student;
    }

    @Override
    public Class<?> getObjectType() {
        return StudentService.class;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }

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

        ConsumerConfigBean bean = applicationContext.getBean(ConsumerConfigBean.class);
        this.classname = bean.getConsumerrefIntername();

    }
}

總結

1)文章寫的賊爛

2)第一次發現Spring中Bean的生命週期很重要

3)註解多了可以自己定義一個註解,就像SpringBoot的註解@SpringBootApplication一樣

4)盧本偉牛逼,反射牛逼,反射牛逼,反射牛逼

5)以前那種看視頻學FactoryBean接口時感覺沒啥卵用,現在是第一次發現FactoryBean接口的真正意義上的使用

6)動手實踐,思路和動手一樣重要,上來就動手反而是徒勞

 

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