介绍

所谓RPC(remote procedure call 远程过程调用)框架实际是提供了一套机制,使得应用程序之间可以进行通信,而且也遵从server/client模型。使用的时候客户端调用server端提供的接口就像是调用本地的函数一样。如下图所示就是一个典型的RPC结构图

gRPC vs. Restful API

gRPC和restful API都提供了一套通信机制,用于server/client模型通信,而且它们都使用http作为底层的传输协议(严格地说, gRPC使用的http2.0,而restful api则不一定)。不过gRPC还是有些特有的优势,如下:

  • gRPC可以通过protobuf来定义接口,从而可以有更加严格的接口约束条件。关于protobuf可以参见笔者之前的小文Google Protobuf简明教程
  • 另外,通过protobuf可以将数据序列化为二进制编码,这会大幅减少需要传输的数据量,从而大幅提高性能。
  • gRPC可以方便地支持流式通信(理论上通过http2.0就可以使用streaming模式, 但是通常web服务的restful api似乎很少这么用,通常的流式数据应用如视频流,一般都会使用专门的协议如HLS,RTMP等,这些就不是我们通常web服务了,而是有专门的服务器应用。

使用场景

  • 需要对接口进行严格约束的情况,比如我们提供了一个公共的服务,很多人,甚至公司外部的人也可以访问这个服务,这时对于接口我们希望有更加严格的约束,我们不希望客户端给我们传递任意的数据,尤其是考虑到安全性的因素,我们通常需要对接口进行更加严格的约束。这时gRPC就可以通过protobuf来提供严格的接口约束。
  • 对于性能有更高的要求时。有时我们的服务需要传递大量的数据,而又希望不影响我们的性能,这个时候也可以考虑gRPC服务,因为通过protobuf我们可以将数据压缩编码转化为二进制格式,通常传递的数据量要小得多,而且通过http2我们可以实现异步的请求,从而大大提高了通信效率。

但是,通常我们不会去单独使用gRPC,而是将gRPC作为一个部件进行使用,这是因为在生产环境,我们面对大并发的情况下,需要使用分布式系统来去处理,而gRPC并没有提供分布式系统相关的一些必要组件。而且,真正的线上服务还需要提供包括负载均衡,限流熔断,监控报警,服务注册和发现等等必要的组件。不过,这就不属于本篇文章讨论的主题了。

安装

本文通过源码方式安装grpc。参考:https://grpc.io/docs/languages/cpp/quickstart/#install-grpc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
git clone --recurse-submodules -b v1.39.0 https://github.com/grpc/grpc

# 配置安装的路径,并添加到环境变量
export MY_INSTALL_DIR=/opt/grpc
mkdir -p $MY_INSTALL_DIR
export PATH="$MY_INSTALL_DIR/bin:$PATH"

# 安装依赖包
sudo apt install -y build-essential autoconf libtool pkg-config

# 开始编译安装
# build and locally install gRPC, Protocol Buffers, and Abseil:

cd grpc
mkdir -p cmake/build
pushd cmake/build
cmake -DgRPC_INSTALL=ON \
-DgRPC_BUILD_TESTS=OFF \
-DCMAKE_INSTALL_PREFIX=$MY_INSTALL_DIR \
../..
make
make install
popd

mkdir -p third_party/abseil-cpp/cmake/build
pushd third_party/abseil-cpp/cmake/build
cmake -DCMAKE_INSTALL_PREFIX=$MY_INSTALL_DIR \
-DCMAKE_POSITION_INDEPENDENT_CODE=TRUE \
../..
make -j
make install
popd

cd third_party/abseil-cpp
./autogen.sh
./configure
make -j8

sudo make install

sudo ldconfig
protoc --version

sql安装

1
sudo apt-get install mysql-server

使用

编译源码中的helloworld例子:

1
2
3
4
5
cd examples/cpp/helloworld
mkdir -p cmake/build
pushd cmake/build
cmake -DCMAKE_PREFIX_PATH=$MY_INSTALL_DIR ../..
make -j

运行server:

1
./greeter_server

打开另一个终端运行客户端,接受到输出如下:

1
2
./greeter_client
Greeter received: Hello world

issue

make过程中出现该问题:fatal error: absl/synchronization/mutex.h: No such file or directory 35 | #include "absl/synchronization/mutex.h"

解决方法:修改该文件夹中的CMakeLists.txt的include_directories为以下:

1
2
3
4
# Include generated *.pb.h files
include_directories("${CMAKE_CURRENT_BINARY_DIR}"
../../../third_party/abseil-cpp
)

CmakeLists.txt使用

方案一

需要提供的cmake文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
cmake_minimum_required(VERSION 3.5.1)

project(demo)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
set(root_dir ${CMAKE_CURRENT_SOURCE_DIR})

# proto and grpc
set(PROTO_DIR proto)
list(APPEND CMAKE_MODULE_PATH ${root_dir}/cmake)

find_package(Threads REQUIRED)
find_package(ProtobufWithTargets REQUIRED)
find_package(gRPC REQUIRED)
message(STATUS "Using protobuf ${Protobuf_VERSION}")
message(STATUS "Using gRPC ${gRPC_VERSION}")

include(GRPCProtoGenerate)
# import's proto
set(GRPC_IMPORT_DIRS "${PROTO_DIR}/comm")
set(GRPC_GENERATE_CPP_APPEND_PATH TRUE)
grpc_generate_cpp(GRPC_SRCS GRPC_HDRS
"${PROTO_DIR}/pum/pum.proto"
"${PROTO_DIR}/comm/comm.proto"
)
message("grpc heads: ${GRPC_HDRS}")
message("grpc srcs: ${GRPC_SRCS}")

add_executable(${PROJECT_NAME}
${avi_dir}/main.cpp
${GRPC_SRCS}
)

target_include_directories(${PROJECT_NAME} PUBLIC
${GRPC_HDRS}/proto
${GRPC_HDRS}/proto/comm
)

target_link_libraries(${PROJECT_NAME}
protobuf::libprotobuf
gRPC::grpc++_reflection
gRPC::grpc++
# cryptopp
)

方案二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
cmake_minimum_required(VERSION 3.5.1)

project(Demo)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")

set(root_dir ${CMAKE_CURRENT_SOURCE_DIR})

# set proto and grpc releatations
find_package(Threads REQUIRED)

# set(protobuf_MODULE_COMPATIBLE TRUE)
find_package(Protobuf CONFIG REQUIRED)
message(STATUS "Using protobuf ${Protobuf_VERSION}")

find_package(gRPC CONFIG REQUIRED)
message(STATUS "Using gRPC ${gRPC_VERSION}")

set(proto_path ${root_dir}/proto)
set(proto_files "comm" "pum")
set(proto_output_path ${proto_path}/cpp)
set(proto_output_list) # reserver the file from proto cpp
foreach(svc ${proto_files})
set(protodir ${proto_path}/${svc})
set(protofile ${protodir}/${svc}.proto)
set(outputdir ${proto_output_path}/${svc})
list(APPEND proto_output_list
"${outputdir}/${svc}.pb.cc"
"${outputdir}/${svc}.pb.h"
"${outputdir}/${svc}.grpc.pb.cc"
"${outputdir}/${svc}.grpc.pb.h"
)
endforeach(svc)

add_executable(${PROJECT_NAME}
demo.cpp
${proto_output_list}
)

target_include_directories(${PROJECT_NAME} PUBLIC
${proto_path}
${proto_path}/cpp/comm
)

target_link_libraries(${PROJECT_NAME}
protobuf::libprotobuf
gRPC::grpc++_reflection
gRPC::grpc++
# cryptopp
)

方案三

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
cmake_minimum_required(VERSION 3.5.1)
project(pumpkin)

cmake_policy(SET CMP0077 NEW)

set(CMAKE_CXX_STANDARD 11)

find_package(Threads REQUIRED)

set(protobuf_MODULE_COMPATIBLE TRUE)
find_package(Protobuf CONFIG REQUIRED)
message(STATUS "Using protobuf ${Protobuf_VERSION}")

set(_PROTOBUF_LIBPROTOBUF protobuf::libprotobuf)
set(_REFLECTION gRPC::grpc++_reflection)

if(CMAKE_CROSSCOMPILING)
find_program(_PROTOBUF_PROTOC protoc)
else()
set(_PROTOBUF_PROTOC $<TARGET_FILE:protobuf::protoc>)
endif()

# Find gRPC installation
# Looks for gRPCConfig.cmake file installed by gRPC's cmake installation.
find_package(gRPC CONFIG REQUIRED)
message(STATUS "Using gRPC ${gRPC_VERSION}")

set(_GRPC_GRPCPP gRPC::grpc++)

if(CMAKE_CROSSCOMPILING)
find_program(_GRPC_CPP_PLUGIN_EXECUTABLE grpc_cpp_plugin)
else()
set(_GRPC_CPP_PLUGIN_EXECUTABLE $<TARGET_FILE:gRPC::grpc_cpp_plugin>)
endif()

set(SRCS)
set(HDRS)
set(PROTO_FILES "datacenter/data.proto" "datacenter/datacenter.proto" "pum/pum.proto")
set(GRPC_GENERATE_CPP_APPEND_PATH TRUE)

if(${GRPC_GENERATE_CPP_APPEND_PATH})
# Create an include path for each file specified
foreach(FIL ${PROTO_FILES})
get_filename_component(ABS_FIL ${FIL} ABSOLUTE)
get_filename_component(ABS_PATH ${ABS_FIL} PATH)
list(FIND _grpc_include_path ${ABS_PATH} _contains_already)

if(${_contains_already} EQUAL -1)
list(APPEND _grpc_include_path -I ${ABS_PATH})
endif()
endforeach()
else()
set(_grpc_include_path -I ${CMAKE_CURRENT_SOURCE_DIR})
endif()

if(DEFINED GRPC_IMPORT_DIRS)
foreach(DIR ${GRPC_IMPORT_DIRS})
get_filename_component(ABS_PATH ${DIR} ABSOLUTE)
list(FIND _grpc_include_path ${ABS_PATH} _contains_already)

if(${_contains_already} EQUAL -1)
list(APPEND _grpc_include_path -I ${ABS_PATH})
endif()
endforeach()
endif()

foreach(FIL ${PROTO_FILES})
get_filename_component(ABS_FIL ${FIL} ABSOLUTE)
get_filename_component(FIL_WE ${FIL} NAME_WE)

# if(NOT GRPC_GENERATE_CPP_APPEND_PATH)
get_filename_component(FIL_DIR ${FIL} DIRECTORY)

if(FIL_DIR)
set(FIL_WE "${FIL_DIR}/${FIL_WE}")
endif()

message(${FIL_WE})

# endif()
set(out_dir ${CMAKE_CURRENT_SOURCE_DIR}/cpp)
list(APPEND SRCS "${out_dir}/${FIL_WE}.pb.cc")
list(APPEND SRCS "${out_dir}/${FIL_WE}.grpc.pb.cc")
list(APPEND HDRS "${out_dir}/")
list(APPEND HDRS "${out_dir}/")

# message(${SRCS})
file(MAKE_DIRECTORY ${out_dir}/${FIL_DIR})
add_custom_command(
OUTPUT "${out_dir}/${FIL_WE}.pb.cc"
"${out_dir}/${FIL_WE}.pb.h"
"${out_dir}/${FIL_WE}.grpc.pb.cc"
"${out_dir}/${FIL_WE}.grpc.pb.h"
COMMAND protobuf::protoc
ARGS "--cpp_out=${out_dir}/${FIL_DIR}"
${_grpc_include_path}
${ABS_FIL}
COMMAND protobuf::protoc
ARGS "--grpc_out=${out_dir}/${FIL_DIR}"
"--plugin=protoc-gen-grpc=$<TARGET_FILE:gRPC::grpc_cpp_plugin>"
${_grpc_include_path}
${ABS_FIL}
DEPENDS ${ABS_FIL} protobuf::protoc gRPC::grpc_cpp_plugin
COMMENT "Running C++ protocol buffer compiler on ${FIL}"
VERBATIM
)
endforeach()

message("src: ${SRCS}")
message("har: ${HDRS}")

# set(LIBRARY_OUTPUT_PATH ${proto_output_path})
add_library(${PROJECT_NAME}
${SRCS}
)

target_include_directories(${PROJECT_NAME} PRIVATE
${HDRS}
)

target_link_libraries(${PROJECT_NAME}
${_GRPC_GRPCPP}
${_PROTOBUF_LIBPROTOBUF}
)