RPC是什么?
RPC全称是远程过程调用。 也是一种计算机通信协议。
可以把它拆开来理解。远程就是其他的计算机,过程就是服务,调用就是客户端去调用服务。
总的来说就是可以调用远程计算机上的服务。通俗的讲就是你可以调用你的本地服务,使用RPC就可以调用别的计算机上的服务。
RPC的好处
用一个东西肯定要知道它有什么好的,不然用它干啥?
- 对于RPC,它可以基于HTTP和TCP协议。
- RPC,使用自定义的TCP协议,可以让请求报文体积更小,这样传输效率就大大提高了。
- 对于序列化和反序列化的性能消耗,RPC可以通过thrift去实现二进制数据的高效传输。
- RPC的负载均衡方面,也有自带的策略,使用起来就不会像HTTP那样还要配置Nginx等。
RPC调用过程
知道是什么,有什么好处,那我们就get它!一般我们学技术都要知道原理和过程,光会用不理解是会出问题的。
这是RPC基本的调用过程,我们通过注册中心去接收服务端的一些信息,然后客户端要执行方法前去注册中心查一下服务在哪,查到就带着参数去调用服务。
看懂这个过程,我们可以用Socket去写一个简单的RPC了。
开发RPC框架之前要了解一下RPC应该写些什么功能,了解步骤的一些情况吧。
功能有两个:暴露服务和引用服务。
开发步骤:
1. 实现RPC框架
2. 定义服务接口,并实现服务接口(实现类)
3. 暴露服务
4. 引用服务(调用)
第一步,先写服务框架类RpcFramework
package rpc_socket;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.ServerSocket;
import java.net.Socket;
//服务框架类
//暴露服务
//引用服务
public class RpcFramework {
/**
* 暴露服务,通过服务对象和服务端口号就能获得服务,也就相当于暴露服务给别人去调
* @param service 服务对象
* @param port 服务端口号
* @throws Exception
*/
public static void export(final Object service,int port) throws Exception{
//保证代码的健壮性,对参数进行校验
if(null==service){
throw new IllegalArgumentException("service instance == null");
}
if(port<=0 || port > 65535){
throw new IllegalArgumentException("Invalid port"+port);
}
//服务socket程序
ServerSocket serverSocket = new ServerSocket(port);
while (true){
//创建一个Socket网络套接字
final Socket socket = serverSocket.accept();
//一个请求对应一个线程
new Thread(new Runnable() {
@Override
public void run() {
//对象传输
ObjectInputStream objectInputStream = null;
ObjectOutputStream objectOutputStream = null;
try{
objectInputStream = new ObjectInputStream(socket.getInputStream());
//通过反射获取方法名
String methodName = objectInputStream.readUTF();
//获取方法参数的类型,泛型
Class<?>[] parameterTypes = (Class<?>[]) objectInputStream.readObject();
//获取方法实参 ,使用对象数组 类封装
Object[] arguments = (Object[]) objectInputStream.readObject();
//
objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
//反射里面的Method,得到了方法
Method method = service.getClass().getMethod(methodName,parameterTypes);
//调用方法并返回结果
Object result = method.invoke(service, arguments);//调用方法,传object和方法参数
//将方法执行完之后的结果返回,响应客户端
objectOutputStream.writeObject(result);
}catch (Exception e){
e.printStackTrace();
}finally {
//倒着关闭资源
try{
objectInputStream.close();
objectOutputStream.close();
socket.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}).start();
}
}
//不知道返回什么类型就返回泛型
//通过host确定主机,通过port确定端口
/**
* 引用服务,可以让客户端去调的
* @param interfaceClass
* @param host
* @param port
* @param <T>
* @return
* @throws Exception
*/
public static <T> T refer(final Class<T> interfaceClass,final String host,final int port) throws Exception{
if(interfaceClass == null){
throw new IllegalArgumentException("interface class == null");
}
//动态代理方式 , java 原生的动态代理对象,必须要实现接口
if(! interfaceClass.isInterface()){
throw new IllegalArgumentException("接口"+interfaceClass.getName()+"必须是接口类!!");
}
if(host == null || host.length() == 0){
throw new IllegalArgumentException("host == null");
}
if(port<=0 || port > 65535){
throw new IllegalArgumentException("Invalid port"+port);
}
//动态代理使用Proxy类,传入三个参数
return (T) Proxy.newProxyInstance(
//代理对象的类加载器
interfaceClass.getClassLoader(),
//代理对象的实现的接口的数据,数组是因为我们可以实现多个接口
new Class<?>[]{interfaceClass},
//使用动态代理的处理内部类方法
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//先构建Socket套接字 和服务端建立连接
Socket socket = new Socket(host,port);
ObjectOutputStream objectOutputStream = null;
ObjectInputStream objectInputStream = null;
try{
//服务端给啥。我们接啥
objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeUTF(method.getName());
objectOutputStream.writeObject(method.getParameterTypes());
objectOutputStream.writeObject(args);
//接住服务端给的result
objectInputStream = new ObjectInputStream(socket.getInputStream());
Object result = objectInputStream.readObject();
//返回对象
return result;
}catch (Exception e){
e.printStackTrace();
}finally {
//关闭资源
objectInputStream.close();
objectOutputStream.close();
socket.close();
}
return null;
}
});
}
}
注释写的很清楚了,我们这个类有两个方法,一个暴露服务,一个引用服务,
用到了反射技术去获取参数,服务方法等一些信息,
对于不确定的返回内容使用泛型可以缓解你的选择恐惧症,
还用到了jdk动态代理(一定要实现接口,没有接口就用不了),
还需要注意的是,使用流和socket,要正着开,反着关,否则容易出现泄露,异常错误一些的情况。
第二步:写个服务接口和实现类
//服务接口,say "hello world"
public interface HelloService {
String hello(String name);
}
//服务接口实现类,代码中调用接口,因为实现类实现了接口,那么调用接口时就会自动找到实现类
public class HelloServiceImpl implements HelloService{
@Override
public String hello(String name) {
return "hello"+name;
}
}
第三步:开发生产方(server)和消费方(consumer)
//服务提供者: 暴露服务/发布服务
public class RpcProduce {
public static void main(String[] args) throws Exception {//这个最好自己写自定义的异常,写Exception不好
HelloService helloService = new HelloServiceImpl();
//通过rpc框架的 export 方法开始暴露服务
RpcFramework.export(helloService,6666);
}
}
//服务消费者 : 消费服务,调用服务接口里面的方法的
public class RpcConsumer {
public static void main(String[] args) throws Exception {
//消费方是在另一台机器上,不能去new 接口去调用的
//所以我们用RPC框架的引用服务,所以这里实际是一个代理对象
HelloService helloService = RpcFramework.refer(HelloService.class,"localhost",6666);
//调用服务对象的方法
for (int i = 0; i < 10; i++) {
String hello = helloService.hello("world"+i);
System.out.println(hello);
Thread.sleep(1000);
}
}
}
最后测试:
先打开服务方,再打开调用方,结果如下:
一个简单的基于Socket的RPC框架就让我们实现了 。
rpc主要用于公司内部的服务调用,性能消耗低,传输高效,服务治理很方便。相对HTTP用于对外的异构环境,浏览器接口调用,app和第三方接口调用来说使用更优!
当然最重要的还是那个流程图,一定要理解,对学微服务,分布式,SOA啥的都有很大帮助!
如果觉得写的还不错的话,请点点关注喔!