栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

4. Protocol Buffers实战

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

4. Protocol Buffers实战

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

protobuf的安装,请参考之前的博客:mac上同时安装proto2和proto3并编译.proto文件

maven + protobuf 配置与使用, 请参考之前的博客: IDEA + maven + protobuf配置(on mac)

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随机生成用户名和电话号码


    commons-lang
    commons-lang
    2.6

具体的代码实现如下:

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;
    }

    
    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();
        }
    }

    
    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 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 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 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框架代码如果感兴趣,可以拜读大神代码

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/771125.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号