Hyperledger fabric grpc 源码分析--- error:connection failed

(目前有点乱,先贴上来,等以后有时间在整理吧。这个问题一直想拿出来分享,还有两个博客,都是相关的,一点点发出来)

最近要在fabric网络和外部添加一层load balance,然后使用node的grpcs调用nginx,再转发到peer或者orderer。但是一直显示code 14 connect failed。log信息少的可怜。所以索性就过一遍代码。找找区别,顺便打打log。

版本

fabric-client和grpc的版本

$ npm ls | grep fabric
+-- fabric-ca-client@1.0.8
+-- fabric-client@1.0.8

$ npm ls | grep grpc
`-- grpc@1.10.1

fabric版本:

这里面要注意,fabric-node-sdk这个版本强制要求grpc的版本要高于1.10.1。

fabric sdk node的grpc

然后我们就开始跟踪fabric进行一个request的全过程。
我们跟踪的方法时client.installChaincode()。

  1. 构建proposal
    其内部针对request和chaincode的相关数据进行了封装,然后用userContext的secure进行签名。最后调用clientUtils.sendPeersProposal(peers, signed_proposal, timeout)
  2. 遍历peers的list,然后调用peer.sendProposal(proposal, timeout)
  3. peer的构建和发起请求

SDK中Peer是继承了Remote类。

Remote类主要就是两件事情:

  • 构造器针对grpc的各个参数进行配置,主要包括
    • ssl-target-name-override:如果server是tls开启的状态,而且hostname的名字和tls证书的CN域名不同,那么就可以在这里指定CN的那个hostname。而且,这个选项更改了grpc的两个属性:
      • grpc.ssl_target_name_override
      • grpc.default_authority这个参数就是针对server的证书进行验证。如果hostname和证书的签名是一致的,则这个参数并不需要。
    • pemserver的tls证书内容。
    • 还有一些其他的grpc设置
      • grpc.max_receive_message_length
      • grpc.max_send_message_length
    • request-timeout配置一个grpc的request超时时间
  • 构建内置类-Endpoint
    • 这个类非常重要,它是构建grpc对象的核心。主要就是针对url判断protocol,如果是grpcs则会使用this.creds = grpc.credentials.createSsl(pembuf)构建一个ssl的通道;如果是grpc则使用grpc.credentials.createInsecure()构建通道。

Peer发起的请求sendProposal,直接调用grpc的方法等待response:self._endorserClient.processProposal(proposal, function(err, proposalResponse){}
这里要注意的是,其调用的grpc的simple RPC方法,发送一个请求,并且等待请求的response。不是array也不是stream的形式。
查看了下grpc的service的定义,果然,直接发送object:

service Endorser {
    rpc ProcessProposal(SignedProposal) returns (ProposalResponse) {}
}
  1. node Grpc调用

src中核心组件就是client.jscredentials.js。前者负责请求调用,后者负责channel的创建接口(前面提到的两个创建通道的方法定义于此)。我们重点看client.js。我们逐个函数(精力有限,先分析用到的)的分析:
(有一些需要后续补充的,EventEmitter-event调用,stream-流)
* createStatusError:如果grpc返回的数据有error,则通过该函数解析,返回error
* ClientUnaryCall绑定一个event-EventEmitter。
* _readsDone当server发送消息完成之后,client会调用方法,并确认状态。默认为ok
* _receiveStatus当从server收到任何status信息时候,调用。
* Client(address, credentials, options)构造函数,创建一个channel。在创建一个grpc的client的时候就调用了。
* makeClientConstructor在后面有一个导出函数,来具体根据需求创建不同类型的Client,其中request的类型
* Client.prototype.makeUnaryRequest普通Grpc调用,创建请求。可以给出序列化和反序列化的方法,以及一些参数和回调函数。
* getCall(channel, method, options):这里面有些参数设置,然后将一个call返回-new grpc.Call(channel, method, deadline, host,parent, propagate_flags);
* hostname:server ip
* deadline:这个connection的timeout
* credential:如果这个client时grpcs(也就是含有cred),就会在call中将其进行设置-call.setCredentials(credentials)
同时,它有三个参数:
* channel:就是实例化一个client的时候创建的channel。
* method:grpc方法。这里是/protos.Endorser/ProcessProposal
* options:一些参数:包括hostname、deadline和credential等。
找一下这个options的来源,其来源于makeUnaryRequest

首先,其会调用makeUnaryRequest,check各种参数。
然后,调用`var call = getCall(this.$channel, method, options);`获取需要的rpc方法。然后创建一个emitter-`new ClientUnaryCall(call)`。
之后,组装一个client_batch,call并将其发送给server,等待response。同时收到response后使用emitter将response的消息填充进metadata。

打了一圈log,在getCall中发现:

Hostname undefined
deadline Infinity
parent undefined
credentials undefined
Init unary Call
ClientUnaryCall {
  domain: null,
  _events: {},
  _eventsCount: 0,
  _maxListeners: undefined,
  call: Call { channel_: Channel {} } }

为毛线都是undefined,貌似发现了问题。
追踪之后发现其来源于makeUnaryRequest的参数,回头check一下校验的代码逻辑。
首先,打印了这几个参数发现:

  • options:undefined,不应该。
  • metadata :在调用service的时候callback的function。
  • callback:undefined
  • argument:proposal的data
    这边是校验的逻辑:
if (options instanceof Function) {
    callback = options;
    if (metadata instanceof Metadata) {
      options = {};
    } else {
      options = metadata;
      metadata = new Metadata();
    }
  } else if (metadata instanceof Function) {
    callback = metadata;
    metadata = new Metadata();
    options = {};
  }
  if (!metadata) {
    metadata = new Metadata();
  }
  if (!options) {
    options = {};
  }
  if (!((metadata instanceof Metadata) &&
    (options instanceof Object) &&
    (callback instanceof Function))) {
    throw new Error("Argument mismatch in makeUnaryRequest");
  }

按照获取的数据来看,其走到了第二个分支:如果metadata是一个function,则callback赋值,options为空对象。

经过查看发现,Channel中应该包含了addr和credential的相关信息。所以在options的时候就取消了。这里可以继续digging。

Tips:一直忘记开启grpc的详细日志,在运行node的程序中使用该环境变量---GRPC_TRACE=allGRPC_VERBOSITY=DEBUG(因为这个是给C++内核用的,所以应该用export)
打开之后,发现有个问题:

Cannot check peer: missing selected ALPN property

貌似是有关ALPN的错误。server和client并不同时支持ALPN。

这里提一点,就是orderer的sendDeliver是用的stream,而不是普通GRPC。

ALPN

openssl 1.0.2以上的版本支持了ALPN。
这个问题是client发起的ssl握手,然后服务端并没有将其APLN或者是NPN的版本发给客户端。
然后,这里提到,我们用GO的sdk(或者是peer的cli)进行调用,就能够连接,并且功能执行正常。

windows & linux

Grpc的ssl版本在windows和linux中使用的并不一样。
windows使用的BoringSSL, Linux使用的是OpenSSL。BoringSSL有可能不能处理证书中domin为ip的情况(还未测试)。
如果发生了一些SSL的错误,可以直接使用openssl或者bssl的命令行进行连接测试:

bssl s_client -connect 127.0.0.1:9110
openssl s_client -connect 127.0.0.1:9110 -showcerts

Tips:

  • BoringSSL已经将所有的ECC算法移除,除了P-256和P-384。同时其还有一些bug。如果是在找不到原因可以去github上看看issue。

  • Grpc-node上build的时候有一个配置,如果该主机不支持ALPN就会rebuild项目排除ALPN的支持。

{
  'variables': {
    'runtime%': 'node',
    # Some Node installations use the system installation of OpenSSL, and on
    # some systems, the system OpenSSL still does not have ALPN support. This
    # will let users recompile gRPC to work without ALPN.
    'grpc_alpn%': 'true',
    # Indicates that the library should be built with gcov.
    'grpc_gcov%': 'false',
    # Indicates that the library should be built with compatibility for musl
    # libc, so that it can run on Alpine Linux. This is only necessary if not
    # building on Alpine Linux
    'grpc_alpine%': 'false'
  }
  • node 程序运行时可以添加环境变量:process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0",强制取消对server证书的授权验证。
Grpc编译(无ALPN的版本)

发现npm支持从源码进行安装的方法-grpc npm。所以我们选择先通过源码安装grpc然后在安装其他的组件。

  1. 从github中获取源码。
git clone https://github.com/grpc/grpc.git

时间会很久。

  1. 更改grpc的源码
    参考之前提到的那个配置项。这里将其改为false
'grpc_alpn%': 'false',

3 npm 编译

npm install grpc --build-from-source 

但是在windows可能会出现问题:node-grpc build on windows。如果在一开始(步骤比较靠前的地方)出现该错误:

 ..
 Building the projects in this solution one at a time. To enable parallel build, please add the "/m" switch.
 WINDOWS_BUILD_WARNING
  "..\IMPORTANT: Due to https:\github.com\nodejs\node\issues\4932, to build this library on Windows, you must first remove C:\Users\jenkins\.node-gyp\4.4.0\include\node\openssl"
  ...
  ..

解决方法,就是把node-gyp的openssl删掉(如果存在着会发现有冲突),具体地址为:C:\Users\<username>\.node-gyp\<node_version>\include\node\openssl
详细的解决方案可以看另外一个博客。

fabric go server端grpc

(待补充)

http2和http1

HTTP/2(超文本传输协议第2版,最初命名为 HTTP 2.0),是HTTP协议的的第二个主要版本,使用于万维网。HTTP/2 是 HTTP 协议自 1999 年 HTTP 1.1 发布后的首个更新,主要基于 SPDY 协议。HTTP/2 标准于2015年5月以 RFC 7540 正式发表,HTTP/2协议规范 rfc

为了实现 HTTP 工作组设定的性能目标,HTTP/2 引入了一个新的二进制分帧层,该层无法与之前的 HTTP/1.x 服务器和客户端向后兼容,因此协议的主版本提升到 HTTP/2。

http/2的优点
  • 采用二进制格式传输数据,而非文本格式。二进制格式在协议的解析和优化扩展上带来更多的优势和可能。

  • 对消息头进行压缩传输,能够节省消息头占用的网络的流量,而 HTTP 1.1 每次请求,都会携带大量冗余头信息,浪费了很多带宽资源,头压缩能够很好的解决该问题。

  • 多路复用,就是多个请求都是通过一个 TCP 连接并发完成, HTTP 1.1 虽然通过 pipeline 也能并发请求,但是多个请求之间的响应会被阻塞的,所以 pipeline 至今也没有被普及应用,而 HTTP/2 做到了真正的并发请求,同时流还支持优先级和流量控制。

  • 服务器推送,服务端能够更快的把资源推送给客户端,例如服务端可以主动把 JS 和 CSS 文件推送给客户端,而不需要客户端解析 HTML 再发送这些请求,当客户端需要的时候,它已经在客户端了。

h2c 和h2

h2c:HTTP/2协议,类型为clear text
h2:HTTP/2协议(加密),例如构建在SSL之上

gRPC

其是谷歌开发的一种RPC协议。主要用于建立在跨语言调用、数据压缩的C/S链接上。gRPC是建立在HTTP/2之上进行连接的,不管是cleartext(h2c,未加密的数据)还是TLS-encrypted(h2)的数据。
一个gRPC的call,其实是实现了一个HTTP的POST请求,对body的数据进行了高效的编码(当然,肯定离不开谷歌的protobuf)。同样gRPC的response也使用了同样的编码以及HTTP的数据规则(比如说status code等)。
gRPC协议并不直接在HTTP/1.X之上传输。gRPC使用HTTP/2是为了能够支持多工(multiplexing)以及流式传输的特性(HTTP/2)。

ALPN和NPN

NPN: Next Protocol Negotiation
ALPN:Application Layer Protocol Negotiation,ALPN wiki

这两个都是TLS的扩展组件。因为 https, SPDY and HTTP/2协议都直接连通了443端口,所以ALPN和NPN让应用层协议能够让应用层协议(plain http/1.1, SPDY or HTTP/2)转化,连通构建在SSL/TLS加密链接上的client和server。

SPDY使用NPN进行转化,HTTP2使用ALPN进行转化。其是建立在SSL/TLS的握手协议流程之上的。
NPN和ALPN都是在SSL/TLS建立链接中进行干预。ALPN会将client支持的应用层协议放在hello message中让server选择一个协议来建立安全链接。NPN则是server列举,client进行选择。

我们可以通过该网站HTTP/2 Test查看浏览器针对各个协议的支持情况。
我们也可以通过该命令行来查看是否支持APLN。

echo | openssl s_client -alpn h2 -connect yourdomain.com:443 | grep ALPN
//check the openssl verison
openssl verison

Tips: openssl版本一定要在1.0.2及其以上。

Nginx支持HTTP/2 (ALPN)

在Nginx上开启 HTTP/2 需要 Nginx 1.9.5 (或者是Nginx Plus R7)以上版本,并且需要 OpenSSL 版本在 1.0.2 以上。
因为 HTTP/2 不仅需要Web服务器还需要一个扩展支持,目前可以用的有 ALPN 和 NPN 两种(Chrome 已经移除了对 NPN 的支持)。只有 OpenSSL 1.0.2 以上版本才开始支持 ALPN 。
如果系统版本不支持或者openssl过低,则需要下载openssl的高版本source code,然后使用--with-openssl显示的指定openssl library的源码位置,然后rebuild整个Nginx项目。
各个操作系统版本针对openssl以及ALPN的支持情况:

image.png

指的注意的是,nginx的一个端口不能绑定多个协议类型,比如说HTTP/1(文本)和ClearText类型的HTTP/2(二进制)绑定在同一个端口。建议如果针对clearText类型的数据,针对不同的协议版本绑定不同的监听端口。因为nginx需要实现设置该端口支持哪一个版本的协议。
针对gRPC,它主要使用HTTP/2当做传输层来使用。
所以当使用nginx来处理普通数据时,一定要小心,其可能有很多种情况。
有三种方法可以让一个HTTP server知道这个请求是http/2:

  1. 使用HTTP(原始文本)进行HTTP/2的升级
  2. 使用HTTPS(加密数据),然后利用ALPN或者NPN转化为TLS建立安全连接。然后进行HTTP的消息传输。
  3. 使用HTTP的原始文本,但是构建HTTP/2的链接(双方直接商议链接方案,事先约定好),直接使用HTTP/2。

nginx第一种不支持,并没有一种能使用HTTP/2链接来进行HTTP/1.1普通文本的数据传输(自动转化),除非事先声明,直接建立HTTP/2的链接。不能主动探测(自动识别,并使用HTTP/2进行连接)。当然第二种方案,例如GRPC的实现,也是可以的。GRPC会清楚的知道这个连接是否需要使用TLS并构建彼此的链接,也就是是否使用HTTP/2的协议。
但是GRPC并不是真正的HTTP。他只是使用了HTTP/2的the binary framing layer,构建一个流控制的、多通道的链接,来进行gRPC的消息的传输。它和Websocket实现HTTP TCP的链接来传输消息是一样的,但是其并不是HTTP,只是用了HTTP的语法定义(规则或者说协议规则)。让他们看起来像是HTTP协议的数据。

Tips:Nginx利用HTTP server监听gRPC的请求,同时使用grpc_pass来进行分发代理。

参考链接

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容