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)动手实践,思路和动手一样重要,上来就动手反而是徒劳

发布了99 篇原创文章 · 获赞 115 · 访问量 19万+

猜你喜欢

转载自blog.csdn.net/qq_37171353/article/details/104847476