1、RPC

1.1 什么是RPC

RPC(Remote Procedure Call),即远程过程调用,过程就是方法,简单来说,它就是一种能够像调用本地方法一样调用远程计算机进程中的方法的技术,在这种调用中,我们不需要了解任何网络通信的细节(当然,就使用来说)

最终解决的问题:让分布式或者微服务系统中不同服务之间的调用像本地调用一样简单

1.2 RPC和HTTP

调用远程服务,HTTP 就可以完成的任务,为什么还需要 RPC 呢?需要注意,这两个并不是同一层次的概念,HTTP 是一种传输协议,RPC 应该是比 HTTP 更高层级的概念。完整的 RPC 实现包含有 传输协议序列化协议,其中,传输协议既可以使用 HTTP,也可以使用 TCP 等,不同的选择可以适应不同的场景

RPC 并不是一个崭新的概念,它实际上就是远程通信的一个更高层级的封装,不同传输协议和序列化协议的组合构成了不同的具体 RPC 实现,比如我们熟知的 RESTful,就是 HTTP + JSON + 一些其他细节构成

1.3 RPC技术的演化

早期有一些很流行的 RPC 实现,比如 CORBA(通用对象请求代理体系结构),Java RMI(远程方法调用),它们都用来构建和链接服务或应用程序,但是,大多数传统 RPC 实现极其复杂,因为它们构建在 TCP 之上,并且还有大量的规范限制

鉴于以上 RPC 实现的局限性,SOAP(简单对象访问协议)应运而生,SOAP 是 SOA(面向服务的架构)中的标准通信技术,能够基于任意底层通信协议进行通信,最常用的是 HTTP,序列化协议使用的是 XML

REST(描述性状态转移)是 ROA(面向资源的架构)的基础,在这种架构中,将应用程序建模为各种资源的集合,客户端可以变更这些资源的状态(增删改查)。REST 的通用实现是 HTTP + JSON,通过 HTTP 将应用程序建模为能够通过唯一标识符表示的资源集合,状态变更操作会采用 HTTP 动作(GET,POST,PUT,DELETE等)。实际上,REST 架构风格已经成为了各种服务间通信中非常流行的方法,但是,随着微服务大行其道以及网络交互的激增,REST 已经无法满足现代化的需求了,其主要原因是以下三个主要的局限性:

  • 基于文本的消息协议效率太低。REST 服务建立在基于文本的传输协议 HTTP1.x 上,使用人类可读的文本格式如 JSON,但是,很多时候我们并不需要这种可读性,如果能够直接发送映射服务和客户端业务逻辑的二进制内容,将大大提高效率
  • 缺乏强类型接口。开发 REST 服务时,应用程序之间并不需要共享服务定义和类型定义,我们要么通过网络查看文本格式,要么通过 API 文档,构建这种分散的应用程序时,会遇到很多不兼容、运行时错误和互操作等问题
  • REST架构风格难以实施。REST 架构风格有很多 “好的实践”,遵循这些实践能构建出真正好用的 REST 服务,但是,它们并没有作为协议的一部分进行强制要求,事实上,大多数 REST 服务不过是通过网络公开的 HTTP 服务,并没有很好地遵循基础的架构风格

由于 REST 的局限性,出现了许多新兴的 RPC 技术,较为流行的有 gRPC、Thrift、GraphQL等

2、gRPC

2.1 gRPC简介

gRPC 是一个现代化的开源 RPC 框架,一开始由 google 开发,是一款语言中立、平台中立、的 RPC 系统,与许多 RPC 系统类似,gRPC 也是基于以下理念:定义一个 服务,指定能够被远程调用的 方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个gRPC 服务器来处理客户端调用,在客户端拥有一个 stub 连接服务端上的方法

2.2 gRPC的优势

gRPC 的优势是它被越来越多人采用的关键所在,主要有以下几个方面:

  • 提供高效的进程间通信。使用一个基于 protocol buffers 的二进制协议而不是文本格式与客户端通信,同时在 HTTP2 上实现,拥有更好的性能
  • 具有简单且定义良好的服务接口。契约优先,必须首先定义服务接口,然后才能去处理细节,简单一致,可扩展
  • 强类型。服务契约清晰地定义了应用程序间通信所使用的类型,分布式应用程序的开发更加稳定
  • 支持多语言。基于 protocol buffers 的服务定义是语言中立的,可以选择任意一种语言具体实现
  • 支持双工流。与传统的 REST 相比,gRPC 能够同时构建传统的请求-响应风格的消息以及客户端流和服务端流
  • 具备内置的商业化特性。如认证、加密、弹性时间、元数据交换、压缩、负载均衡以及服务发现等
  • 与云原生生态进行了集成。gRPC 是 CNCF(云原生计算基金会)的一部分,大多数现代框架和技术都对 gRPC 提供了原生支持
  • 业界成熟。通过在谷歌进行的大量实战测试,gRPC 已经发展成熟,被许多公司采用

2.3 gRPC的缺点

gRPC 也存在一定劣势,选择它用来构建应用程序时,需要注意以下三点:

  • gRPC 不太适合面向外部的服务。gRPC 具有契约驱动、强类型等特点,这会限制向外部暴露服务的灵活性,对客户端有诸多限制,所以更适合用在内部服务器之间通信
  • 避免巨大的服务定义变更。如果出现巨大的服务定义变更,通常需要重新生成客户端代码和服务端代码,会让整个开发生命周期变得复杂,需要小心引入破坏性的变更
  • 生态系统相对较小。与传统 REST 等协议相比,gRPC 仍然处于起步阶段,浏览器和移动应用程序对 gRPC 的支持才刚刚起步

3、一个简单gRPC服务的golang实现

3.1 环境准备

  • 下载 protoc 编译器:protobuf,选择合适的平台,解压后将可执行文件加入环境变量,此编译器用来编译服务定义文件 .proto 生成指定语言的目标代码,这些代码用来实现 gRPC 服务以及客户端 stub

  • 安装 grpc-go 插件用来生成 go 目标代码
go install google.golang.org/protobuf/cmd/protoc-gen-go
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc
  • 创建代码目录 product-info,实现一个简单的查看商品信息和添加商品的 rpc 服务,在其中新建三个文件夹 proto、server、client 分别存放服务定义文件和生成的目标代码、服务端程序实现、客户端程序实现,然后执行 go mod init product-info 初始化模块。当然这里只是示例程序,实际场景中服务代码和客户端代码一般都不在同一个机器上,更不可能在同一个模块下了,最终目录结构如下:

3.2 服务定义

开发 gRPC 应用程序时,要首先定义服务接口,然后生成服务端骨架和客户端 stub,客户端通过调用其中定义的方法来访问远程服务器上的方法,服务定义都以 protocol buffers 的形式记录,也就是 gRPC 所使用的服务定义语言

  • 在 proto 目录下新建服务定义文件 product-info.proto
// 版本
syntax = "proto3";
// proto文件所属包名
package proto;
// 声明生成的go文件所属的包,路径末尾为包名,相对路径是相对于编译生成目标代码时的工作路径
option go_package = "./proto"; // 包含两个远程方法的 rpc 服务,远程方法只能有一个参数和一个返回值
service ProductInfo {
rpc addProduct(Product) returns (ProductID);
rpc getProduct(ProductID) returns (Product);
} // 自定义消息类型,用这种方法传递多个参数,必须使用唯一数字标识每个字段
message Product {
string id = 1;
string name = 2;
string description = 3;
float price = 4;
} message ProductID {
string value = 1;
}
  • 编译服务定义文件生成目标源代码,这一步之后在 proto 文件下生成了以下两个文件:

    • product-info.pb.go,包含用于填充、序列化、检索请求和响应消息类型的所有 protocol buffers 代码
    • product-info_grpc.pb.go,包含服务端需要继承实现和客户端进行调用的接口定义
# go_out 和 go-grpc-out 目录是相对于服务定义文件中 go_package 指定的目录
protoc proto/product-info.proto --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative

3.3 服务端实现

编译生成服务端骨架的时候,已经得到了建立 gRPC 连接、相关消息类型和接口的基础代码,接下来就是实现得到的接口,在 server 文件夹中新建服务端主程序 main.go:

package main

import (
"context"
"log"
"net" pb "product-info/proto" "github.com/gofrs/uuid"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
) const (
port = ":50051"
) // 对服务器的抽象,用来实现服务方法
type server struct {
pb.UnimplementedProductInfoServer
} // 存放商品,模拟业务逻辑
var productMap map[string]*pb.Product // 实现 AddProduct 方法
func (s *server) AddProduct(ctx context.Context, in *pb.Product) (*pb.ProductID, error) {
out, err := uuid.NewV4()
if err != nil {
return nil, status.Errorf(codes.Internal, "Error while generating Product ID", err)
}
in.Id = out.String()
if productMap == nil {
productMap = make(map[string]*pb.Product)
}
productMap[in.Id] = in
log.Printf("Product %v : %v - Added.", in.Id, in.Name)
return &pb.ProductID{Value: in.Id}, nil
} // 实现 GetProduct 方法
func (s *server) GetProduct(ctx context.Context, in *pb.ProductID) (*pb.Product, error) {
product, exists := productMap[in.Value]
if exists && product != nil {
log.Printf("Product %v : %v - Retrieved.", product.Id, product.Name)
return product, nil
}
return nil, status.Errorf(codes.NotFound, "Product does not exist.", in.Value)
} func main() {
// 创建一个 tcp 监听器
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// 创建一个 gRPC 服务器实例
s := grpc.NewServer()
// 将服务注册到 gRPC 服务器上
pb.RegisterProductInfoServer(s, &server{})
// 绑定 gRPC 服务器到指定 tcp
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}

3.4 客户端实现

接下来创建客户端程序来与服务器对话,之前编译服务定义文件生成的目标源代码已经包含了访问细节的实现,我们只需要创建客户端实例就可以直接调用远程方法。在 client 文件夹中创建客户端主程序 main.go:

package main

import (
"context"
"log"
"time" pb "product-info/proto" "google.golang.org/grpc"
) const (
// 服务端地址
address = "localhost:50051"
) func main() {
// 创建 gRPC 连接
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close() // 创建客户端 stub,利用它调用远程方法
c := pb.NewProductInfoClient(conn) name := "XiaoMi 11"
description := "XiaoMi 11 with MIUI 12.5"
price := float32(3999.00) // 创建 Context 传递给远程调用,包含一些元数据,如终端用户标识、授权令牌以及请求截止时间等
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
// 调用远程方法
r, err := c.AddProduct(ctx, &pb.Product{Name: name, Description: description, Price: price})
if err != nil {
log.Fatalf("Could not add product: %v", err)
}
log.Printf("Product ID: %s added successfully", r.Value) product, err := c.GetProduct(ctx, &pb.ProductID{Value: r.Value})
if err != nil {
log.Fatalf("Could not get product: %v", err)
}
log.Printf("Product: %v", product.String())
}

3.5 构建和运行

最终工作空间如下:

分别构建运行服务端和客户端程序,go build 或者直接 go run

  • 启动服务端:go run ./server/main.go
  • 启动客户端:go run ./client/main.go

  • 服务端 log:

到这里就成功构建了一个简单的 gRPC 服务,并在客户端调用成功。当然这只是一个简单的入门程序,更多的细节还需要更加深入的学习,另外,gRPC 是支持多语言的,这里采用 golang 实现了服务端和客户端程序,其他的语言构建 gRPC 服务也都遵循类似的步骤,且客户端和服务端代码无关,也可用不同的语言实现,其他语言的用法可见 官方文档

gRPC入门—golang实现的更多相关文章

  1. Go语言入门篇-gRPC基于golang & java简单实现

    一.什么是RPC 1.简介: RPC:Remote Procedure Call,远程过程调用.简单来说就是两个进程之间的数据交互. 正常服务端的接口服务是提供给用户端(在Web开发中就是浏览器)或者 ...

  2. Go 中的 gRPC 入门详解

    目录 Go GRPC 入门 1,安装包 2,gRPC 服务端 3,gRPC 客户端 4,编译运行 5,其它 GRPC Protobuf buffer 字段类型 字段规则 Protobuf gRPC 四 ...

  3. 跟我一起学Go系列:gRPC 入门必备

    RPC 的定义这里就不再说,看文章的同学都是成熟的开发.gRPC 是 Google 开源的高性能跨语言的 RPC 方案,该框架的作者 Louis Ryan 阐述了设计这款框架的动机,有兴趣的同学可以看 ...

  4. 微服务架构攀登之路(三)之gRPC入门

    一.gRPC入门 1. gRPC 简介 gRPC 由 google 开发,是一款语言中立.平台中立.开源的远程过程调用系统 gRPC 客户端和服务端可以在多种环境中运行和交互,例如用 java 写一个 ...

  5. grpc 入门(一)--hello world

    一,从rpc接口的定义说起,下面给一个最简单的grpc示例--hello world 在这个rpc横行的世界里,实现一个rpc很重要的一件事就是定义一个好接口,一个好的接口定义会让你省去很多麻烦.熟悉 ...

  6. google的grpc在golang中的使用

    GRPC是google开源的一个高性能.跨语言的RPC框架,基于HTTP2协议,基于protobuf 3.x,基于Netty 4.x. 前面写过一篇golang标准库的rpc包的用法,这篇文章接着讲一 ...

  7. grpc入门(三)

    grpc入门(三) 一.介绍 本文是关于grpc的第三篇博文,是对前两篇博文的具体代码实现,秉着个人一贯的风格,没有太多抒情和总结,直接就上代码. 文章代码参考:https://github.com/ ...

  8. Go GRPC 入门(二)

    前言 最近较忙,其实准备一篇搞定的 中途有事,只能隔了一天再写 正文 pb.go 需要注意的是,在本个 demo 中,客户端与服务端都是 Golang,所以在客户端与服务端都公用一个 pb.go 模板 ...

  9. window下golang使用gRPC入门案例&net core客户端

    gRPC是google开源高性能分布式RPC框架,支持http/2 双向数据流传输及Protobuff,可以在任何环境下运行. 它可以有效地将数据中心内和跨数据中心的服务与可插拔支持进行负载均衡,跟踪 ...

随机推荐

  1. JDBC核心技术(获取数据库链接、数据库事务、数据库链接池)

    @ 目录 前言 数据的持久化 Java数据存储技术 JDBC介绍 JDBC体系结构 获取数据库链接 Driver接口 加载注册JDBC驱动 获取数据库链接 数据库链接方式(实例) 方式一:代码中显示出 ...

  2. 认识 Spring Cloud Alibaba

    个人理解 Spring Cloud Alibaba 就是 Spring Cloud 的微服务规范的一种实现,外加一些阿里云的商业组件 Spring Cloud 是什么 Spring Cloud 为开发 ...

  3. docker中运行envoy 报错 cannot bind '0.0.0.0:80': Permission denied

    docker-compose文件 version: '3' services: envoy: image: envoyproxy/envoy-alpine:v1.15-latest volumes: ...

  4. [bug] 前台表单添加数据,后台返回成功消息,但数据库相应字段值为null

    原因 前端代码中输入框字段值和后端定义的属性值不一致 分析 bean . controller 如下图,浏览器收到后台返回消息,在后台也可打印user信息,说明已成功接收前端JSON数据并转换为use ...

  5. 华为eNSP模拟器— telnet实验

    华为eNSP模拟器-telnet实验 一.实验一 路由交换之间实现telnet登陆 实验拓扑 实验目的: 路由器作为 telnet 服务器 交换机作为客户端去连接路由器 实验步骤: 路由器配置 < ...

  6. Ubuntu 16.04 Bridge配置

    Ubuntu 16.04 Bridge配置 一.安装网桥拓展包 bridge-utils root@ubuntu:~# apt-get install bridge-utils 二.创建网桥设备 // ...

  7. Rust 泛型

    Rust 泛型 泛型程序设计(generic programming)是程序设计语言的一种风格或范式.泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些 ...

  8. python基础之面向对象(三))(实战:烤地瓜(SweetPotato))

    一.分析"烤地瓜"的属性和方法 示例属性如下: cookedLevel : 这是数字:0~3表示还是生的,超过3表示半生不熟,超过5表示已经烤好了,超过8表示已经烤成木炭了!我们的 ...

  9. Java 语言的主要特性

    Java语言是简单的 Java语言的语法与C语言和C++语言很接近,使得大多数程序员很容易学习和使用. Java丢弃了C++中很少使用的.很难理解的.令人迷惑的那些特性,如操作符重载.多继承.自动的强 ...

  10. Linux中级之lvs三个模式的图像补充(nat,dr,tun)

    负载均衡(Load Balance)集群提供了一种廉价.有效.透明的方法,来扩展网络设备和服务器的负载.带宽.增加吞吐量.加强网络数据处理能力.提高网络的灵活性和可用性. (1)单台计算机无法承受大规 ...