神了,原来你是这么干的。

本文深入探讨了RPC框架的工作原理,介绍了其关键技术如Zookeeper、Netty和Protostuff的使用,详细解析了RPC的核心流程和服务调用机制,以及如何解决多线程环境下消息匹配的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

# 介绍

RPC,远程调用过程的称呼,它调用远程计算机上的服务就像调用本地服务一样。RPC可以很好的解耦系统,比如我们常见的WebService就是基于Http协议的一种PRC,PRC总体架构如下:

WeChat6631585aaa28c239c31922f382e54217

# 关键技术
  • 服务发布和订阅:在服务端我们使用Zookeeper提供注册服务地址,在客户端我们从Zookeeper获取可用的服务地址。

  • 通信:在通信方面我们可以使用Netty作为通信方式。

  • Spring:使用Spring的配置服务,加载Bean,扫描注解。

  • 动态代理:客户端使用了代理模式透明化服务调用。

  • 消息编解码:使用了Protostuff来序列化和反序列化消息

# 核心流程
  • 服务消费方(client)的调用以本地调用服务的方式调用。
  • client stub接收到调用后负责将方法、参数等组装从能够进行网络传输的消息体。
  • server stub找到服务地址,并将消息发送给服务端。
  • server stub根据接收到的消息进行解码,根据解码结果调用相应的本地服务。
  • 本地服务将执行结果返回给server stub。
  • server stub将返回结果打包成消息发送给消费方。
  • client stub接收到消息后进行解码。
  • 服务消费方最终得到需求的请求结果。

流程如下图所示:

WeChat06347399645615732e9971e858e4fc27

# 消息编解码

首先我们看下消息的数据结构:(接口名称+ 方法名+参数类型和参数值+超时时间+requestID)。

  • 接口名称

    在我们定义接口的时候取的名字:HelloWorldServer,如果不传的话,服务端不知道你要调用哪个接口了。

  • 方法名

    一个接口会有很多个方法,所以我们在调用的时候不仅传接口名称,还有传方法名。

  • 参数类型和参数值

    是对应方法中的参数类型和参数值的。

  • 超时时间

    设置超时时间,一旦请求超过这个时间,就认为是失败了。

  • requestID

    标识唯一的请求方式。

  • 服务端返回的消息体

    一般包括以下内容。返回值+状态 code+requestID

  • 序列化

    一般使用 Protobuf、Thrift、Avro 等成熟的序列化解决方案来搭建 RPC 框架。

# 通信过程

如果我们使用Netty作为通信方式的话,一般会使用channel.writeAndFlush()方法来发送消息二进制串,这个方法是从发出消息到接收消息结果的整个过程都是一个异步的。对应当前线程来说,将请求发送出去之后,线程就一直往下执行了,至于服务端返的结果,是服务端处理完成之后在以消息的信息发送给客户端。这样我们就会碰到以下二个问题:

第一:怎么让当前的线程暂停,等结果返回之后再往下执行。

第二:如果有多个线程同步进行远程方法的调用,这时候建立在client server之间的socket连接上会有很多双方方式的消息传递,前后顺序也可能是随机的,server处理完成之后,将结果消息发送给client,client收到很多消息,怎么知道哪个消息的结果是之前哪个线程发送的呢?

如下图所示:

WeChat0d0081eade59f62e83c3db5d74328e6b

线程A和线程B同时向client server发送请求requestA和requestB,socket先后将requestB和requestA发送至server,而server可能将requestB先返回,尽管requestB请求到达时间更晚,我们需要一种机制保证requestA丢给ThreadA,requestB丢给ThreadB。

  • RequestID生成AtomicLong

    Client线程每次通过socket调用一次远程接口前,生成一个唯一的ID,既requestID,requestID必须保证在一个socket里是唯一的,一般AtomicLong从0开始累计生成唯一的ID;

  • 把回调对象callback存到全局的concurrentHashMap

    将处理结果的回调对象callback,存到全局的concurrentHashMap里面。put(requestID,callback)

  • synchronized获取回调对象callback的锁并自旋wait

    当线程调用channel.writeAndFlush()发送消息后,紧接着执行callback的get()方法试图获取返回的结果,在get内部,使用synchronized获取回调对象的callback的锁,在检测是否已经获取到结果,如果没有则调用callback的wait方法,释放callback上的锁,让当前线程处于等待状态。

  • 监听消息的线程收到消息,找到callback上的锁并唤醒。

    服务端接收到请求并处理后,将 response 结果(此结果中包含了前面的 requestID)发送给客户端,客户端 socket 连接上专门监听消息的线程收到消息,分析结果,取到 requestID,再从前面的 ConcurrentHashMap 里面 get(requestID),从而找到 callback 对象,再用 synchronized 获取 callback 上的锁,将方法调用结果设置到 callback 对象里,再调用 callback.notifyAll()唤醒前面处于等待状态的线程。

    public Object get() {
    	synchronized (this) { // 旋锁 while (true) { // 是否有结果了
    		If (!isDone){
    				wait(); //没结果释放锁,让当前线程处于等待状态
    				}else{//获取数据并处理
        			}
    			} 
    	}
    }
    private void setDone(Response res) {
    		this.res = res;
    		isDone = true;
    		synchronized (this) { //获取锁,因为前面 wait()已经释放了 callback 的锁了
    		notifyAll(); // 唤醒处于等待的线程 }
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值