GRPC學習之路(3)——protobuf的作用

上一篇文章介紹的是nginx做爲負載均衡與後端grpc的集成,沒有繼續深入下去,因爲這個需要線上真正實踐纔會有更多的感悟,之後入職後有體會的話再繼續寫吧。

剛好我也比較好奇protobuf到底和grpc是個什麼關係,protobuf在整個rpc的過程中起的是什麼作用,所以我之後的幾篇文章都會是關於protobuf的。

我還是從網上找了幾個網上的資料,看看前人的研究成果:

proto3官網 毫不猶豫推薦官網的文章

Google Protocol Buffer 的使用和原理 這個文章有點老,介紹的不是proto3,但是作者研究的挺深入的。

proto3語言指南 這個有點像官網英文的翻譯和作者自己的總結,也非常不錯

什麼是ProtoBuf

摘抄一段別人的描述:Protocol Buffers 是一種輕便高效的結構化數據存儲格式,可以用於結構化數據串行化,或者說序列化。它很適合做數據存儲或 RPC 數據交換格式。可用於通訊協議、數據存儲等領域的語言無關、平臺無關、可擴展的序列化結構數據格式。看到這個描述第一感覺是雲裏霧裏,研究後總結protobuf的以下幾點:

  • grpc作爲rpc的一種,肯定有服務間的網絡調用,調用就一定會有數據的傳輸,而這個數據的傳輸就用的是protobuf去序列化和反序列化的,一個請求的內容在調用方序列化通過網絡傳送到服務方,服務方就能用protobuf反序列化出來,後面的文章會講解細節。
  • protobuf有一個類似thrift的IDL(接口描述語言),就是描述一個對象包括什麼字段,一個服務包括什麼接口,簡單如下:

message Person {
string name = 1;
int32 id = 2;
string email = 3;
}

  • protobuf還有專門的工具生成對象代碼,你用這些代碼就能將對象序列化到文件或者流中,當然也能反序列化成對象,後面的例子會具體介紹。
  • protobuf支持多種語言,常見的比如go,c++,java,php等,具體可以參考官網

怎麼使用ProtoBuf

準備工作

與前面介紹grpc使用的步驟一樣,現在準備一個.proto文件

syntax = "proto3";

package tutorial;

option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";

message Person {
  string name = 1;
  int32 id = 2;
  string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }

  repeated PhoneNumber phones = 4;
}

message AddressBook {
  repeated Person people = 1;
}

使用前面文章介紹的maven plugin生成代碼。

拷貝代碼

將生成好的代碼AddressBookProtos.java拷貝到你新建的工程,類似如下結構:

序列化對象

參考官網的說明,新建一個WriteObject.java, 代碼如下,通過輸入逐漸構造Person對象,並寫入一個文件,記得在啓動參數里加上文件的路徑

static Person PromptForAddress(BufferedReader stdin,
    PrintStream stdout) throws IOException {
    Person.Builder person = Person.newBuilder();

    stdout.print("Enter person ID: ");
    person.setId(Integer.valueOf(stdin.readLine()));

    stdout.print("Enter name: ");
    person.setName(stdin.readLine());

    stdout.print("Enter email address (blank for none): ");
    String email = stdin.readLine();
    if (email.length() > 0) {
        person.setEmail(email);
    }

    while (true) {
        stdout.print("Enter a phone number (or leave blank to finish): ");
        String number = stdin.readLine();
        if (number.length() == 0) {
            break;
        }

        Person.PhoneNumber.Builder phoneNumber =
            Person.PhoneNumber.newBuilder().setNumber(number);

        stdout.print("Is this a mobile, home, or work phone? ");
        String type = stdin.readLine();
        if (type.equals("mobile")) {
            phoneNumber.setType(Person.PhoneType.MOBILE);
        } else if (type.equals("home")) {
            phoneNumber.setType(Person.PhoneType.HOME);
        } else if (type.equals("work")) {
            phoneNumber.setType(Person.PhoneType.WORK);
        } else {
            stdout.println("Unknown phone type.  Using default.");
        }

        person.addPhones(phoneNumber);
    }

    return person.build();
}

public static void main(String[] args) throws IOException {
    if (args.length != 1) {
        System.err.println("Usage:  AddPerson ADDRESS_BOOK_FILE");
        System.exit(-1);
    }

    AddressBook.Builder addressBook = AddressBook.newBuilder();

    // Read the existing address book.
    try {
        addressBook.mergeFrom(CodedInputStream.newInstance(new FileInputStream(args[0])));
    } catch (FileNotFoundException e) {
        System.out.println(args[0] + ": File not found.  Creating a new file.");
    }

    // Add an address.
    addressBook.addPeople(
        PromptForAddress(new BufferedReader(new InputStreamReader(System.in)),
            System.out));

    // Write the new address book back to disk.
    FileOutputStream output = new FileOutputStream(args[0]);
    addressBook.build().writeTo(output);
    output.close();
}

運行後用sublime打開寫入的文件如下, 具體爲什麼是這種後面我會具體研究,現在所要知道的是這種序列化的方式肯定比一般的xml和json要節省空間的

反序列化對象

參考官網新建ReadObject.java, 代碼如下, 讀取同樣的文件,反序列化後就能取出對應的字段值

static void Print(AddressBook addressBook) {
    for (Person person: addressBook.getPeopleList()) {
        System.out.println("Person ID: " + person.getId());
        System.out.println("  Name: " + person.getName());

        for (Person.PhoneNumber phoneNumber : person.getPhonesList()) {
            switch (phoneNumber.getType()) {
                case MOBILE:
                    System.out.print("  Mobile phone #: ");
                    break;
                case HOME:
                    System.out.print("  Home phone #: ");
                    break;
                case WORK:
                    System.out.print("  Work phone #: ");
                    break;
            }
            System.out.println(phoneNumber.getNumber());
        }
    }
}

public static void main(String[] args) throws IOException {
    if (args.length != 1) {
        System.err.println("Usage:  ListPeople ADDRESS_BOOK_FILE");
        System.exit(-1);
    }

    // Read the existing address book.
    AddressBook addressBook =
        AddressBook.parseFrom(new FileInputStream(args[0]));

    Print(addressBook);
}

運行一下結果如下:

Person ID: 1
Name: zack

總結

至此,大概瞭解了一下怎麼單獨使用protobuf以及它在grpc中的作用,後面我會繼續研究protobuf3的語法細節和序列化細節

另附文章中提到的工程文件:

Proto3Tutorial

歡迎關注我的個人的博客www.zhijianliu.cn, 虛心求教,有錯誤還請指正輕拍,謝謝

版權聲明:本文出自志健的原創文章,未經博主允許不得轉載

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章