RPC学习笔记

介绍

从单机走向分布式,产生了很多分布式的通信方式

  • 最古老有效也是最不过时的TCP/UDP二进制传输,事实上所有的通信方式归根到底是TCP/UDP
  • CORBA(Common Object Request Broker Architecture) 古老而复杂的,支持面向对象的通信协议
  • Web Service(SOA SOAP RDDI WSDL..) 基于HTTP+XML的标准化Web Api
  • Restful (Represenational State Transfer) 回归简单化本源的Web Api事实标准 http+json
  • RMI Remote method invocation Java内部的分布式通信协议
  • JMS Java Message Service JavaEE中的消息框架标准,为很多MQ所支持
  • RPC Remote Procedure Call 远程方法调用,这只是一个统称,重点在于方法的调用(不支持对象的概念),具体实现甚至可以用RMI Restful等等去实现,但一般不用,因为RMI不能跨语言,restful效率太低。多用于服务器集群间的通信,因此常使用更加高效,短小精悍的传输模式以提高效率
    完整RPC框架:序列化,服务通信,服务注册,服务发现,服务治理,服务监控,服务负载均衡

实现

1

最原始的客户端 服务端

public iterface IUserService{
  User finUserById(int id);
}
public class IUserServiceImpl implements IUserService {
    @Override
    public User findUserById(int id) {
        return new User(id,"Alice");
    }//直接new模拟数据库查询
}
public class Client {
   public static void main(String[] args) throws Exception {
  Socket socket = new Socket('127.0.0.1', 8088)
  ByteArrayOutputStream baos = new ByteArrayOutputStream()
  DataoutputStream dos = new DataoutputStream(baos);
  dos.write(123); // 缺少灵活性
  // 发送查询id
 socket.getOutputStream().write(baos.toByteArray()
 socket.getOutputStream().flush()
  // 接收服务器返回结果
  DataInputStream dis = new 
  DataInputStream(socket.getInputStream())
  int id = dis.readInt();
  String name = dis.readUTF();
  User user = new User(id, name);
  dos.close();
  socket.close();
)
}
}
public class Server {
   public static void main(String[] args) throws Exception {
  DataInputStream dis = new DataInputStream(socket.getInputStream())
  DataOutputStream dos = new DataOutputStream(socket.getOutputStream())
  int id = dis.readInt()
  IUserService service = new IUserServiceImpl();
  User user = service.findUserById(id);
  dos.write(user.getId())
  dos.writeUTF(user.getName())
  dos.flush()
}}

2

客户端不需要知道网络细节,只需要知道接口用法

public class Client {
  ...
    Stub stub = new Stub();
     ...(stub.findUserById(123));
}
public class Server{
  public static boolean running = true;
  ...main... {
    ServerSocket server = new ServerSOcket(8080)
    while(runnning) {
      Socket client = server.accept()
      process(client)
      client.close()
    }
    server.close()
}
  public static void process(Socket socket) throws Exception {
     DataInputStream dis = new DataInputStream(socket.getInputStream())
  DataOutputStream dos = new DataOutputStream(socket.getOutputStream())
  int id = dis.readInt()
  IUserService service = new IUserServiceImpl();
  User user = service.findUserById(id);
  dos.write(user.getId())
  dos.writeUTF(user.getName())
  dos.flush()
}
}
public class Stub {
  public User findUserById(int id) .. {
    Socket socket = new Socket('127.0.0.1', 8088)
  ByteArrayOutputStream baos = new ByteArrayOutputStream()
  DataoutputStream dos = new DataoutputStream(baos);
  dos.write(123); // 缺少灵活性
  // 发送查询id
 socket.getOutputStream().write(baos.toByteArray()
 socket.getOutputStream().flush()
  // 接收服务器返回结果
  DataInputStream dis = new 
  DataInputStream(socket.getInputStream())
  int idtmp = dis.readInt();
  if (idtmp != id) ...('error')
  String name = dis.readUTF();
  User user = new User(id, name);
   return User;
  }
}

3

改动客户端,在rpc2的基础上,把stub改成代理方式,stub不是new出来的,而是使用他的静态方法getStub,这个版本相对于上个版本的优点还不能直观体现,因为硬编码部分还没有改动完成,rpc4将通用化这个动态代理

// client
IUserService stub = Stub.getStub()
...(stub.findUserById(123))
// stub
InvocationHandler h = new InvocationHandler() {
  @override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Socket socket = new Socket('127.0.0.1', 8088)
  ByteArrayOutputStream baos = new ByteArrayOutputStream()
  DataoutputStream dos = new DataoutputStream(baos);
  dos.write(123); // 缺少灵活性
  // 发送查询id
 socket.getOutputStream().write(baos.toByteArray()
 socket.getOutputStream().flush()
  // 接收服务器返回结果
  DataInputStream dis = new 
  DataInputStream(socket.getInputStream())
  int idtmp = dis.readInt();
  if (idtmp != id) ...('error')
  String name = dis.readUTF();
  User user = new User(id, name);
   return User;
}
};
Object o = Proxy.newProxyInstance(IUserService.class.getClassLoader(), new Class[]{IUserService.class}, h);
...(o.getClass.getName());
...(o.getClass().getInterface()[0]);
return (IUserService) o;

4

这个版本彻底改变了客户端与服务端的通信方式:
客户端不仅仅传递参数给服务器,还要传输调用的方法名,参数类型,因此可以适用不同的方法的调用而不用改变代码,stub中使用反射拿到需要传递的上述信息,sub也不用改变代码,顺便这个版本改用了Object Input/output Stream,注意这个时候要确保传输的对象可序列化,服务端,客户端,stub均有改动
总之,这个版本在同一个服务类型(IUserService)之下,调用任何方法都不需要改动stub和服务端代码,只需要改动IUserService接口

// client
IUserService service = Stub.getStub();
...(service.findUserById(123));
// server
public static process(Socket socket) .. {
  DataInputStream osi= new DataInputStream(socket.getInputStream())
  DataOutputStream oos = new DataOutputStream(socket.getOutputStream())
  int id = dis.readInt()
  // 为了适应客户端变化而做的改动
  String method = ois.readObject();
  Class[] parameterTypes = (Class[])ois.readObject()
  Object[] paramters = (Object[])ois.readObject();
  // 服务类型暂时还是写死的,不够灵活
  IUserService service = new IUserServiceImpl();
  Method method = service.getClass().getMethod(methodName, parameterTypes);
  User user = (User)method.invoke(service, parameters);
User user = service.findUserById(id);
  dos.writeObject(user);
  dos.flush()
}
// stub
InvocationHandler h = new InvocationHandler() {
  @override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Socket socket = new Socket('127.0.0.1', 8088)
  ByteArrayOutputStream baos = new ByteArrayOutputStream()
  DataoutputStream oos = new DataoutputStream(baos);
  // 通用化的改动
  oos.writeUTF(method.getMethodName());
  oos.writeObject(method.getParameterTypes());
  oos.writeObject(args);
  oos.flush();
  // 接收服务器返回结果
  DataInputStream ois = new 
  DataInputStream(socket.getInputStream())
   User user = (User)ois.readObject();
   return user;
}
};
Object o = Proxy.newProxyInstance(IUserService.class.getClassLoader(), new Class[]{IUserService.class}, h);
...(o.getClass.getName());
...(o.getClass().getInterface()[0]);
return (IUserService) o;

5

这个版本在之前通用化的基础上,连服务类型都能改变,变成通用的,主要将服务类型作为参数传入getStub,为了验证server和stub的通用性,这里client调用了两个不同服务的不同接口,都可以正常运行

IUserService service = (IUserService)Stub.getStub(IUserService.class)
IProductService service2 = (IProductService)Stub.getStub(IProductService.class)
...(service.findUserById(123))
...(service2.findProductByName('bob'))
// server
public static process(Socket socket) .. {
  DataInputStream osi= new DataInputStream(socket.getInputStream())
  DataOutputStream oos = new DataOutputStream(socket.getOutputStream())
  int id = dis.readInt()
  // 为了适应客户端变化而做的改动
  String clazzName = ois.readUTF();
  String method = ois.readObject();
  Class[] parameterTypes = (Class[])ois.readObject()
  Object[] paramters = (Object[])ois.readObject();
  // 服务类型暂时还是写死的,不够灵活
  // IUserService service = new IUserServiceImpl();
   // 本来是硬编码new出来的,现在变成从注册表查询到服务类,如果使用spring还可以直接根据配置植入bean然后根据bean查找
 Object service = registerTable.get(clazzName).newInstance() 
 Method method = service.getClass().getMethod(methodName, parameterTypes);
  User user = (User)method.invoke(service, parameters);
User user = service.findUserById(id);
  dos.writeObject(user);
  dos.flush()
}
// stub
// 添加了服务类型的传输
public class Stub {
  static Object getStub(class c) {
    ...
    // 增加服务类型传输
    oos.writeUTF(c.getName())
  }
   。。。
    Object o = Proxy.newProxyInstance(c.getClassLoader(), new Class[]{c}, h);//这里要写成通用的c,而不是固定的接口
}

RPC序列化框架

序列化:对象 -> 二进制
对象 -> json格式/xml格式 ->二进制

  • 1 java.io.Serializable
  • 2 Hessian
  • 3 google protobuf 复杂 效率高
  • 4 facebook Thrift 复杂 效率高
  • 5 kyro
  • 6 fst
  • 7 json 序列化框架 Jackson, google Gson, Ali FastJson
  • 8 xmlRpc(xstream)
    ...

Hession

public class HelloHession {
  public static void main()... {
    User u = new User(1, 'zhangsan')
    byte[] bytes = seriablize(u);
    ...(bytes.length)
    User u1 = (User)deserialize(bytes);
  }
  public static byte[] seralize(object o) {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    Hession2Output output = new Hession2Output(baos)
    output.writeObject(o)
    output.flush()
    byte[] bytes = baos.toByteArray()
    baos.close()
    output.close()
    return bytes;
  }
  public static byte[] deseralize(object o) {
     ByteArrayInStream bais = new ByteArrayInStream();
    Hession2Input input = new Hession2Input(baos)
    Object o = input.readObject();
    bais.close();
    input.close();
    return o;
  }
}

通讯协议

  • http
  • http2.0(gRPC) 双工
  • TCP
    • 同步/异步 , 阻塞/非阻塞
  • WebService
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容

  • RPC是什么? RPC 的全称是 Remote Procedure Call 是一种进程间通信方式。 它允许程序调...
    1angxi阅读 5,270评论 5 21
  • 最近上手公司一个老项目, 遇到了一个RPC服务调用失败的问题, 由于之前只接触过RestCilent这一种远程服务...
    春风知桺阅读 1,050评论 0 2
  • RPC协议 RPC协议同HTTP协议一样,都是属于应用层的协议,可以通过学习HTTP的协议来理解RPC协议. HT...
    LegendGo阅读 414评论 0 0
  • 【定义】 Java RMI:Java远程方法调用,即Java RMI(Java Remote Method Inv...
    小梁同学jxy阅读 186评论 0 0
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,520评论 28 53