Netty轮子--基于Netty去实现Dubbo-RPC的功能

RPC基本介绍

设计模式
算法与数据结构

1)RPC(Remote Procedure Call)
    远程过程调用,是一个计算机通讯协议,该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外的为这个
      交互作用编程;

2)2个或多个应用程序都分布在不同的服务器上,他们之间的调用都像是本地方法调用一样;

3)常用的RPC框架有: 比较知名的如:
    阿里的Dubbo
    google的gRPC
    Go语言的rpcX
    Apache的thrift
    Spring旗下的Spring Cloud

4)基本流程
  1.请求  2.编码  3.发送                                                 10.接收  11.解码 12.结果
                          4.接收  5.解码 6.调用api 7.响应 8.编码 9.发送

  在RPC中的2个术语:
    Client: 服务消费者
    Server: 服务提供者

5)RPC调用流程说明
  (1)服务消费方(Client)以本地调用方式调用服务

  (2)client stub接收到调用后负责将方法、参数等封装成能够进行网络传输的消息体
  (3)client stub将消息进行编码并发送到服务端
  (4)server stub收到消息后进行解码
  (5)server stub根据解码结果调用本地服务
  (6)本地服务执行并将结果返回给server stub
  (7)server stub将返回导入结果进行编码并发送至消费方
  (8)client stub接收到消息并进行解码

  (9)服务消息方(Client)得到结果

  小结: RPC的目标就是将2-8这些步骤都封装起来,用户无需关心这些细节,就可以像调用本地方法一样即可完成远程服务调用;

6)RPC 实现的4个部分
  customer: 客户端(消费者)
    ClientBootstrap // 启动一个消费者-->NettyClient
  
  provider: 服务端(提供者)
    HelloServiceImpl类
    ServerBootstrap // 启动一个服务提供者-->NettyServer

  netty:    底层封装
    客户端:
      1.NettyClient
      2.NettyClientHandler

    服务端:
      1.NettyServer
      2.NettyServerHandler

  publicinterface: 共有的接口
    HelloService

7)最核心的地方: 如何去创建一个代理对象(代理模式)


8)编码器和解码器:
  (1)编码器:StringEncoder
  (2)解码器:StringDecoder
  (3)业务处理器: BusinessHandler
      客户端发送过来请求信息时,每次发送消息必须以 某个协议字符串开头;

9)启动层:包上一层
  startServer(根据传递的参数调用不同的方式,这个是对外暴露的接口):
    startServer0

10)先写NettyClientHandler(下面2个和它有关)
  (1)下面2个和NettyClientHandler有关
      1:NettyClient
      2:ClientBootstrap

  (2)(重点)synchronized Object call: 也就是实现了Callable接口
      调用有关;
      被代理对象调用,发送数据给服务端 --> wait  --> 等待唤醒 --> 返回结果;
      因此不用犹豫不用彷徨,加上synchronized;

  (3)3个基本接口
    1:channelActive(与服务器的连接创建后,就会被调用,ctx传递给context,做成属性)

    2:synchronized channelRead(收到服务器的数据后调用)
        理解为何 要用notify: 去唤醒等待的线程:
          client--->server

          客户端Handler有个Call方法,客户端先调用Call方法把数据发送给服务端(通过反射机制调用),
            发送完毕后,消息还没有马上回来,因此在Call里面我会进行一个wait;

          基于Netty的原则,服务器端会送数据是会送到channelRead方法,  Call中一直在等待(wait),还没有返回呢,
            通过代理对象调用Handler的Call方法,调用之后把信息发送过去就在这等待,数据是发送给Handler的另外一个方法,
            因此我需要用notify来唤醒它,唤醒后,代码接着继续执行,把结果给到了result,
            这个时候,我们再把结果返回给代理对象;

          因此,等在channelRead中唤醒call方法时,结果已经拿到了

          因此: channelRead和call是有一个同步的关系的,
                  因此call中用了wait,
                  channelRead 和notify,
                  并且call和channelRead中也要在方法前加上synchronized

    3:exceptionCaught


      我的疑问是: 
          wait后, 是线程阻塞这了吗?  如果是阻塞的,那么需要谁去唤醒接着执行呢?
          channelRead是在哪个线程被调用的呢?(这个通过打印当前线程信息即可知道)
 
  (4)3个属性
    1:ChannelHandlerContext context; // 因为要在 其它方法里面 使用到当前Handler里面的上下文,因此需要保存下上下文当成属性

    2:String result; // 将来客户端调用后,返回的结果

    3:String patam;  // 客户端调用方法时传递进来的参数

        客户端发送数据前,将这个参数传递给服务端,服务端即可知道想要调用的是什么方法,
          反射一下调用相关方法,然后客户端即可在channelRead中得到数据

11)方法调用先后顺序
  channelActive
  setPara
  call-->wait后阻塞
  channelRead-->等待返回,并且notify
  call-->在notify后,继续执行

  如果出了异常,就调用exceptionCaught 

12)synchronized的理解
  (1)NettyClientHandler可能在每个workerGroup中使用,而且如果是业务线程池去执行任务,那么
       加上synchronized在方法前,就只能有一个线程去访问到这个synchronized;

  (2)试试多个线程去加和减去一个全局变量???

13)NettyClientHandler.java
  也是有一个线程池,因为它只连接一个服务器,因此thread name每一个都是一样的

14)最难的地方: 创建代理对象, getBean. 使用代理模式获取代理对象
    serivceClass: 要创建的哪个service对象
    providerName: 与服务器通讯时的协议头是什么

    Thread.currentThread().getContextClassLoader():
        获取类的加载器,用当前线程去获取;

    new Class[]{serivceClass}:
      获取类的一个实例

    (proxy, method, args) : 将来代理对象将要调用的方法得到的东西
        proxy: 代理对象本身
        method: 方法
        args: 将来传递的参数-->‘协议头’ 传递进来的信息,如:
          providerName: 协议头 
          args[0]: 就是客户端调用api时, 如: hello(xxx) 里面的xxx参数

    用lambda表达式进行调用:
       (proxy, method, args)->{
          ... // 这段代码客户端每调用一次,就是会进入到该代码块,反复执行的!
       }

    返回值: return executor.submit(client).get();
      submit(client)这个是一个Callable对象,也就是接收的是实现了Callable接口的对象,
         刚好就是我们的client;

      然后.get()它的结果:
        这个get是做什么的呢? 就是Handler中实现的call方法的return的结果拿到

15)ClientBootstrap.java
  (1)里面写好协议头
  (2)创建一个消费者: NettyClient
  (3)获取代理对象,进行强转一下:
     HelloService service = (HelloService) customer.getBean(HelloService.class, providerName);

16)思考:既然RPC实现的是调用远程的方法跟本地的一样,所以没必要搞成异步的

17)服务提供方是并不是同一个实例
  private int count = 0; 
    发现每次打印都是打印的1;

  private static int count = 0;
    不管你服务端产生多少个这样的对象实例,都是共用的;

18)channelRead和call方法必须有一个同步的控制;

19).hello真正被调用的地方在于: getBean里面的 ->{xxx}这里:
  只要通过代理对象去调用,那么就可以进入

20)把业务放到线程池中执行,并得到它的结果
    executor.submit(client).get()

 

你可能感兴趣的:(【netty】)