一、概述

What's gRPC?
gRPC is a modern open source high performance RPC framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services.(gRPC是可以在任何环境中运行的现代的开源高性能RPC框架。它可以通过可插拔的支持来有效地连接数据中心内和跨数据中心的服务,以实现负载平衡,跟踪,运行状况检查和身份验证。它也适用于分布式计算的最后一英里,以将设备,移动应用程序和浏览器连接到后端服务。)
我们可以用一句话来概括:A high-performance, open-source universal RPC framework
RPC(remote procedure call 远程过程调用)框架实际是提供了一套机制,使得应用程序之间可以进行通信,而且也遵从server/client模型。使用的时候客户端调用server端提供的接口就像是调用本地的函数一样。

so
在什么情况下需要使用gRPC呢?
需要对接口进行严格约束的情况,比如我们提供了一个公共的服务,很多人,甚至公司外部的人也可以访问这个服务,这时对于接口我们希望有更加严格的约束,我们不希望客户端给我们传递任意的数据,尤其是考虑到安全性的因素,我们通常需要对接口进行更加严格的约束。这时gRPC就可以通过protobuf来提供严格的接口约束。
对于性能有更高的要求时。有时我们的服务需要传递大量的数据,而又希望不影响我们的性能,这个时候也可以考虑gRPC服务,因为通过protobuf我们可以将数据压缩编码转化为二进制格式,通常传递的数据量要小得多,而且通过http2我们可以实现异步的请求,从而大大提高了通信效率。
但是,通常我们不会去单独使用gRPC,而是将gRPC作为一个部件进行使用,这是因为在生产环境,我们面对大并发的情况下,需要使用分布式系统来去处理,而gRPC并没有提供分布式系统相关的一些必要组件。而且,真正的线上服务还需要提供包括负载均衡,限流熔断,监控报jing,服务注册和发现等等必要的组件。

二、测试

接下来开始gRPC的Hello World测试
gRPC的使用通常包括如下几个步骤:
1、通过protobuf来定义接口和数据类型
2、编写gRPC server端代码
3、编写gRPC client端代码

1、通过protobuf来定义接口和数据类型

我实在mac中使用的python,mac中自带python2,因为需要,自己安装了python3。
首先pip3 安装grpc和protobuf

(lxc) liuxuchong:untitled liuxuchong$  pip3 install grpcio
Collecting grpcio
  Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ReadTimeoutError("HTTPSConnectionPool(host='files.pythonhosted.org', port=443): Read timed out. (read timeout=15)")': /packages/0d/27/0413a5dffd7ddca4ea43cffd22f46ec2b26a5ed18c974e4448763e758a9b/grpcio-1.25.0-cp37-cp37m-macosx_10_9_x86_64.whl
  Downloading https://files.pythonhosted.org/packages/0d/27/0413a5dffd7ddca4ea43cffd22f46ec2b26a5ed18c974e4448763e758a9b/grpcio-1.25.0-cp37-cp37m-macosx_10_9_x86_64.whl (2.3MB)
    100% |████████████████████████████████| 2.3MB 8.1kB/s 
Collecting six>=1.5.2 (from grpcio)
  Using cached https://files.pythonhosted.org/packages/65/26/32b8464df2a97e6dd1b656ed26b2c194606c16fe163c695a992b36c11cdf/six-1.13.0-py2.py3-none-any.whl
Installing collected packages: six, grpcio
Successfully installed grpcio-1.25.0 six-1.13.0

(lxc) liuxuchong:untitled liuxuchong$  pip3 install protobuf
Collecting protobuf
  Downloading https://files.pythonhosted.org/packages/a5/c6/a8b6a74ab1e165f0aaa673a46f5c895af8780976880c98934ae82060356d/protobuf-3.10.0-cp37-cp37m-macosx_10_9_intel.whl (1.4MB)
    100% |████████████████████████████████| 1.4MB 83kB/s 
Requirement already satisfied: setuptools in ./venv/lxc/lib/python3.7/site-packages/setuptools-40.8.0-py3.7.egg (from protobuf) (40.8.0)
Requirement already satisfied: six>=1.9 in ./venv/lxc/lib/python3.7/site-packages (from protobuf) (1.13.0)
Installing collected packages: protobuf
Successfully installed protobuf-3.10.0

定义protobuf

下面定义一个简单的protobuf文件,在其中声明一个grpc服务。
创建一个proto目录,并在其中创建grpchello.proto文件,如下内容。

syntax = "proto3";
package grpcDemo;

message HelloRequest {
   string name = 1;
}

message HelloReply {
   string message = 1;
}

service gRPC {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

编译protobuf

使用protobuf的编译器,为我们生成python版本的Message定义和服务的架手脚。

lxc) liuxuchong:untitled liuxuchong$  python -m grpc-tools.protoc -I./proto --python_out=. --grpc_python_out=. grpchello.proto
/Users/liuxuchong/PycharmProjects/untitled/venv/lxc/bin/python: Error while finding module specification for 'grpc-tools.protoc' (ModuleNotFoundError: No module named 'grpc-tools')

提示我们没有安装grpc-tools,用pip安装一下

python3 -m pip install --user grpcio-tools

然后在运行上面的命令

在当前目录下,生成2个文件:

  • grpchello_pb2.py
  • grpchello_pb2_grpc.py

查看第一个文件,消息定义文件:

# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler.  DO NOT EDIT!
# source: grpchello.proto

import sys
_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()

DESCRIPTOR = _descriptor.FileDescriptor(
  name='grpchello.proto',
  package='grpcDemo',
  syntax='proto3',
  serialized_options=None,
  serialized_pb=_b('\n\x0fgrpchello.proto\x12\x08grpcDemo\"\x1c\n\x0cHelloRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"\x1d\n\nHelloReply\x12\x0f\n\x07message\x18\x01 \x01(\t2B\n\x04gRPC\x12:\n\x08SayHello\x12\x16.grpcDemo.HelloRequest\x1a\x14.grpcDemo.HelloReply\"\x00\x62\x06proto3')
)

_HELLOREQUEST = _descriptor.Descriptor(
  name='HelloRequest',
  full_name='grpcDemo.HelloRequest',
  filename=None,
  file=DESCRIPTOR,
  containing_type=None,
  fields=[
    _descriptor.FieldDescriptor(
      name='name', full_name='grpcDemo.HelloRequest.name', index=0,
      number=1, type=9, cpp_type=9, label=1,
      has_default_value=False, default_value=_b("").decode('utf-8'),
      message_type=None, enum_type=None, containing_type=None,
      is_extension=False, extension_scope=None,
      serialized_options=None, file=DESCRIPTOR),
  ],
  extensions=[
  ],
  nested_types=[],
  enum_types=[
  ],
  serialized_options=None,
  is_extendable=False,
  syntax='proto3',
  extension_ranges=[],
  oneofs=[
  ],
  serialized_start=29,
  serialized_end=57,
)

然后看下grpc服务定义:

from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()

DESCRIPTOR = _descriptor.FileDescriptor(
  name='grpchello.proto',
  package='grpcDemo',
  syntax='proto3',
  serialized_options=None,
  serialized_pb=_b('\n\x0fgrpchello.proto\x12\x08grpcDemo\"\x1c\n\x0cHelloRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"\x1d\n\nHelloReply\x12\x0f\n\x07message\x18\x01 \x01(\t2B\n\x04gRPC\x12:\n\x08SayHello\x12\x16.grpcDemo.HelloRequest\x1a\x14.grpcDemo.HelloReply\"\x00\x62\x06proto3')
)

_HELLOREQUEST = _descriptor.Descriptor(
  name='HelloRequest',
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()

DESCRIPTOR = _descriptor.FileDescriptor(
  name='grpchello.proto',
  package='grpcDemo',
  syntax='proto3',
  serialized_options=None,
  serialized_pb=_b('\n\x0fgrpchello.proto\x12\x08grpcDemo\"\x1c\n\x0cHelloRequest\\
x12\x0c\n\x04name\x18\x01 \x01(\t\"\x1d\n\nHelloReply\x12\x0f\n\x07message\x18\x011
 \x01(\t2B\n\x04gRPC\x12:\n\x08SayHello\x12\x16.grpcDemo.HelloRequest\x1a\x14.grpcc
Demo.HelloReply\"\x00\x62\x06proto3')
)

_HELLOREQUEST = _descriptor.Descriptor(
  name='HelloRequest',
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
import grpc

import grpchello_pb2 as grpchello__pb2

class gRPCStub(object):
  # missing associated documentation comment in .proto file
  pass

  def __init__(self, channel):
    """Constructor.

    Args:
      channel: A grpc.Channel.
    """
    self.SayHello = channel.unary_unary(
        '/grpcDemo.gRPC/SayHello',
        request_serializer=grpchello__pb2.HelloRequest.SerializeToString,
        response_deserializer=grpchello__pb2.HelloReply.FromString,
        )

class gRPCServicer(object):
  # missing associated documentation comment in .proto file
  pass

  def SayHello(self, request, context):
    # missing associated documentation comment in .proto file
    pass
    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
    context.set_details('Method not implemented!')
    raise NotImplementedError('Method not implemented!')

def add_gRPCServicer_to_server(servicer, server):
  rpc_method_handlers = {
      'SayHello': grpc.unary_unary_rpc_method_handler(
          servicer.SayHello,
          request_deserializer=grpchello__pb2.HelloRequest.FromString,
          response_serializer=grpchello__pb2.HelloReply.SerializeToString,
      ),
  }
  generic_handler = grpc.method_handlers_generic_handler(
      'grpcDemo.gRPC', rpc_method_handlers)
  server.add_generic_rpc_handlers((generic_handler,))
  • 在grpc服务架手脚定义中,定义了gRPCStub,这是给client端使用,调用grpc服务的。
  • 定义的服务类gRPCServicer,方法SayHello需要我们在子类中进行实现。定义的add_gRPCServicer_to_server方法,用于把实现的类和grpc API调用注册起来。

这里使用的几个主要方法(类):

  • grpc.server – Creates a Server with which RPCs can be serviced
  • grpc.method_handlers_generic_handler – Creates a GenericRpcHandler from RpcMethodHandlers.
  • grpc.unary_unary_rpc_method_handler – Creates an RpcMethodHandler for a unary-unary RPC method.

实现服务

在我们的实现服务的类中,使用服务方法,并在网络中暴露出来。

# -*- coding: utf-8 -*-
import grpc
import time
from concurrent import futures 
import grpchello_pb2, grpchello_pb2_grpc

_HOST = 'localhost'
_PORT = '8188'

_ONE_DAY_IN_SECONDS = 60 * 60 * 24

class gRPCServicerImpl(grpchello_pb2_grpc.gRPCServicer):

    def SayHello(self, request, context):
        print ("called with " + request.name)
        return grpchello_pb2.HelloReply(message='Hello, %s!' % request.name)

def serve():
  server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
  grpchello_pb2_grpc.add_gRPCServicer_to_server(gRPCServicerImpl(), server)
  server.add_insecure_port('[::]:'+_PORT)
  server.start()
  try:
    while True:
      time.sleep(_ONE_DAY_IN_SECONDS)
  except KeyboardInterrupt:
    server.stop(0)

if __name__ == '__main__':
    serve()

这里包括2个实现:

  • 1、在grpc的API的实现(服务实现类)gRPCServicerImpl中,实现SayHello方法。
  • 2、然后,定义网络服务和端口,把grpc的API注册到网络服务的处理上。这里简单利用了grpc.server类。

使用客户端client

在客户端,调用grpc的服务API。

# -*- coding: utf-8 -*-
"""The Python implementation of the gRPC client."""
from __future__ import print_function
import grpc
from grpchello_pb2  import *    ## or import grpchello_pb2
from grpchello_pb2_grpc import *
## No grpcDemo!  from grpcDemo import grpchello_pb2, grpchello_pb2_grpc #error!

_PORT = '8188'

def run():
    conn = grpc.insecure_channel(_HOST + ':' + _PORT)
    client = gRPCStub(channel=conn)
    response = client.SayHello(HelloRequest(name='lxc'))
    print("received: " + response.message)

## 
if __name__ == '__main__':

    if len(sys.argv)== 2:
        print (sys.argv[1])
        _HOST = sys.argv[1]
    else:
        _HOST = 'localhost'

    #    
    run()

说明:

  • 1、 def insecure_channel(target, options=None):
    – Creates an insecure Channel to a server.
  • 2、 客户端使用服务的Stub,调用API。

测试

分别启动服务,然后再启动客户端,可以看到调用结果。
也可以启动java、c#版本的grpc服务端、客户端,都能调用成功。

然后运行

(lxc) liuxuchong:untitled liuxuchong$  python3 grpchello_pb2.py
Traceback (most recent call last):
  File "grpchello_pb2.py", line 7, in <module>
    from google.protobuf import descriptor as _descriptor
ModuleNotFoundError: No module named 'google'

提示没有google模版
网上查了一下解决方法如下

pip3 install google
pip3 install protobuf

然后运行另一个py文件

python3 grpchello_pb2_grpc.py
Traceback (most recent call last):
  File "grpchello_pb2_grpc.py", line 2, in <module>
    import grpc

奇怪,刚才明明装了,还提示没有模版,又装了一次

pip3 install grpcio
Collecting grpcio
  Using cached https://files.pythonhosted.org/packages/0d/27/0413a5dffd7ddca4ea43cffd22f46ec2b26a5ed18c974e4448763e758a9b/grpcio-1.25.0-cp37-cp37m-macosx_10_9_x86_64.whl
Requirement already satisfied: six>=1.5.2 in /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages (from grpcio) (1.13.0)
Installing collected packages: grpcio
Successfully installed grpcio-1.25.0

果然刚才装的丢了。。。
然后运行python3 get_service.py

gRPC HelloWorld测试
打开一个新的窗口运行
python3 client.py
gRPC HelloWorld测试

在原来的窗口可以看到called with lxc
gRPC HelloWorld测试

参考资料:
https://www.jianshu.co×××c947d98e192
https://blog.csdn.net/whereismatrix/article/details/78595550