什么是RPC
RPC即远程过程调用(Remote Procedure Call),通过RPC可以实现在本地对远程服务的调用,用于分布式系统中不同服务间的调用。比如订单服务、仓库服务部署在两台机器上,创建订单时需要查询仓库中的库存,此时就可以使用RPC。
为什么要用RPC
就如上面所述,查询库存,直接发送REST请求不就可以了,为什么要用RPC。REST请求使用HTTP协议实现,而RPC则不一定使用HTTP。比如常用的RPC框架Dubbo支持的协议就有dubbo、rmi、http等,这里是Dubbo支持的协议,感兴趣的可以看一下 。RPC 可以获得更好的性能(省去了 HTTP 报头等一系列东西),记得看过之前看过一篇测试的文章,dubbo协议的RPC请求的效率是HTTP请求的1.5~2倍左右。
RPC原理及简单RPC框架Demo
1、客户端调用服务端提供的服务,客户端需要拿到服务端注册发布的接口。
2、客户端调用接口的时候通过网络传输数据到服务端(协议就是用于网络层)。
3、服务端处理请求后,返回给客户端结果。
接口定义及实现
public interface IHello {
String hello(String msg);
}
暴露服务的接口,服务端及客户端都有
public class HelloImpl implements IHello {
public HelloImpl() {}
@Override
public String hello(String msg) {
return "Hello," + msg;
}
}
接口实现,只有服务端有
客户端
客户端需要与服务端建立网络连接,并传输数据,接收返回结果。对于客户端来说,只关心调用的是接口的什么方法及参数,数据传输由框架完成。这里用到JDK的动态代理,调用服务接口时,代理类进行处理。
RpcProxy
public class RpcProxy {
public static Object getClientProxy(String serviceName, Class<?> service, String host, int port) {
return Proxy.newProxyInstance(service.getClassLoader(), new Class[]{service},
new RpcInvocationHandler(serviceName, host, port));
}
}
RpcInvocationHandler
public RpcInvocationHandler(String service, String host, int port) {
this.service = service;
this.host = host;
this.port = port;
}
/**
* 使用socket连接,传递需调用的服务、方法、参数信息到服务端
* 返回调用结果
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//建立连接
Socket socket = null;
ObjectOutputStream objectOutputStream = null;
ObjectInputStream objectInputStream = null;
Object result = null;
RpcRequest rpcRequest = new RpcRequest(service, method.getName(), args);
try {
socket = new Socket(host, port);
objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(rpcRequest);
objectOutputStream.flush();
objectInputStream = new ObjectInputStream(socket.getInputStream());
result = objectInputStream.readObject();
} catch (Exception e) {
System.out.println("远程调用失败");
} finally {
if (null != objectInputStream) {
objectInputStream.close();
}
if (null != objectOutputStream) {
objectOutputStream.close();
}
if (null != socket) {
socket.close();
}
}
return result;
}
RpcRequest:网络传输封装的对象,实现序列化接口。
public RpcRequest(String serviceName, String methodName, Object[] args) {
this.serviceName = serviceName;
this.methodName = methodName;
this.args = args;
}
ClientDemo
public class ClientDemo {
private static final String host = "127.0.0.1";
private static final int port = 9999;
public static void main(String[] args) {
IHello helloService = (IHello) RpcProxy.getClientProxy("HelloService",
IHello.class, host, port);
System.out.println(helloService.hello("RPC"));
}
}
服务端
服务端注册服务,监听网络连接,处理请求后返回结果。
public class Server {
//serviceName -> serviceImpl
private Map<String, Object> services;
private int port;
private ExecutorService executorService;
public Server(int port) {
services = new HashMap<>();
this.port = port;
executorService = Executors.newCachedThreadPool();
}
/**
* 服务发布
*/
public void register(String serviceName, Object service) {
if (service != null) {
services.put(serviceName, service);
System.out.println("服务注册成功");
}
}
/**
* 监听
* 服务处理
* */
public void start() {
ServerSocket serverSocket = null;
Socket socket = null;
try {
serverSocket = new ServerSocket(port);
System.out.println("服务启动");
for(;;) {
socket = serverSocket.accept();
executorService.submit(new RequestProcessor(socket, services));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != serverSocket) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != socket) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
RequestProcessor 请求处理线程
public class RequestProcessor implements Runnable {
private Socket socket;
private Map services;
public RequestProcessor(Socket socket, Map services) {
this.socket = socket;
this.services = services;
}
@Override
public void run() {
System.out.println("开始处理请求");
ObjectInputStream objectInputStream = null;
ObjectOutputStream objectOutputStream = null;
try {
objectInputStream = new ObjectInputStream(socket.getInputStream());
RpcRequest request = (RpcRequest) objectInputStream.readObject();
Object[] args =request.getArgs();
Class<?>[] paraTypes = new Class[args.length];
for (int i = 0 ; i < args.length; i++) {
paraTypes[i] = args[i].getClass();
}
Object service = services.get(request.getServiceName());
if (null != service) {
Method method = service.getClass().getMethod(request.getMethodName(), paraTypes);
Object object = method.invoke(service, request.getArgs());
objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(object);
objectOutputStream.flush();
System.out.println("处理完毕");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != objectInputStream) {
try {
objectInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != objectOutputStream) {
try {
objectOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != socket) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
RpcServerDemo
public class RpcServerDemo {
public static void main(String[] args) {
IHello helloService = new HelloImpl();
Server server = new Server(9999);
server.register("HelloService", helloService);
server.start();
}
}
启动服务端后,启动客户端
客户端输出:Hello,RPC
服务端输出:服务注册成功
服务启动
开始处理请求
处理完毕
至此,一个简单的RPC实现就完成了。
思考:
1、客户端接口代理的通过Spring的AOP实现?
2、客户端储存着服务端的IP地址及端口信息,为了提高系统吞吐量,服务端会有多个提供者,客户端怎么高效的、动态的感知和存储服务端的网络信息;服务的负载均衡?
3、服务端的接口实现通过Spring实现注入和管理?