<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Grpc on Yeqown</title>
    <link>https://www.yeqown.xyz/tags/grpc/</link>
    <description>Recent content in Grpc on Yeqown</description>
    <generator>Hugo</generator>
    <language>en-US</language>
    <lastBuildDate>Thu, 08 May 2025 09:35:14 +0800</lastBuildDate>
    <atom:link href="https://www.yeqown.xyz/tags/grpc/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>在 Kubernetes 中实现 gRPC 流量的镜像和对比</title>
      <link>https://www.yeqown.xyz/2025/05/08/%E5%9C%A8-Kubernetes%E4%B8%AD%E5%AE%9E%E7%8E%B0gRPC%E6%B5%81%E9%87%8F%E9%95%9C%E5%83%8F/</link>
      <pubDate>Thu, 08 May 2025 09:35:14 +0800</pubDate>
      <guid>https://www.yeqown.xyz/2025/05/08/%E5%9C%A8-Kubernetes%E4%B8%AD%E5%AE%9E%E7%8E%B0gRPC%E6%B5%81%E9%87%8F%E9%95%9C%E5%83%8F/</guid>
      <description>&lt;p&gt;本文主要解决在服务重构过程中如何保证新旧服务行为一致性的问题。&lt;/p&gt;&#xA;&lt;h2 id=&#34;场景描述&#34;&gt;场景描述&lt;a class=&#34;anchor&#34; href=&#34;#%e5%9c%ba%e6%99%af%e6%8f%8f%e8%bf%b0&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;现有一个 python 开发的 gRPC 微服务提供了一些 &lt;strong&gt;数据查询&lt;/strong&gt; 接口 供 上层应用使用，随着业务流量的增加运维这个服务的成本也逐渐增加，为了降低运维成本和提高性能 (木有擅长 python 高性能的开发)，因此选择了使用 go 语言对这个服务进行重写。在开发完成之后，需要对新服务的 gRPC 接口进行验证。&lt;/p&gt;&#xA;&lt;p&gt;这种场景对测试开发人员来说，实在是太熟悉了吧？典型的 &lt;strong&gt;重放验证&lt;/strong&gt;，马上能想到的验证手段就是：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;如果有存量的单元测试，那么直接重新跑一遍单元测试就能快速的完成验证。&lt;/li&gt;&#xA;&lt;li&gt;没有单元测试的情况，那么可以将新服务部署起来，通过流量复制的方式将旧服务的流量复制到新服务上，然后对比两个服务的返回结果是否一致。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;pre class=&#34;mermaid&#34;&gt;&#xA;flowchart LR&#xA;    %% 定义布局方向和间距&#xA;    subgraph s1[&amp;#34;方案一: 单元测试验证&amp;#34;]&#xA;        direction TB&#xA;        UT[单元测试] --&amp;gt;|执行| NS1[新服务]&#xA;    end&#xA;    &#xA;    subgraph s2[&amp;#34;方案二: 流量复制验证&amp;#34;]&#xA;        direction TB&#xA;        C[客户端] --&amp;gt;|请求| OS[旧服务]&#xA;        OS --&amp;gt;|响应| C&#xA;        OS --&amp;gt;|复制流量| NS2[新服务]&#xA;        NS2 --&amp;gt;|对比响应| OS&#xA;    end&#xA;&#xA;    %% 设置布局方向和对齐方式&#xA;    s1 ~~~ s2&#xA;&lt;/pre&gt;&#xA;&lt;p&gt;但是很遗憾 😭，并没有成熟的单元测试；测试人员也都是人肉测试，对于内部服务的接口验证帮助不大，因此这里采用第二种方式进行验证。&lt;/p&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;服务均采用 Kubernetes 部署。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;方案介绍&#34;&gt;方案介绍&lt;a class=&#34;anchor&#34; href=&#34;#%e6%96%b9%e6%a1%88%e4%bb%8b%e7%bb%8d&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;HTTP 流量的复制重放工具就很多很成熟了，而且往往在 网关/代理 一侧就能实现流量复制甚至对比。&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;工具&lt;/th&gt;&#xA;          &lt;th&gt;分类&lt;/th&gt;&#xA;          &lt;th&gt;文档链接&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Nginx mirror&lt;/td&gt;&#xA;          &lt;td&gt;网关/代理&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://nginx.org/en/docs/http/ngx_http_mirror_module.html&#34;&gt;https://nginx.org/en/docs/http/ngx_http_mirror_module.html&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;APISIX&lt;/td&gt;&#xA;          &lt;td&gt;网关/代理&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://apisix.apache.org/docs/apisix/plugins/proxy-mirror/&#34;&gt;https://apisix.apache.org/docs/apisix/plugins/proxy-mirror/&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Istio: Virtual Service mirror&lt;/td&gt;&#xA;          &lt;td&gt;Service Mesh&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://istio.io/latest/docs/tasks/traffic-management/mirroring/&#34;&gt;https://istio.io/latest/docs/tasks/traffic-management/mirroring/&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;GoReplay&lt;/td&gt;&#xA;          &lt;td&gt;流量镜像&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://github.com/buger/goreplay&#34;&gt;https://github.com/buger/goreplay&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;tcpcopy&lt;/td&gt;&#xA;          &lt;td&gt;流量镜像&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://github.com/session-replay-tools/tcpcopy&#34;&gt;https://github.com/session-replay-tools/tcpcopy&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;tcpcopy 相比于其他工具方案，虽然不能直接使用，但是其作用于 TCP 传输层，功能会更丰富。相比之下，gRPC 流量复制工具就没有那么成熟了。&lt;/p&gt;</description>
    </item>
    <item>
      <title>protoc-gen-fieldmask插件</title>
      <link>https://www.yeqown.xyz/2022/01/25/protoc-gen-fieldmask%E6%8F%92%E4%BB%B6/</link>
      <pubDate>Tue, 25 Jan 2022 13:46:36 +0800</pubDate>
      <guid>https://www.yeqown.xyz/2022/01/25/protoc-gen-fieldmask%E6%8F%92%E4%BB%B6/</guid>
      <description>&lt;h3 id=&#34;背景&#34;&gt;背景&lt;a class=&#34;anchor&#34; href=&#34;#%e8%83%8c%e6%99%af&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;gRPC 作为服务端的常用框架，它通过 protocol-buffers 语言来定义服务，同时也约定了请求和响应的格式，这样在服务端和客户端之间就可以通过 protoc 生成的代码直接运行而不用考虑编码传输问题了。&lt;/p&gt;&#xA;&lt;p&gt;但是，可能会遇到这样的场景：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;RPC 响应中 &lt;strong&gt;&lt;em&gt;无用的字段过多&lt;/em&gt;&lt;/strong&gt; , 浪费带宽和无效计算，如下图所示：&lt;/p&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;这里的无用字段是指，在响应中，没有用到的字段，这些字段可以忽略掉，不会影响客户端的使用。&lt;/p&gt;&#xA;&lt;/blockquote&gt;  &lt;img src=&#34;https://www.yeqown.xyz/images/fieldmask-case1.jpg&#34;&gt;&#xA;&lt;p&gt;或许 &lt;strong&gt;拆分接口&lt;/strong&gt; 是一个好的办法，但是可能会因为这样那样的原因（信息粒度降低导致接口太多了，有些地方就是需要聚合信息；细粒度的API设计同时会导致代码重复增加），可能无法推动拆分改造。同时如果没有拆分标准，亦或团队内成员不能严格遵守标准，那么拆分也只是重复问题而已。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;RPC 增量更新时，如何判断零值字段是否需要更新？&lt;/p&gt;&#xA;&lt;p&gt;对于 unset 和 zero value 不好区分的语言中（比如：go），在提供服务的一方遇到 &lt;strong&gt;&lt;em&gt;增量更新&lt;/em&gt;&lt;/strong&gt; 的场景时就会遇到这样的情况：&lt;/p&gt;&#xA;&lt;p&gt;对于这种情况当然可以也有一些方法来解决，比如：&lt;strong&gt;使用指针来定义数据基本类型&lt;/strong&gt;，那么在使用的时候如果判定为 &lt;code&gt;nil&lt;/code&gt; 就说明没有设置，如果不为 &lt;code&gt;nil&lt;/code&gt; 且为零值，那么就说明也是需要更新的。不过这样解决的缺点就是，&lt;code&gt;nil refference panic&lt;/code&gt; 的概率又增加了，在使用时也稍微麻烦了一点。&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://www.yeqown.xyz/images/fieldmask-case2.jpg&#34;&gt;·&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;解决方案&#34;&gt;解决方案&lt;a class=&#34;anchor&#34; href=&#34;#%e8%a7%a3%e5%86%b3%e6%96%b9%e6%a1%88&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;其实我们在思考上述两种场景的时候，把 &lt;strong&gt;客户端&lt;/strong&gt; 和 &lt;strong&gt;服务端&lt;/strong&gt; 的角色提取出来，就会发现这两个场景都是从 &lt;strong&gt;服务端&lt;/strong&gt; 的视角遇到的问题，两个场景都是类似的：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;客户端需要哪些字段，服务端不知道&lt;/li&gt;&#xA;&lt;li&gt;客户端更新了哪些字段，服务端也不知道&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;但是，其实客户端是知道的，因此让客户端把这部分信息传递给服务端就行了。因此我们可以用 &lt;a href=&#34;&#34;&gt;FieldMask&lt;/a&gt; 字段，用来传递客户端需要的字段，服务端就只返回需要的字段；客户端的告诉服务端需要哪些字段，服务端就更新哪些字段。&lt;/p&gt;&#xA;&lt;p&gt;但是 &lt;code&gt;FieldMask&lt;/code&gt; 只是一个定义，在具体的使用场景中还需要开发者自己编写一些辅助方法，来实现功能。那么是不是可以提供一个插件，让开发者可以只编写 &lt;code&gt;proto&lt;/code&gt; 文件，便可以自动生成一些辅助方法呢？答案是肯定的，预览效果如下：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-protobuf&#34; data-lang=&#34;protobuf&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;message&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;UserInfoRequest&lt;/span&gt; {&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; user_id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  google.protobuf.FieldMask field_mask &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; [&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (fieldmask.option.Option)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;in &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {gen&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;},&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (fieldmask.option.Option)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;out &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {gen&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;, message&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;UserInfoResponse&amp;#34;&lt;/span&gt;}&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ];&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;message&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Address&lt;/span&gt; {&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; country &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; province &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;message&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;UserInfoResponse&lt;/span&gt; {&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; user_id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; email &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;·&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Address address &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;message&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;NonEmpty&lt;/span&gt; {}&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;service&lt;/span&gt; UserInfo {&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;rpc&lt;/span&gt; GetUserInfo(UserInfoRequest) &lt;span style=&#34;color:#66d9ef&#34;&gt;returns&lt;/span&gt; (UserInfoResponse) {}&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;rpc&lt;/span&gt; UpdateUserInfo(UserInfoRequest) &lt;span style=&#34;color:#66d9ef&#34;&gt;returns&lt;/span&gt; (NonEmpty) {}&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;生成的代码如下：&lt;/p&gt;</description>
    </item>
    <item>
      <title>Kubernetes中gRPC Load Balancing分析和解决</title>
      <link>https://www.yeqown.xyz/2020/09/22/Kubernetes%E4%B8%ADgRPC-Load-Balancing%E5%88%86%E6%9E%90%E5%92%8C%E8%A7%A3%E5%86%B3/</link>
      <pubDate>Tue, 22 Sep 2020 13:33:20 +0800</pubDate>
      <guid>https://www.yeqown.xyz/2020/09/22/Kubernetes%E4%B8%ADgRPC-Load-Balancing%E5%88%86%E6%9E%90%E5%92%8C%E8%A7%A3%E5%86%B3/</guid>
      <description>&lt;h3 id=&#34;背景&#34;&gt;背景&lt;a class=&#34;anchor&#34; href=&#34;#%e8%83%8c%e6%99%af&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;第一次，线上遇到大量接口RT超过10s触发了系统告警，运维反馈k8s集群无异常，负载无明显上升。将报警接口相关的服务重启一番后发现并无改善。但是开发人员使用链路追踪系统发现，比较慢的请求总是某个gRPC服务中的几个POD导致，由其他POD处理的请求并不会出现超时告警。&lt;/p&gt;&#xA;&lt;p&gt;第二次，同样遇到接口RT超过阈值触发告警，从k8s中查到某个gRPC服务（关键服务）重启次数异常，查看重启原因时发现是&lt;code&gt;OOM Killed&lt;/code&gt;，&lt;code&gt;OOM killed&lt;/code&gt;并不是负载不均衡直接导致的，但是也有一定的关系，这个后面再说。前两次由于监控不够完善（于我而言，运维的很多面板都没有权限，没办法排查）。期间利用pprof分析了该服务内存泄漏点，并修复上线观察。经过第二次问题并解决之后，线上超时告警恢复正常水平，但是该 deployment 下的几个POD占用资源（Mem / CPU / Network-IO），差距甚大。&lt;/p&gt;&#xA;&lt;img src=&#34;https://www.yeqown.xyz/images/k8s-grpc-lb-mem1.jpg&#34;/&gt;&#xA;&lt;img src=&#34;https://www.yeqown.xyz/images/k8s-grpc-lb-mem2.jpg&#34;/&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;第二张图是运维第一次发现该服务OOM killed 之后调整了内存上限从 512MB =&amp;gt; 1G，然而只是让它死得慢一点而已。&#xA;从上面两张图能够石锤的是该服务一定存在内存泄漏。Go项目内存占用的分析，我总结了如下的排查步骤：&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1. 代码泄漏（pprof）（可能原因 goroutine泄漏；闭包）&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;2. Go Runtime + Linux 内核（RSS虚高导致OOM）https://github.com/golang/go/issues/23687&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;3. 采集指标不正常（container_memory_working_set_bytes）&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;2，3 是基于第1点能基本排除代码问题的后续步骤。&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;解决和排查手段：&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1. pprof 通过heap + goroutine 是否异常，来定位泄漏点&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;运行&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;go tool pprof&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;命令时加上--nodefration&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;0.05参数，表示如果调用的子函数使用的CPU、memory不超过 5%，就忽略它。&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;2. 确认go版本和内核版本，确认是否开启了MADV_FREE，导致RSS下降不及时&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;1.12+ 和 linux内核版本大于 4.5&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;。&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;3.  RSS + Cache 内存检查&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;gt; Cache 过大的原因 https://www.cnblogs.com/zh94/p/11922714.html &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;// IO密集：手动释放或者定期重启&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;查看服务器内存使用情况： &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;free -g&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;查看进程内存情况：      &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;pidstat -rI -p 13744&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;查看进程打开的文件：    &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;lsof -p 13744&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;查看容器内的PID：      &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;docker inspect --format &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;{{ .State.Pid}}&amp;#34;&lt;/span&gt; 6e7efbb80a9d&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;查看进程树，找到目标:   &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;pstree -p 13744&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;参考：https://eddycjy.com/posts/why-container-memory-exceed/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;通过上述步骤，我发现了该POD被&lt;code&gt;OOM killed&lt;/code&gt;还有另一个元凶就是，&lt;strong&gt;日志文件占用&lt;/strong&gt;。这里就不过多的详述了，搜索方向是 “一个运行中程序在内存中如何组织 + Cache内存是由哪些部分构成的”。这部分要达到的目标是：一个程序运行起来它为什么占用了这么些内存，而不是更多或者更少。&lt;/p&gt;</description>
    </item>
    <item>
      <title>一次gRPC使用不当导致goroutine泄漏排查记录</title>
      <link>https://www.yeqown.xyz/2020/01/17/%E8%AE%B0%E4%B8%80%E6%AC%A1%E6%8E%92%E6%9F%A5gRPC%E4%BD%BF%E7%94%A8%E4%B8%8D%E5%BD%93%E5%AF%BC%E8%87%B4%E7%9A%84goroutine%E6%B3%84%E6%BC%8F/</link>
      <pubDate>Fri, 17 Jan 2020 13:20:17 +0800</pubDate>
      <guid>https://www.yeqown.xyz/2020/01/17/%E8%AE%B0%E4%B8%80%E6%AC%A1%E6%8E%92%E6%9F%A5gRPC%E4%BD%BF%E7%94%A8%E4%B8%8D%E5%BD%93%E5%AF%BC%E8%87%B4%E7%9A%84goroutine%E6%B3%84%E6%BC%8F/</guid>
      <description>&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;由于保留必要的“罪证”，因此某些异常只能通过文字来描述了～&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h3 id=&#34;背景&#34;&gt;背景&lt;a class=&#34;anchor&#34; href=&#34;#%e8%83%8c%e6%99%af&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;昨晚上10点左右，前端童鞋反映开发环境接口响应超时，但过了几分钟后又恢复了，于是有了这一篇文章。&lt;/p&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;其实很久以前就出现了内存占用异常的情况～，只是占用并不高也就是50MB左右，加上当时还忙着写业务需求就没有急着加上pprof来检查。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;p&gt;首先通过&lt;code&gt;运维平台(k8s based)&lt;/code&gt;直观发现了该pod数量从1变成了2, 再结合新增pod的启动时间，我发现该时间正好是前端童鞋反映状况的时间节点，稍后我检查了下该服务的资源限制如下图：&lt;/p&gt;&#xA;&lt;img src=&#34;https://www.yeqown.xyz/images/pprof-goroutine-leak-1.png&#34;/&gt;&#xA;&lt;p&gt;那么前端童鞋反映的问题就很明显了，由于某种原因导致了pod内存超限，触发了运维平台对于内存超限的“容忍机制”。表现为: 新增一个pod用于缓解服务压力，老服务由于无法申请更多内存会导致崩溃或其他异常（无法响应客户端请求），这与反映的情况一致。&lt;/p&gt;&#xA;&lt;img src=&#34;https://www.yeqown.xyz/images/pprof-goroutine-leak-3.png&#34;/&gt;&#xA;&lt;h3 id=&#34;pprof排查&#34;&gt;pprof排查&lt;a class=&#34;anchor&#34; href=&#34;#pprof%e6%8e%92%e6%9f%a5&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;知道了服务内存异常，想要具体定位的话，这时候就需要pprof上场了。&lt;/p&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;如果你需要重启服务才能开启pprof的话，那么只能等待复现了。这里我在开发环境和测试环境一直开启了pprof，因此可以直接检查。个人觉得，这样还可以帮助开发和测试，完成最初的性能分析😼。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h4 id=&#34;内存检查&#34;&gt;内存检查&lt;a class=&#34;anchor&#34; href=&#34;#%e5%86%85%e5%ad%98%e6%a3%80%e6%9f%a5&#34;&gt;#&lt;/a&gt;&lt;/h4&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;go tool pprof --http&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;:8080 https://service.host.com/debug/pprof/heap&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这个命令是在本地打开一个web服务，直接可视化该服务的内存占用情况。也可以使用:&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;go tool pprof https://service.host.com/debug/pprof/heap&lt;/code&gt; 使用交互模式来分析。通过这个步骤定位到了 grpc相关的包内存占用异常分为两个部分：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;50MB+ google.golang.org/grpc/internal/transport.newBufWriter&#xA;50MB+ bufio.NewReaderSize&#xA;http2 相关库的占用也比较多&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这一切都指向了我们使用的gRPC，可是为啥使用gRPC会用到这么“多”内存呢？接着分析&lt;/p&gt;&#xA;&lt;h4 id=&#34;goroutine检查&#34;&gt;goroutine检查&lt;a class=&#34;anchor&#34; href=&#34;#goroutine%e6%a3%80%e6%9f%a5&#34;&gt;#&lt;/a&gt;&lt;/h4&gt;&#xA;&lt;p&gt;打开一看 &lt;strong&gt;&lt;a href=&#34;https://service.host.com/debug/pprof/&#34;&gt;https://service.host.com/debug/pprof/&lt;/a&gt;&lt;strong&gt;一看，goroutine和heap居“高”(4000+)不下，虽然对于动辄10W+的别人家的服务来说，这点根本不算事，但在我们这种小作坊里可就算异常了。点开看goroutine查看详情，有四个部分的goroutine分别有900个左右，这里就算初步定位了&lt;/strong&gt;“gRPC客户端使用了较多的goroutine，但是却没有正确的结束掉”&lt;/strong&gt;，如下图（这是解决后的截的图）：&lt;/p&gt;&#xA;&lt;img src=&#34;https://www.yeqown.xyz/images/pprof-goroutine-leak-2.jpg&#34;/&gt;&#xA;&lt;h4 id=&#34;pprof总结&#34;&gt;pprof总结&lt;a class=&#34;anchor&#34; href=&#34;#pprof%e6%80%bb%e7%bb%93&#34;&gt;#&lt;/a&gt;&lt;/h4&gt;&#xA;&lt;p&gt;服务中使用的gRPC客户端出了某些故障，导致了goroutine泄漏，引发了OOM（Out Of Memory）。如下图：&lt;/p&gt;&#xA;&lt;img src=&#34;https://www.yeqown.xyz/images/pprof-goroutine-leak-3.png&#34;/&gt;&#xA;&lt;h3 id=&#34;代码排查&#34;&gt;代码排查&lt;a class=&#34;anchor&#34; href=&#34;#%e4%bb%a3%e7%a0%81%e6%8e%92%e6%9f%a5&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;上一步已经定位到是gRPC客户端的问题，那么就可以直接从代码上手了。我心里已经有一个“嫌疑犯”了，如下：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; (&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;defaultHandler&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;handler&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;timeout&lt;/span&gt;        = &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Second&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// _              pb.UserServiceClient = &amp;amp;handler{}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Init of usersvc.handler&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Init&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;rpcAddr&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// ... 略去不表&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;handler&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// rpc configs&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;rpcAddr&lt;/span&gt;          &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;           &lt;span style=&#34;color:#a6e22e&#34;&gt;pb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;UserServiceClient&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;lastGrpcReqError&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;h&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;handler&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;connectRPC&lt;/span&gt;() {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;h&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;h&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;lastGrpcReqError&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 这里判断的本意是：如果客户端初始化失败，&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 或者期间因为异常情况，导致客户端与服务端连接中断的情况下尝试重连。&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;        // 但是忽略了gRPC实现中，对于客户端的处理：&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 1. grpc.Dail 是异步的&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 2. grpc 有自己的重连机制&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;        // 这一部分我还没有看完，就不乱发表看法了。&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;conn&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;grpc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Dial&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;h&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;rpcAddr&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;grpc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;WithInsecure&lt;/span&gt;())&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;logger&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Std&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Errorf&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;could not dial: %s with err: %v&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;h&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;rpcAddr&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;logger&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Std&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Infof&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;usersvc.client.connectRPC called&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;h&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt; = &lt;span style=&#34;color:#a6e22e&#34;&gt;pb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;NewUserServiceClient&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;conn&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// QueryBasicInfoByID based default Handler .&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;QueryBasicInfoByID&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;pb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ByIDForm&lt;/span&gt;) (&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;pb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;BasicInfoResponse&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;defaultHandler&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;connectRPC&lt;/span&gt;()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;cancel&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;WithTimeout&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Background&lt;/span&gt;(), &lt;span style=&#34;color:#a6e22e&#34;&gt;timeout&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;defer&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cancel&lt;/span&gt;()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;resp&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;defaultHandler&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;QueryBasicInfoByID&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;in&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;defaultHandler&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;lastGrpcReqError&lt;/span&gt; = &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;resp&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;抛开本意不谈，这样的写法也是不OK的。。。因为&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
