什么是RPC?
RPC(Remote Procedure Call ——远程过程调用),它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络的技术。
一次RPC同步调用流程:
1)服务消费方(client)以本地调用方式调用客户端存根;
2)什么叫客户端存根?就是远程方法在本地的模拟对象,一样的也有方法名,也有方法参数,client stub接收到调用后负责将方法名、方法的参数等包装,并将包装后的信息通过网络发送到服务端;
3)服务端收到消息后,交给代理存根进行解码为实际的方法名和参数;
4) server stub根据解码结果调用服务器上本地的实际服务;
5)本地服务执行并将结果返回给server stub;
6)server stub将返回结果打包成消息并发送至消费方;
7)client stub接收到消息,并进行解码;
8)服务消费方得到最终结果。
RPC框架的目标就是要中间步骤都封装起来,让我们进行远程方法调用的时候感觉到就像在本地调用一样。
实现RPC框架需要解决的哪些问题
代理问题
代理本质上是要解决什么问题呢?要解决的是被调用的服务,本质上是远程的服务,但是调用者不知道也不关心,调用者只要结果,具体的事情由代理的那个对象来负责这件事。既然是远程代理,当然是要用代理模式了。
代理(Proxy)模式,通过代理对象访问目标对象。这样做的好处:在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。那我们这里额外的功能操作是干什么,通过网络访问远程服务。
jdk的代理有两种实现方式:静态代理和动态代理。
public class Client2 {
//远程调用类
public static IUserService getStub() throws Exception{
//创建代理类
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Socket socket = new Socket("127.0.0.1", 8888);
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(out);
dos.writeInt(13);
socket.getOutputStream().write(out.toByteArray());
socket.getOutputStream().flush();
DataInputStream dis = new DataInputStream(socket.getInputStream());
int ReceId = dis.readInt();
String name = dis.readUTF();
User user = new User(ReceId, name);
dos.close();
socket.close();
return user;
}
};
//执行动态代理(传入类加载器、接口、代理对象; 返回对象)
Object o = Proxy.newProxyInstance(IUserService.class.getClassLoader(),
new Class[]{IUserService.class},handler);
return (IUserService)o;
}
}
序列化问题
序列化问题在计算机里具体是什么?我们的方法调用,有方法名,方法参数,这些可能是字符串,可能是我们自己定义的java的类,但是在网络上传输或者保存在硬盘的时候,网络或者硬盘并不认得什么字符串或者javabean,它只认得二进制的01串,怎么办?要进行序列化,网络传输后要进行实际调用,就要把二进制的01串变回我们实际的java的类,这个叫反序列化。java里已经为我们提供了相关的机制Serializable。
举例,登记的服务实例化。
登记的服务有可能在我们的系统中就是一个名字,怎么变成实际执行的对象实例,当然是使用反射机制。
反射机制是什么?
反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取信息以及动态调用对象的方法的功能称为反射机制。
反射机制主要提供了以下功能:
•在运行时判断任意一个对象所属的类;
•在运行时判断任意一个类所具有的成员变量和方法;
•在运行时构造任意一个类的对象;
•在运行时调用任意一个对象的方法;
•生成动态代理。
public class Client3 {
//远程调用类
public static Object getStub(final Class clazz) throws Exception{
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Socket socket = new Socket("127.0.0.1", 8888);
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
//TODO 送入的class 灵活了
String className = clazz.getName();
String methodName = method.getName();
Class[] parametersTypes = method.getParameterTypes();
//TODO 传递class到服务器
oos.writeUTF(className);
oos.writeUTF(methodName);
oos.writeObject(parametersTypes);
oos.writeObject(args);
oos.flush();
//TODO 返回对象
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Object o = ois.readObject();
oos.close();
socket.close();
return o ;
}
};
Object o = Proxy.newProxyInstance(clazz.getClassLoader(),
new Class[]{clazz},handler);
return o;
}
}
/**
* 服务端:服务更灵活-提供多个类、多个方法的远程接口调用
*/
public class Server3 {
private static boolean running = true;
public static void main(String[] args) throws Exception{
ServerSocket serverSocket = new ServerSocket(8888);
while (running){
Socket socket = serverSocket.accept();
process(socket);
socket.close();
}
serverSocket.close();
}
private static void process(Socket socket) throws Exception{
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
ObjectInputStream ois = new ObjectInputStream(in);
//TODO 拿到客户端传递过来的class
String clazzName =ois.readUTF();
String methodName =ois.readUTF();
Class[] parameterTypes = (Class[])ois.readObject();
Object[] args =(Object[])ois.readObject();
//反射拿到class
Class clazz =Class.forName(clazzName);
if(clazz.isInterface()){
if(clazzName.equals("com.ivy.netty.pre.IUserService")){
clazz = UserServiceImpl.class;
}
//这里可以使用反射机制拿到所有接口对应的实现类
}
Method method = clazz.getMethod(methodName,parameterTypes);
Object object = method.invoke(clazz.newInstance(),args);
//TODO 返回值:使用对象进行返回
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(object);
oos.flush();
}
}
Dubbo是一个典型的RPC运用
-
服务容器负责启动,加载,运行服务提供者。
-
服务提供者在启动时,向注册中心注册自己提供的服务。
-
服务消费者在启动时,向注册中心订阅自己所需的服务。
-
注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
-
服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
-
服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
在网络通讯上,Dubbo和SpringCloud的差别
Dubbo和Spring Cloud都是目前非常流行的服务治理框架,它们都提供了分布式微服务的解决方案。在网络通讯上,Dubbo和Spring Cloud有以下差别:
- 通信协议: Dubbo使用自定义的RPC通信协议,支持多种序列化方式,如Hessian、Java序列化、JSON等。而Spring Cloud使用的是HTTP或其他的基于HTTP的RESTful风格的通信协议。
- 服务提供者和消费者的关系: Dubbo采用的是服务提供者和消费者的直连模式,消费者需要知道服务提供者的IP地址和端口号才能进行调用。而Spring Cloud是通过服务注册和发现机制构建的分布式系统,服务消费者只需要使用服务名即可调用相应的服务,无需关心服务提供者的IP地址和端口号。
- 服务注册中心: Dubbo采用Zookeeper作为默认的服务注册中心,而Spring Cloud可以使用Eureka、Nacos、Consul、Zookeeper等多种服务注册中心。
- 服务调用方式: Dubbo只支持同步调用方式,消费者发送请求到提供者并等待响应结果,而Spring Cloud支持同步和异步两种调用方式。
- 在性能方面,Dubbo的性能较Spring Cloud更加优秀。
需要注意的是,Dubbo和Spring Cloud并不是竞争关系,而是在不同的应用场景和业务需求下选择不同的技术架构。