奇技指南
gRPC是一个高性能、通用的开源 RPC 框架,由 Google 开发并基于 HTTP/2 协议标准而设计。本文介绍花椒服务端的 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 的传递方式如下:
1GET /index HTTP/1.1
2grpc-metadata-platform: ios
3grpc-metadata-device_id: xxxxxxxxx
4grpc-metadata-timestamp: 1562641496
5grpc-metadata-locale: en_US
6grpc-metadata-version: 1.0.0
7grpc-metadata-user_id:
8grpc-metadata-user_token:
9Host: gateway.hostame.com
基础库
dart
为了便于客户端调用,连接复用及通用参数传递,我们封装了 dart 的基础库。
BaseClient 维护了针对 HOST 缓存的连接池,同时也提供了接口需要传递的 metadata 信息。
1var base = BaseClient(host: 'rpc.hostame.com', port: 443, secure: true);
2final md = await base.metadata;
3final stub = AuthClient(base.channel, options: CallOptions(metadata: md));
golang
golang 后端服务需要同时支持 gRPC 和 gateway 两种请求方式。为了简化部署和上线依赖,gateway 和 gRPC 的功能放在了一起,并通过拦截器注入对应的功能,主要包括 gRPC 统计,访问日志,接口鉴权,请求参数校验,gateway JSON 编码等。
1 svrMux := &ServerMux{
2 ...
3 ServeMux: http.NewServeMux(),
4 }
5 svrMux.svr = grpc.NewServer(
6 grpc.UnaryInterceptor(
7 middleware.ChainUnaryServer(
8 recovery.UnaryServerInterceptor(recovery.WithRecoveryHandler(svrMux.recoveryHandler)),
9 prometheus.UnaryServerInterceptor, // prometheus 统计
10 svrMux.UnaryResponseInterceptor(),
11 proto.UnaryServerInterceptor(), // metadata 解析
12 log.UnaryServerInterceptor(conf.GlobalCallback()), // 访问日志
13 auth.UnaryServerInterceptor(conf.GlobalCallback()), // 接口鉴权
14 validator.UnaryServerInterceptor(), // 请求参数校验
15 )),
16 grpc.StreamInterceptor(
17 middleware.ChainStreamServer(
18 ...
19 )))
20 svrMux.mux = runtime.NewServeMux(
21 runtime.WithMarshalerOption(runtime.MIMEWildcard, json.Proto), // json 设置
22 runtime.WithProtoErrorHandler(svrMux.protoErrorHandler), // 错误处理
23 runtime.WithStreamErrorHandler(svrMux.streamErrorHandler),
24 )
25 // gRPC gateway mux
26 svrMux.ServeMux.Handle("/", svrMux.mux)
引用到的 package
1github.com/golang/protobuf
2github.com/grpc-ecosystem/go-grpc-middleware
3github.com/grpc-ecosystem/go-grpc-prometheus
4github.com/grpc-ecosystem/grpc-gateway
5github.com/lnnujxxy/protoc-gen-validate
6github.com/youlu-cn/grpc-gen
7go.uber.org/zap
8google.golang.org/grpc
开发流程
为了提高开发效率,方便维护及模块复用,服务端按功能进行组件化开发。每个组件可以单独运行一个服务,也可以和其它组件共同组成一个服务。每个组件都需要实现 Component
接口:
1type Component interface {
2 // 组件名称
3 Name() string
4 // 初始化存储
5 InitStorage() error
6 // 初始化注册 gRPC
7 InitGRPC(svc Service) error
8 // 初始化注册 gateway
9 InitGateway(svc Service) error
10 // cron 回调
11 StorageCron()
12}
对应组件开发完成后,需要开发对应的服务容器,步骤如下。
初始化 base package
1base.Init(context.TODO(), cfg, &global.Callback{
2 Authenticator: &auth.Callback{},
3 LogCapture: &log.Capture{},
4})
如需对外提供服务,需要提供端口及 TLS 证书
base.DefaultServer.AddPublicServer(rpcPort, gatewayPort, setting.TLSConfig)
组件注册
1base.DefaultServer.RegisterComponent(&user.Component{})
2base.DefaultServer.RegisterComponent(&push.Component{})
3...
监听服务
base.DefaultServer.Serve()
接口定义及实现
proto 规范
gRPC 基于标准化的 IDL(ProtoBuf)来生成服务器端和客户端代码,我们决定将所有的接口描述及文档说明都放到 proto 文件中,便于查看及修改。对 proto 的接口描述及注释的规范如下:
1// 消息类型注释,支持多行,
2// 支持 markdown 语法:
3//
4// > blockquote
5//
6// | Syntax | Description |
7// | ----------- | ----------- |
8// | Header | Title |
9// | Paragraph | Text |
10message ExampleMessage {
11 uint64 id = 1; // 字段注释,简洁
12}
13
14// 服务说明,支持 markdown 语法
15//
16// 1. First item
17// 2. Second item
18// 3. Third item
19//
20// ```json
21// {
22// "firstName": "John",
23// "lastName": "Smith",
24// "age": 25
25// }
26// ```
27service Example {
28 option (auth.visible) = {
29 scope: PUBLIC_SCOPE // Service scope:仅内网可见或对外可见
30 };
31
32 // 方法说明,支持 markdown
33 //
34 // - [x] Write the press release
35 // - [ ] Update the website
36 // - [ ] Contact the media
37 rpc test (ExampleMessage) returns (ExampleMessage) {
38 option (auth.access) = {
39 level: LOW_ACCESS_LEVEL // 接口请求权限
40 };
41 option (google.api.http) = {
42 post: "/example/test"
43 body: "*"
44 };
45 }
46}
代码生成
golang
1gengo:
2 @protoc -Iproto \
3 -I${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
4 -I${GOPATH}/src/github.com/lnnujxxy/protoc-gen-validate \
5 -I${GOPATH}/src/github.com/youlu-cn/grpc-gen/protoc-gen-auth \
6 --go_out=plugins=grpc:go/pb \
7 --grpc-gateway_out=logtostderr=true:go/pb \
8 --validate_out="lang=go:go/pb" \
9 --auth_out="lang=go:go/pb" \
10 proto/*.proto
SDK 引入
golang 使用 go mod 的方式直接引入 pb 生成的 .go 文件
dart
1ifeq ($(shell uname), Darwin)
2PROTO_ROOT_DIR = $(shell brew --prefix)/Cellar/protobuf/*
3else
4PROTO_ROOT_DIR = /usr/local
5endif
6
7gendart:
8 @protoc --dart_out=dart/front_user/lib/src/generated $(PROTO_ROOT_DIR)/include/google/protobuf/*.proto
9 @protoc -Iproto \
10 -I${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
11 -I${GOPATH}/src/github.com/lnnujxxy/protoc-gen-validate \
12 -I${GOPATH}/src/github.com/youlu-cn/grpc-gen/protoc-gen-auth \
13 --dart_out=grpc:dart/user/lib proto/*.proto
SDK 引入
修改 pubspec.yaml,执行 flutter packages get 或 flutter packages upgrade
1dependencies:
2 flutter:
3 sdk: flutter
4
5 protobuf: ^0.13.4
6 grpc: ^1.0.1
7 user:
8 git:
9 url: [email protected]:project/repo.git
10 path: dart/user
已知问题:
dart 在对 protobuf 生成的类型做 json 编码时,json 中的 key 是字段号而非名字,导致无法与其它语言交互。ISSUE (https://github.com/dart-lang/protobuf/issues/220)
文档生成
gRPC gateway 提供了通过 proto 文件生成 swagger API 文档,缺点是只支持 gateway 的 RESTful 接口,并且默认的展示方式有点不符合我们的常规文档使用方式。
我们基于 protoc 插件开发了 protoc-gen-markdown 工具,可以由 proto 文件生成 markdown 文档,提供 gRPC 接口描述,以及 RESTful 接口描述及 JSON 示例,提供全文目录,支持锚点导航等。生成方式如下:
1gendoc:
2 @protoc -Iproto \
3 -I${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
4 -I${GOPATH}/src/github.com/lnnujxxy/protoc-gen-validate \
5 -I${GOPATH}/src/github.com/youlu-cn/grpc-gen/protoc-gen-auth \
6 --markdown_out=":doc" \
7 proto/*.proto
文档会在对应路径生成接口列表 README.md,以及每个 protobuf 对应的接口文档。
调试
传统的 RESTful 接口在调试及问题排查时,可以通过抓包或者 MitM(中间人攻击)的方式,配置也比较容易。而 gRPC 因为使用了 HTTP2 及 protobuf 二进制流,抓包及数据流反解难度相对较高,调试及问题排查时会比较复杂。为了解决这个问题,我们通过服务端注入的方式,配合查询后台过滤对应的请求日志,从而实现如下类似抓包的效果。
后续计划
gRPC Streaming
框架层集成全链路 Trace 支持
迭代优化框架,提供对应脚手架,简化新组件/服务创建及开发流程
界世的你当不
只做你的肩膀
无
360官方技术公众号
技术干货|一手资讯|精彩活动
空·
文章浏览阅读1.6k次。MIPS体系结构 Q & A Part 0x01 寄存器与内存访问 最近问我MIPS体系结构相关问题的人越来越多,在这里小结一下。 一般在职业有段者水平的MIPS体系结构问题,都能在这里找到答案。 1. Q: MIPS有多少一般用途的寄存器? A: 32个。 2. Q: 我在看反汇编代码的时候,看到一些寄存器名叫zero, a1, a2...还有sp, ra这样的名字。
文章浏览阅读530次。我们在使用popupwindow的时候经常会用到showAsDropDown 的方法。我们有时候会设置上下的偏移量。y轴方向的偏移量比较正常的。然而我们的x轴偏移量就有点不一样了。如果我们的空间没沾满。的情况下。他会与你设置的View左下角对其的。如果占满了他会偏移以适应屏幕。如果你这时候想设置x轴方向偏移需要popupWindow.getContentView().getMeasuredWidth()- view.getMeasuredWidth()。这样就与右边对齐了。这地方注意一下。你获取的可能_showasdropdown 偏移无效
文章浏览阅读8.6k次,点赞21次,收藏59次。SQL语句文章目录SQL语句1.什么是SQL 2.SQL功能分类3.SQL数据类型4.DDL创建数据库修改数据库创建学生表添加一列查看表的字段信息修改一个表的字段类型删除一列修改表名查看表的创建细节修改表的字符集为gbk修改表的列名删除表5.DML查询表中的所有数据 插入操作更新操作删除操作6.DQL查询所有列结果集查询指定列的数据条件查询1.什么是SQL SQL是Structured Q..._sql概念?sql常见的6中类型操作?
文章浏览阅读5.7k次,点赞3次,收藏7次。前言在漫长的开发过程中,权限认证是一个永恒不变的话题,随着技术的发展,从以前的基于sessionId的方式,变为如今的token方式。session常用于单体应用,后来由于微服务的兴起,分布式应用占了很大的一部分。本文将为大家介绍基于session的单体应用授权认证方式。后续会介绍基于token的认证方式。什么是认证输入账号和密码登录的过程就是认证,看是否合法。认证是为了保护系统的隐私数据和资源。用户的身份合法才能访问该系统资源。用户认证就是判断一个用户身份是否合法的过程,合法继续访问,不合._session不包含授权信息
文章浏览阅读614次。刚无意中看到MoreWindows博主秒杀多线程面试题(http://blog.csdn.net/column/details/killthreadseries.html),第一篇就有他收集的面试题。那我就用来检验一下自己学的怎么样吧。 前面的选择题那些跳过,直接看最后的编程题。第三题(某培训机构的练习题):子线程循环 10 次,接着主线程循环 100 次,接着又回到子线程循环 10 次,接着再回到
文章浏览阅读67次。在这个数字化时代的今天,微商城受到了广泛的关注,许多传统线下店铺开始寻找新的零售转型。微信小程序的出现为许多商家和中小型企业带来了新的转型机会。那么究竟微商城如何开通呢?又该如何利用微商城引流呢?一、开通微商城的必要性:微信小程序本身就是在微信平台的基础上建立的,微信平台的用户众多,利用这些海量的用户,给微信小程序提供了发展的平台,微信小程序可以给商家带来更高效率低成本的引流模式,商家可以花更少的钱寻找到目标用户。二、微商城如何引流?直播的很大优势就是容易与消费者建立信任感,就算起初消费者对商家不信
文章浏览阅读5.1k次。我滴个亲娘嘞,没想到安装这逼玩意那个难搞定,虚拟机重装了5次,花了一星期时间,参考了n多资料,废话不多说,直接来根据oracle官网上提供的文档,得到如下信息binutils-2.23.52.0.1-12.el7.x86_64 compat-libcap1-1.10-3.el7.x86_64 gcc-4.8.2-3.el7.x86_64 gcc-c++-4.8.2-3.el7.x86_64
文章浏览阅读2.7w次,点赞21次,收藏208次。需要先卸载ros-indigo-desktop-full:sudo apt-get remove ros-kinetic-desktop-full 上述命令只是删除ros-indigo-desktop-full而已,并没有删除gazebo7和默认的依赖库。需要执行以下操作,删除所有与gazebo有关的东西,否则在后续安装中会提示依赖项问题。sudo apt-get remov..._卸载 ubuntu 16.04 kinetic版本ros_jacka654321的博客-csdn博客
文章浏览阅读1.1k次。通过js来读取每个单元格内容,然后列举出来。_js读取右侧单元格的代码是
文章浏览阅读47次。这次我要推荐的是《Computer Systems: A Programmer’s Perspective》(下文简称CSAPP),在国内可以买到其影印版。它也已经被翻译成为中文,即中国电力出版社的《深入理解计算机系统(修订版)》。基本知识如果说我上次推荐的SICP强调的是编程能力,抽象能力的话,那么CSAPP这本书强调的便是作为一个程序员的基本知...
文章浏览阅读644次。本文作者:https://blog.csdn.net/sinat_35866463/article/details/83316487目录秋招历程C++ 基础篇:数据结构与算法篇:计网与操作系统篇 :游戏技术篇:逻辑思维篇:1.老鼠喝药的问题:2.100大小的数组放了1-99 100个数,只有一个数重复了,找出重复的那个数。3.单向链表如何判断是否成环?3.1 扩展:并指出成环的点在哪里?3.2 再扩展: 这个链表的长度是多少?4.单向链表怎..._c++面经链表
文章浏览阅读355次。我新建了一个 CSV Data set config:变量名为 code,Recycle on EOF 为 true,stop thread on EOF 为 false.Recycle on EOF:默认情况下为 true,如果到达文件末尾时 JMeter 从头重新启动;EOF - end of filestop thread on EOF:如果达到 EOF,则停止当前线程我定义了五个线程:但是我的 csv 文件里只有三行:我在 HTTP 请求里,引用 csv data set co_jmeter 并发测试csv