什么是RPC:  

  RPC(Remote Procedure Call,远程过程调用),一般用来实现部署在不同机器上的系统之间的方法调用,使得程序能够像访问本地系统资源一样,通过网络传输去访问远端系统资源;对于客户端来说, 传输层使用什么协议,序列化、反序列化都是透明的。

  在分布式架构中,难免会涉及多个独立的服务之间的通讯,比如一个简单的电商系统中,按照业务领域拆分成三个独立的应用,用户,订单,商品三者之间,当商品模块需要访问用户模块,获取该用户是否购买该商品等等的业务场景,就会设计独立服务之间的通讯。这个时候需要用到远程的服务调用。其中阿里的 dubbo框架就是目前最主流的RPC框架。

  要实现一个简单的RPC需要考虑到什么呢? 两个服务之间的通讯,基于 TCP/IP通讯的基础之上,进行远程服务调用需要进行序列化,通过动态代码获得服务的代理对象,还有传输的安全性,服务管理,然后进行传输,在被调用方需要进行反序列化解析,通过反射去调用方法,并返回请求结果。

服务端:

  服务端需要发布一个服务,那么需要一个服务接口及实现:

public interface IGpHello {
String sayHello(String msg);
}

  实现:

public class GpHelloImpl implements IGpHello{
@Override
public String sayHello(String msg) {
return " RPC ,Hello , "+msg;
}
}

  这里的服务接口需要在客户端进行调用,不过客户端不必进行实现,下面就不再重复编写了。那么对于服务端如何去监听请求?以及对于客户端请求对象的序列化就必须要有个实现序列化接口的传输对象,这个我们等到客户端发送请求的时候再进行编写。那么对于服务端监听请求可以通过Socket来实现,然后对于监听到的请求进行处理,我这里采用线程池去处理,既然通过线程池去处理,通过类的单一职责原则这里可以写一个类专门去处理请求,我这里这个类是ProcessorHandler:

public class RpcServer {
//创建一个线程池
private static final ExecutorService executorService=Executors.newCachedThreadPool(); public void publisher(final Object service,int port){
ServerSocket serverSocket=null;
try{
serverSocket=new ServerSocket(port); //启动一个服务监听 while(true){ //循环监听
Socket socket=serverSocket.accept(); //监听服务
//通过线程池去处理请求
executorService.execute(new ProcessorHandler(socket,service));
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(serverSocket!=null){
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
} }
}
}

  ProcessorHandler:

public class ProcessorHandler implements Runnable{

    private Socket socket;
private Object service; //服务端发布的服务 public ProcessorHandler(Socket socket, Object service) {
this.socket = socket;
this.service = service;
} @Override
public void run() {
//处理请求
ObjectInputStream inputStream=null;
try {
//获取客户端的输入流
inputStream=new ObjectInputStream(socket.getInputStream());
//反序列化远程传输的对象RpcRequest
RpcRequest request=(RpcRequest) inputStream.readObject();
Object result=invoke(request); //通过反射去调用本地的方法 //通过输出流讲结果输出给客户端
ObjectOutputStream outputStream=new ObjectOutputStream(socket.getOutputStream());
outputStream.writeObject(result);
outputStream.flush();
inputStream.close();
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}finally {
if(inputStream!=null){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private Object invoke(RpcRequest request) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
//一下均为反射操作,目的是通过反射调用服务
Object[] args=request.getParameters();
Class<?>[] types=new Class[args.length];
for(int i=0;i<args.length;i++){
types[i]=args[i].getClass();
}
Method method=service.getClass().getMethod(request.getMethodName(),types);
return method.invoke(service,args);
}
}

  上面提及的 RpcRequest 就是我们的传输对象,这个对象稍后在客户端代码中会有体现。接着我们去把这个服务发布出来:

public static void main(String[] args) {
IGpHello iGpHello=new GpHelloImpl();
RpcServer rpcServer=new RpcServer();
rpcServer.publisher(iGpHello,8080);
}

客户端:

  客户端需要服务端的接口但是不需要实现,就把上面的接口复制到客户端的项目里就可以。下面我们需要去编写这个进行序列化传输的对象,必须实现序列化接口,而且这个 serialVersionUID 是唯一的,然后在服务端也有一个同样的类进行反序列化。 serialVersionUID要一致,不然会导致反序列化失败:

public class RpcRequest implements Serializable {

    private static final long serialVersionUID = -9100893052391757993L;
private String className;
private String methodName;
private Object[] parameters; public String getClassName() {
return className;
} public void setClassName(String className) {
this.className = className;
} public String getMethodName() {
return methodName;
} public void setMethodName(String methodName) {
this.methodName = methodName;
} public Object[] getParameters() {
return parameters;
} public void setParameters(Object[] parameters) {
this.parameters = parameters;
}
}

  接着我们需要获得远程服务的代理对象,然后通过这个代理对象去调用远程方法,所以这里需要有个获取代理对象的类:

  newProxyInstance方法用来返回一个代理对象,这个方法总共有3个参数,ClassLoader loader用来指明生成代理对象使用哪个类装载器,Class<?>[] interfaces用来指明生成哪个对象的代理对象,通过接口指定,InvocationHandler h用来指明产生的这个代理对象要做什么事情。我们这边需要实现自己的 InvocationHandler,需要便写一个类去实现InvocationHandler接口,我这里是RemoteInvocationHandler, 所以我们只需要调用newProxyInstance方法就可以得到某一个对象的代理对象了。

public class RpcClientProxy {

	/**
* 创建客户端的远程代理。通过远程代理进行访问
* static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
* @param interfaceCls
* @param host
* @param port
* @param <T>
* @return
*/
public <T> T clientProxy(final Class<T> interfaceCls, final String host, final int port) {
// 使用到了动态代理。
return (T) Proxy.newProxyInstance(interfaceCls.getClassLoader(), new Class[] { interfaceCls },
new RemoteInvocationHandler(host, port));
}
}

  RemoteInvocationHandler,调用远程的服务,获取代理对象,这里也是用Socket去实现,然后我这边是写了单独的一个类去连接远程的服务,进行流的传输这个类是TCPTransport:

public class RemoteInvocationHandler implements InvocationHandler {
private String host;
private int port; public RemoteInvocationHandler(String host, int port) {
this.host = host;
this.port = port;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//组装请求
RpcRequest request=new RpcRequest();
request.setClassName(method.getDeclaringClass().getName());
request.setMethodName(method.getName());
request.setParameters(args);
//通过tcp传输协议进行传输
TCPTransport tcpTransport=new TCPTransport(this.host,this.port);
//发送请求
return tcpTransport.send(request);
}
}

  TCPTransport:

public class TCPTransport {

    private String host;

    private int port;

    public TCPTransport(String host, int port) {
this.host = host;
this.port = port;
} //创建一个socket连接
private Socket newSocket(){
System.out.println("创建一个新的连接");
Socket socket;
try{
socket=new Socket(host,port);
return socket;
}catch (Exception e){
throw new RuntimeException("连接建立失败");
}
} public Object send(RpcRequest request){
Socket socket=null;
try {
socket = newSocket();
//获取输出流,将客户端需要调用的远程方法参数request发送给
ObjectOutputStream outputStream=new ObjectOutputStream
(socket.getOutputStream());
outputStream.writeObject(request);
outputStream.flush();
//获取输入流,得到服务端的返回结果
ObjectInputStream inputStream=new ObjectInputStream
(socket.getInputStream());
Object result=inputStream.readObject();
inputStream.close();
outputStream.close();
return result; }catch (Exception e ){
throw new RuntimeException("发起远程调用异常:",e);
}finally {
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

  就这样客户端的代码编写完毕,可以发起远程调用了:

public static void main(String[] args) {
RpcClientProxy rpcClientProxy=new RpcClientProxy(); IGpHello hello=rpcClientProxy.clientProxy
(IGpHello.class,"localhost",8080);
System.out.println(hello.sayHello("wuzz"));
}

  结果输出

创建一个新的连接
RPC ,Hello , wuzz

  RPC 远程服务调用到此结束,RPC远程服务调用是分布式项目的核心,接下去我们可以一步步的去展开分布式服务的学习拉。。。

分布式通讯架构RPC简单实现的更多相关文章

  1. alluxio源码解析-rpc调用概述-client和worker之间的block模块的通讯架构(netty版本)(3)

    (1.8版本)client和worker之间的block模块的通讯架构 block作为alluxio文件读取或者存储的最小基本单位,都是通过BlockOutStream和BlockInputtream ...

  2. Java[2] 分布式服务架构之java远程调用技术浅析(转http://www.uml.org.cn/zjjs/201208011.asp)

    转自:http://www.uml.org.cn/zjjs/201208011.asp 在分布式服务框架中,一个最基础的问题就是远程服务是怎么通讯的,在Java领域中有很多可实现远程通讯的技术,例如: ...

  3. 【转】RPC简单介绍

    RPC简单介绍 RPC 1. RPC是什么 RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络 ...

  4. 分布式服务(RPC)+分布式消息队列(MQ)面试题精选

    ​ 分布式系统(distributed system)是建立在网络之上的软件系统.正是因为软件的特性,所以分布式系统具有高度的内聚性和透明性.因此,网络和分布式系统之间的区别更多的在于高层软件(特别是 ...

  5. .net 大型分布式电子商务架构说明

    .net大型分布式电子商务架构说明 背景 构建具备高可用,高扩展性,高性能,能承载高并发,大流量的分布式电子商务平台,支持用户,订单,采购,物流,配送,财务等多个项目的协作,便于后续运营报表,分析,便 ...

  6. 分布式网站架构后续:zookeeper技术浅析

    Zookeeper是hadoop的一个子项目,虽然源自hadoop,但是我发现zookeeper脱离hadoop的范畴开发分布式框架的运用 越来越多.今天我想谈谈zookeeper,本文不谈如何使用z ...

  7. net大型分布式电子商务架构

    net大型分布式电子商务架构 背景 构建具备高可用,高扩展性,高性能,能承载高并发,大流量的分布式电子商务平台,支持用户,订单,采购,物流,配送,财务等多个项目的协作,便于后续运营报表,分析,便于运维 ...

  8. Atitit.分布式远程调用  rpc  rmi  CORBA的关系

    Atitit.分布式远程调用  rpc  rmi  CORBA的关系 1. 远程调用(包括rpc,rmi,rest)1 2. 分布式调用大体上就分为两类,RPC式的,REST式的1 3. RPC(远程 ...

  9. zz《分布式服务架构 原理、设计与实战》综合

    这书以分布式微服务系统为主线,讲解了微服务架构设计.分布式一致性.性能优化等内容,并介绍了与微服务系统紧密联系的日志系统.全局调用链.容器化等. 还是一样,每一章摘抄一些自己觉得有用的内容,归纳整理, ...

随机推荐

  1. Hadoop之YARN思维导图

  2. 深入理解jQuery中的each方法

    写在前面 我们先回顾一下数组中的forEach方法吧.在数组的实例上有个forEach方法供所有实例使用,forEach里面接收一个回调函数,而且回调函数默认接收三个参数:当前项,索引,数组 .for ...

  3. 5-24 css内容的补充

    1,标准文档流 宏观的将,我们的web页面和ps等设计软件有本质的区别,web 网页的制作,是个“流”,从上而下 ,像 “织毛衣”.而设计软件 ,想往哪里画东西,就去哪里画 标准文档流下 有哪些微观现 ...

  4. LOJ #6268 分拆数

    不会五边形数的菜鸡的分块乱搞 LOJ #6268 题意 求前$ n$个数的整数划分方案数,$ n \leq 10^5$ $ Solution$ 考虑暴力$ DP$ $ f(x,y)$表示放了$ x$个 ...

  5. day 8 - 1 文件操作

    文件操作 注意: 1. r+ 最为常用 2.encoding 的编码格式一定要与文件编码格式一致 读取 r  rb #在本地创建 txt 格式的文件默认使用 gbk 格式 f = open('e:/p ...

  6. mysql 原理 ~ 索引通说

    简介: 来说说索引吧目的:为了加快数据库的数据查找速度,索引应用而生基础知识基本定义  1 遍历 所谓遍历二叉树,就是按一定的规则和顺序走遍二叉树的所有结点,使每一个结点都被访问一次,而且只被访问一次 ...

  7. Sprng4之JDBC--很原始的使用方法

    \[www.dev1234.com]一头扎进Spring4视频教程\一头扎进Spring4源码\[www.java1234.com]<一头扎进Spring4>第九讲 源码\Spring40 ...

  8. Social Grouping for Multi-Target Tracking and Head Pose Estimation in Video(翻译)

    0 - ABSTRACT 许多计算机任务在缺少上下文信息的情况下的处理会更加困难.例如,在多相机跟踪任务下,行人可能在不同照相机下面因为有这不同的姿势和灯光条件而看起来很不一样.类似地,在低分辨率高角 ...

  9. Django学习手册 - 基于requests API验证(一)

    验证需要知道requests提交数据的几种方式: GET 方式: # get 方式,传递数值可以直接通过url传递:(服务端接受 GET) requests.get(url='http://127.0 ...

  10. Nginx在局域网中使用ip_hash负载均衡策略,访问全部分发到同一个后台服务器

    Nginx的ip_hash算法都将一个ip地址的前三段作为hash的关键字