分布式通讯架构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远程服务调用是分布式项目的核心,接下去我们可以一步步的去展开分布式服务的学习拉。。。