分布式通讯架构RPC简单实现
什么是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简单实现的更多相关文章
- alluxio源码解析-rpc调用概述-client和worker之间的block模块的通讯架构(netty版本)(3)
(1.8版本)client和worker之间的block模块的通讯架构 block作为alluxio文件读取或者存储的最小基本单位,都是通过BlockOutStream和BlockInputtream ...
- Java[2] 分布式服务架构之java远程调用技术浅析(转http://www.uml.org.cn/zjjs/201208011.asp)
转自:http://www.uml.org.cn/zjjs/201208011.asp 在分布式服务框架中,一个最基础的问题就是远程服务是怎么通讯的,在Java领域中有很多可实现远程通讯的技术,例如: ...
- 【转】RPC简单介绍
RPC简单介绍 RPC 1. RPC是什么 RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络 ...
- 分布式服务(RPC)+分布式消息队列(MQ)面试题精选
分布式系统(distributed system)是建立在网络之上的软件系统.正是因为软件的特性,所以分布式系统具有高度的内聚性和透明性.因此,网络和分布式系统之间的区别更多的在于高层软件(特别是 ...
- .net 大型分布式电子商务架构说明
.net大型分布式电子商务架构说明 背景 构建具备高可用,高扩展性,高性能,能承载高并发,大流量的分布式电子商务平台,支持用户,订单,采购,物流,配送,财务等多个项目的协作,便于后续运营报表,分析,便 ...
- 分布式网站架构后续:zookeeper技术浅析
Zookeeper是hadoop的一个子项目,虽然源自hadoop,但是我发现zookeeper脱离hadoop的范畴开发分布式框架的运用 越来越多.今天我想谈谈zookeeper,本文不谈如何使用z ...
- net大型分布式电子商务架构
net大型分布式电子商务架构 背景 构建具备高可用,高扩展性,高性能,能承载高并发,大流量的分布式电子商务平台,支持用户,订单,采购,物流,配送,财务等多个项目的协作,便于后续运营报表,分析,便于运维 ...
- Atitit.分布式远程调用 rpc rmi CORBA的关系
Atitit.分布式远程调用 rpc rmi CORBA的关系 1. 远程调用(包括rpc,rmi,rest)1 2. 分布式调用大体上就分为两类,RPC式的,REST式的1 3. RPC(远程 ...
- zz《分布式服务架构 原理、设计与实战》综合
这书以分布式微服务系统为主线,讲解了微服务架构设计.分布式一致性.性能优化等内容,并介绍了与微服务系统紧密联系的日志系统.全局调用链.容器化等. 还是一样,每一章摘抄一些自己觉得有用的内容,归纳整理, ...
随机推荐
- 进程池爬取并存入mongodb
设置进程池爬取拉钩网: # coding = utf- import json import pymongo import pandas as pd import requests from lxml ...
- linux 统计某目录文件的行数
通过find 正则搜索文件 find . -regex '.*\.c\|.*\.h' 每个文件的行数 find . -regex '.*\.c\|.*\.h' | xargs wc -l 显示文件的总 ...
- 【Git】Git常见错误
错误1.fatal: refusing to merge unrelated histories 致命的:拒绝合并不相关的历史 原因:比如我本地分支是V1.0,我现在想要合并远程master分支上的内 ...
- Django学习手册 - ORM数据类型
DOM 字段/参数 配置格式: Module.字段(参数) 常用的字段归纳: 数字 models.AutoField() 自增列(int),必须设置为主键 models.IntegerField() ...
- JAVA锁和volatile的内存语义&volatile的使用场景
JAVA锁的内存语义 当线程释放锁时,JMM(Java Memory Model)会把该线程对应的本地内存中的共享变量刷新到主内存中. 当线程获取锁时,JMM会将该线程对应的本地内存置为无效.从而使得 ...
- 核心编程9 文件和文件的输入输出 (os模块)
1 python内建函数open和file 文件打开方便读取:f = open('文件名','模式','缓冲模式') #'r'读取,'w'写入(先清空后创建).'a'追加 详情文件模 ...
- v2v-VMware/VSphere中虚机离线迁移至openstack平台
先决条件 exsi到openstack的迁移,分为两种,一种是静态迁移,另一种是在线迁移. 静态迁移(offline migration)也叫做常规迁移,离线迁移.在迁移之前将虚拟机暂停,同时拷贝虚拟 ...
- flask 连接数据库
FLASK 连接mysql 数据库 1 # -*- encoding: utf-8 -*- 2 3 from flask import Flask 4 #导入第三方连接库 5 from flask_s ...
- 刷题之路第三题--Longest Substring Without Repeating Characters
问题简介:求给定字符串中最长的字符不重复的字符串的长度 问题详解: 给定一个字符串,寻找给定字符串中包含的最长的字符不重复的字符串的长度 注:答案必须是子字符串,不是子序列 是连续的字符不重复的字符串 ...
- 使用SQL*Plus连接数据库
About SQL*Plus SQL*Plus is the primary command-line interface to your Oracle database. You use SQL*P ...