花椒服务端 gRPC 开发实践

2023-11-01 05:10

本文主要是介绍花椒服务端 gRPC 开发实践,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

背景

在移动端平台开发中,为了增加代码复用,降低开发成本,通常会需要采用跨平台的开发技术,花椒也不例外。本次新的单品开发,由于时间紧,人员有限,经过调研选型,最终确定了 Flutter 方案(具体选型过程不在本文讨论之内)。

为了让客户端更专注业务实现,降低接口联调测试成本,我们选用了 gRPC 方案。gRPC 是一个高性能、通用的开源 RPC 框架,由 Google 开发并基于 HTTP/2 协议标准而设计,基于 ProtoBuf(Protocol Buffers)序列化协议开发,且支持当前主流开发语言。gRPC 通过定义一个服务并指定一个可以远程调用的带有参数和返回类型的的方法,使客户端可以直接调用不同机器上的服务应用的方法,就像是本地对象一样。在服务端,服务实现这个接口并且运行 gRPC 服务处理客户端调用。在客户端,有一个 stub 提供和服务端相同的方法。

gRPC

特点:

  • 基于标准化的 IDL(ProtoBuf)来生成服务器端和客户端代码,支持多种主流开发语言。同时可以更好的支持团队与团队之间的接口设计,开发,测试,协作等。

  • 基于 HTTP/2 设计,支持双向流,多路复用,头部压缩。

  • 支持流式发送和响应,批量传输数据,提升性能。

  • ProtoBuf 序列化数据抓包、调试难度较大。我们使用服务端注入方式提供了用户或设备过滤,请求及返回值日志捕获,并开发对应后台模拟抓包展示。

  • 相比 JSON, 对前端不够友好。gRPC 生态提供了 Gateway 的方式为 gRPC 服务代理出 RESTful 接口。

  • ProtoBuf 提供了非常强的扩展性,可以为 protoc 开发定制插件,从而扩展 proto 文件的功能及描述性。

gRPC-Web

gRPC-Web 为前端浏览器提供了 JavaScript 库用来访问 gRPC 服务,但是需要通过 Envoy 提供代理服务。相比 JSON 的方式对前端有够友好,同时也增加了服务端的部署成本。因此在这次项目中前端未使用 gRPC 服务,而是由 gRPC-Gateway 提供代理的 RESTful 接口。

gRPC-Gateway

grpc-gateway 是 protoc 的一个插件,它能读取 gRPC 的服务定义并生成反向代理服务器,将 RESTful 的 JSON 请求转换为 gRPC 的方式。这样无需太多工作即可实现一套基于 gRPC 服务的 RESTful 接口,方便前端使用调用接口,同时也方便开发过程中通过 Postman/Paw 之类的工具调试接口。

gateway -> gRPC 映射方式:

  • HTTP 源 IP 添加到 gRPC 的 X-Forwarded-For 请求头

  • HTTP 请求 Host 添加到 gRPC 的 X-Forwarded-Host 请求头

  • HTTP 请求头 Authorization 添加到 gRPC 的 authorization 请求头

  • HTTP 请求头带 Grpc-Metadata- 前缀的映射到 gRPC 的 metadata(key 名不带前缀)

例如,gRPC 接口要求的通用的 metadata 参数(如 platform,device_id 等)在 HTTP RESTful 的传递方式如下:

GET /index HTTP/1.1
grpc-metadata-platform: ios
grpc-metadata-device_id: xxxxxxxxx
grpc-metadata-timestamp: 1562641496
grpc-metadata-locale: en_US
grpc-metadata-version: 1.0.0
grpc-metadata-user_id: 
grpc-metadata-user_token: 
Host: gateway.hostame.com

基础库

dart

为了便于客户端调用,连接复用及通用参数传递,我们封装了 dart 的基础库。

BaseClient 维护了针对 HOST 缓存的连接池,同时也提供了接口需要传递的 metadata 信息。

var base = BaseClient(host: 'rpc.hostame.com', port: 443, secure: true);
final md = await base.metadata;
final stub = AuthClient(base.channel, options: CallOptions(metadata: md));

Golang

Golang 后端服务需要同时支持 gRPC 和 Gateway 两种请求方式。为了简化部署和上线依赖,Gateway 和 gRPC 的功能放在了一起,并通过拦截器注入对应的功能,主要包括 gRPC 统计,访问日志,接口鉴权,请求参数校验,gateway JSON 编码等。

svrMux := &ServerMux{...ServeMux: http.NewServeMux(),}svrMux.svr = grpc.NewServer(grpc.UnaryInterceptor(middleware.ChainUnaryServer(recovery.UnaryServerInterceptor(recovery.WithRecoveryHandler(svrMux.recoveryHandler)),prometheus.UnaryServerInterceptor, // prometheus 统计svrMux.UnaryResponseInterceptor(),proto.UnaryServerInterceptor(), // metadata 解析log.UnaryServerInterceptor(conf.GlobalCallback()), // 访问日志auth.UnaryServerInterceptor(conf.GlobalCallback()), // 接口鉴权validator.UnaryServerInterceptor(), // 请求参数校验)),grpc.StreamInterceptor(middleware.ChainStreamServer(...)))svrMux.mux = runtime.NewServeMux(runtime.WithMarshalerOption(runtime.MIMEWildcard, json.Proto), // json 设置runtime.WithProtoErrorHandler(svrMux.protoErrorHandler), // 错误处理runtime.WithStreamErrorHandler(svrMux.streamErrorHandler),)// gRPC gateway muxsvrMux.ServeMux.Handle("/", svrMux.mux)

引用到的 package:

github.com/golang/protobuf
github.com/grpc-ecosystem/go-grpc-middleware
github.com/grpc-ecosystem/go-grpc-prometheus
github.com/grpc-ecosystem/grpc-gateway
github.com/lnnujxxy/protoc-gen-validate
github.com/youlu-cn/grpc-gen
go.uber.org/zap
google.golang.org/grpc

开发流程:

为了提高开发效率,方便维护及模块复用,服务端按功能进行组件化开发。每个组件可以单独运行一个服务,也可以和其它组件共同组成一个服务。每个组件都需要实现 Component 接口:

type Component interface {// 组件名称Name() string// 初始化存储InitStorage() error// 初始化注册 gRPCInitGRPC(svc Service) error// 初始化注册 gatewayInitGateway(svc Service) error// cron 回调StorageCron()
}

对应组件开发完成后,需要开发对应的服务容器,步骤如下。

初始化 base package:

base.Init(context.TODO(), cfg, &global.Callback{Authenticator: &auth.Callback{},LogCapture:    &log.Capture{},
})

如需对外提供服务,需要提供端口及 TLS 证书:

base.DefaultServer.AddPublicServer(rpcPort, gatewayPort, setting.TLSConfig)

组件注册:

base.DefaultServer.RegisterComponent(&user.Component{})
base.DefaultServer.RegisterComponent(&push.Component{})
...

监听服务:

base.DefaultServer.Serve()

接口定义及实现

proto 规范

gRPC 基于标准化的 IDL(ProtoBuf)来生成服务器端和客户端代码,我们决定将所有的接口描述及文档说明都放到 proto 文件中,便于查看及修改。对 proto 的接口描述及注释的规范如下:

// 消息类型注释,支持多行,
// 支持 markdown 语法:
//
// > blockquote
//
// | Syntax | Description |
// | ----------- | ----------- |
// | Header | Title |
// | Paragraph | Text |
message ExampleMessage {uint64 id = 1; // 字段注释,简洁
}// 服务说明,支持 markdown 语法
//
// 1. First item
// 2. Second item
// 3. Third item
//
// ```json
// {
//   "firstName": "John",
//   "lastName": "Smith",
//   "age": 25
// }
// ```
service Example {option (auth.visible) = {scope: PUBLIC_SCOPE // Service scope:仅内网可见或对外可见};// 方法说明,支持 markdown//// - [x] Write the press release// - [ ] Update the website// - [ ] Contact the mediarpc test (ExampleMessage) returns (ExampleMessage) {option (auth.access) = {level: LOW_ACCESS_LEVEL // 接口请求权限};option (google.api.http) = {post: "/example/test"body: "*"};}
}

代码生成

Golang

gengo:@protoc -Iproto \-I${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \-I${GOPATH}/src/github.com/lnnujxxy/protoc-gen-validate \-I${GOPATH}/src/github.com/youlu-cn/grpc-gen/protoc-gen-auth \--go_out=plugins=grpc:go/pb \--grpc-gateway_out=logtostderr=true:go/pb \--validate_out="lang=go:go/pb" \--auth_out="lang=go:go/pb" \proto/*.proto

SDK 引入:Golang 使用 go mod 的方式直接引入 pb 生成的 .go 文件。

dart

ifeq ($(shell uname), Darwin)
PROTO_ROOT_DIR = $(shell brew --prefix)/Cellar/protobuf/*
else
PROTO_ROOT_DIR = /usr/local
endifgendart:@protoc --dart_out=dart/front_user/lib/src/generated $(PROTO_ROOT_DIR)/include/google/protobuf/*.proto@protoc -Iproto \-I${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \-I${GOPATH}/src/github.com/lnnujxxy/protoc-gen-validate \-I${GOPATH}/src/github.com/youlu-cn/grpc-gen/protoc-gen-auth \--dart_out=grpc:dart/user/lib proto/*.proto

SDK 引入:

修改 pubspec.yaml,执行 flutter packages get 或 flutter packages upgrade。

dependencies:flutter:sdk: flutterprotobuf: ^0.13.4grpc: ^1.0.1user:git:url: git@github.com:project/repo.gitpath: dart/user

已知问题:dart 在对 protobuf 生成的类型做 json 编码时,json 中的 key 是字段号而非名字,导致无法与其它语言交互。

K8s已经成为一线大厂分布式平台的标配技术。你是不是还在惆怅怎么掌握它?来这里,大型互联网公司一线工程师亲授,不来虚的,直接上手实战,3天时间带你搭建K8s平台,快速学会K8s,点击下方图片可了解培训详情。

文档生成

gRPC Gateway 提供了通过 proto 文件生成 swagger API 文档,缺点是只支持 Gateway 的 RESTful 接口,并且默认的展示方式有点不符合我们的常规文档使用方式。

我们基于 protoc 插件开发了 protoc-gen-markdown 工具,可以由 proto 文件生成 markdown 文档,提供 gRPC 接口描述,以及 RESTful 接口描述及 JSON 示例,提供全文目录,支持锚点导航等。生成方式如下:

gendoc:@protoc -Iproto \-I${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \-I${GOPATH}/src/github.com/lnnujxxy/protoc-gen-validate \-I${GOPATH}/src/github.com/youlu-cn/grpc-gen/protoc-gen-auth \--markdown_out=":doc" \proto/*.proto

文档会在对应路径生成接口列表 README.md,以及每个 protobuf 对应的接口文档。

调试

传统的 RESTful 接口在调试及问题排查时,可以通过抓包或者 MitM(中间人攻击)的方式,配置也比较容易。而 gRPC 因为使用了 HTTP2 及 protobuf 二进制流,抓包及数据流反解难度相对较高,调试及问题排查时会比较复杂。为了解决这个问题,我们通过服务端注入的方式,配合查询后台过滤对应的请求日志,从而实现如下类似抓包的效果。

后续计划

  • gRPC Streaming

  • 框架层集成全链路 Trace 支持

  • 迭代优化框架,提供对应脚手架,简化新组件/服务创建及开发流程

文章来源:花椒技术,点击查看原文。

Kubernetes入门与进阶实战培训

Kubernetes入门与进阶实战培训将于2020年9月18日在北京开课,3天时间带你系统掌握Kubernetes,学习效果不好可以继续学习。本次培训包括:Docker基础、容器技术、Docker镜像、数据共享与持久化、Docker实践、Kubernetes基础、Pod基础与进阶、常用对象操作、服务发现、Helm、Kubernetes核心组件原理分析、Kubernetes服务质量保证、调度详解与应用场景、网络、基于Kubernetes的CI/CD、基于Kubernetes的配置管理等。点击下方图片或者阅读原文链接查看详情。

这篇关于花椒服务端 gRPC 开发实践的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/320713

相关文章

Spring Security简介、使用与最佳实践

《SpringSecurity简介、使用与最佳实践》SpringSecurity是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架,本文给大家介绍SpringSec... 目录一、如何理解 Spring Security?—— 核心思想二、如何在 Java 项目中使用?——

防止Linux rm命令误操作的多场景防护方案与实践

《防止Linuxrm命令误操作的多场景防护方案与实践》在Linux系统中,rm命令是删除文件和目录的高效工具,但一旦误操作,如执行rm-rf/或rm-rf/*,极易导致系统数据灾难,本文针对不同场景... 目录引言理解 rm 命令及误操作风险rm 命令基础常见误操作案例防护方案使用 rm编程 别名及安全删除

C++统计函数执行时间的最佳实践

《C++统计函数执行时间的最佳实践》在软件开发过程中,性能分析是优化程序的重要环节,了解函数的执行时间分布对于识别性能瓶颈至关重要,本文将分享一个C++函数执行时间统计工具,希望对大家有所帮助... 目录前言工具特性核心设计1. 数据结构设计2. 单例模式管理器3. RAII自动计时使用方法基本用法高级用法

PHP应用中处理限流和API节流的最佳实践

《PHP应用中处理限流和API节流的最佳实践》限流和API节流对于确保Web应用程序的可靠性、安全性和可扩展性至关重要,本文将详细介绍PHP应用中处理限流和API节流的最佳实践,下面就来和小编一起学习... 目录限流的重要性在 php 中实施限流的最佳实践使用集中式存储进行状态管理(如 Redis)采用滑动

SpringBoot 多环境开发实战(从配置、管理与控制)

《SpringBoot多环境开发实战(从配置、管理与控制)》本文详解SpringBoot多环境配置,涵盖单文件YAML、多文件模式、MavenProfile分组及激活策略,通过优先级控制灵活切换环境... 目录一、多环境开发基础(单文件 YAML 版)(一)配置原理与优势(二)实操示例二、多环境开发多文件版

使用docker搭建嵌入式Linux开发环境

《使用docker搭建嵌入式Linux开发环境》本文主要介绍了使用docker搭建嵌入式Linux开发环境,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录1、前言2、安装docker3、编写容器管理脚本4、创建容器1、前言在日常开发全志、rk等不同

ShardingProxy读写分离之原理、配置与实践过程

《ShardingProxy读写分离之原理、配置与实践过程》ShardingProxy是ApacheShardingSphere的数据库中间件,通过三层架构实现读写分离,解决高并发场景下数据库性能瓶... 目录一、ShardingProxy技术定位与读写分离核心价值1.1 技术定位1.2 读写分离核心价值二

深入浅出Spring中的@Autowired自动注入的工作原理及实践应用

《深入浅出Spring中的@Autowired自动注入的工作原理及实践应用》在Spring框架的学习旅程中,@Autowired无疑是一个高频出现却又让初学者头疼的注解,它看似简单,却蕴含着Sprin... 目录深入浅出Spring中的@Autowired:自动注入的奥秘什么是依赖注入?@Autowired

MySQL分库分表的实践示例

《MySQL分库分表的实践示例》MySQL分库分表适用于数据量大或并发压力高的场景,核心技术包括水平/垂直分片和分库,需应对分布式事务、跨库查询等挑战,通过中间件和解决方案实现,最佳实践为合理策略、备... 目录一、分库分表的触发条件1.1 数据量阈值1.2 并发压力二、分库分表的核心技术模块2.1 水平分

Python实战之SEO优化自动化工具开发指南

《Python实战之SEO优化自动化工具开发指南》在数字化营销时代,搜索引擎优化(SEO)已成为网站获取流量的重要手段,本文将带您使用Python开发一套完整的SEO自动化工具,需要的可以了解下... 目录前言项目概述技术栈选择核心模块实现1. 关键词研究模块2. 网站技术seo检测模块3. 内容优化分析模