内容简介:在上一篇的gRPC 的介绍以及实践 中,而在文末,我简单介绍了给 Node.js 做的其实在这之前,我看了另外就是
在上一篇的gRPC 的介绍以及实践 中,而在文末,我简单介绍了给 Node.js 做的 grpc-helper ,但是现在,我觉得得用一篇完整的博客来好好介绍,毕竟还是想要给大家用的,以下我会介绍我实现这个 工具 的过程,以及我的一些实现思路。
其实在这之前,我看了 官方的讨论 ,而且也调研了当中提到一些帮助类工具,比如 grpc-caller ,因该说我不太喜欢这种 API 风格,不够简单明了,并且也没有我想要的一些高级功能。
另外就是 rxjs-grpc 了,只是它是基于 RxJS 来做的,如果你对它不熟悉,怕是也难以选择(当然,可以了解下,号称是 可取代 Promise 的 )。
因此我想了想,除了最重要的 Promise API 功能(毕竟 callback 的风格早就应该被淘汰了),我想要的功能主要有:
- 服务发现 :比如支持 DNS 服务发现,其它的可以是 consul etcd 等;
- 客户端负载均衡 :支持 Round roubin 负载均衡;
- 健康检查 :支持上游的健康检查,剔除不健康的后端以及重新加入健康的后端
- 断路器 :一旦上游出错了,能够及时断开;
- 监控指标 :能够提供监控指标,方便发现以及处理问题;
好了,相信你也应该看出来了,我想要的无非就是 负载均衡加上 Promise API ,因为上面的几点都是一个负载均衡器应该做的事情。
实现的话,还是用 TypeScript,不明白的可以看看我之前的介绍: 使用 TypeScript 开发 NPM 模块 。
Promise API
于是首先是需要提供一个非常简便的 Promise API 接口,我们都知道 grpc 以客户端以及服务端是否使用了流分成了四种风格的接口:
- Unary:客户端 & 服务端没有流;
- Client stream:客户端有流,服务端没有流;
- Server stream:客户端没有流,服务端有流;
- Bidi stream:客户端 & 服务端都有流;
而在这四种接口中,只有 Unary 以及 Client stream 有返回值 callback 风格的接口,这从设计上也符合一致性的风格,只是我们不喜欢用而已。
因此,一开始,我是这么设计的:
将 callback 风格的
client.SayHello({name: 'foo'}, (err, rst) => { ... });
变为
const res = await client.SayHello({name: 'foo'});
但是我忽略了服务端返回的 status 以及 metadata ,应该说大部分情况下,只是 response 就能满足大部分需求,但是我做的是一个比较基础的库,那就应该提供完整的功能,于是,我加入了下设计:
const call = client.SayHello({name: 'foo'}, (err, rst) => { ... }); call.on('status', (status) => {}); call.on('metadata', (metadata) => {}); const peer = call.getPeer();
变为
const { message, status, metadata, peer } = await client.SayHello({name: 'foo'});
这样也就非常简单明了了,实现起来也不难,我同时提供了 resolveFullResponse
参数,默认为 false,这样,大部分情况下,如果不需要 status 之类的返回值,只需要第一种设计,那基本上也不需要改动参数。
同时,我还参考了 @murgatroid99 在 官方讨论 中的设计,将 Client stream 接口也改成了 Promise 风格的接口:
const stream = new stream.PassThrough({ objectMode: true }); const promise = helper.SayMultiHello(stream); stream.write({ name: 'foo1' }); stream.write({ name: 'foo2' }); stream.write({ name: 'foo3' }); stream.end(); const result = await promise; // { message: 'hello foo1,foo2,foo3' }
负载均衡
应该说这是一个现代的负载均衡器应该做的事情,我参考了 grpc-go 的设计,引入了 Resolver Watcher 以及 Balancer 几个抽象接口。
- Resolver:目前主要是 static 以及 dns,static 即直接解析服务端的地址,而 dns 则是利用 Node.js 的 dns.resolveSrv 解析 Srv 记录(具体使用场景可参考 这里 );
- Watcher:即实时 watch 服务发现,及时更新服务端的记录;
- Balancer:即实现 Round robin 负载均衡算法,挑选可用的服务端;
而在 上次的文章中 ,我也提到了 grpc-node 中,现在还没有实现负载均衡能力,而且它目前的实现,还不能很方便的提供给我们很方便定制这个功能的接口,于是,目前能做的便是直接给每个服务端生成一个 client,然后在这个基础之上进行负载均衡的实现。
于是,最初的设计是:
class Helper() { constructor() { const resolver = new Resolver(addr); const clientCreator = new ClientCreator() this.lb = new Balancer(resolver, clientCreator); this.lb.start(); } getClient() { return this.lb.get(); } } const helper = new Helper(); helper.getClient().SayHello()
但是显然这样不够简便,于是我直接在 helper 的 constructor 中加入了这些方法,使得初始化之后直接将方法绑定到 helper 上面:
each(methodNames, method => { this[method] = (...args) => { const client = this.lb.get(); // 从 balancer 获取 client return client[method](...args); }; });
于是,我们最终的 API 就很简单了:
helper.SayHello()
其它的负载均衡功能限于篇幅不再详细介绍,可参考源码实现。
其它功能
主要是监控指标以及全局 deadline,我直接使用了 grpc-node 提供 interceptors,拿监控指标举例:
const histogram = new promClient.Histogram({ name: 'grpc_response_duration_seconds', help: 'Histogram of grpc response in seconds', labelNames: ['peer', 'method', 'code'], }); export function getMetricsInterceptor() { return function metricsInterceptor(options, nextCall) { const call = nextCall(options); const endTimer = histogram.startTimer({ peer: call.getPeer(), method: options.method_definition.path, }); const requester = (new grpc.RequesterBuilder()) .withStart(function(metadata: grpc.Metadata, _listener: grpc.Listener, next: Function) { const newListener = (new grpc.ListenerBuilder()) .withOnReceiveStatus(function(status: grpc.StatusObject, next: Function) { endTimer({ code: status.code, }); next(status); }).build(); next(metadata, newListener); }).build(); return new grpc.InterceptingCall(call, requester); }; }
你也可以根据自己的需求,禁用默认的监控指标,创建 helper 的时候将 metrics
设置为 false
,然后将自己实现的 interceptors 传入 grpcOpts 即可:
const helper = new GRPCHelper({ packageName: 'helloworld', serviceName: 'Greeter', protoPath: path.resolve(__dirname, './hello.proto'), sdUri: 'dns://_grpc._tcp.greeter', metrics: false, grpcOpts: { interceptors: [you-metrics-interceptor-here] } });
总结
好了,总体来说,这个工具的实现不复杂,但是需要花费挺多精力去具体实现,同时我也觉得如果不在这里给这个工具好好宣传一下的话,很容易就会变成只有我自己使用的一个工具,一些问题也不会发现,工具本身也无法进一步发展。
同时,我也相信,我这个工具最终会被官方的功能所取代,但是如果官方能够采用或者参考我的设计的话,那也是不错的结果。
另外,工具现在正在我们的测试环境中使用,正式环境也有部分在使用,所以各位如果有机会也不妨试试。
最后,给个 Star 也是极好的 :P 。
首发于 Github issues: https://github.com/xizhibei/blog/issues/86 ,欢迎 Star 以及 Watch
本文采用 署名-非商业性使用-相同方式共享(BY-NC-SA) 进行许可 作者:习之北 (@xizhibei)以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- RecyclerView使用指南(一)—— 基本使用
- 如何使用Meteorjs使用URL参数
- 使用 defer 还是不使用 defer?
- 使用 Typescript 加强 Vuex 使用体验
- [译] 何时使用 Rust?何时使用 Go?
- UDP协议的正确使用场合(谨慎使用)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
JavaScript
David Flanagan / O'Reilly Media / 2011-5-13 / GBP 39.99
The book is a programmer's guide and comprehensive reference to the core JavaScript language and to the client-side JavaScript APIs defined by web browsers. The sixth edition covers HTML 5 and ECMA......一起来看看 《JavaScript》 这本书的介绍吧!
JS 压缩/解压工具
在线压缩/解压 JS 代码
SHA 加密
SHA 加密工具