{"id":10006047,"date":"2022-07-28T17:53:24","date_gmt":"2022-07-28T09:53:24","guid":{"rendered":"https:\/\/ityet.com\/?p=10006047"},"modified":"2022-07-28T17:53:24","modified_gmt":"2022-07-28T09:53:24","slug":"%e6%9c%8d%e5%8a%a1%e5%8f%91%e7%8e%b0%e5%8e%9f%e7%90%86%e5%88%86%e6%9e%90%e4%b8%8e%e6%ba%90%e7%a0%81%e8%a7%a3%e8%af%bb","status":"publish","type":"post","link":"https:\/\/itmax.cn\/?p=10006047","title":{"rendered":"\u670d\u52a1\u53d1\u73b0\u539f\u7406\u5206\u6790\u4e0e\u6e90\u7801\u89e3\u8bfb"},"content":{"rendered":"<p>\u5728\u5fae\u670d\u52a1\u67b6\u6784\u4e2d\uff0c\u6709\u8bb8\u591a\u7ed5\u4e0d\u5f00\u7684\u6280\u672f\u8bdd\u9898\u3002\u6bd4\u5982\u670d\u52a1\u53d1\u73b0\u3001\u8d1f\u8f7d\u5747\u8861\u3001\u6307\u6807\u76d1\u63a7\u3001\u94fe\u8def\u8ffd\u8e2a\uff0c\u4ee5\u53ca\u670d\u52a1\u6cbb\u7406\u76f8\u5173\u7684\u8d85\u65f6\u63a7\u5236\u3001\u7194\u65ad\u3001\u964d\u7ea7\u3001\u9650\u6d41\u7b49\uff0c\u8fd8\u6709RPC\u6846\u67b6\u3002\u8fd9\u4e9b\u90fd\u662f\u5fae\u670d\u52a1\u67b6\u6784\u7684\u57fa\u7840\uff0c\u53ea\u6709\u6253\u7262\u8fd9\u4e9b\u57fa\u7840\uff0c\u624d\u6562\u8bf4\u5bf9\u5fae\u670d\u52a1\u662f\u6709\u4e86\u4e00\u70b9\u7406\u89e3\uff0c\u51fa\u95e8\u4e5f\u597d\u610f\u601d\u548c\u522b\u4eba\u6253\u62db\u547c\u4e86\uff0c\u88ab\u4eba\u63d0\u95ee\u7684\u65f6\u5019\u4e5f\u80fd\u4f83\u4f83\u800c\u8c08\u4e86\uff0c\u7ebf\u4e0a\u51fa\u4e86\u95ee\u9898\u5f80\u5f80\u4e5f\u80fd\u5bfb\u6839\u6eaf\u6e90\u5185\u5fc3\u4e0d\u614c\u4e86\uff0c\u65c1\u8fb9\u7684\u5973\u540c\u4e8b\u5c0f\u82b3\u770b\u7740\u4f60\u7684\u65f6\u5019\u4e5f\u662f\u6ee1\u773c\u7684\u5c0f\u53ef\u7231\u4e86\u3002<\/p>\n<p>\u5728\u300a\u5fae\u670d\u52a1\u5b9e\u8df5\u300b\u516c\u4f17\u53f7\uff0c\u4e4b\u524d\u5199\u4e86\u300ago-zero\u5fae\u670d\u52a1\u5b9e\u6218\u7cfb\u5217\u300b\u7684\u7cfb\u5217\u6587\u7ae0\uff0c\u8fd9\u4e2a\u7cfb\u5217\u7684\u6587\u7ae0\u66f4\u591a\u7684\u662f\u504f\u5411\u4e1a\u52a1\u529f\u80fd\u548c\u9ad8\u5e76\u53d1\u4e0b\u7684\u670d\u52a1\u4f18\u5316\u7b49\u3002\u672c\u4eba\u6c34\u5e73\u6709\u9650\uff0c\u96be\u514d\u6709\u5199\u7684\u4e0d\u8db3\u7684\u5730\u65b9\uff0c\u4f46\u4e5f\u4f9d\u7136\u5f97\u5230\u4e86\u5927\u5bb6\u7684\u652f\u6301\u4e0e\u9f13\u52b1\uff0c\u500d\u611f\u8363\u5e78\uff0c\u6240\u4ee5\u51b3\u5b9a\u8d81\u70ed\u6253\u94c1\uff0c\u4e58\u80dc\u8ffd\u51fb\uff0c\u7ee7\u7eed\u7ed9\u5927\u5bb6\u8f93\u51fa\u5e72\u8d27\u3002<\/p>\n<p>\u300a\u5f7b\u5e95\u641e\u61c2\u7cfb\u5217\u300b\u4f1a\u57fa\u4e8e <a href=\"https:\/\/link.segmentfault.com\/?enc=6qR9N%2BNiY8dHhusnLVVvtw%3D%3D.4QKHR5brnKSV7GykPIJkR8J7AE71GA6UtpSI34K4xxJ6rwft4mnd2QcWSPWgVPzZRN%2Fp7%2BL46DJQ04vR1%2Fqw1Q%3D%3D\" rel=\"nofollow\">go-zero v1.3.5<\/a> \u548c <a href=\"https:\/\/link.segmentfault.com\/?enc=uuyW9n5sk28hFH2LI6iO5A%3D%3D.6%2B4pMUOHVO9NeeFeRCCJwIdma9a4biX7T1StVXJPOHQeQOwIqN7R%2F2G4zwJdX0Jj\" rel=\"nofollow\">grpc-go v1.47.0<\/a> \u548c\u5927\u5bb6\u4e00\u8d77\u5b66\u4e60\u5fae\u670d\u52a1\u67b6\u6784\u7684\u65b9\u65b9\u9762\u9762\uff0c\u4e3b\u8981\u5f62\u5f0f\u662f\u7406\u8bba+\u6e90\u7801+\u6848\u4f8b\uff0c\u5982\u679c\u65f6\u95f4\u5141\u8bb8\u4e5f\u53ef\u80fd\u4f1a\u52a0\u4e0a\u914d\u5957\u89c6\u9891\u3002<\/p>\n<p>\u672c\u7bc7\u6587\u7ae0\u4f5c\u4e3a\u8be5\u7cfb\u5217\u7684\u7b2c\u4e00\u7bc7\uff0c\u4f1a\u5148\u4ecb\u7ecd\u76f8\u5bf9\u6bd4\u8f83\u7b80\u5355\u7684\u670d\u52a1\u53d1\u73b0\u76f8\u5173\u5185\u5bb9\u3002<\/p>\n<p>\u64b8\u8896\u5b50\u5f00\u641e\uff0c\u5965\u5229\u7ed9\uff01\uff01\uff01<\/p>\n<h2>\u670d\u52a1\u53d1\u73b0<\/h2>\n<p>\u4e3a\u4ec0\u4e48\u5728\u5fae\u670d\u52a1\u67b6\u6784\u4e2d\uff0c\u9700\u8981\u5f15\u5165\u670d\u52a1\u53d1\u73b0\u5462\uff1f\u672c\u8d28\u4e0a\uff0c\u670d\u52a1\u53d1\u73b0\u7684\u76ee\u7684\u662f\u89e3\u8026\u7a0b\u5e8f\u5bf9\u670d\u52a1\u5177\u4f53\u4f4d\u7f6e\u7684\u4f9d\u8d56\uff0c\u5bf9\u4e8e\u5fae\u670d\u52a1\u67b6\u6784\u6765\u8bf4\uff0c\u670d\u52a1\u53d1\u73b0\u4e0d\u662f\u53ef\u9009\u7684\uff0c\u800c\u662f\u5fc5\u987b\u7684\u3002\u56e0\u4e3a\u5728\u751f\u4ea7\u73af\u5883\u4e2d\u670d\u52a1\u63d0\u4f9b\u65b9\u90fd\u662f\u4ee5\u96c6\u7fa4\u7684\u65b9\u5f0f\u5bf9\u5916\u63d0\u4f9b\u670d\u52a1\uff0c\u96c6\u7fa4\u4e2d\u670d\u52a1\u7684IP\u968f\u65f6\u90fd\u53ef\u80fd\u53d1\u751f\u53d8\u5316\uff0c\u6bd4\u5982\u670d\u52a1\u91cd\u542f\uff0c\u53d1\u5e03\uff0c\u6269\u7f29\u5bb9\u7b49\uff0c\u56e0\u6b64\u6211\u4eec\u9700\u8981\u7528\u4e00\u672c\u201c\u901a\u8baf\u5f55\u201d\u53ca\u65f6\u83b7\u53d6\u5230\u5bf9\u5e94\u7684\u670d\u52a1\u8282\u70b9\uff0c\u8fd9\u4e2a\u83b7\u53d6\u7684\u8fc7\u7a0b\u5176\u5b9e\u5c31\u662f\u201c\u670d\u52a1\u53d1\u73b0\u201d\u3002<\/p>\n<p><img decoding=\"async\" referrerpolicy=\"no-referrer\" src=\"https:\/\/segmentfault.com\/img\/remote\/1460000042233342\" alt=\"\" title=\"\" loading=\"lazy\"><\/p>\n<p>\u8981\u7406\u89e3\u670d\u52a1\u53d1\u73b0\uff0c\u9700\u8981\u77e5\u9053\u670d\u52a1\u53d1\u73b0\u89e3\u51b3\u4e86\u5982\u4e0b\u4e09\u4e2a\u95ee\u9898\uff1a<\/p>\n<ul>\n<li>\n<p>\u670d\u52a1\u7684\u6ce8\u518c\uff08Service Registration\uff09<\/p>\n<p>\u5f53\u670d\u52a1\u542f\u52a8\u7684\u65f6\u5019\uff0c\u5e94\u8be5\u901a\u8fc7\u67d0\u79cd\u5f62\u5f0f\uff08\u6bd4\u5982\u8c03\u7528API\u3001\u4ea7\u751f\u4e0a\u7ebf\u4e8b\u4ef6\u6d88\u606f\u3001\u5728Etcd\u4e2d\u8bb0\u5f55\u3001\u5b58\u6570\u636e\u5e93\u7b49\u7b49\uff09\u628a\u81ea\u5df1\uff08\u670d\u52a1\uff09\u7684\u4fe1\u606f\u901a\u77e5\u7ed9\u670d\u52a1\u6ce8\u518c\u4e2d\u5fc3\uff0c\u8fd9\u4e2a\u8fc7\u7a0b\u4e00\u822c\u662f\u7531\u5fae\u670d\u52a1\u6846\u67b6\u6765\u5b8c\u6210\uff0c\u4e1a\u52a1\u4ee3\u7801\u65e0\u611f\u77e5\u3002<\/p>\n<\/li>\n<li>\n<p>\u670d\u52a1\u7684\u7ef4\u62a4\uff08Service Maintaining\uff09<\/p>\n<p>\u5c3d\u7ba1\u5728\u5fae\u670d\u52a1\u6846\u67b6\u4e2d\u901a\u5e38\u90fd\u63d0\u4f9b\u4e0b\u7ebf\u673a\u5236\uff0c\u4f46\u5e76\u6ca1\u6709\u529e\u6cd5\u4fdd\u8bc1\u6bcf\u6b21\u670d\u52a1\u90fd\u80fd\u4f18\u96c5\u4e0b\u7ebf\uff08Graceful Shutdown\uff09\uff0c\u800c\u4e0d\u662f\u7531\u4e8e\u5b95\u673a\u3001\u65ad\u7f51\u7b49\u539f\u56e0\u7a81\u7136\u5931\u8054\uff0c\u6240\u4ee5\uff0c\u5728\u5fae\u670d\u52a1\u6846\u67b6\u4e2d\u5c31\u5fc5\u987b\u8981\u5c3d\u53ef\u80fd\u7684\u4fdd\u8bc1\u7ef4\u62a4\u7684\u670d\u52a1\u5217\u8868\u7684\u6b63\u786e\u6027\uff0c\u4ee5\u907f\u514d\u8bbf\u95ee\u4e0d\u53ef\u7528\u670d\u52a1\u8282\u70b9\u7684\u5c34\u5c2c\u3002<\/p>\n<\/li>\n<li>\n<p>\u670d\u52a1\u7684\u53d1\u73b0\uff08Service Discovery\uff09<\/p>\n<p>\u8fd9\u91cc\u6240\u8bf4\u7684\u53d1\u73b0\u662f\u72ed\u4e49\u7684\uff0c\u5b83\u7279\u6307\u6d88\u8d39\u8005\u4ece\u5fae\u670d\u52a1\u6846\u67b6\uff08\u670d\u52a1\u53d1\u73b0\u6a21\u5757\uff09\u4e2d\uff0c\u628a\u4e00\u4e2a\u670d\u52a1\u6807\u8bc6\uff08\u4e00\u822c\u662f\u670d\u52a1\u540d\uff09\u8f6c\u6362\u4e3a\u670d\u52a1\u5b9e\u9645\u4f4d\u7f6e\uff08\u4e00\u822c\u662fip\u5730\u5740\uff09\u7684\u8fc7\u7a0b\u3002\u8fd9\u4e2a\u8fc7\u7a0b\uff08\u53ef\u80fd\u662f\u8c03\u7528API\uff0c\u76d1\u542cEtcd\uff0c\u67e5\u8be2\u6570\u636e\u5e93\u7b49\uff09\u4e1a\u52a1\u4ee3\u7801\u65e0\u611f\u77e5\u3002<\/p>\n<\/li>\n<\/ul>\n<p>\u670d\u52a1\u53d1\u73b0\u6709\u4e24\u79cd\u6a21\u5f0f\uff0c\u5206\u522b\u662f\u670d\u52a1\u7aef\u670d\u52a1\u53d1\u73b0\u548c\u5ba2\u6237\u7aef\u670d\u52a1\u53d1\u73b0\uff0c\u4e0b\u9762\u5206\u522b\u8fdb\u884c\u4ecb\u7ecd\u3002<\/p>\n<h3>\u670d\u52a1\u7aef\u670d\u52a1\u53d1\u73b0<\/h3>\n<p>\u5bf9\u4e8e\u670d\u52a1\u7aef\u670d\u52a1\u53d1\u73b0\u6765\u8bf4\uff0c\u670d\u52a1\u8c03\u7528\u65b9\u65e0\u9700\u5173\u6ce8\u670d\u52a1\u53d1\u73b0\u7684\u5177\u4f53\u7ec6\u8282\uff0c\u53ea\u9700\u8981\u77e5\u9053\u670d\u52a1\u7684DNS\u57df\u540d\u5373\u53ef\uff0c\u652f\u6301\u4e0d\u540c\u8bed\u8a00\u7684\u63a5\u5165\uff0c\u5bf9\u57fa\u7840\u8bbe\u65bd\u6765\u8bf4\uff0c\u9700\u8981\u4e13\u95e8\u652f\u6301\u8d1f\u8f7d\u5747\u8861\u5668\uff0c\u5bf9\u4e8e\u8bf7\u6c42\u94fe\u8def\u6765\u8bf4\u591a\u4e86\u4e00\u6b21\u7f51\u7edc\u8df3\u8f6c\uff0c\u53ef\u80fd\u4f1a\u6709\u6027\u80fd\u635f\u8017\u3002\u4e5f\u53ef\u4ee5\u628a\u54b1\u4eec\u6bd4\u8f83\u719f\u6089\u7684 nginx \u53cd\u5411\u4ee3\u7406\u7406\u89e3\u4e3a\u670d\u52a1\u7aef\u670d\u52a1\u53d1\u73b0\u3002<\/p>\n<p><img decoding=\"async\" referrerpolicy=\"no-referrer\" src=\"https:\/\/segmentfault.com\/img\/remote\/1460000042233343\" alt=\"\" title=\"\" loading=\"lazy\"><\/p>\n<h3>\u5ba2\u6237\u7aef\u670d\u52a1\u53d1\u73b0<\/h3>\n<p>\u5bf9\u4e8e\u5ba2\u6237\u7aef\u670d\u52a1\u53d1\u73b0\u6765\u8bf4\uff0c\u7531\u4e8e\u5ba2\u6237\u7aef\u548c\u670d\u52a1\u7aef\u91c7\u7528\u4e86\u76f4\u8fde\u7684\u65b9\u5f0f\uff0c\u6bd4\u670d\u52a1\u7aef\u670d\u52a1\u53d1\u73b0\u5c11\u4e86\u4e00\u6b21\u7f51\u7edc\u8df3\u8f6c\uff0c\u5bf9\u4e8e\u670d\u52a1\u8c03\u7528\u65b9\u6765\u8bf4\u9700\u8981\u5185\u7f6e\u8d1f\u8f7d\u5747\u8861\u5668\uff0c\u4e0d\u540c\u7684\u8bed\u8a00\u9700\u8981\u5404\u81ea\u5b9e\u73b0\u3002<\/p>\n<p>\u5bf9\u4e8e\u5fae\u670d\u52a1\u67b6\u6784\u6765\u8bf4\uff0c\u6211\u4eec\u671f\u671b\u7684\u662f\u53bb\u4e2d\u5fc3\u5316\u4f9d\u8d56\uff0c\u4e2d\u5fc3\u5316\u7684\u4f9d\u8d56\u4f1a\u8ba9\u67b6\u6784\u53d8\u5f97\u590d\u6742\uff0c\u5f53\u51fa\u73b0\u95ee\u9898\u7684\u65f6\u5019\u4e5f\u4f1a\u8ba9\u6574\u4e2a\u6392\u67e5\u94fe\u8def\u53d8\u5f97\u7e41\u7410\uff0c\u6240\u4ee5\u5728 go-zero \u4e2d\u91c7\u7528\u7684\u662f\u5ba2\u6237\u7aef\u670d\u52a1\u53d1\u73b0\u7684\u6a21\u5f0f\u3002<\/p>\n<p><img decoding=\"async\" referrerpolicy=\"no-referrer\" src=\"https:\/\/segmentfault.com\/img\/remote\/1460000042233344\" alt=\"\" title=\"\" loading=\"lazy\"><\/p>\n<h2>gRPC\u7684\u670d\u52a1\u53d1\u73b0<\/h2>\n<p>gRPC\u63d0\u4f9b\u4e86\u81ea\u5b9a\u4e49Resolver\u7684\u80fd\u529b\u6765\u5b9e\u73b0\u670d\u52a1\u53d1\u73b0\uff0c\u901a\u8fc7 <strong>Register<\/strong>\u65b9\u6cd5\u6765\u8fdb\u884c\u6ce8\u518c\u81ea\u5b9a\u4e49\u7684Resolver\uff0c\u81ea\u5b9a\u4e49\u7684Resolver\u9700\u8981\u5b9e\u73b0Builder\u63a5\u53e3\uff0c\u5b9a\u4e49\u5982\u4e0b\uff1a<\/p>\n<p><strong>grpc-go\/resolver\/resolver.go:261<\/strong><\/p>\n<pre><code class=\"go\">type Builder interface {\n    Build(target Target, cc ClientConn, opts BuildOptions) (Resolver, error)\n    Scheme() string\n}<\/code><\/pre>\n<p>\u5148\u8bf4\u4e0b <code>Scheme()<\/code> \u65b9\u6cd5\u7684\u4f5c\u7528\uff0c\u8be5\u65b9\u6cd5\u8fd4\u56de\u4e00\u4e2astirng\u3002\u6ce8\u518c\u7684 <code>Resolver<\/code> \u4f1a\u88ab\u4fdd\u5b58\u5728\u4e00\u4e2a\u5168\u5c40\u7684\u53d8\u91cfm\u4e2d\uff0cm\u662f\u4e00\u4e2amap\uff0c\u8fd9\u4e2amap\u7684key\u5373\u4e3a <code>Scheme()<\/code> \u65b9\u6cd5\u8fd4\u56de\u7684\u5b57\u7b26\u4e32\u3002\u4e5f\u5c31\u662f\u591a\u4e2aResolver\u662f\u901a\u8fc7Scheme\u6765\u8fdb\u884c\u533a\u5206\u7684\uff0c\u6240\u4ee5\u6211\u4eec\u5b9a\u4e49 <code>Resolver<\/code> \u7684\u65f6\u5019 <code>Scheme<\/code> \u4e0d\u8981\u91cd\u590d\uff0c\u5426\u5219 <code>Resolver<\/code> \u5c31\u4f1a\u88ab\u8986\u76d6\u3002<\/p>\n<p><strong>grpc-go\/resolver\/resolver.go:49<\/strong><\/p>\n<pre><code class=\"go\">func Register(b Builder) {\n    m[b.Scheme()] = b\n}<\/code><\/pre>\n<p>\u518d\u6765\u770b\u4e0bBuild\u65b9\u6cd5\uff0cBuild\u65b9\u6cd5\u6709\u4e09\u4e2a\u53c2\u6570\uff0c\u8fd8\u6709Resolver\u8fd4\u56de\u503c\uff0c\u4e4d\u4e00\u770b\u4e0d\u77e5\u9053\u8fd9\u4e9b\u53c2\u6570\u662f\u5e72\u561b\u7684\uff0c\u9047\u5230\u8fd9\u79cd\u60c5\u51b5\u8be5\u600e\u4e48\u529e\u5462\uff1f\u5176\u5b9e\u4e5f\u5f88\u7b80\u5355\uff0c\u53bb\u6e90\u7801\u91cc\u770b\u4e00\u4e0bBuild\u65b9\u6cd5\u5728\u54ea\u91cc\u88ab\u8c03\u7528\u7684\uff0c\u5c31\u77e5\u9053\u4f20\u5165\u7684\u53c2\u6570\u662f\u54ea\u91cc\u6765\u7684\uff0c\u662f\u4ec0\u4e48\u542b\u4e49\u4e86\u3002<\/p>\n<p>\u4f7f\u7528gRPC\u8fdb\u884c\u670d\u52a1\u8c03\u7528\u524d\uff0c\u9700\u8981\u5148\u521b\u5efa\u4e00\u4e2a <strong>ClientConn<\/strong> \u5bf9\u8c61\uff0c\u6700\u7ec8\u53d1\u8d77\u8c03\u7528\u7684\u65f6\u5019\uff0c\u5176\u5b9e\u662f\u8c03\u7528\u4e86 <strong>ClientConn<\/strong> \u7684 <strong>Invoke<\/strong> \u65b9\u6cd5\uff0c\u53ef\u4ee5\u770b\u4e0b\u5982\u4e0b\u4ee3\u7801\uff0c\u5176\u4e2d <strong>ClientConn<\/strong> \u662f\u901a\u8fc7\u8c03\u7528 <strong>NewGreeterClient<\/strong> \u4f20\u5165\u7684\uff0c<strong>NewGreeterClient<\/strong> \u4e3a <strong>protoc<\/strong> \u81ea\u52a8\u751f\u6210\u7684\u4ee3\u7801\uff0c\u5e76\u8d4b\u503c\u7ed9 <strong>cc<\/strong> \u5c5e\u6027\uff0c\u793a\u4f8b\u4ee3\u7801\u4e2d\u521b\u5efa <strong>ClientConn<\/strong> \u8c03\u7528\u7684\u662f <strong>Dial<\/strong> \u65b9\u6cd5\uff0c\u5e95\u5c42\u4e5f\u4f1a\u8c03\u7528 <strong>DialContext<\/strong>\uff1a<\/p>\n<p><strong>grpc-go\/clientconn.go:104<\/strong><\/p>\n<pre><code class=\"go\">func Dial(target string, opts ...DialOption) (*ClientConn, error) {\n    return DialContext(context.Background(), target, opts...)\n}<\/code><\/pre>\n<p>\u521b\u5efa <strong>ClientConn<\/strong> \u5bf9\u8c61\uff0c\u5e76\u4f20\u9012\u7ed9\u81ea\u52a8\u751f\u6210\u7684 <strong>greeterClient<\/strong><\/p>\n<p><strong>grpc-go\/examples\/helloworld\/greeter_client\/main.go:42<\/strong><\/p>\n<pre><code class=\"go\">func main() {\n    flag.Parse()\n\n    \/\/ Set up a connection to the server.\n    conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))\n    if err != nil {\n        log.Fatalf(&quot;did not connect: %v&quot;, err)\n    }\n    defer conn.Close()\n\n    c := pb.NewGreeterClient(conn)\n    \/\/ Contact the server and print out its response.\n    ctx, cancel := context.WithTimeout(context.Background(), time.Second)\n    defer cancel()\n    r, err := c.SayHello(ctx, &amp;pb.HelloRequest{Name: *name})\n    if err != nil {\n        log.Fatalf(&quot;could not greet: %v&quot;, err)\n    }\n    log.Printf(&quot;Greeting: %s&quot;, r.GetMessage())\n}<\/code><\/pre>\n<p>\u6700\u7ec8\u901a\u8fc7 <strong>Invoke<\/strong> \u65b9\u6cd5\u771f\u6b63\u53d1\u8d77\u8c03\u7528\u8bf7\u6c42\u3002<\/p>\n<p><strong>grpc-go\/examples\/helloworld\/helloworld\/helloworld_grpc.pb.go:39<\/strong><\/p>\n<pre><code class=\"go\">func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {\n    out := new(HelloReply)\n    err := c.cc.Invoke(ctx, &quot;\/helloworld.Greeter\/SayHello&quot;, in, out, opts...)\n    if err != nil {\n        return nil, err\n    }\n    return out, nil\n}<\/code><\/pre>\n<p>\u5728\u4e86\u89e3\u4e86\u5ba2\u6237\u7aef\u8c03\u7528\u53d1\u8d77\u7684\u6d41\u7a0b\u540e\uff0c\u6211\u4eec\u91cd\u70b9\u770b\u4e0b <strong>ClientConn<\/strong> \u65b9\u6cd5\uff0c\u8be5\u65b9\u6cd5\u5de8\u957f\uff0c\u53ea\u770b\u6211\u4eec\u5173\u6ce8\u7684Resolver\u90e8\u5206\u3002<strong>ClientConn<\/strong> \u7b2c\u4e8c\u4e2a\u53c2\u6570 <strong>Target<\/strong> \u7684\u8bed\u6cd5\u53ef\u4ee5\u53c2\u8003 <strong><a href=\"https:\/\/link.segmentfault.com\/?enc=OrJcMxSLzEXfwL8xQ5vkgw%3D%3D.UVyya6QTVB7HNi%2FglRD%2FE9XsQkvThGcsgzzM1alyN8OU9xXywjr63KRlE2f0kmdLXIG1Zi2PuwduCJWzYGMqdw%3D%3D\" rel=\"nofollow\">https:\/\/github.com\/grpc\/grpc\/&#8230;<\/a><\/strong> \uff0c\u91c7\u7528\u4e86URI\u7684\u683c\u5f0f\uff0c\u5176\u4e2d\u7b2c\u4e00\u90e8\u5206\u8868\u793aResolver\u7684\u540d\u79f0\uff0c\u5373\u81ea\u5b9a\u4e49Builder\u65b9\u6cd5Scheme\u7684\u8fd4\u56de\u503c\u3002\u683c\u5f0f\u5982\u4e0b\uff1a<\/p>\n<pre><code class=\"shell\">dns:[\/\/authority\/]host[:port] -- DNS\uff08\u9ed8\u8ba4\uff09<\/code><\/pre>\n<p>\u7ee7\u7eed\u5f80\u4e0b\u770b\uff0c\u901a\u8fc7\u8c03\u7528 <strong>parseTargetAndFindResolver<\/strong> \u65b9\u6cd5\u6765\u83b7\u53d6Resolver<\/p>\n<p><strong>grpc-go\/clientconn.go:251<\/strong><\/p>\n<pre><code class=\"go\">resolverBuilder, err := cc.parseTargetAndFindResolver()<\/code><\/pre>\n<p>\u5728 <strong>parseTargetAndFindResolver<\/strong> \u65b9\u6cd5\u4e2d\uff0c\u4e3b\u8981\u5c31\u662f\u628a <strong>target<\/strong> \u4e2d\u7684resolver name\u89e3\u6790\u51fa\u6765\uff0c\u7136\u540e\u6839\u636eresolver name\u53bb\u4e0a\u9762\u6211\u4eec\u63d0\u5230\u7684\u4fdd\u5b58Resolver\u7684\u5168\u5c40\u53d8\u91cfm\u4e2d\u53bb\u627e\u5bf9\u5e94\u7684Resolver\u3002<\/p>\n<p><strong>grpc-go\/clientconn.go:1574<\/strong><\/p>\n<pre><code class=\"go\">func (cc *ClientConn) parseTargetAndFindResolver() (resolver.Builder, error) {\n    \/\/ \u975e\u5173\u952e\u4ee3\u7801\u7701\u7565 ...\n  \n    var rb resolver.Builder\n    parsedTarget, err := parseTarget(cc.target)\n  \n    \/\/ \u975e\u5173\u952e\u4ee3\u7801\u7701\u7565 ...\n  \n    rb = cc.getResolver(parsedTarget.Scheme)\n    if rb == nil {\n        return nil, fmt.Errorf(&quot;could not get resolver for default scheme: %q&quot;, parsedTarget.Scheme)\n    }\n    cc.parsedTarget = parsedTarget\n    return rb, nil\n}<\/code><\/pre>\n<p>\u63a5\u7740\u5f80\u4e0b\u770b\uff0c\u627e\u5230\u6211\u4eec\u81ea\u5df1\u6ce8\u518c\u7684Resolver\u4e4b\u540e\uff0c\u53c8\u8c03\u7528\u4e86 <strong>newCCResolverWrapper<\/strong> \u65b9\u6cd5\uff0c\u628a\u6211\u4eec\u81ea\u5df1\u7684Resolver\u4e5f\u4f20\u4e86\u8fdb\u53bb<\/p>\n<p><strong>grpc-go\/clientconn.go:292<\/strong><\/p>\n<pre><code class=\"go\">rWrapper, err := newCCResolverWrapper(cc, resolverBuilder)<\/code><\/pre>\n<p>\u8fdb\u5165\u5230 <strong>newCCResolverWrapper<\/strong> \u65b9\u6cd5\u4e2d\uff0c\u5728\u8fd9\u4e2a\u65b9\u6cd5\u4e2d\u7ec8\u4e8e\u627e\u5230\u4e86\u6211\u4eec\u81ea\u5b9a\u4e49\u7684 <strong>Builder<\/strong> \u7684 <strong>Build<\/strong> \u65b9\u6cd5\u5728\u54ea\u91cc\u88ab\u8c03\u7528\u4e86\uff0c\u5728 <strong>grpc-go\/resolver_conn_wrapper.go:72<\/strong> \u8c03\u7528\u4e86\u6211\u4eec\u81ea\u5b9a\u4e49\u7684Build\u65b9\u6cd5\uff0c\u5176\u4e2d\u7b2c\u4e00\u53c2\u6570target\u4f20\u5165\u7684\u4e3acc.parseTarget\uff0ccc\u4e3a <strong>newCCResolverWrapper<\/strong> \u7b2c\u4e00\u4e2a\u53c2\u6570\uff0c\u5373 <strong>ClientConn<\/strong> \u5bf9\u8c61\u3002cc.parseTarget\u662f\u5728\u4e0a\u9762\u63d0\u5230\u7684\u83b7\u53d6\u81ea\u5b9a\u4e49Resolver\u65b9\u6cd5 <strong>parseTargetAndFindResolver<\/strong> \u4e2d\u6700\u540e\u8d4b\u503c\u7684\uff0c\u5176\u4e2dScheme\u3001Authority\u3001Endpoint\u5206\u522b\u5bf9\u5e94Target\u8bed\u6cd5\u4e2d\u5b9a\u4e49\u7684\u4e09\u90e8\u5206\uff0c\u8fd9\u51e0\u4e2a\u5c5e\u6027\u5373\u5c06\u88ab\u5e9f\u5f03\uff0c\u53ea\u4fdd\u7559URL\u5c5e\u6027\uff0c\u5b9a\u4e49\u5982\u4e0b\uff1a<\/p>\n<p><strong>grpc-go\/resolver\/resolver.go:245<\/strong><\/p>\n<pre><code class=\"go\">type Target struct {\n    \/\/ Deprecated: use URL.Scheme instead.\n    Scheme string\n    \/\/ Deprecated: use URL.Host instead.\n    Authority string\n    \/\/ Deprecated: use URL.Path or URL.Opaque instead. The latter is set when\n    \/\/ the former is empty.\n    Endpoint string\n    \/\/ URL contains the parsed dial target with an optional default scheme added\n    \/\/ to it if the original dial target contained no scheme or contained an\n    \/\/ unregistered scheme. Any query params specified in the original dial\n    \/\/ target can be accessed from here.\n    URL url.URL\n}\n<\/code><\/pre>\n<p>URL\u7684Scheme\u5bf9\u5e94Target\u7684Scheme\uff0cURL\u7684Host\u5bf9\u5e94Target\u7684Authority\uff0cURL\u7684Path\u5bf9\u5e94Target\u7684Endpoint<\/p>\n<p><strong>\/usr\/local\/go\/src\/net\/url\/url.go:358<\/strong><\/p>\n<pre><code class=\"go\">type URL struct {\n    Scheme      string\n    Opaque      string    \/\/ encoded opaque data\n    User        *Userinfo \/\/ username and password information\n    Host        string    \/\/ host or host:port\n    Path        string    \/\/ path (relative paths may omit leading slash)\n    RawPath     string    \/\/ encoded path hint (see EscapedPath method)\n    ForceQuery  bool      \/\/ append a query ('?') even if RawQuery is empty\n    RawQuery    string    \/\/ encoded query values, without '?'\n    Fragment    string    \/\/ fragment for references, without '#'\n    RawFragment string    \/\/ encoded fragment hint (see EscapedFragment method)\n}<\/code><\/pre>\n<p>\u7ee7\u7eed\u770b\u4f20\u5165\u81ea\u5b9a\u4e49Build\u65b9\u6cd5\u7684\u7b2c\u4e8c\u4e2a\u53c2\u6570cc\uff0c\u8fd9\u4e2acc\u53c2\u6570\u662f\u4e00\u4e2a\u63a5\u53e3 <strong>ClientConn<\/strong>\uff0c\u4e0d\u8981\u548c\u6211\u4eec\u4e4b\u524d\u8bb2\u7684\u521b\u5efa\u5ba2\u6237\u7aef\u8c03\u7528\u7528\u7684 ClientConn\u6df7\u6dc6\uff0c\u8fd9\u4e2a <strong>ClientConn<\/strong>\u5b9a\u4e49\u5982\u4e0b\uff1a<\/p>\n<p><strong>grpc-go\/resolver\/resolver.go:203<\/strong><\/p>\n<pre><code class=\"go\">type ClientConn interface {\n    UpdateState(State) error\n    ReportError(error)\n    NewAddress(addresses []Address)\n    NewServiceConfig(serviceConfig string)\n    ParseServiceConfig(serviceConfigJSON string) *serviceconfig.ParseResult\n}<\/code><\/pre>\n<p><strong>ccResolverWrapper<\/strong> \u5b9e\u73b0\u4e86\u8fd9\u4e2a\u63a5\u53e3\uff0c\u5e76\u4f5c\u4e3a\u81ea\u5b9a\u4e49Build\u65b9\u6cd5\u7684\u7b2c\u4e8c\u4e2a\u53c2\u6570\u4f20\u5165<\/p>\n<p><strong>grpc-go\/resolver_conn_wrapper.go:36<\/strong><\/p>\n<pre><code class=\"go\">type ccResolverWrapper struct {\n    cc         *ClientConn\n    resolverMu sync.Mutex\n    resolver   resolver.Resolver\n    done       *grpcsync.Event\n    curState   resolver.State\n\n    incomingMu sync.Mutex \/\/ Synchronizes all the incoming calls.\n}<\/code><\/pre>\n<p>\u81ea\u5b9a\u4e49Build\u65b9\u6cd5\u7684\u7b2c\u4e09\u4e2a\u53c2\u6570\u4e3a\u4e00\u4e9b\u914d\u7f6e\u9879\uff0cnewCCResolverWrapper\u5b9e\u73b0\u5982\u4e0b\uff1a<\/p>\n<p><strong>grpc-go\/resolver_conn_wrapper.go:48<\/strong><\/p>\n<pre><code class=\"go\">func newCCResolverWrapper(cc *ClientConn, rb resolver.Builder) (*ccResolverWrapper, error) {\n    ccr := &amp;ccResolverWrapper{\n        cc:   cc,\n        done: grpcsync.NewEvent(),\n    }\n\n    var credsClone credentials.TransportCredentials\n    if creds := cc.dopts.copts.TransportCredentials; creds != nil {\n        credsClone = creds.Clone()\n    }\n    rbo := resolver.BuildOptions{\n        DisableServiceConfig: cc.dopts.disableServiceConfig,\n        DialCreds:            credsClone,\n        CredsBundle:          cc.dopts.copts.CredsBundle,\n        Dialer:               cc.dopts.copts.Dialer,\n    }\n\n    var err error\n    ccr.resolverMu.Lock()\n    defer ccr.resolverMu.Unlock()\n    ccr.resolver, err = rb.Build(cc.parsedTarget, ccr, rbo)\n    if err != nil {\n        return nil, err\n    }\n    return ccr, nil\n}<\/code><\/pre>\n<p>\u597d\u4e86\uff0c\u5230\u8fd9\u91cc\u6211\u4eec\u5df2\u7ecf\u77e5\u9053\u4e86\u81ea\u5b9aResolver\u7684Build\u65b9\u6cd5\u5728\u54ea\u91cc\u88ab\u8c03\u7528\uff0c\u4ee5\u53ca\u4f20\u5165\u7684\u53c2\u6570\u7684\u7531\u6765\u4ee5\u53ca\u542b\u4e49\uff0c\u5982\u679c\u4f60\u662f\u7b2c\u4e00\u6b21\u770bgRPC\u6e90\u7801\u7684\u8bdd\u53ef\u80fd\u73b0\u5728\u5df2\u7ecf\u6709\u70b9\u61f5\u4e86\uff0c\u53ef\u4ee5\u591a\u8bfb\u51e0\u904d\uff0c\u4e3a\u5927\u5bb6\u63d0\u4f9b\u4e86\u65f6\u5e8f\u56fe\u914d\u5408\u4ee3\u7801\u9605\u8bfb\u6548\u679c\u66f4\u4f73\uff1a<\/p>\n<p><img decoding=\"async\" referrerpolicy=\"no-referrer\" src=\"https:\/\/segmentfault.com\/img\/remote\/1460000042233345\" alt=\"\" title=\"\" loading=\"lazy\"><\/p>\n<h2>go-zero\u4e2d\u5982\u4f55\u5b9e\u73b0\u7684\u670d\u52a1\u53d1\u73b0<\/h2>\n<p>\u901a\u8fc7\u5bf9gRPC\u670d\u52a1\u53d1\u73b0\u76f8\u5173\u5185\u5bb9\u7684\u5b66\u4e60\uff0c\u6211\u4eec\u5927\u6982\u5df2\u7ecf\u77e5\u9053\u4e86\u670d\u52a1\u53d1\u73b0\u662f\u600e\u4e48\u56de\u4e8b\u4e86\uff0c\u6709\u4e86\u7406\u8bba\uff0c\u63a5\u4e0b\u6765\u6211\u4eec\u5c31\u4e00\u8d77\u770b\u4e0bgo-zero\u662f\u5982\u4f55\u57fa\u4e8egRPC\u505a\u670d\u52a1\u53d1\u73b0\u7684\u3002<\/p>\n<p>\u901a\u8fc7\u4e0a\u9762\u7684\u65f6\u5e8f\u56fe\u53ef\u4ee5\u770b\u5230\u7b2c\u4e00\u6b65\u662f\u9700\u8981\u81ea\u5b9a\u4e49Resolver\uff0c\u7b2c\u4e8c\u6b65\u6ce8\u518c\u81ea\u5b9a\u4e49\u7684Resolver\u3002<\/p>\n<p>go-zero\u7684\u670d\u52a1\u53d1\u73b0\u662f\u5728\u5ba2\u6237\u7aef\u5b9e\u73b0\u7684\u3002\u5728\u521b\u5efazRPC\u5ba2\u6237\u7aef\u7684\u65f6\u5019\uff0c\u901a\u8fc7init\u65b9\u6cd5\u8fdb\u884c\u4e86\u81ea\u5b9a\u4e49Resolver\u7684\u6ce8\u518c\u3002<\/p>\n<p><strong>go-zero\/zrpc\/internal\/client.go:23<\/strong><\/p>\n<pre><code class=\"go\">func init() {\n    resolver.Register()\n}<\/code><\/pre>\n<p>\u5728go-zero\u4e2d\u9ed8\u8ba4\u6ce8\u518c\u4e86\u56db\u4e2a\u81ea\u5b9a\u4e49\u7684Resolver\u3002<\/p>\n<p><strong>go-zero\/zrpc\/resolver\/internal\/resolver.go:35<\/strong><\/p>\n<pre><code class=\"go\">func RegisterResolver() {\n    resolver.Register(&amp;directResolverBuilder)\n    resolver.Register(&amp;discovResolverBuilder)\n    resolver.Register(&amp;etcdResolverBuilder)\n    resolver.Register(&amp;k8sResolverBuilder)\n}<\/code><\/pre>\n<p>\u901a\u8fc7 <strong>goctl<\/strong> \u81ea\u52a8\u751f\u6210\u7684rpc\u4ee3\u7801\u9ed8\u8ba4\u4f7f\u7528\u7684\u662fetcd\u4f5c\u4e3a\u670d\u52a1\u6ce8\u518c\u4e0e\u53d1\u73b0\u7ec4\u4ef6\u7684\uff0c\u56e0\u6b64\u6211\u4eec\u91cd\u70b9\u6765\u770b\u4e0bgo-zero\u662f\u5982\u4f55\u57fa\u4e8eetcd\u5b9e\u73b0\u670d\u52a1\u6ce8\u518c\u4e0e\u53d1\u73b0\u7684\u3002<\/p>\n<p>etcdBuilder\u8fd4\u56de\u7684Scheme\u503c\u4e3aetcd<\/p>\n<p><strong>go-zero\/zrpc\/resolver\/internal\/etcdbuilder.go:7<\/strong><\/p>\n<pre><code class=\"go\">func (b *etcdBuilder) Scheme() string {\n    return EtcdScheme\n}<\/code><\/pre>\n<p><strong>go-zero\/zrpc\/resolver\/internal\/resolver.go:15<\/strong><\/p>\n<pre><code class=\"go\">EtcdScheme = &quot;etcd&quot;<\/code><\/pre>\n<p>\u8fd8\u8bb0\u5f97\u6211\u4eec\u4e0a\u9762\u8bb2\u8fc7\u7684\u5417\uff1f\u5728\u65f6\u5e8f\u56fe\u7684\u7b2c\u4e94\u6b65\u548c\u7b2c\u516d\u6b65\uff0c\u4f1a\u901a\u8fc7scheme\u53bb\u5168\u5c40\u7684m\u4e2d\u5bfb\u627e\u81ea\u5b9a\u4e49\u7684Resolver\uff0c\u800cscheme\u662f\u4eceDialContext\u7b2c\u4e8c\u4e2a\u53c2\u6570target\u4e2d\u89e3\u6790\u51fa\u6765\u7684\uff0c\u90a3\u6211\u4eec\u770b\u4e0bgo-zero\u8c03\u7528DialContext\u7684\u65f6\u5019\uff0c\u4f20\u5165\u7684target\u503c\u662f\u4ec0\u4e48\u3002target\u662f\u901a\u8fc7 <strong>BuildTarget<\/strong> \u65b9\u6cd5\u83b7\u53d6\u6765\u7684\uff0c\u5b9a\u4e49\u5982\u4e0b\uff1a<\/p>\n<p><strong>go-zero\/zrpc\/config.go:72<\/strong><\/p>\n<pre><code class=\"go\">func (cc RpcClientConf) BuildTarget() (string, error) {\n    if len(cc.Endpoints) &gt; 0 {\n        return resolver.BuildDirectTarget(cc.Endpoints), nil\n    } else if len(cc.Target) &gt; 0 {\n        return cc.Target, nil\n    }\n\n    if err := cc.Etcd.Validate(); err != nil {\n        return &quot;&quot;, err\n    }\n\n    if cc.Etcd.HasAccount() {\n        discov.RegisterAccount(cc.Etcd.Hosts, cc.Etcd.User, cc.Etcd.Pass)\n    }\n    if cc.Etcd.HasTLS() {\n        if err := discov.RegisterTLS(cc.Etcd.Hosts, cc.Etcd.CertFile, cc.Etcd.CertKeyFile,\n            cc.Etcd.CACertFile, cc.Etcd.InsecureSkipVerify); err != nil {\n            return &quot;&quot;, err\n        }\n    }\n\n    return resolver.BuildDiscovTarget(cc.Etcd.Hosts, cc.Etcd.Key), nil\n}<\/code><\/pre>\n<p>\u6700\u7ec8\u751f\u6210target\u7ed3\u679c\u7684\u65b9\u6cd5\u5982\u4e0b\uff0c\u4e5f\u5c31\u662f\u5bf9\u4e8eetcd\u6765\u8bf4\uff0c\u6700\u7ec8\u751f\u6210\u7684target\u683c\u5f0f\u4e3a\uff1a<\/p>\n<pre><code class=\"shell\">etcd:\/\/127.0.0.1:2379\/product.rpc<\/code><\/pre>\n<p><strong>go-zero\/zrpc\/resolver\/target.go:17<\/strong><\/p>\n<pre><code class=\"go\">func BuildDiscovTarget(endpoints []string, key string) string {\n    return fmt.Sprintf(&quot;%s:\/\/%s\/%s&quot;, internal.DiscovScheme,\n        strings.Join(endpoints, internal.EndpointSep), key)\n}<\/code><\/pre>\n<p>\u4f3c\u4e4e\u6709\u70b9\u4e0d\u5bf9\u52b2\uff0cscheme\u4e0d\u5e94\u8be5\u662fetcd\u4e48\uff1f\u4e3a\u4ec0\u4e48\u662fdiscov\uff1f\u5176\u5b9e\u662f\u56e0\u4e3aetcd\u548cdiscov\u5171\u7528\u4e86\u4e00\u5957Resolver\u903b\u8f91\uff0c\u4e5f\u5c31\u662fgRPC\u901a\u8fc7scheme\u627e\u5230\u5df2\u7ecf\u6ce8\u518c\u7684discov Resolver\uff0c\u8be5Resolver\u5bf9\u5e94\u7684Build\u65b9\u6cd5\u540c\u6837\u9002\u7528\u4e8eetcd\uff0cdiscov\u53ef\u4ee5\u8ba4\u4e3a\u662f\u5bf9\u670d\u52a1\u53d1\u73b0\u7684\u4e00\u4e2a\u62bd\u8c61\uff0cetcdResolver\u7684\u5b9a\u4e49\u5982\u4e0b\uff1a<\/p>\n<p><strong>go-zero\/zrpc\/resolver\/internal\/etcdbuilder.go:3<\/strong><\/p>\n<pre><code class=\"go\">type etcdBuilder struct {\n    discovBuilder\n}<\/code><\/pre>\n<h3>\u670d\u52a1\u6ce8\u518c<\/h3>\n<p>\u5728\u8be6\u7ec6\u770b\u57fa\u4e8eetcd\u7684\u81ea\u5b9a\u4e49Resolver\u903b\u8f91\u4e4b\u524d\uff0c\u6211\u4eec\u5148\u6765\u770b\u4e0bgo-zero\u7684\u670d\u52a1\u6ce8\u518c\uff0c\u5373\u5982\u4f55\u628a\u670d\u52a1\u4fe1\u606f\u6ce8\u518c\u5230etcd\u4e2d\u7684\uff0c\u6211\u4eec\u4ee5 <strong>lebron\/apps\/product\/rpc<\/strong> \u8fd9\u4e2a\u670d\u52a1\u4e3a\u4f8b\u8fdb\u884c\u8bf4\u660e\u3002<\/p>\n<p>\u5728product-rpc\u7684\u914d\u7f6e\u6587\u4ef6\u4e2d\u914d\u7f6e\u4e86Etcd\uff0c\u5305\u62ecetcd\u7684\u5730\u5740\u548c\u670d\u52a1\u5bf9\u5e94\u7684key\uff0c\u5982\u4e0b\uff1a<\/p>\n<p><strong>lebron\/apps\/product\/rpc\/etc\/product.yaml:4<\/strong><\/p>\n<pre><code class=\"yaml\">ListenOn: 127.0.0.1:9002\n\nEtcd:\n  Hosts:\n  - 127.0.0.1:2379\n  Key: product.rpc<\/code><\/pre>\n<p>\u8c03\u7528zrpc.MustNewServer\u521b\u5efagRPC server\uff0c\u63a5\u7740\u4f1a\u8c03\u7528 <strong>NewRpcPubServer<\/strong> \u65b9\u6cd5\uff0c\u5b9a\u4e49\u5982\u4e0b\uff1a<\/p>\n<p><strong>go-zero\/zrpc\/internal\/rpcpubserver.go:17<\/strong><\/p>\n<pre><code class=\"go\">func NewRpcPubServer(etcd discov.EtcdConf, listenOn string, opts ...ServerOption) (Server, error) {\n    registerEtcd := func() error {\n        pubListenOn := figureOutListenOn(listenOn)\n        var pubOpts []discov.PubOption\n        if etcd.HasAccount() {\n            pubOpts = append(pubOpts, discov.WithPubEtcdAccount(etcd.User, etcd.Pass))\n        }\n        if etcd.HasTLS() {\n            pubOpts = append(pubOpts, discov.WithPubEtcdTLS(etcd.CertFile, etcd.CertKeyFile,\n                etcd.CACertFile, etcd.InsecureSkipVerify))\n        }\n        pubClient := discov.NewPublisher(etcd.Hosts, etcd.Key, pubListenOn, pubOpts...)\n        return pubClient.KeepAlive()\n    }\n    server := keepAliveServer{\n        registerEtcd: registerEtcd,\n        Server:       NewRpcServer(listenOn, opts...),\n    }\n\n    return server, nil\n}<\/code><\/pre>\n<p>\u5728\u542f\u52a8Server\u7684\u65f6\u5019\uff0c\u8c03\u7528Start\u65b9\u6cd5\uff0c\u5728Start\u65b9\u6cd5\u4e2d\u4f1a\u8c03\u7528registerEtcd\u8fdb\u884c\u771f\u6b63\u7684\u670d\u52a1\u6ce8\u518c<\/p>\n<p><strong>go-zero\/zrpc\/internal\/rpcpubserver.go:44<\/strong><\/p>\n<pre><code class=\"go\">func (s keepAliveServer) Start(fn RegisterFn) error {\n    if err := s.registerEtcd(); err != nil {\n        return err\n    }\n\n    return s.Server.Start(fn)\n}<\/code><\/pre>\n<p>\u5728KeepAlive\u65b9\u6cd5\u4e2d\uff0c\u9996\u5148\u521b\u5efaetcd\u8fde\u63a5\uff0c\u7136\u540e\u8c03\u7528register\u65b9\u6cd5\u8fdb\u884c\u670d\u52a1\u6ce8\u518c\uff0c\u5728register\u9996\u5148\u521b\u5efa\u79df\u7ea6\uff0c\u79df\u7ea6\u9ed8\u8ba4\u65f6\u95f4\u4e3a10\u79d2\u949f\uff0c\u6700\u540e\u901a\u8fc7Put\u65b9\u6cd5\u8fdb\u884c\u6ce8\u518c\u3002<\/p>\n<p><strong>go-zero\/core\/discov\/publisher.go:125<\/strong><\/p>\n<pre><code class=\"go\">func (p *Publisher) register(client internal.EtcdClient) (clientv3.LeaseID, error) {\n    resp, err := client.Grant(client.Ctx(), TimeToLive)\n    if err != nil {\n        return clientv3.NoLease, err\n    }\n\n    lease := resp.ID\n    if p.id &gt; 0 {\n        p.fullKey = makeEtcdKey(p.key, p.id)\n    } else {\n        p.fullKey = makeEtcdKey(p.key, int64(lease))\n    }\n    _, err = client.Put(client.Ctx(), p.fullKey, p.value, clientv3.WithLease(lease))\n\n    return lease, err\n}<\/code><\/pre>\n<p>key\u7684\u89c4\u5219\u5b9a\u4e49\u5982\u4e0b\uff0c\u5176\u4e2dkey\u4e3a\u5728\u914d\u7f6e\u6587\u4ef6\u4e2d\u914d\u7f6e\u7684Key\uff0c\u8fd9\u91cc\u4e3aproduct.rpc\uff0cid\u4e3a\u79df\u7ea6id\u3002value\u4e3a\u670d\u52a1\u7684\u5730\u5740\u3002<\/p>\n<p><strong>go-zero\/core\/discov\/clients.go:39<\/strong><\/p>\n<pre><code class=\"go\">func makeEtcdKey(key string, id int64) string {\n    return fmt.Sprintf(&quot;%s%c%d&quot;, key, internal.Delimiter, id)\n}<\/code><\/pre>\n<p>\u5728\u4e86\u89e3\u4e86\u670d\u52a1\u6ce8\u518c\u7684\u6d41\u7a0b\u540e\uff0c\u6211\u4eec\u542f\u52a8product-rpc\u670d\u52a1\uff0c\u7136\u540e\u901a\u8fc7\u5982\u4e0b\u547d\u4ee4\u67e5\u770b\u670d\u52a1\u6ce8\u518c\u7684\u5730\u5740\uff1a<\/p>\n<pre><code class=\"shell\">$ etcdctl get product.rpc --prefix\nproduct.rpc\/7587864068988009477\n127.0.0.1:9002<\/code><\/pre>\n<p>\u5728 <strong>KeepAlive<\/strong> \u65b9\u6cd5\u4e2d\uff0c\u670d\u52a1\u6ce8\u518c\u5b8c\u540e\uff0c\u6700\u540e\u4f1a\u8c03\u7528 <strong>keepAliveAsync<\/strong> \u8fdb\u884c\u79df\u7ea6\u7684\u7eed\u671f\uff0c\u4ee5\u4fdd\u8bc1\u670d\u52a1\u4e00\u76f4\u662f\u5b58\u6d3b\u7684\u72b6\u6001\uff0c\u5982\u679c\u670d\u52a1\u5f02\u5e38\u9000\u51fa\u4e86\uff0c\u90a3\u4e48\u4e5f\u5c31\u65e0\u6cd5\u8fdb\u884c\u7eed\u671f\uff0c\u670d\u52a1\u53d1\u73b0\u4e5f\u5c31\u80fd\u81ea\u52a8\u8bc6\u522b\u5230\u8be5\u670d\u52a1\u5f02\u5e38\u4e0b\u7ebf\u4e86\u3002<\/p>\n<h3>\u670d\u52a1\u53d1\u73b0<\/h3>\n<p>\u73b0\u5728\u5df2\u7ecf\u628a\u670d\u52a1\u6ce8\u518c\u5230etcd\u4e2d\u4e86\uff0c\u7ee7\u7eed\u6765\u770b\u5982\u4f55\u53d1\u73b0\u8fd9\u4e9b\u670d\u52a1\u5730\u5740\u3002\u6211\u4eec\u56de\u5230 <strong>etcdBuilder<\/strong> \u7684Build\u65b9\u6cd5\u7684\u5b9e\u73b0\u3002<\/p>\n<p>\u8fd8\u8bb0\u5f97\u7b2c\u4e00\u4e2a\u53c2\u6570target\u662f\u4ec0\u4e48\u5417\uff1f\u5982\u679c\u4e0d\u8bb0\u5f97\u4e86\u53ef\u4ee5\u5f80\u4e0a\u7ffb\u518d\u590d\u4e60\u4e00\u4e0b\uff0c\u9996\u5148\u4ecetarget\u4e2d\u89e3\u6790\u51faetcd\u7684\u5730\u5740\uff0c\u548c\u670d\u52a1\u5bf9\u5e94\u7684key\u3002\u7136\u540e\u521b\u5efaetcd\u8fde\u63a5\uff0c\u63a5\u7740\u6267\u884cupdate\u65b9\u6cd5\uff0c\u5728update\u65b9\u6cd5\u4e2d\uff0c\u901a\u8fc7\u8c03\u7528cc.UpdateState\u65b9\u6cd5\u8fdb\u884c\u670d\u52a1\u72b6\u6001\u7684\u66f4\u65b0\u3002<\/p>\n<p><strong>go-zero\/zrpc\/resolver\/internal\/discovbuilder.go:14<\/strong><\/p>\n<pre><code class=\"go\">func (b *discovBuilder) Build(target resolver.Target, cc resolver.ClientConn, _ resolver.BuildOptions) (\n    resolver.Resolver, error) {\n    hosts := strings.FieldsFunc(targets.GetAuthority(target), func(r rune) bool {\n        return r == EndpointSepChar\n    })\n    sub, err := discov.NewSubscriber(hosts, targets.GetEndpoints(target))\n    if err != nil {\n        return nil, err\n    }\n\n    update := func() {\n        var addrs []resolver.Address\n        for _, val := range subset(sub.Values(), subsetSize) {\n            addrs = append(addrs, resolver.Address{\n                Addr: val,\n            })\n        }\n        if err := cc.UpdateState(resolver.State{\n            Addresses: addrs,\n        }); err != nil {\n            logx.Error(err)\n        }\n    }\n    sub.AddListener(update)\n    update()\n\n    return &amp;nopResolver{cc: cc}, nil\n}<\/code><\/pre>\n<p>\u5982\u679c\u5fd8\u8bb0\u4e86Build\u65b9\u6cd5\u7b2c\u4e8c\u4e2a\u53c2\u6570cc\u7684\u8bdd\uff0c\u53ef\u4ee5\u5f80\u4e0a\u7ffb\u7ffb\u518d\u590d\u4e60\u4e00\u4e0b\uff0ccc.UpdateState\u65b9\u6cd5\u5b9a\u4e49\u5982\u4e0b\uff0c\u6700\u7ec8\u4f1a\u8c03\u7528 <strong>ClientConn<\/strong> \u7684 <strong>updateResolverState<\/strong> \u65b9\u6cd5\uff1a<\/p>\n<p><strong>grpc-go\/resolver_conn_wrapper.go:94<\/strong><\/p>\n<pre><code class=\"go\">func (ccr *ccResolverWrapper) UpdateState(s resolver.State) error {\n    ccr.incomingMu.Lock()\n    defer ccr.incomingMu.Unlock()\n    if ccr.done.HasFired() {\n        return nil\n    }\n    ccr.addChannelzTraceEvent(s)\n    ccr.curState = s\n    if err := ccr.cc.updateResolverState(ccr.curState, nil); err == balancer.ErrBadResolverState {\n        return balancer.ErrBadResolverState\n    }\n    return nil\n}<\/code><\/pre>\n<p>\u7ee7\u7eed\u770b <strong>Build<\/strong> \u65b9\u6cd5\uff0cupdate\u65b9\u6cd5\u4f1a\u88ab\u6dfb\u52a0\u5230\u4e8b\u4ef6\u76d1\u542c\u4e2d\uff0c\u5f53\u6709PUT\u548cDELETE\u4e8b\u4ef6\u89e6\u53d1\uff0c\u90fd\u4f1a\u8c03\u7528update\u65b9\u6cd5\u8fdb\u884c\u670d\u52a1\u72b6\u6001\u7684\u66f4\u65b0\uff0c\u4e8b\u4ef6\u76d1\u542c\u662f\u901a\u8fc7etcd\u7684Watch\u673a\u5236\u5b9e\u73b0\uff0c\u4ee3\u7801\u5982\u4e0b\uff1a<\/p>\n<p><strong>go-zero\/core\/discov\/internal\/registry.go:295<\/strong><\/p>\n<pre><code class=\"go\">func (c *cluster) watchStream(cli EtcdClient, key string) bool {\n    rch := cli.Watch(clientv3.WithRequireLeader(c.context(cli)), makeKeyPrefix(key), clientv3.WithPrefix())\n    for {\n        select {\n        case wresp, ok := &lt;-rch:\n            if !ok {\n                logx.Error(&quot;etcd monitor chan has been closed&quot;)\n                return false\n            }\n            if wresp.Canceled {\n                logx.Errorf(&quot;etcd monitor chan has been canceled, error: %v&quot;, wresp.Err())\n                return false\n            }\n            if wresp.Err() != nil {\n                logx.Error(fmt.Sprintf(&quot;etcd monitor chan error: %v&quot;, wresp.Err()))\n                return false\n            }\n\n            c.handleWatchEvents(key, wresp.Events)\n        case &lt;-c.done:\n            return true\n        }\n    }\n}<\/code><\/pre>\n<p>\u5f53\u6709\u4e8b\u4ef6\u89e6\u53d1\u7684\u65f6\u5019\uff0c\u4f1a\u8c03\u7528\u4e8b\u4ef6\u5904\u7406\u51fd\u6570 <strong>handleWatchEvents<\/strong> \uff0c\u6700\u7ec8\u4f1a\u8c03\u7528 <strong>Build<\/strong> \u65b9\u6cd5\u4e2d\u5b9a\u4e49\u7684update\u8fdb\u884c\u670d\u52a1\u72b6\u6001\u7684\u66f4\u65b0\uff1a<\/p>\n<p><strong>go-zero\/core\/discov\/internal\/registry.go:172<\/strong><\/p>\n<pre><code class=\"go\">func (c *cluster) handleWhandleWatchEventsatchEvents(key string, events []*clientv3.Event) {\n    c.lock.Lock()\n    listeners := append([]UpdateListener(nil), c.listeners[key]...)\n    c.lock.Unlock()\n\n    for _, ev := range events {\n        switch ev.Type {\n        case clientv3.EventTypePut:\n            c.lock.Lock()\n            if vals, ok := c.values[key]; ok {\n                vals[string(ev.Kv.Key)] = string(ev.Kv.Value)\n            } else {\n                c.values[key] = map[string]string{string(ev.Kv.Key): string(ev.Kv.Value)}\n            }\n            c.lock.Unlock()\n            for _, l := range listeners {\n                l.OnAdd(KV{\n                    Key: string(ev.Kv.Key),\n                    Val: string(ev.Kv.Value),\n                })\n            }\n        case clientv3.EventTypeDelete:\n            c.lock.Lock()\n            if vals, ok := c.values[key]; ok {\n                delete(vals, string(ev.Kv.Key))\n            }\n            c.lock.Unlock()\n            for _, l := range listeners {\n                l.OnDelete(KV{\n                    Key: string(ev.Kv.Key),\n                    Val: string(ev.Kv.Value),\n                })\n            }\n        default:\n            logx.Errorf(&quot;Unknown event type: %v&quot;, ev.Type)\n        }\n    }\n}<\/code><\/pre>\n<p>\u7b2c\u4e00\u6b21\u4f1a\u8c03\u7528 <strong>load<\/strong> \u65b9\u6cd5\uff0c\u83b7\u53d6key\u5bf9\u5e94\u7684\u670d\u52a1\u5217\u8868\uff0c\u901a\u8fc7etcd\u524d\u7f00\u5339\u914d\u7684\u65b9\u5f0f\u83b7\u53d6\uff0c\u83b7\u53d6\u65b9\u5f0f\u5982\u4e0b\uff1a<\/p>\n<pre><code class=\"go\">func (c *cluster) load(cli EtcdClient, key string) {\n    var resp *clientv3.GetResponse\n    for {\n        var err error\n        ctx, cancel := context.WithTimeout(c.context(cli), RequestTimeout)\n        resp, err = cli.Get(ctx, makeKeyPrefix(key), clientv3.WithPrefix())\n        cancel()\n        if err == nil {\n            break\n        }\n\n        logx.Error(err)\n        time.Sleep(coolDownInterval)\n    }\n\n    var kvs []KV\n    for _, ev := range resp.Kvs {\n        kvs = append(kvs, KV{\n            Key: string(ev.Key),\n            Val: string(ev.Value),\n        })\n    }\n\n    c.handleChanges(key, kvs)\n}<\/code><\/pre>\n<p>\u83b7\u53d6\u7684\u670d\u52a1\u5730\u5740\u5217\u8868\uff0c\u901a\u8fc7map\u5b58\u50a8\u5728\u672c\u5730\uff0c\u5f53\u6709\u4e8b\u4ef6\u89e6\u53d1\u7684\u65f6\u5019\u901a\u8fc7\u64cd\u4f5cmap\u8fdb\u884c\u670d\u52a1\u5217\u8868\u7684\u66f4\u65b0\uff0c\u8fd9\u91cc\u6709\u4e2a\u9690\u85cf\u7684\u8bbe\u8ba1\u8003\u8651\u662f\u5f53 etcd \u8fde\u4e0d\u4e0a\u6216\u8005\u51fa\u73b0\u6545\u969c\u65f6\uff0c\u5185\u5b58\u91cc\u7684\u670d\u52a1\u5730\u5740\u5217\u8868\u4e0d\u4f1a\u88ab\u66f4\u65b0\uff0c\u4fdd\u969c\u4e86\u5f53 etcd \u6709\u95ee\u9898\u65f6\uff0c\u670d\u52a1\u53d1\u73b0\u4f9d\u7136\u53ef\u4ee5\u5de5\u4f5c\uff0c\u4fdd\u969c\u670d\u52a1\u7ee7\u7eed\u6b63\u5e38\u8fd0\u884c\u3002\u903b\u8f91\u76f8\u5bf9\u6bd4\u8f83\u76f4\u89c2\uff0c\u8fd9\u91cc\u5c31\u4e0d\u518d\u8d58\u8ff0\uff0c\u4ee3\u7801\u903b\u8f91\u5728 <strong>go-zero\/core\/discov\/subscriber.go:76<\/strong> \uff0c\u4e0b\u9762\u662fgo-zero\u670d\u52a1\u53d1\u73b0\u7684\u65f6\u5e8f\u56fe<\/p>\n<p><img decoding=\"async\" referrerpolicy=\"no-referrer\" src=\"https:\/\/segmentfault.com\/img\/remote\/1460000042233346\" alt=\"\" title=\"\" loading=\"lazy\"><\/p>\n<h2>\u7ed3\u675f\u8bed<\/h2>\n<p>\u5230\u8fd9\u91cc\u670d\u52a1\u53d1\u73b0\u76f8\u5173\u7684\u5185\u5bb9\u5df2\u7ecf\u8bb2\u5b8c\u4e86\uff0c\u5185\u5bb9\u8fd8\u662f\u6709\u70b9\u591a\u7684\uff0c\u7279\u522b\u662f\u4ee3\u7801\u90e8\u5206\u9700\u8981\u53cd\u590d\u4ed4\u7ec6\u9605\u8bfb\u624d\u80fd\u52a0\u6df1\u7406\u89e3\u3002<\/p>\n<p>\u6211\u4eec\u4e00\u8d77\u6765\u7b80\u5355\u56de\u987e\u4e0b\u672c\u7bc7\u7684\u5185\u5bb9\uff1a<\/p>\n<ul>\n<li>\u9996\u5148\u4ecb\u7ecd\u4e86\u670d\u52a1\u53d1\u73b0\u7684\u6982\u5ff5\uff0c\u4ee5\u53ca\u670d\u52a1\u53d1\u73b0\u9700\u8981\u89e3\u51b3\u54ea\u4e9b\u95ee\u9898<\/li>\n<li>\u670d\u52a1\u53d1\u73b0\u7684\u4e24\u79cd\u6a21\u5f0f\uff0c\u5206\u522b\u662f\u670d\u52a1\u7aef\u53d1\u73b0\u6a21\u5f0f\u548c\u5ba2\u6237\u7aef\u53d1\u73b0\u6a21\u5f0f<\/li>\n<li>\u63a5\u7740\u4e00\u8d77\u5b66\u4e60\u4e86gRPC\u63d0\u4f9b\u7684\u6ce8\u518cResolver\u7684\u80fd\u529b\uff0c\u901a\u8fc7\u6ce8\u518cResolver\u6765\u5b9e\u73b0\u81ea\u5b9a\u4e49\u7684\u670d\u52a1\u53d1\u73b0\u529f\u80fd\uff0c\u4ee5\u53cagRPC\u5185\u90e8\u662f\u5982\u4f55\u5bfb\u627e\u5230\u81ea\u5b9a\u4e49\u7684Resolver\u548c\u89e6\u53d1\u8c03\u7528\u81ea\u5b9a\u4e49Resolver\u7684\u903b\u8f91<\/li>\n<li>\n<p>\u6700\u540e\u5b66\u4e60\u4e86go-zero\u4e2d\u670d\u52a1\u53d1\u73b0\u7684\u5b9e\u73b0\u539f\u7406\uff0c<\/p>\n<ul>\n<li>\u5148\u662f\u4ecb\u7ecd\u4e86go-zero\u7684\u670d\u52a1\u6ce8\u518c\u6d41\u7a0b\uff0c\u6f14\u793a\u4e86\u6700\u7ec8\u6ce8\u518c\u7684\u6548\u679c<\/li>\n<li>\u63a5\u7740\u4ece\u81ea\u5b9a\u4e49Resolver\u7684Build\u65b9\u6cd5\u51fa\u53d1\uff0c\u4e86\u89e3\u5230\u5148\u662f\u901a\u8fc7\u524d\u7f00\u5339\u914d\u7684\u65b9\u5f0f\u83b7\u53d6\u5bf9\u5e94\u7684\u670d\u52a1\u5217\u8868\u5b58\u5728\u672c\u5730\uff0c\u7136\u540e\u8c03\u7528UpdateState\u65b9\u6cd5\u66f4\u65b0\u670d\u52a1\u72b6\u6001<\/li>\n<li>\u901a\u8fc7Watch\u7684\u65b9\u5f0f\u76d1\u542c\u670d\u52a1\u72b6\u6001\u7684\u53d8\u5316\uff0c\u76d1\u542c\u5230\u53d8\u5316\u540e\u4f1a\u89e6\u53d1\u8c03\u7528update\u65b9\u6cd5\u66f4\u65b0\u672c\u5730\u7684\u670d\u52a1\u5217\u8868\u548c\u8c03\u7528UpdateState\u66f4\u65b0\u670d\u52a1\u7684\u72b6\u6001\u3002<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>\u670d\u52a1\u53d1\u73b0\u662f\u7406\u89e3\u5fae\u670d\u52a1\u67b6\u6784\u7684\u57fa\u7840\uff0c\u5e0c\u671b\u5927\u5bb6\u80fd\u4ed4\u7ec6\u7684\u9605\u8bfb\u672c\u6587\uff0c\u5982\u679c\u6709\u7591\u95ee\u53ef\u4ee5\u968f\u65f6\u627e\u6211\u8ba8\u8bba\uff0c\u5728\u793e\u533a\u7fa4\u4e2d\u53ef\u4ee5\u641c\u7d22dawn_zhou\u627e\u5230\u6211\u3002<\/p>\n<p>\u901a\u8fc7\u670d\u52a1\u53d1\u73b0\u83b7\u53d6\u5230\u670d\u52a1\u5217\u8868\u540e\uff0c\u63a5\u7740\u5c31\u4f1a\u901a\u8fc7Invoke\u65b9\u6cd5\u8fdb\u884c\u670d\u52a1\u8c03\u7528\uff0c\u5728\u670d\u52a1\u8c03\u7528\u7684\u65f6\u5019\u5c31\u6d89\u53ca\u5230\u8d1f\u8f7d\u5747\u8861\uff0c\u901a\u8fc7\u8d1f\u8f7d\u5747\u8861\u9009\u62e9\u4e00\u4e2a\u5408\u9002\u7684\u8282\u70b9\u53d1\u8d77\u8bf7\u6c42\u3002\u8d1f\u8f7d\u5747\u8861\u662f\u4e0b\u4e00\u7bc7\u6587\u7ae0\u8981\u8bb2\u7684\u5185\u5bb9\uff0c\u656c\u8bf7\u671f\u5f85\u3002<\/p>\n<p>\u5e0c\u671b\u672c\u7bc7\u6587\u7ae0\u5bf9\u4f60\u6709\u6240\u5e2e\u52a9\uff0c\u4f60\u7684\u70b9\u8d5e\u662f\u4f5c\u8005\u6301\u7eed\u8f93\u51fa\u7684\u6700\u5927\u52a8\u529b\u3002<\/p>\n<h2>\u9879\u76ee\u5730\u5740<\/h2>\n<p><a href=\"https:\/\/link.segmentfault.com\/?enc=PlYAjqeorHU%2FFZncw6BKOw%3D%3D.IXHVeT%2FtF10IghXbSVduA3Id5zFFvtIhUn7AWwrXub%2FtXzwsOhL9H35DOSD3KF5z\" rel=\"nofollow\">https:\/\/github.com\/zeromicro\/go-zero<\/a><\/p>\n<p>\u6b22\u8fce\u4f7f\u7528 <code>go-zero<\/code> \u5e76 <strong>star<\/strong> \u652f\u6301\u6211\u4eec\uff01<\/p>\n<h2>\u5fae\u4fe1\u4ea4\u6d41\u7fa4<\/h2>\n<p>\u5173\u6ce8\u300e<strong>\u5fae\u670d\u52a1\u5b9e\u8df5<\/strong>\u300f\u516c\u4f17\u53f7\u5e76\u70b9\u51fb <strong>\u4ea4\u6d41\u7fa4<\/strong> \u83b7\u53d6\u793e\u533a\u7fa4\u4e8c\u7ef4\u7801\u3002<\/p>\n<p><a href=\"https:\/\/segmentfault.com\/a\/1190000042233340\">https:\/\/segmentfault.com\/a\/1190000042233340<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u5728\u5fae\u670d\u52a1\u67b6\u6784\u4e2d\uff0c\u6709\u8bb8\u591a\u7ed5\u4e0d\u5f00\u7684\u6280\u672f\u8bdd\u9898\u3002\u6bd4\u5982\u670d\u52a1\u53d1\u73b0\u3001\u8d1f\u8f7d\u5747\u8861\u3001\u6307\u6807\u76d1\u63a7\u3001\u94fe\u8def\u8ffd\u8e2a\uff0c\u4ee5\u53ca\u670d\u52a1\u6cbb\u7406\u76f8\u5173\u7684\u8d85\u65f6\u63a7\u5236 [&hellip;]<\/p>\n","protected":false},"author":0,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[],"tags":[],"class_list":["post-10006047","post","type-post","status-publish","format-standard","hentry"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/posts\/10006047","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/types\/post"}],"replies":[{"embeddable":true,"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=10006047"}],"version-history":[{"count":0,"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/posts\/10006047\/revisions"}],"wp:attachment":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=10006047"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=10006047"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=10006047"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}