Protobuf(四):Protocol Buffers实战

1. 工欲善其事,必先利其器

2. 基于protobuf实现数据存储

2.1 学以致用

  • 直接使用protobuf官网的示例代码,稍作修改写入address_book.proto文件中

    // 只修改了java_package
    option java_package = "com.sunrise.protos";
    
  • 根据protobuf官方文档的介绍,message对应的Java类将由自己的序列化和反序列化的方法
    在这里插入图片描述

  • 自己的需求:

    • 借助writeTo(output)方法,将通讯录(AddressBook)序列化到指定文件中
    • 借助parseFrom(input)方法,从文件中读取通讯录数据并反序列化为AddressBook对象

2.2 代码实现

  • 代码中,将使用commons-lang随机生成用户名和电话号码

    <dependency>
        <groupId>commons-lang</groupId>
        <artifactId>commons-lang</artifactId>
        <version>2.6</version>
    </dependency>
    
  • 具体的代码实现如下:

    package com.sunrise.protos;
    
    public class AddressBookTest {
        private static final Random random = new Random();
        private static int count = 0;
    
        /**
         * 生成指定数量的通讯记录
         **/
        public AddressBook generateAddressBook(int n) {
            // 构建n个person
            AddressBook.Builder builder = AddressBook.newBuilder();
            for (int i = 0; i < n; i++) {
                builder.addPeople(generatePerson());
            }
    
            return builder.build();
        }
    
        // 构建一条通讯记录,也就是构建一个person实例
        private Person generatePerson() {
            int id = ++count;
            String name = "user_" + RandomStringUtils.random(4, false, true);
            String email = RandomStringUtils.random(12, true, true).toLowerCase() + "@qq.com";
    
            int phoneNumberType = random.nextInt(3);
            Person.PhoneNumber phoneNumber = Person.PhoneNumber.newBuilder()
                    .setNumber(RandomStringUtils.random(11, false, true))
                    .setType(Person.PhoneType.valueOf(phoneNumberType))
                    .build();
    
            // 创建person
            Person person = Person.newBuilder()
                    .setId(id)
                    .setName(name)
                    .setEmail(email)
                    .addPhones(phoneNumber)
                    .build();
    
            return person;
        }
    
        /**
         * 将通讯录通过writeTo()方法,序列化后以二进制的形式写入指定的file
         **/
        public void writeToFile(AddressBook book, String file) {
            try (FileOutputStream outputStream = new FileOutputStream(file)) {
                book.writeTo(outputStream);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 从指定的file中读取通信录,并反序列化为AddressBook实例
         **/
        public AddressBook readFromFile(String file) {
            try (FileInputStream in = new FileInputStream(file)) {
                return AddressBook.parseFrom(in);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        /**
         * 打印通讯录
         **/
        public void printAddressBook(AddressBook addressBook) {
            for (Person person : addressBook.getPeopleList()) {
                System.out.println(person.toString());
            }
        }
    }
    
  • 使用AddressBookTest,实现通讯录的创建和读取

    public static void main(String[] args) {
        AddressBookTest test = new AddressBookTest();
        System.out.println("开始创建通讯录...");
    
        AddressBook addressBook = test.generateAddressBook(1);
        test.printAddressBook(addressBook);
        test.writeToFile(addressBook, "address_book");
        System.out.println("成功创建通讯录!");
    
        System.out.println("开始读取通讯录...");
        AddressBook read = test.readFromFile("address_book");
        System.out.println("成功读取读取通讯录!");
        test.printAddressBook(read);
    }
    
  • 执行结果如下:

3. 基于protobuf实现自己的rpc框架(伪代码)

  • 本人才疏学浅,基于protobuf实现自己的rpc框架时,发现困难重重
  • 最大的困难就是:不知道如何实现相关接口,让client和server能建立通信
  • 欢迎大佬与我交流

3.1 定义一个service

  • 大家都知道大名鼎鼎的gRPC,其实gRPC的实现就是基于protobuf实现的

  • server和client之间的RPC方法,由service进行定义

  • 这是一个简单的service定义,方法名为大驼峰形式,请求参数为message Request,返回参数为message Response

    syntax = "proto2";
    
    package rpc_service;
    
    option java_multiple_files = true;
    option java_generic_services = true;
    option java_package = "com.sunrise.service";
    option java_outer_classname = "RpcService";
    
    message Request {
      required string token = 1;
      required int32 id = 2;
      optional string user = 3;
    }
    
    message Response {
      required int32 status_code = 1;
      message User {
        required int32 id = 1;
        optional string  user = 2;
        optional int32  age = 3;
        optional string address = 4;
      }
      optional User user = 2;
      optional string toast = 3;
    }
    
    service MyRpcService {
      rpc SearchUser(Request) returns(Response);
    }
    
  • 编译生成的Java代码,相关接口和方法存放在抽象类MyRpcService,类名由service Xyz决定

  • 编译时,如果java_generic_services 为false,将不会产生MyRpcService类,也就无法定义建立rpc通信框架

3.2 查看service中的非阻塞接口

3.2.1 非阻塞的server端

  • MyRpcService中,有一个抽象方法searchUser(),对应的就是service中定义的SearchUSer这个RPC方法

    public abstract void searchUser(
          com.google.protobuf.RpcController controller,
          com.sunrise.service.Request request,
          com.google.protobuf.RpcCallback<com.sunrise.service.Response> done);
    
  • 从方法定义中的RpcCallback可知,searchUser()是一个非阻塞的方法

  • 除了这些方法,MyRpcService中还有一些实现com.google.protobuf.Servic接口的方法,方法介绍见官方文档或源码注释

  • 有时,考虑到业务会继承Service类,实现自己RPC server,MyRpcService中还定义了一个Interface接口,里面只有自定义的PRC方法

    public interface Interface {
      public abstract void searchUser(
          com.google.protobuf.RpcController controller,
          com.sunrise.service.Request request,
          com.google.protobuf.RpcCallback<com.sunrise.service.Response> done);
    
    }
    
  • 通过实现Interface接口,然后通过newReflectiveService()方法创建Service

  • 关于Service类,自己的理解:非阻塞状态,对应的就是MyRpcService,从newReflectiveService()方法的实现可以看出

    public static com.google.protobuf.Service newReflectiveService(
            final Interface impl) {
        return new MyRpcService() {
            @java.lang.Override
            public void searchUser(
                    com.google.protobuf.RpcController controller,
                    com.sunrise.service.Request request,
                    com.google.protobuf.RpcCallback<com.sunrise.service.Response> done) {
                impl.searchUser(controller, request, done);
            }
    
        };
    }
    

3.2.2 非阻塞的client

  • MyRpcService.Stub类,是留给RPC client的、用于实现client远程调用server端方法的stub(木桩,类似于一个通道)

  • 基于RpcChannel创建Stub,就可以实现client和server之间的通信,从而可以调用RPC方法searchUser()

  • Stub的创建方法有两种:一是基于工厂方法newStub,而是直接new Stub

    MyRpcService.Stub stub = MyRpcService.newStub(new MyRpcChannel(8080));
    MyRpcService.Stub stub = new MyRpcService.Stub(new MyRpcChannel(8080);
    
  • 创建好stub后,clientd就可以直接调用searchUser(),就像调用本地的服务一样

    stub.searchUser(new MyRpcController(), Request.newBuilder()
             .setId(20)
             .setToken("client_token")
             .build(), parameter -> System.out.println(parameter.toString()));
    

3.3 查看service中的阻塞接口

3.3.1 阻塞的server

  • 既然存在非阻塞接口Interface,相应地,也存在阻塞接口BlockingInterface

    public class BlockingServer implements MyRpcService.BlockingInterface {
    
        @Override
        public Response searchUser(RpcController controller, Request request) throws ServiceException {
            // 省略具体实现
        }
    }
    
    
  • 使用时,需要实现BlockingInterface接口,然后基于的newReflectiveBlockingService()创建对应的BlockingService

    public static void main(String[] args) {
        BlockingService blockingService = MyRpcService.newReflectiveBlockingService(new BlockingServer()); 
    }
    
  • 包名com.sunrise.protos由proto文件中的java_package进行设置,若未设置则由package xxx;中的包名替代代替

  • AddressBookProtos类是表示该proto文件的wrapper类,由java_outer_classname进行设置

  • 之所以存在多个Java文件,而非只有一个指定的wrapper类AddressBookProtos,这是因为设置了java_multiple_files = true

3.3.2 阻塞的client

  • Stub是非阻塞状态下,client访问server的stub;BlockingStub则是阻塞状态下,client访问server的stub
  • 与Stub不同的是,BlockingStub不能直接通过new BlockingStub()进行创建,因为其构造函数为private
  • 基于BlockingStub创建client并访问server的RPC方法的伪代码如下:
    public static void main(String[] args) throws ServiceException {
        MyRpcService.BlockingInterface blockingStub = MyRpcService.newBlockingStub(new MyBlockingRpcChannel());
        blockingStub.searchUser(new MyRpcController(), Request.newBuilder().build());
    }
    

3.4 后续规划

  • 由于时间和分人能力问题,笔者暂时不打算基于protobuf实现一个自己的RPC框架
  • 如有需要,后续可能会学习gRPC
  • 通过搜索,自己在网上找到了一个大神基于protubuf实现的RPC框架代码
  • 如果感兴趣,可以拜读大神代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值