< 返回版块

teshin 发表于 2023-02-02 21:29

Tags:gRPC,动态代理,http转grpc

@TOC

前言

 沟通服务间接口内容(尤其是前后端接口),是非常让人头疼的事。极其容易扯皮。接口文档写起来也很痛苦,每个字段的改动都需要及时更新,否则就会出问题。服务端通信如果用rpc通信的话,一般会有proto或者thrift文件。这个文件很长时间里被我们当成接口文档用,用着用着发现,真tm好用。既减少了扯皮,还不用写接口文档。那可不可以用grpc和前端通信那,一开始我们的做法是用grpc-gateway。把grpc的接口映射成http接口。但这种方式需要编译gateway的pb文件,对服务也是有侵入的。后来随着我在公司的时间越来越长,接手的服务越来越多(经常需要发版的项目就有十几个),这种方式维护起来十分糟心,后一直想寻求一种一劳永逸的解决方法?

 本人之前很长一段时间从事saas,paas的开发。对于一些服务而言,既要提供grpc访问的能力,也要对外提供http访问的能力(做saas就是这么卑微)。并且这种需求通常不是一开始就提出来的,而是对一个已经稳定运行的庞大的服务做改造。而这会导致爬屎山,鉴权不一致等一系列问题。那有没有一种无侵入的协议转换能力?

  grpc是基于http2协议,而http2是长连接。这对k8s部署的服务非常不友好。在这我猜肯定有很多小伙伴说可以用linked,istio等基于Service Mesh的解决方案。一是这些技术是近两年才稳定下来的,以前问题很多,根本不敢用,当然现在istio已经流行起来了,可以很完美的做到grpc的负载均衡和很优秀的流量管理。但依然存在不满足实际需求的情况,比如对grpc流量做精细过滤,细到每个请求的精准控制。这种二次开发的需求是很难在istio上完成。尤其是对一些小公司而言。 基于很多原因的考虑,最终诞生了搞一个grpc动态代理的想法,并初步实现。

grpc

  在云原生,容器化,微服务的大背景下。rpc也彻底奠定了服务间通信协议的霸主地位。众多rpc框架中grpc和thrift是最流行最受欢迎的rpc框架。在实际开发中,我两个框架都有深入的使用过。相较而言,我更喜欢grpc的风格。背靠google大树(已经是CNCF孵化项目),多语言都支持,基于protobuf极致编码和急速传输,等等优点就不一一详述。有兴趣的可以看grpc官网,上面吹的比我吹的好。

实践

github地址

https://github.com/woshihaoren4/grpc-proxy

第一步

先将代码clone下来到本地,我这里用的mac系统 因为编译需要cargo环境,需要先安装rust,参考教程:https://www.rust-lang.org/zh-CN/ 上面有很详细的中文教程。 我安装的是:rustc 1.66.0 (69f9c33d7 2022-12-12) 版本

第二步

先进入项目根目录,运行起测试例子

./example/helloworld server

没有权限的话,需要先加权限,然后再运行
chmod +x ./example/helloworld

这个例子使用golang编写的简单的grpc服务,实现上没有啥特殊的部分,值得注意的是需要给grpc服务加上反射

//grpc的HelloWorld方法实现,就是在字符串上加一个 world
func (s *Service) HelloWorld(ctx context.Context, req *proto.HelloWorldRequest) (*proto.HelloWorldResponse, error) {
        return &proto.HelloWorldResponse{Response: req.Request + " world"}, nil
}
//这里相当于main函数
func server(ctx *wdevent.Context) error {
        ls, _ := net.Listen("tcp", ":8888")
        gs := grpc.NewServer()
        proto.RegisterHelloWorldServiceServer(gs, new(Service))
        reflection.Register(gs)
        logrus.Infoln("grpc server start workd ....")
        gs.Serve(ls)
        return nil
}

再看一下pb文件,需要注意的是在option里指明 需要映射的http的路径和方法

syntax = "proto3";

package proto;
option go_package = "./proto";
import "google/api/annotations.proto";

// HelloWorld Service
service HelloWorldService {
  rpc HelloWorld(HelloWorldRequest) returns (HelloWorldResponse){
    option (google.api.http) = {
      post: "/api/v2/hello"
      body: "*"
    };
  };
}
message HelloWorldRequest {
  string request = 1;
}
message HelloWorldResponse {
  string response = 1;
}

第三步

修改配置文件如下,路径:./src/config/config.toml。当前项目中的配置文件已经写好了这些内容,不需要再配置什么了。当然不放心也可以查看一下。

[[proxy_sink]]
name = "hello"
addr = "127.0.0.1:8888"
  • 需要在addr中指明上面服务的地址和端口。

第四步

另起终端,编译并运行项目

cargo run -- run

国内没有科学上网的话会有些慢,主要是下载包比较慢,可以用清华源或者其他的,教程参考:https://www.w3cschool.cn/cargo_guide/cargo_guide-uxdg3l62.html 服务启动后会打印如下日志,说明服务启动成功 在这里插入图片描述

测试

发起一个http请求

curl --location --request POST 'http://127.0.0.1:6789/api/v2/hello' \
--header 'Content-Type: application/json' \
--data-raw '{
    "request": "hello"
}'

可以看到返回内容:{"response": "hello world"} 并且在代理服务上产生访问日志: 在这里插入图片描述 到这里一个简单的演示就成功了

结构和原理

主要是根据grpc的反射的描述,生成http路由,并动态完成json和proto的映射。 更进一步的原理和结构,未完待续~

尾语

当前版本还只是一个比较初级的版本,功能还很初级。 还有很多功能需要完善,架构也可能会有大的变动,所有上一节并没有详细描述。 作者预计但不承诺会继续完成下面的内容。

  • restful支持:这个功能是P0级,在我go版本的grpc动态代理服务中经常被用到,在可预计的规划里一定会实现。
  • 事件系统:该功能是为了方便二次开发,很有必要。也有可能用中间件模式,类似traefik
  • 负载均衡/sidecar:负载均衡是为了用在服务网关上,sidecar是用在pod里,二者会选一个实现,我倾向于前者,和我之前写的rust-ingress联动上。这里自荐一波rust-ingress项目:https://gitee.com/yutiandou/rust-ingress
  • 性能优化,这个会一直持续做下去,欢迎有性能极致追求的小伙伴能够共同前进
  • 实时反射:目前是通过配置文件,在启动的时候加载服务源。好的(懒人)方案是proto文件变化后能够实时监控到,下一步会完成这个功能。

欢迎有兴趣的小伙伴提出建议,并热烈欢迎大家参与进来。

评论区

写评论
作者 teshin 2023-02-25 11:49

你说的我很赞同,但是面对一座还在盈利的s山,无法铲平它,如果又不想s山上堆s,最好的选择是另起炉灶

--
👇
github.com/shanliu/lsys: 不是别人场景不够复杂,这叫管理无能瞎搞的结果,接着后面打补丁。

--
👇
woshihaoren4: 你没用过,是你的需要场景足够简单,不需要用,而不是它没用。

github.com/shanliu/lsys 2023-02-03 20:40

不是别人场景不够复杂,这叫管理无能瞎搞的结果,接着后面打补丁。

--
👇
woshihaoren4: 你没用过,是你的需要场景足够简单,不需要用,而不是它没用。

github.com/shanliu/lsys 2023-02-03 20:38

最优 定义是什么?

--
👇
PaiGack: 难道一开始就用最优的有啥问题吗?而且 proto 有强制约束,这不比 json 看起来方便?

--
👇
shanliu: ``` 用proto是在数据传输已成为系统性能瓶颈的时候的选项 而估计99%的应用都打不到那个级别 很多人用proto是因为框架自带,而不是系统瓶颈使然 如果系统瓶颈不在数据传输 直接http+json不是差的选择 自动http转gRPC 真不知道存在意义是什么? 发现很多国内公司都是为了彰显技术而强行上一些其实没什么卵用的东西 看着都头大


DogLi 2023-02-03 14:04

没有困难,执照困难也要上

PaiGack 2023-02-03 11:38

难道一开始就用最优的有啥问题吗?而且 proto 有强制约束,这不比 json 看起来方便?

--
👇
shanliu: ``` 用proto是在数据传输已成为系统性能瓶颈的时候的选项 而估计99%的应用都打不到那个级别 很多人用proto是因为框架自带,而不是系统瓶颈使然 如果系统瓶颈不在数据传输 直接http+json不是差的选择 自动http转gRPC 真不知道存在意义是什么? 发现很多国内公司都是为了彰显技术而强行上一些其实没什么卵用的东西 看着都头大

作者 teshin 2023-02-03 11:16

你没用过,是你的需要场景足够简单,不需要用,而不是它没用。

作者 teshin 2023-02-03 11:13

我在实际开发中遇到过很多需要使用这种服务的场景

  • 比如对grpc服务进行自动化测试,直接用grpc是很不方便的,转成http就很方便测试脚本编写和升级
  • 比如saas场景中常见的,服务既要对内提供grpc服务,又要对外提供http服务,而且拥有不同的鉴权方式。这种就必须要用代理。
  • 再比如,传统前后端对接是使用接口文档,这种方式有很大的沟通成本,还容易出错。通过grpc代理向外提供http服务和对应的proto文件,前端可以直接用proto生成请求接口。这不就直接拒绝扯皮,拒绝错误吗。 当然所有程序 只有合适的,没有最好的。用代理会牺牲一部分性能。根据自身需求选择即可。
github.com/shanliu/lsys 2023-02-02 22:46

有句老话说的好:有困难要上,没困难就创造困难上

--
👇
shanliu: ``` 用proto是在数据传输已成为系统性能瓶颈的时候的选项 而估计99%的应用都打不到那个级别 很多人用proto是因为框架自带,而不是系统瓶颈使然 如果系统瓶颈不在数据传输 直接http+json不是差的选择 自动http转gRPC 真不知道存在意义是什么? 发现很多国内公司都是为了彰显技术而强行上一些其实没什么卵用的东西 看着都头大

github.com/shanliu/lsys 2023-02-02 22:45
用proto是在数据传输已成为系统性能瓶颈的时候的选项
而估计99%的应用都打不到那个级别
很多人用proto是因为框架自带,而不是系统瓶颈使然
如果系统瓶颈不在数据传输 直接http+json不是差的选择
自动http转gRPC 真不知道存在意义是什么?
发现很多国内公司都是为了彰显技术而强行上一些其实没什么卵用的东西
看着都头大
1 共 9 条评论, 1 页