gRPC概述

gRPC是由 google开发的一款高性能开源远程过程调用(RPC)框架,主要面向高性能C/S模式应用场景,基于HTTP/2协议标准设计,支持常见的各类编程语言。

一、概述

1、RPC

RPC(Remote Procedure Call)即远程过程调用,采用客户端-服务端(Client/Server)模式。常见的RPC框架有Google的gRPC、Facebook的Thrift、Baidu的bRPC等。

从广义角度来说,所有本身应用程序之外的调用都可以归类为RPC调用,无论是微服务、第三方HTTP接口,还是读写数据库中间件Mysql、Redis等。

2、gRPC

gRPC与常见的rpc框架类似,主要基于定义服务的设计思路,通过IDL预先定义服务名称、方法名称/参数/返回值,并生成对应的服务端和客户端代码,再对服务端方法进行实现后即可方便的进行远程调用。

grpc默认使用protocol buffers作为描述服务接口描述和消息格式定义的语言(IDL),如需要也可以使用其他IDL代替。

源代码:https://github.com/grpc/grpc.git

3、优缺点

gRPC优势:
  • 支持多种语言:C++、Java、Go、Python、Ruby、C#、Node.js、Android Java、Objective-C、PHP等;
  • 基于 IDL (Interface Define Language)接口定义语言定义服务,通过 proto3 工具生成指定语言的数据结构、服务端接口以及客户端 Stub,传输数据更小、更快,使用更简单;
  • 通信协议基于标准 HTTP/2 设计,支持消息头压缩、单 TCP 的多路复用、服务端推送、双向流等特性,使得 gRPC 在移动端设备上更加省电和节省网络流量;
  • 序列化支持 PB(Protocol Buffer)和 JSON,PB 是一种语言无关的高性能序列化框架,基于 HTTP/2 + PB, 保障了 RPC 调用的高性能;
  • 安装简单,扩展方便(用该框架每秒可达到百万个RPC)

gRPC不足:
  • 长连接流量不易调度,不能很好的支持负载均衡
  • 不支持浏览器请求
  • 不支持1对多广播
gRPC与REST API的主要区别:
  • 数据/负载:json文本,大,可读  -> pb压缩二进制,小,不可读
  • 传输协议:HTTP -> HTTP/2
  • 代码生成:不支持 -> 支持

 

二、编译安装

# 安装依赖
sudo apt install -y cmake build-essential autoconf libtool pkg-config
# 下载源码 (v1.46是最后一个支持c++11编译的版本)
git clone --recurse-submodules -b v1.46.7 --depth 1 --shallow-submodules https://github.com/grpc/grpc
# 编译安装
mkdir -p grpc/cmake/build && cd grpc/cmake/build
cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DCMAKE_INSTALL_PREFIX=$MY_INSTALL_DIR ../..
make
sudo make install
ldconfig

# 示例编译
cd examples/cpp/helloworld
mkdir -p cmake/build && cd cmake/build
cmake ../..
make

# 示例测试
./greeter_server
./greeter_client
    Greeter received: Hello world

 

三、实例

1、服务定义

定义一个robot服务,包含Hello方法,并定义参数和返回值格式:

$ vim  protos/robot_service.proto

syntax = "proto3";

package robot_service;

// 服务和方法定义
service Robot {
  rpc Hello (HelloRequest) returns (HelloReply) {}

  rpc HelloStreamReply (HelloRequest) returns (stream HelloReply) {}
}

// 参数定义
message HelloRequest {
  string name = 1;
}

// 返回值定义
message HelloReply {
  string message = 1;
}

2、生成服务端/客户端代码

使用cmake编译.proto文件:

$ vim CMakeLists.txt

cmake_minimum_required(VERSION 3.8)

project(TestGrpc C CXX)

# Common set
find_package(Threads REQUIRED)
find_package(Protobuf CONFIG REQUIRED)
message(STATUS "Using protobuf ${Protobuf_VERSION}")
find_package(gRPC CONFIG REQUIRED)
message(STATUS "Using gRPC ${gRPC_VERSION}")
set(_PROTOBUF_LIBPROTOBUF protobuf::libprotobuf)
set(_REFLECTION gRPC::grpc++_reflection)
set(_PROTOBUF_PROTOC $<TARGET_FILE:protobuf::protoc>)
set(_GRPC_GRPCPP gRPC::grpc++)
set(_GRPC_CPP_PLUGIN_EXECUTABLE $<TARGET_FILE:gRPC::grpc_cpp_plugin>)


# Proto file
get_filename_component(serv_proto "protos/robot_service.proto" ABSOLUTE)
get_filename_component(serv_proto_path "${serv_proto}" PATH)
# Generated sources
set(serv_proto_srcs "${CMAKE_CURRENT_BINARY_DIR}/robot_service.pb.cc")
set(serv_proto_hdrs "${CMAKE_CURRENT_BINARY_DIR}/robot_service.pb.h")
set(serv_grpc_srcs "${CMAKE_CURRENT_BINARY_DIR}/robot_service.grpc.pb.cc")
set(serv_grpc_hdrs "${CMAKE_CURRENT_BINARY_DIR}/robot_service.grpc.pb.h")
add_custom_command(
      OUTPUT "${serv_proto_srcs}" "${serv_proto_hdrs}" "${serv_grpc_srcs}" "${serv_grpc_hdrs}"
      COMMAND ${_PROTOBUF_PROTOC}
      ARGS --grpc_out "${CMAKE_CURRENT_BINARY_DIR}"
        --cpp_out "${CMAKE_CURRENT_BINARY_DIR}"
        -I "${serv_proto_path}"
        --plugin=protoc-gen-grpc="${_GRPC_CPP_PLUGIN_EXECUTABLE}"
        "${serv_proto}"
      DEPENDS "${serv_proto}")

$ mkdir build && cd build
$ cmake .. && make

生成的代码:

$ tree
├── CMakeCache.txt
├── libserv_grpc_proto.a
├── Makefile
├── robot_client
├── robot_server
├── robot_service.grpc.pb.cc
├── robot_service.grpc.pb.h
├── robot_service.pb.cc
└── robot_service.pb.h

通过robot_service.grpc.pb.h头文件可以看到服务端类为Robot::Service,客户端类为Robot::Stub。

3、方法实现

服务端代码:

$ vim robot_server.cc

#include <iostream>
#include <memory>
#include <string>

#include <grpcpp/ext/proto_server_reflection_plugin.h>
#include <grpcpp/grpcpp.h>
#include <grpcpp/health_check_service_interface.h>

#include "robot_service.grpc.pb.h"

using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
using robot_service::Robot;
using robot_service::HelloReply;
using robot_service::HelloRequest;

// 服务端方法实现
class RobotServiceImpl final : public Robot::Service {
  Status Hello(ServerContext* context, const HelloRequest* request,
                  HelloReply* reply) override {
    std::string prefix("hello ");
    reply->set_message(prefix + request->name());
    return Status::OK;
  }
};

void StartServer() {
  // 启用默认的健康检查服务
  grpc::EnableDefaultHealthCheckService(true);
  // 初始化服务器反射,方便在运行时获取有关gRPC服务的信息
  grpc::reflection::InitProtoReflectionServerBuilderPlugin();

  // 注册并启动服务
  std::string server_address("0.0.0.0:60061");
  RobotServiceImpl service;
  ServerBuilder builder;  // 用于创建和启动grpc::Server实例的构建器类
  builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());  // 登记服务监听地址以绑定要创建的grpc::Server对象(在没有任何身份验证机制的情况下)
  builder.RegisterService(&service);  // 注册服务实例
  std::unique_ptr<Server> server(builder.BuildAndStart());  //返回准备好处理调用的正在运行的服务器
  std::cout << "Server listening on " << server_address << std::endl;

  // 等待服务器关闭(必须有其他线程负责关闭服务器,才能使此调用返回)
  server->Wait();
}

int main(int argc, char** argv) {
  StartServer();

  return 0;
}

客户端代码实现:

$ vim robot_client.cc

#include <iostream>
#include <memory>
#include <string>

#include <grpcpp/grpcpp.h>

#include "robot_service.grpc.pb.h"

using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
using robot_service::Robot;
using robot_service::HelloReply;
using robot_service::HelloRequest;

class RobotClient {
 public:
  // 使用proto生成的类NewStub方法创建客户端
  RobotClient(std::shared_ptr<Channel> channel)
      : stub_(Robot::NewStub(channel)) {}

  // 发送客户端请求
  std::string Hello(const std::string& user) {
    // 组织参数和返回值
    HelloRequest request;
    request.set_name(user);
    HelloReply reply; // 返回值
    ClientContext context;  // 客户端上下文

    // 发送请求
    Status status = stub_->Hello(&context, request, &reply);

    // 返回值处理
    if (status.ok()) {
      return reply.message();
    } else {
      std::cout << status.error_code() << ": " << status.error_message() << std::endl;
      return "RPC failed";
    }
  }

 private:
  std::unique_ptr<Robot::Stub> stub_;
};


int main(int argc, char** argv) {
  // 实例化客户端
  std::string target_str("localhost:60061");
  RobotClient robot(grpc::CreateChannel(target_str, grpc::InsecureChannelCredentials())); // 暂不使用身份验证
  // 发送请求
  std::string user("mars");
  std::string reply = robot.Hello(user);
  std::cout << "Client received: " << reply << std::endl;

  return 0;
}

4、测试

为客户端添加CMake编译设置:

vim CMakeLists.txt

...

# serv_grpc_proto
add_library(serv_grpc_proto
  ${serv_grpc_srcs}
  ${serv_grpc_hdrs}
  ${serv_proto_srcs}
  ${serv_proto_hdrs})
target_link_libraries(serv_grpc_proto
  ${_REFLECTION}
  ${_GRPC_GRPCPP}
  ${_PROTOBUF_LIBPROTOBUF})

# Targets robot_[async_](client|server)
foreach(_target
  robot_client robot_server 
  )
  add_executable(${_target} "${_target}.cc")
  target_link_libraries(${_target}
    serv_grpc_proto
    ${_REFLECTION}
    ${_GRPC_GRPCPP}
    ${_PROTOBUF_LIBPROTOBUF})
endforeach()

编译测试:

# build
$  mkdir build && cd build
$  cmake ..
$  make

# run
$  ./robot_server
Server listening on 0.0.0.0:60061
 $  ./robot_client
Client received: hello mars

5、异步改造

上边的实例,客户端和服务端在调用时都是同步阻塞的,接下来我们把它改造成异步的方式。

 

yan 23.1.29

参考:

grpc.io

Added explicit dependency to abseil by adding find_package(absl)

gRPC 之负载均衡

欢迎关注下方“非著名资深码农“公众号进行交流~

发表评论

邮箱地址不会被公开。