RPC 整理

RPC-Reomto proceducer call 远程过程调用 基于 java

一下主要基于自己的理解、这里我将http调用也视为一种rpc调用。

远程 这里远程应该相对于进程而言,甚至可能是线程。

过程调用 简单地说就是执行代码(服务、业务逻辑)。

远程协议(姑且这么叫) 既然要远程,势必设计两端之间的通讯,在最通用化的假设下,该协议一般就是网络协议。所以相关的远程协议一般设计TCP/IP(Socket通讯),http通讯(主流的rest Api就可以看成是一种远程调用),以及基于TCP/IP协议族的自定义协议。

调用的核心 从调用的时间流来看,调用的核心主要就是告诉目标服务要请求的服务,要传递的参数、以及客户端取得最终的执行结果。

RPC的实现主要的两种类型 一种以http为主,纯粹的http请求,返回结果,还有一种以TCP/IP为主,类似于实现一个服务代理,屏蔽网络请求,就像纯粹的本地调用一样(当然也可能是基于http协议封装的框架、如spring cloud的Fetch技术)。

RPC的实现 面对单一语言的RPC是相对简单的,对于要跨语言的最主要的是要保持调用接口的同意。

RPC、RMI、SOAP、REST RPC强调的的是端之间的调用和结果返回;RMI可以说是RPC的一种实现,是一种用面向对象技术实现的RPC技术;SOAP 简单对线访问协议,强调协议性和服务;REST(Representational State Transfer)一种特殊的RPC,既可以是RPC,也可以不是,一种网络应用的架构设计风格,可以认为是一种轻量级的SOAP,弱化了协议的要求、使用一种类似于约定的协议模式。

微服务与远程调用的关系 微服务的一个核心技术就是服务调用链,微服务中存在着大量的服务调用,服务间调用根据距离度量可以使用Eventbus、RPC、http等调用,Eventbus主要的适合的场景还是应用内调用,当然也有把远程调用封装成Eventbus形式的,RPC和http(rest)基本是同级的,甚至在有些概念下是有部分重合的。http相对于RPC协议的优点是目前为止大部分的服务都是以http形式出现的,http协议拥有最为出色的跨平台跨协议和历史性,开发部署十分简单。而RPC的主要优点就是效率和调用,大部分的RPC框架都基于TCP、UDP协议来实现,因此相比http,RPC框架往往拥有更高的效率,其次是调用问题,大部分RPC框架都是通过类似代理的技术手段,屏蔽了远程调用,使远程调用看起来像本地调用一样。

分布式与微服务的关系 分布式服务服务其实也是一种微服务,只不过微服务是一种架构理念、更强调的是服务的粒度化、服务的编排,以一个一个服务为设计开发的单位,由于概念更抽象,其事务性质可能很难保障,而分布式系统主要是一个系统,往往是分布式系统中所有的微服务都是为一个核心产品的实现服务,其服务间调用可能更要强调事务性。

主流远程框架 Thrift、谷歌的基于ProtoBuf的gRPC、Dubbo、Hessian、java RMI。

主流框架简介

对于大部分远程框架、技术,其实现理念总是实现一个服务接口,然后在背调服务上实现服务的实现,通过一些技术手段,创建一个接口的实现类,通过该类进行远程调用,对于更通用的远程服务,可能还会实现一个类似于注册中心的东西,用于注册和发现服务,与CS中的服务器和客户端不同,在RPC中客户端和服务端的地位是相同的,他们可以相互提供服务,其实有时候还是应该不同,至少从表现上来看是这样子的。

java RMI

RMI使用java的远程消息交换协议(JRMP-Java Remote Message Protocol)实现远程协议,只能在java语言开发的系统间进行RPC。所有的java参数、返回值都必须是可序列化的(这里把基本数据类型也视为可序列化)。首先看一下RMI的调用链

这里我们关注的rmi核心是Sub,Sub主要的工作是暴露服务端提供的接口,然后在调用的时候帮你屏蔽网络细节,让你可以像本地调用一样调用远程服务。Sub的消息通过向下传输、网络传输后最终到达Skeleton,然后Skeleton调用Server上的真正实现。

要实现一个RMI调用,基础步骤如下:

1、创建你的服务接口、实现你的服务。

2、编写服务端、客户端程序。

3、注册服务、远程调用。

首先来实现一个Echo服务

// service

public interface EchoService extends Remote {

String echo(String name) throws RemoteException;

}
// service impl

public class EchoServiceImpl extends UnicastRemoteObject implements EchoService {

public EchoServiceImpl() throws RemoteException {

super();

}

public String echo(String name) throws RemoteException {

return String.format("hello %s", name);

}

}

服务实现的唯一不同就是要实现一个远程接口 Remote代表一个远程服务注册到Naming中心,其实现要继承UnicastRemoteObject这样可以保证客户端可以获取到这个对象的存根

​
// server

public class Server {

public static void main(String[] args) {

try {

LocateRegistry.createRegistry(1099);

Naming.bind("rmi://localhost:1099/echo", new EchoServiceImpl());

} catch (RemoteException | MalformedURLException | AlreadyBoundException e) {

e.printStackTrace();

}

}

}

​
// client

public class Client {

public static void main(String[] args) {

try {

EchoService echoService = (EchoService) Naming.lookup("rmi://localhost:1099/echo");

System.out.println(echoService.echo("jsen"));

} catch (RemoteException | NotBoundException | MalformedURLException e) {

e.printStackTrace();

}

}

}

这里没有看到Stub和Skelton,因为在jdk1.4后rmi使用动态代理来实现RPC技术。

自己使用Sub、Skelton方法实现(RMI)远程调用

// EchoServiceSub

public class EchoServiceSub implements EchoService {

private Socket socket;

public EchoServiceSub(Socket socket) {

this.socket = socket;

}

@Override

public String echo(String name) {

try(Socket s = socket) {

ObjectOutputStream objectOutputStream = new ObjectOutputStream(s.getOutputStream());

objectOutputStream.writeObject("echo##" + name);

objectOutputStream.flush();

return new ObjectInputStream(s.getInputStream()).readObject().toString();

} catch (IOException | ClassNotFoundException e) {

e.printStackTrace();

}

return null;

}

}
// EchoServiceSkeleton

public class EchoServiceSkeleton extends Thread {

private EchoService echoService;

private EchoServiceSkeleton(EchoService echoService) {

this.echoService = echoService;

}

@Override

public void run() {

try {

var serverSocket = new ServerSocket(8080);

var socket = serverSocket.accept();

while (socket != null) {

ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());

String parameter = objectInputStream.readObject().toString();

String[] parameters = parameter.split("##");

String method = parameters[0];

String parm = parameters[1];

ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());

if ("echo".equals(method)) {

objectOutputStream.writeObject(echoService.echo(parm));

} else {

objectOutputStream.writeObject("no method found");

}

objectOutputStream.flush();

socket = serverSocket.accept();

}

} catch (IOException | ClassNotFoundException e) {

e.printStackTrace();

}

}

public static void main(String[] args) throws RemoteException {

new EchoServiceSkeleton(new EchoServiceImpl()).start();

}

}
// ClientSub

public class ClientSub {

public static void main(String[] args) {

try {

EchoService echoService = new EchoServiceSub(new Socket("localhost", 8080));

System.out.println(echoService.echo("jack"));

} catch (IOException e) {

e.printStackTrace();

}

}

}

使用动态代理实现(RMI)

自己实现代理就是直接通过客户端参数在服务器上动态构造实现类,执行远程命令,客户端可以使用动态代理来屏蔽网络调用的细节,我们还可以利用spring的BeanFactory来实现服务端实现类的管理。

这里我们首先定义一个Protocol类来封装请求:

// Protocol

@Data

@EqualsAndHashCode(callSuper = false)

@Accessors(chain = true)

@AllArgsConstructor

public class Protocol implements Serializable {

private Class<?> serviceImplName;

private String methodName;

private Object[] params;

Class<?>[] paramTypes;

}

服务端核心:创建实现类,执行方法,返回结果

// Handler

public class Handler extends Thread {

private Socket socket;

public Handler(Socket socket) {

this.socket = socket;

}

@Override

public void run() {

try(Socket s = socket) {

ObjectInputStream objectInputStream = new ObjectInputStream(s.getInputStream());

Protocol protocol = (Protocol)objectInputStream.readObject();

Object result = protocol.getServiceImplName().getDeclaredMethod(protocol.getMethodName(),

protocol.getParamTypes()).invoke(protocol.getServiceImplName().getConstructor().newInstance(),

protocol.getParams());

ObjectOutputStream objectOutputStream = new ObjectOutputStream(s.getOutputStream());

objectOutputStream.writeObject(result);

} catch (IOException | ClassNotFoundException | IllegalAccessException | InvocationTargetException | InstantiationException | NoSuchMethodException e) {

e.printStackTrace();

}

}

}

客户端核心:动态代理屏蔽网络细节,这里没有创建一个实现类,只是实现了一个EchoService的Proxy,调用EchoService的每个方法(API),会自动封装参数,请求网络。

// Proxy

public class Proxy<T> {

public T createProxy(Class<?> serviceImplName, InetSocketAddress inetSocketAddress) {

return (T) java.lang.reflect.Proxy.newProxyInstance(serviceImplName.getClassLoader(), new Class<?>[]{serviceImplName.getInterfaces()[0]}, (proxy, method, args) -> {

Socket socket = new Socket();

socket.connect(inetSocketAddress);

try(Socket s = socket) {

Protocol protocol = new Protocol(serviceImplName, method.getName(), args, method.getParameterTypes());

ObjectOutputStream objectOutputStream = new ObjectOutputStream(s.getOutputStream());

objectOutputStream.writeObject(protocol);

ObjectInputStream objectInputStream = new ObjectInputStream(s.getInputStream());

return objectInputStream.readObject();

}

});

}

}

服务端:直接简单粗暴的socket while true

// ServerProxy

public class ServerProxy extends Thread {

private int port;

public ServerProxy(int port) {

this.port = port;

}

@Override

public void run() {

try {

var serverSocket = new ServerSocket(port);

while (true) {

new Handler(serverSocket.accept()).start();

}

} catch (IOException e) {

e.printStackTrace();

}

}

public static void main(String[] args) {

new ServerProxy(8080).start();

}

}

客户端:

// ClientProxy

public class ClientProxy {

public static void main(String[] args) throws RemoteException {

EchoService echoService = new Proxy<EchoService>().createProxy(EchoServiceImpl.class, new InetSocketAddress("localhost", 8080));

System.out.println(echoService.echo("proxy"));

}

}

Thrift、gRpc

thrift最早由facebook于2007年开发,是一个跨语言的RPC框架,其主要支持的语言有http://thrift.apache.org/docs/Languages,由于是一个跨语言的,因此就必须要定义一个特殊的格式(protocol)来起到连接各种语言消除差异的媒介,因此,包括gRPC也是这样,都先要定义IDL语言,由IDL来生成各种语言下的适配实现。

1、生成一个最简单的echo.thrift IDL

namespace java com.jsen.test.thrift.service

// 定义服务

service EchoService {

string echo(1:string name)

}

2、编译IDL文件 thrift -r -gen java echo.thrift 调用该命令会生成一个gen-java的包,该包下会有一个与服务同名的EchoService.java,这个文件是thrift的核心,该类下有三个子类Client,Processor,IFace机器异步实现Async类,其中IFace类是服务接口类,Client和Processor分别是客户端类和服务端类,我们要把实现逻辑写在IFace的实现类里面,因此接下来写一个Echo实现类。

public class EchoServiceImpl implements EchoService.Iface {

public String echo(String name) throws TException {

return String.format("hello %s", name);

}

}
public class Server {

public static void main(String[] args) {

try {

TProcessor processor = new EchoService.Processor<EchoService.Iface>(new EchoServiceImpl());

TServerSocket serverSocket = new TServerSocket(8080);

TServer.Args tArgs = new TServer.Args(serverSocket);

tArgs.processor(processor);

tArgs.protocolFactory(new TBinaryProtocol.Factory());

TServer tServer = new TSimpleServer(tArgs);

tServer.serve();

} catch (TTransportException e) {

e.printStackTrace();

}

}

}
public class Client {

public static void main(String[] args) {

try(TSocket tSocket = new TSocket("localhost", 8080)) {

TProtocol tProtocol = new TBinaryProtocol(tSocket);

EchoService.Client client = new EchoService.Client(tProtocol);

tSocket.open();

System.out.println(client.echo("thrift"));

} catch (TException e) {

e.printStackTrace();

}

}

}

3、上面代码中有两个重要概念TTransport和TProtocol,TTransport是定义传输的,TProtocol是协议层提供了各种默认的传输协议,也可以自定义传输协议,常见的有TBinaryProtocol、TJSONProtocol、TDebugProtocol等协议。因此thrift支持多种传输方式和传输协议。

4、还有一个概念叫TServer,定义的是服务类型,这里的TSimpleServer是一个单线程的服务,一般用于测试,TThreadPoolServer和TNonblockingServer分别是多线程下的阻塞和非阻塞IO服务。

猜你喜欢

转载自blog.csdn.net/jsenht/article/details/82712717
RPC