前置知识
了解rpc 和http的不同
对于服务来说有基于http的restful风格的远程调用还有rpc的远程调用
它们之间最显著的区别就是 http一般对外服务之间的调用
而rpc则是内部的方法调用
好比http的调用就是用户购买模块调用购物车模块
而rpc则是购买模块内部请求其他集齐上资源的远程调用
tcp 和 udp
我们知道tcp 和 udp 都是传输层的协议但是它们之间还是有很多不同的
tcp是安全的可靠的但是效率低
udp是无状态的不可靠的但是效率高
远程服务调用的过程
tips: 序列化就是将我们的对象转化为报文,而对应的反序列化就是反过来
(字符流 - > 字节流)
搭建RPC之前的考虑
-
建立远程通信
- socket (BIO)
- netty(NIO)
-
数据传输
-
序列化和反序列化
xml/json/Protobuf/avro/kyro.hessian
这些序列化和反序列化的技术一般注重点有几个
- 是否跨语言
- 压缩比(压缩的比较小那么传输的效率就比较高)
- 序列化效率
调用条件
比如a 需要调用远端的b上的服务首先肯定是需要知道机器的ip和端口号 才知道是哪一个进程
然后传递的参数: 知道是哪一个类,哪一个方法,具体的形参
而且远程调用的使用反射来调用方法
Http Client 分装好的框架
- okhttp
- feigh (底层继承了ribbon 负载均衡 拿到注册中心的服务)
- web service
- restTempleate
动态代理
这里使用jdk的的代理模式
public class Proxy{
public <T> T proxyThisMethod(final Class<T> interfaceCls){
return (T)Proxy.newProxyInvocation(interfaceCls.getClassLoader(),new Class<?>[] @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return "代理中";
}
});
}
}
原码实现
下面进行每一个模块的分层思路
不要忘记了将生产者端的接口模块打成jar包使用maven导入消费者端
- 首先需要有两个项目 一个是消费者,一个是生产者
- 生产者这边有两个部分,一个是暴露的接口部分,一个是内部的业务代码
- 消费者这里先将生产者的暴露接口导入项目中
- 暴露的接口内部除了接口之外还有一个 反射调用的实体类 也是消费者通过rpc传递的消息
- 类名
- 方法名
- 参数类型
- 参数值
// 传递的参数
public class RpcRequest{
private String className;
private String methodName;
private Object[] params;
private Class[] types;
}
// 接口
public interface IHello{
public void hello(String name);
}
- 消费者需要实现一个jdk的动态代理类,这个类还需要接收传递的ip和端口
public class RpcProxy{
public T <T> invokeThisMeth(final Class<T> interfaceCls,String ip , int port){
return (T)Proxy.getNewInvodeMethod(interface.classLoader(),Class<t>[]{interface},new RpcProxyHandler(ip,port))
}
}
- 消费者需要创建这个handler用来动态代理类 内部调用了实际发送对象的send方法
public class RpcProxyHandler implemnets InvocationHandler{
private String ip;
private Integer port;
public Object invode(Object proxy, Method method, Object[] args){
RpcRequest rpc = new RpcRequest();
// 设置调用的类名
rpc.setMethodName(method.getDeclaringClass().getName())
// 调用的方法名
rpc.setClassName(method.getName());
// 调用的参数
rpc.setObject(args);
// 调用的参数类型
rpc.setTypes(method.getParameterTypes())
RpcSendMessage msg = RpcSendMessage(rpc,ip,port);
return msg.sendMessage(ip,port);
}
}
- 费者继续实现这个对应的发送方法
public class RpcProxySendMessage{
private String ip;
private int port;
public Object sendMessage(Object rpcRequest,String ip,int port){
try(
Socket socket = new Socket(ip,port);
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
) {
outputStream.wirteObject(rpcRequest);
outputStream.flush();
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
return inputStream.readObject();
} catch(Exception e){
} finally {
inputStream.flush();
}
return null;
}
}
- 在生产者这边创建一个接口的实现类
public class IHelloImpl implements IHello{
public void hello(String name){
sout..name..rpcProxy..ok
}
}
- 在生产者端实现消息的接收类和方法
public class ProxyClass{
private String ip;
private int port;
public Objcet publisher(Object object){
// 创建多线程
ExecutorService executorService = Executors.newFixedThreadPool(10);
try(
Socket socket = new Socket(ip,port);
ObjectInputStream inputStream = new InputStream(socket.getInputStream());
ObjectOutputStream outputStream = new OutputStream(socket.getOutputStream());
){
while(true){
socket.accpte();
executorService.executor(()->{
try(
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutoutStream());
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
){
// 入参
Object rpcRequest = inputStream.readObject();
// 调用反射
Object invoke = invoke(rpcRequest);
// 出参
outputStream.writeObject(invoke,obj);
} finally {
}
})
}
} catch (Exception e){
} finally {
}
}
// 反射
public Object invoke(RpcRequest rpcRequest, Object obj){
// 通过类名拿到class对象
Class clazz = Class.forName(rpcRequest.getClassName());
// 通过方法名返回类下对应的方法
Method method = clazz.getMethod(rpcRequest.getMethodName(),rpcRequest.getObject());
// 反射调用
return method.invoke(obj,rpcRequest.getTypes());
}
}
- 生产者将对应的实现方法通过publisher发布到对应的端口
public class Main{
psvm{
IHelloImpl hello = new IHelloImpl();
ProxyClass proxy = new ProxyClass("localhost",8080);
proxy.publisher(hello);
}
}
- 消费者通过调用代理类调用远程方法
public class Main{
psvm{
RpcProxySendMessage rpc = new RpcProxySendMessage();
IHello hello = rpc.sendMessage(IHello.class,"localhost",8080);
hello.hello("msg");
}
}
调用结果
总结
其实这个小demo包含挺多的 首先比较难记的就是形参,具体需要的参数重复多 必须要进一步的理解以下业务的过程
首先创建接口和实现类 这些都是在服务端的 客户端需要远程调用这个方法,而因为只有接口暴露出来,所以对应的动态代理类需要的形参肯定有这个接口的class类,之后通过动态代理返回一个代理之后的对象,直接调用方法就可以了
上述是发生在客户端的调用过程
而客户端的代理过程具体需要实现需要创建一个固定写法的代理类和handler类来管理本次的信息和发送数据
而这个代理类需要接收的参数除了需要代理的类之外还有调用的ip地址和端口将这个参数传到handler中 而这个类需要实现invokemethodhandler接口 重写内部的invoke代理方法以及调用发送方法 代理方法没什么好说的就是填入rpc的传递参数 然后将ip和端口初始到发送类中 让后把初始化的rpc实体类传递给发送方法
而发送方法需要新建一个bio的socket通信模式,新建一个socket 初始化ip和端口 新建对应的inputStream和outputStream到try中 调用出参的outputStram中的writeObject方法 让后调用入参的inputStream.readObject()返回
以上就是客户端的全过程
服务端首先需要新建一个类来发布这个接口的实现类 这个发布类中除了发布的方法还需要有一个反射方法来调用本地方法
发布的方法肯定需要接收一个发布的类obj 至于发布的端口号和ip都在初始化的时候将参数传递进来了 首先我们保持客户端的持续监听 于是初始化socket 之后就将socket.accept放入一个whilel(true)中内部还需要先接收入参inputStream.readObject() 将发送过来的rpcRequest对象和刚刚需要代理的类一起发送给反射方法
反射方法首先通过rpc的参数中的类名,拿到对应的class对象 之后传递rpc参数中的方法名和参数类型调用对象中的getMethod方法返回一个method对象 最后放回method对象中的invoke方法将需要代理的对象和参数的具体值传递进去
接收到反射方法返回的对象之后将这个对象通过出参返回回去outputStream.writeObject(inboikr);最后关闭就结束了