RPC端点和RPC端点引用
- RpcEndPoint:RPC分布式的一个实例,他用于指定消息的处理,比如接收消息。
- RpcEndpointRef:RpcEndPoint的引用,也就是说,他指向的是服务端的RpcEndPoint,所以RpcEndpointRef会有服务端的RPC地址。
下图中,服务提供方的ip和端口是192.168.1.3,如果服务调用方想调用服务提供方的RpcEndPoint,必须持有对应RpcEndPoint的RpcEndPointRef。
RpcEnv
Spark是分布式的,每个节点都需要通讯,所以节点启动的时候,就会实例化一个RpcEnv,用于通讯,默认为NettyRpcEnv,所以每个RpcEnv中都封装netty。服务调用方通过RpcEndPointRef调用netty的客户端,netty的客户端通过netty调用netty的服务端,netty的服务端进而调用服务提供方的RpcEndPoint。
消息调度器
netty的服务端并没有直接调用服务提供方的RpcEndPoint,而是通过消息调度器Dispatcher负责将RPC消息路由到要该对此消息处理的RpcEndpoint,消息调度器Dispatcher能有效提高NettyRpcEnv对消息异步处理并最大提升并行处理能力。
Dispatcher维护着endpoints的map,叫做endpoints,key为每个RpcEndPoint的名称,value为RpcEndPoint对应的EndpointData。RpcEndPoint进行注册到Dispatcher的时候,实际上就是存放在这个map里。
当服务调用方调用服务提供方的RpcEndPoint的时候,需要提供RpcEndPoint的名称。下图中,RpcEndPoint假设name为aa,所以RpcEndPointRef会把aa也发送过去。
EndpointData
EndpointData包括RpcEndPoint的名称,Inbox,RpcEndPoint,RpcEndPointRef,RpcEndPointRef就是指向当前的RpcEndPoint,当向本地的RpcEndPoint发送消息时,可以通过对应的RpcEndPointRef进行发送。
Inbox
每个RpcEndpoint都有一个对应的Inbox,Inbox维护着InboxMessage类型的消息列表messages。下图中,RpcEndPointRef把hello消息发送给name为aa的RpcEndPoint,这个hello最后会存储在RpcEndPoint对应的Inbox中的消息列表messages。此时这个消息并没有被消费。
阻塞队列-receivers
Inbox的消息队列有消息了,然后把name为aa的RpcEndPoint对应的EndpointData放入Dispatcher的阻塞队列receivers。
线程池-threadpool
Dispatcher中有一个线程池,跑的都是MessageLoop类型的线程,这些线程会监听阻塞队列receivers,当阻塞队列receivers里有消息了,就会把消息拿出来。
为了说明多线程的情况,我们假设服务调用放发送了hello和world两个消息。此时线程1拿出来的是aa的EndpointData,线程1就会处理EndpointData中Inbox里的消息hello。
默认情况下,不允许多个线程处理一个Inbox里的消息,当线程2拿到aa的EndpointData时,发现已经有线程在处理了,他就直接返回。
线程1处理完hello后,发现队列还有消息,于是把world拿出来进行处理。
如果设置为允许多个线程处理一个Inbox里的消息,线程2直接处理world的消息。
RpcEndpoint生命周期
上面已经讲了消息处理流程,也就是把消息存放在Inbox中,然后把EndpointData放入Dispatcher的阻塞队列,然后由线程池中的线程拿出EndpointData,对EndpointData里面的Inbox的消息进行处理。
当RpcEndpoint注册的时候,会初始化Inbox,Inbox初始化的时候,就会把Onstart的消息放入Inbox中,然后放入Dispatcher的阻塞队列。所以最终就会处理Inbox的Onstart消息,也就是RpcEndpoint的Onstart方法。
所以RpcEndpoint生命周期就是constructor -> onStart ,RpcEndpoint在运行中,会处理 receive 方法,停止的时候,会调动onStop方法,所以整个生命周期就是constructor -> onStart -> receive -> onStop。
RpcEndpointVerifier
服务调用方通过RpcEndpointRef调用服务提供方的name为aa的RpcEndpoint,那在实例化RpcEndpointRef的时候,是需要知道服务提供方是否有name为aa的RpcEndpoint。在RpcEnv初始化的时候,会向Dispatcher注册一个name为endpoint-verifier的RpcEndpoint,当服务调用方实例化name为aa的RpcEndpointRef的时候,就会通过name为endpoint-verifier的RpcEndpointRef调用服务提供方的RpcEndpoint,询问是否存在name为aa的RpcEndpoint,name为endpoint-verifier的RpcEndpoint会通过Dispatcher的endpoints判断是否有这个key,如果有,说明存在。
outboxes
RpcEndpointRef发送消息的时候,根据RpcEndpoint的ip地址和端口,从outboxes中拿对应的Outbox。这个outboxes是RpcEnv的一个map对象,key就是ip地址和端口封装的RpcAddress对象,value是Outbox。所以每个ip+port都有对应的Outbox。
Outbox
Outbox有一个OutboxMessage类型的消息列表messages,比如发送给name为aa的RpcEndpoint两个消息,hello和world时,就会把这两个消息存放在messages中。
这个队列只能一个线程进行消费,也就是说如果线程1在消费hello的时候,线程2存放完world时,发现已经有其他线程在消费,于是就直接返回。线程1消费完hello的时候,发现消息列表里还有world消息,于是继续消费。
消费实际上就是通过netty的客户端发送消息给netty的服务端。
源码思维导图
RpcEnv的创建
NettyRpcEnv#stop
Dispatcher#postMessage
Dispatcher#stop
客户端发送请求
服务端处理请求
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。