<?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 </title>
    <link>https://note.lican.site/tags/grpc/</link>
    <description>Recent content in Grpc on </description>
    <generator>Hugo</generator>
    <language>en</language>
    <copyright>© lican.asia All rights reserved</copyright>
    <lastBuildDate>Sat, 20 Oct 2018 12:00:00 +0000</lastBuildDate>
    <atom:link href="https://note.lican.site/tags/grpc/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>「连载十」分布式链路追踪 gRPC &#43; Opentracing &#43; Zipkin</title>
      <link>https://note.lican.site/posts/posts/go/grpc/2018-10-20-zipkin/</link>
      <pubDate>Sat, 20 Oct 2018 12:00:00 +0000</pubDate>
      <guid>https://note.lican.site/posts/posts/go/grpc/2018-10-20-zipkin/</guid>
      <description>&lt;p&gt;在实际应用中，你做了那么多 Server 端，写了 N 个 RPC 方法。想看看方法的指标，却无处下手？&lt;/p&gt;&#xA;&lt;p&gt;本文将通过 gRPC + Opentracing + Zipkin 搭建一个&lt;strong&gt;分布式链路追踪系统&lt;/strong&gt;来实现查看整个系统的链路、性能等指标。&lt;/p&gt;&#xA;&lt;h2 id=&#34;opentracing&#34;&gt;Opentracing&lt;/h2&gt;&#xA;&lt;h3 id=&#34;是什么&#34;&gt;是什么&lt;/h3&gt;&#xA;&lt;p&gt;OpenTracing 通过提供平台无关、厂商无关的API，使得开发人员能够方便的添加（或更换）追踪系统的实现&lt;/p&gt;&#xA;&lt;p&gt;不过 OpenTracing 并不是标准。因为 CNCF 不是官方标准机构，但是它的目标是致力为分布式追踪创建更标准的 API 和工具&lt;/p&gt;&#xA;&lt;h3 id=&#34;名词解释&#34;&gt;名词解释&lt;/h3&gt;&#xA;&lt;h4 id=&#34;trace&#34;&gt;Trace&lt;/h4&gt;&#xA;&lt;p&gt;一个 trace 代表了一个事务或者流程在（分布式）系统中的执行过程&lt;/p&gt;&#xA;&lt;h4 id=&#34;span&#34;&gt;Span&lt;/h4&gt;&#xA;&lt;p&gt;一个 span 代表在分布式系统中完成的单个工作单元。也包含其他 span 的 “引用”，这允许将多个 spans 组合成一个完整的 Trace&lt;/p&gt;&#xA;&lt;p&gt;每个 span 根据 OpenTracing 规范封装以下内容：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;操作名称&lt;/li&gt;&#xA;&lt;li&gt;开始时间和结束时间&lt;/li&gt;&#xA;&lt;li&gt;key:value span Tags&lt;/li&gt;&#xA;&lt;li&gt;key:value span Logs&lt;/li&gt;&#xA;&lt;li&gt;SpanContext&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h4 id=&#34;tags&#34;&gt;Tags&lt;/h4&gt;&#xA;&lt;p&gt;Span tags（跨度标签）可以理解为用户自定义的 Span 注释。便于查询、过滤和理解跟踪数据&lt;/p&gt;&#xA;&lt;h4 id=&#34;logs&#34;&gt;Logs&lt;/h4&gt;&#xA;&lt;p&gt;Span logs（跨度日志）可以记录 Span 内特定时间或事件的日志信息。主要用于捕获特定 Span 的日志信息以及应用程序本身的其他调试或信息输出&lt;/p&gt;&#xA;&lt;h4 id=&#34;spancontext&#34;&gt;SpanContext&lt;/h4&gt;&#xA;&lt;p&gt;SpanContext 代表跨越进程边界，传递到子级 Span 的状态。常在追踪示意图中创建上下文时使用&lt;/p&gt;</description>
    </item>
    <item>
      <title>「连载九」gRPC Deadlines</title>
      <link>https://note.lican.site/posts/posts/go/grpc/2018-10-16-deadlines/</link>
      <pubDate>Tue, 16 Oct 2018 12:00:00 +0000</pubDate>
      <guid>https://note.lican.site/posts/posts/go/grpc/2018-10-16-deadlines/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;&#xA;&lt;p&gt;在前面的章节中，已经介绍了 gRPC 的基本用法。那你想想，让它这么裸跑真的没问题吗？&lt;/p&gt;&#xA;&lt;p&gt;那么，肯定是有问题了。今天将介绍 gRPC Deadlines 的用法，这一个必备技巧。内容也比较简单&lt;/p&gt;&#xA;&lt;h2 id=&#34;deadlines&#34;&gt;Deadlines&lt;/h2&gt;&#xA;&lt;p&gt;Deadlines 意指截止时间，在 gRPC 中强调 TL;DR（Too long, Don&amp;rsquo;t read）并建议&lt;strong&gt;始终设定截止日期&lt;/strong&gt;，为什么呢？&lt;/p&gt;&#xA;&lt;h3 id=&#34;为什么要设置&#34;&gt;为什么要设置&lt;/h3&gt;&#xA;&lt;p&gt;当未设置 Deadlines 时，将采用默认的 DEADLINE_EXCEEDED（这个时间非常大）&lt;/p&gt;&#xA;&lt;p&gt;如果产生了阻塞等待，就会造成大量正在进行的请求都会被保留，并且所有请求都有可能达到最大超时&lt;/p&gt;&#xA;&lt;p&gt;这会使服务面临资源耗尽的风险，例如内存，这会增加服务的延迟，或者在最坏的情况下可能导致整个进程崩溃&lt;/p&gt;&#xA;&lt;h2 id=&#34;grpc&#34;&gt;gRPC&lt;/h2&gt;&#xA;&lt;h3 id=&#34;client&#34;&gt;Client&lt;/h3&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;func main() {&#xA;    ...&#xA;&#x9;ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Duration(5 * time.Second)))&#xA;&#x9;defer cancel()&#xA;&#xA;&#x9;client := pb.NewSearchServiceClient(conn)&#xA;&#x9;resp, err := client.Search(ctx, &amp;amp;pb.SearchRequest{&#xA;&#x9;&#x9;Request: &amp;#34;gRPC&amp;#34;,&#xA;&#x9;})&#xA;&#x9;if err != nil {&#xA;&#x9;&#x9;statusErr, ok := status.FromError(err)&#xA;&#x9;&#x9;if ok {&#xA;&#x9;&#x9;&#x9;if statusErr.Code() == codes.DeadlineExceeded {&#xA;&#x9;&#x9;&#x9;&#x9;log.Fatalln(&amp;#34;client.Search err: deadline&amp;#34;)&#xA;&#x9;&#x9;&#x9;}&#xA;&#x9;&#x9;}&#xA;&#xA;&#x9;&#x9;log.Fatalf(&amp;#34;client.Search err: %v&amp;#34;, err)&#xA;&#x9;}&#xA;&#xA;&#x9;log.Printf(&amp;#34;resp: %s&amp;#34;, resp.GetResponse())&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&#xA;&lt;li&gt;context.WithDeadline：会返回最终上下文截止时间。第一个形参为父上下文，第二个形参为调整的截止时间。若父级时间早于子级时间，则以父级时间为准，否则以子级时间为最终截止时间&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {&#xA;&#x9;if cur, ok := parent.Deadline(); ok &amp;amp;&amp;amp; cur.Before(d) {&#xA;&#x9;&#x9;// The current deadline is already sooner than the new one.&#xA;&#x9;&#x9;return WithCancel(parent)&#xA;&#x9;}&#xA;&#x9;c := &amp;amp;timerCtx{&#xA;&#x9;&#x9;cancelCtx: newCancelCtx(parent),&#xA;&#x9;&#x9;deadline:  d,&#xA;&#x9;}&#xA;&#x9;propagateCancel(parent, c)&#xA;&#x9;dur := time.Until(d)&#xA;&#x9;if dur &amp;lt;= 0 {&#xA;&#x9;&#x9;c.cancel(true, DeadlineExceeded) // deadline has already passed&#xA;&#x9;&#x9;return c, func() { c.cancel(true, Canceled) }&#xA;&#x9;}&#xA;&#x9;c.mu.Lock()&#xA;&#x9;defer c.mu.Unlock()&#xA;&#x9;if c.err == nil {&#xA;&#x9;&#x9;c.timer = time.AfterFunc(dur, func() {&#xA;&#x9;&#x9;&#x9;c.cancel(true, DeadlineExceeded)&#xA;&#x9;&#x9;})&#xA;&#x9;}&#xA;&#x9;return c, func() { c.cancel(true, Canceled) }&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&#xA;&lt;li&gt;context.WithTimeout：很常见的另外一个方法，是便捷操作。实际上是对于 WithDeadline 的封装&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {&#xA;&#x9;return WithDeadline(parent, time.Now().Add(timeout))&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&#xA;&lt;li&gt;status.FromError：返回 GRPCStatus 的具体错误码，若为非法，则直接返回 &lt;code&gt;codes.Unknown&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;server&#34;&gt;Server&lt;/h3&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;type SearchService struct{}&#xA;&#xA;func (s *SearchService) Search(ctx context.Context, r *pb.SearchRequest) (*pb.SearchResponse, error) {&#xA;&#x9;for i := 0; i &amp;lt; 5; i++  {&#xA;&#x9;&#x9;if ctx.Err() == context.Canceled {&#xA;&#x9;&#x9;&#x9;return nil, status.Errorf(codes.Canceled, &amp;#34;SearchService.Search canceled&amp;#34;)&#xA;&#x9;&#x9;}&#xA;&#xA;&#x9;&#x9;time.Sleep(1 * time.Second)&#xA;&#x9;}&#xA;&#xA;&#x9;return &amp;amp;pb.SearchResponse{Response: r.GetRequest() + &amp;#34; Server&amp;#34;}, nil&#xA;}&#xA;&#xA;func main() {&#xA;&#x9;...&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;而在 Server 端，由于 Client 已经设置了截止时间。Server 势必要去检测它&lt;/p&gt;</description>
    </item>
    <item>
      <title>「连载八」对 RPC 方法做自定义认证</title>
      <link>https://note.lican.site/posts/posts/go/grpc/2018-10-14-per-rpc-credentials/</link>
      <pubDate>Sun, 14 Oct 2018 12:00:00 +0000</pubDate>
      <guid>https://note.lican.site/posts/posts/go/grpc/2018-10-14-per-rpc-credentials/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;&#xA;&lt;p&gt;在前面的章节中，我们介绍了两种（证书算一种）可全局认证的方法：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/EDDYCJY/blog/blob/master/grpc/grpc-tls.md&#34;&gt;TLS 证书认证&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/EDDYCJY/blog/blob/master/grpc/ca-tls.md&#34;&gt;基于 CA 的 TLS 证书认证&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/EDDYCJY/blog/blob/master/grpc/interceptor.md&#34;&gt;Unary and Stream interceptor&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;而在实际需求中，常常会对某些模块的 RPC 方法做特殊认证或校验。今天将会讲解、实现这块的功能点&lt;/p&gt;&#xA;&lt;h2 id=&#34;课前知识&#34;&gt;课前知识&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;type PerRPCCredentials interface {&#xA;    GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)&#xA;    RequireTransportSecurity() bool&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在 gRPC 中默认定义了 PerRPCCredentials，它就是本章节的主角，是 gRPC 默认提供用于自定义认证的接口，它的作用是将所需的安全认证信息添加到每个 RPC 方法的上下文中。其包含 2 个方法：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;GetRequestMetadata：获取当前请求认证所需的元数据（metadata）&lt;/li&gt;&#xA;&lt;li&gt;RequireTransportSecurity：是否需要基于 TLS 认证进行安全传输&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;目录结构&#34;&gt;目录结构&lt;/h2&gt;&#xA;&lt;p&gt;新建 simple_token_server/server.go 和 simple_token_client/client.go，目录结构如下：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;go-grpc-example&#xA;├── client&#xA;│   ├── simple_client&#xA;│   ├── simple_http_client&#xA;│   ├── simple_token_client&#xA;│   └── stream_client&#xA;├── conf&#xA;├── pkg&#xA;├── proto&#xA;├── server&#xA;│   ├── simple_http_server&#xA;│   ├── simple_server&#xA;│   ├── simple_token_server&#xA;│   └── stream_server&#xA;└── vendor&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;grpc&#34;&gt;gRPC&lt;/h2&gt;&#xA;&lt;h3 id=&#34;client&#34;&gt;Client&lt;/h3&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;package main&#xA;&#xA;import (&#xA;&#x9;&amp;#34;context&amp;#34;&#xA;&#x9;&amp;#34;log&amp;#34;&#xA;&#xA;&#x9;&amp;#34;google.golang.org/grpc&amp;#34;&#xA;&#xA;&#x9;&amp;#34;github.com/EDDYCJY/go-grpc-example/pkg/gtls&amp;#34;&#xA;&#x9;pb &amp;#34;github.com/EDDYCJY/go-grpc-example/proto&amp;#34;&#xA;)&#xA;&#xA;const PORT = &amp;#34;9004&amp;#34;&#xA;&#xA;type Auth struct {&#xA;&#x9;AppKey    string&#xA;&#x9;AppSecret string&#xA;}&#xA;&#xA;func (a *Auth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {&#xA;&#x9;return map[string]string{&amp;#34;app_key&amp;#34;: a.AppKey, &amp;#34;app_secret&amp;#34;: a.AppSecret}, nil&#xA;}&#xA;&#xA;func (a *Auth) RequireTransportSecurity() bool {&#xA;&#x9;return true&#xA;}&#xA;&#xA;func main() {&#xA;&#x9;tlsClient := gtls.Client{&#xA;&#x9;&#x9;ServerName: &amp;#34;go-grpc-example&amp;#34;,&#xA;&#x9;&#x9;CertFile:   &amp;#34;../../conf/server/server.pem&amp;#34;,&#xA;&#x9;}&#xA;&#x9;c, err := tlsClient.GetTLSCredentials()&#xA;&#x9;if err != nil {&#xA;&#x9;&#x9;log.Fatalf(&amp;#34;tlsClient.GetTLSCredentials err: %v&amp;#34;, err)&#xA;&#x9;}&#xA;&#xA;&#x9;auth := Auth{&#xA;&#x9;&#x9;AppKey:    &amp;#34;eddycjy&amp;#34;,&#xA;&#x9;&#x9;AppSecret: &amp;#34;20181005&amp;#34;,&#xA;&#x9;}&#xA;&#x9;conn, err := grpc.Dial(&amp;#34;:&amp;#34;+PORT, grpc.WithTransportCredentials(c), grpc.WithPerRPCCredentials(&amp;amp;auth))&#xA;&#x9;...&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在 Client 端，重点实现 &lt;code&gt;type PerRPCCredentials interface&lt;/code&gt; 所需的方法，关注两点即可：&lt;/p&gt;</description>
    </item>
    <item>
      <title>「连载七」让你的服务同时提供 HTTP 接口</title>
      <link>https://note.lican.site/posts/posts/go/grpc/2018-10-12-grpc-http/</link>
      <pubDate>Fri, 12 Oct 2018 12:00:00 +0000</pubDate>
      <guid>https://note.lican.site/posts/posts/go/grpc/2018-10-12-grpc-http/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;接口需要提供给其他业务组访问，但是 RPC 协议不同无法内调，对方问能否走 HTTP 接口，怎么办？&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;微信（公众号、小程序）等第三方回调接口只支持 HTTP 接口，怎么办&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;我相信你在实际工作中都会遇到如上问题，在 gRPC 中都是有解决方案的，本章节将会进行介绍 🤔&lt;/p&gt;&#xA;&lt;h2 id=&#34;为什么可以同时提供-http-接口&#34;&gt;为什么可以同时提供 HTTP 接口&lt;/h2&gt;&#xA;&lt;p&gt;关键一点，gRPC 的协议是基于 HTTP/2 的，因此应用程序能够在单个 TCP 端口上提供 HTTP/1.1 和 gRPC 接口服务（两种不同的流量）&lt;/p&gt;&#xA;&lt;h2 id=&#34;怎么同时提供-http-接口&#34;&gt;怎么同时提供 HTTP 接口&lt;/h2&gt;&#xA;&lt;h3 id=&#34;检测协议&#34;&gt;检测协议&lt;/h3&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;if r.ProtoMajor == 2 &amp;amp;&amp;amp; strings.Contains(r.Header.Get(&amp;#34;Content-Type&amp;#34;), &amp;#34;application/grpc&amp;#34;) {&#xA;    server.ServeHTTP(w, r)&#xA;} else {&#xA;    mux.ServeHTTP(w, r)&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;流程&#34;&gt;流程&lt;/h3&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;检测请求协议是否为 HTTP/2&lt;/li&gt;&#xA;&lt;li&gt;判断 Content-Type 是否为 application/grpc（gRPC 的默认标识位）&lt;/li&gt;&#xA;&lt;li&gt;根据协议的不同转发到不同的服务处理&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;grpc&#34;&gt;gRPC&lt;/h2&gt;&#xA;&lt;h3 id=&#34;tls&#34;&gt;TLS&lt;/h3&gt;&#xA;&lt;p&gt;在前面的章节，为了便于展示因此没有简单封装&lt;/p&gt;&#xA;&lt;p&gt;在本节需复用代码，重新封装了，可详见：&lt;a href=&#34;https://github.com/EDDYCJY/go-grpc-example/tree/master/pkg/gtls&#34;&gt;go-grpc-example&lt;/a&gt;&lt;/p&gt;&#xA;&lt;h3 id=&#34;目录结构&#34;&gt;目录结构&lt;/h3&gt;&#xA;&lt;p&gt;新建 simple_http_client、simple_http_server 目录，目录结构如下：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;go-grpc-example&#xA;├── client&#xA;│   ├── simple_client&#xA;│   ├── simple_http_client&#xA;│   └── stream_client&#xA;├── conf&#xA;├── pkg&#xA;│   └── gtls&#xA;├── proto&#xA;├── server&#xA;│   ├── simple_http_server&#xA;│   ├── simple_server&#xA;│   └── stream_server&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;server&#34;&gt;Server&lt;/h3&gt;&#xA;&lt;p&gt;在 simple_http_server 目录下新建 server.go，写入文件内容：&lt;/p&gt;</description>
    </item>
    <item>
      <title>「连载六」Unary and Stream interceptor</title>
      <link>https://note.lican.site/posts/posts/go/grpc/2018-10-10-interceptor/</link>
      <pubDate>Wed, 10 Oct 2018 12:00:00 +0000</pubDate>
      <guid>https://note.lican.site/posts/posts/go/grpc/2018-10-10-interceptor/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;&#xA;&lt;p&gt;我想在每个 RPC 方法的前或后做某些事情，怎么做？&lt;/p&gt;&#xA;&lt;p&gt;本章节将要介绍的拦截器（interceptor），就能帮你在合适的地方实现这些功能。&lt;/p&gt;&#xA;&lt;h2 id=&#34;有几种方法&#34;&gt;有几种方法&lt;/h2&gt;&#xA;&lt;p&gt;在 gRPC 中，大类可分为两种 RPC 方法，与拦截器的对应关系是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;普通方法：一元拦截器（grpc.UnaryInterceptor）&lt;/li&gt;&#xA;&lt;li&gt;流方法：流拦截器（grpc.StreamInterceptor）&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;看一看&#34;&gt;看一看&lt;/h2&gt;&#xA;&lt;h3 id=&#34;grpcunaryinterceptor&#34;&gt;grpc.UnaryInterceptor&lt;/h3&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;func UnaryInterceptor(i UnaryServerInterceptor) ServerOption {&#xA;&#x9;return func(o *options) {&#xA;&#x9;&#x9;if o.unaryInt != nil {&#xA;&#x9;&#x9;&#x9;panic(&amp;#34;The unary server interceptor was already set and may not be reset.&amp;#34;)&#xA;&#x9;&#x9;}&#xA;&#x9;&#x9;o.unaryInt = i&#xA;&#x9;}&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;函数原型：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;type UnaryServerInterceptor func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error)&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;通过查看源码可得知，要完成一个拦截器需要实现 &lt;code&gt;UnaryServerInterceptor&lt;/code&gt; 方法。形参如下：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ctx context.Context：请求上下文&lt;/li&gt;&#xA;&lt;li&gt;req interface{}：RPC 方法的请求参数&lt;/li&gt;&#xA;&lt;li&gt;info *UnaryServerInfo：RPC 方法的所有信息&lt;/li&gt;&#xA;&lt;li&gt;handler UnaryHandler：RPC 方法本身&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;grpcstreaminterceptor&#34;&gt;grpc.StreamInterceptor&lt;/h3&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;func StreamInterceptor(i StreamServerInterceptor) ServerOption&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;函数原型：&lt;/p&gt;</description>
    </item>
    <item>
      <title>「连载五」基于 CA 的 TLS 证书认证</title>
      <link>https://note.lican.site/posts/posts/go/grpc/2018-10-08-ca-tls/</link>
      <pubDate>Mon, 08 Oct 2018 12:00:00 +0000</pubDate>
      <guid>https://note.lican.site/posts/posts/go/grpc/2018-10-08-ca-tls/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;&#xA;&lt;p&gt;在上一章节中，我们提出了一个问题。就是如何保证证书的可靠性和有效性？你如何确定你 Server、Client 的证书是对的呢？&lt;/p&gt;&#xA;&lt;h2 id=&#34;ca&#34;&gt;CA&lt;/h2&gt;&#xA;&lt;p&gt;为了保证证书的可靠性和有效性，在这里可引入 CA 颁发的根证书的概念。其遵守 X.509 标准&lt;/p&gt;&#xA;&lt;h3 id=&#34;根证书&#34;&gt;根证书&lt;/h3&gt;&#xA;&lt;p&gt;根证书（root certificate）是属于根证书颁发机构（CA）的公钥证书。我们可以通过验证 CA 的签名从而信任 CA ，任何人都可以得到 CA 的证书（含公钥），用以验证它所签发的证书（客户端、服务端）&lt;/p&gt;&#xA;&lt;p&gt;它包含的文件如下：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;公钥&lt;/li&gt;&#xA;&lt;li&gt;密钥&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;生成-key&#34;&gt;生成 Key&lt;/h3&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;openssl genrsa -out ca.key 2048&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;生成密钥&#34;&gt;生成密钥&lt;/h3&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;openssl req -new -x509 -days 7200 -key ca.key -out ca.pem&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;h4 id=&#34;填写信息&#34;&gt;填写信息&lt;/h4&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Country Name (2 letter code) []:&#xA;State or Province Name (full name) []:&#xA;Locality Name (eg, city) []:&#xA;Organization Name (eg, company) []:&#xA;Organizational Unit Name (eg, section) []:&#xA;Common Name (eg, fully qualified host name) []:go-grpc-example&#xA;Email Address []:&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;server&#34;&gt;Server&lt;/h3&gt;&#xA;&lt;h4 id=&#34;生成-csr&#34;&gt;生成 CSR&lt;/h4&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;openssl req -new -key server.key -out server.csr&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;h5 id=&#34;填写信息-1&#34;&gt;填写信息&lt;/h5&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Country Name (2 letter code) []:&#xA;State or Province Name (full name) []:&#xA;Locality Name (eg, city) []:&#xA;Organization Name (eg, company) []:&#xA;Organizational Unit Name (eg, section) []:&#xA;Common Name (eg, fully qualified host name) []:go-grpc-example&#xA;Email Address []:&#xA;&#xA;Please enter the following &amp;#39;extra&amp;#39; attributes&#xA;to be sent with your certificate request&#xA;A challenge password []:&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;CSR 是 Cerificate Signing Request 的英文缩写，为证书请求文件。主要作用是 CA 会利用 CSR 文件进行签名使得攻击者无法伪装或篡改原有证书&lt;/p&gt;</description>
    </item>
    <item>
      <title>「连载四」TLS 证书认证</title>
      <link>https://note.lican.site/posts/posts/go/grpc/2018-10-07-grpc-tls/</link>
      <pubDate>Sun, 07 Oct 2018 12:00:00 +0000</pubDate>
      <guid>https://note.lican.site/posts/posts/go/grpc/2018-10-07-grpc-tls/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;&#xA;&lt;p&gt;在前面的章节里，我们介绍了 gRPC 的四种 API 使用方式。是不是很简单呢 😀&lt;/p&gt;&#xA;&lt;p&gt;此时存在一个安全问题，先前的例子中 gRPC Client/Server 都是明文传输的，会不会有被窃听的风险呢？&lt;/p&gt;&#xA;&lt;p&gt;从结论上来讲，是有的。在明文通讯的情况下，你的请求就是裸奔的，有可能被第三方恶意篡改或者伪造为“非法”的数据&lt;/p&gt;&#xA;&lt;h2 id=&#34;抓个包&#34;&gt;抓个包&lt;/h2&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://image.eddycjy.com/15e68df2ba9aa7cace3e26e35c79f200.jpg&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://image.eddycjy.com/ebebd3ea7d306ad2fcd311f1d8b46cc0.jpg&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;嗯，明文传输无误。这是有问题的，接下将改造我们的 gRPC，以便于解决这个问题 😤&lt;/p&gt;&#xA;&lt;h2 id=&#34;证书生成&#34;&gt;证书生成&lt;/h2&gt;&#xA;&lt;h3 id=&#34;私钥&#34;&gt;私钥&lt;/h3&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;openssl ecparam -genkey -name secp384r1 -out server.key&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;自签公钥&#34;&gt;自签公钥&lt;/h3&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;openssl req -new -x509 -sha256 -key server.key -out server.pem -days 3650&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;h4 id=&#34;填写信息&#34;&gt;填写信息&lt;/h4&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Country Name (2 letter code) []:&#xA;State or Province Name (full name) []:&#xA;Locality Name (eg, city) []:&#xA;Organization Name (eg, company) []:&#xA;Organizational Unit Name (eg, section) []:&#xA;Common Name (eg, fully qualified host name) []:go-grpc-example&#xA;Email Address []:&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;生成完毕&#34;&gt;生成完毕&lt;/h3&gt;&#xA;&lt;p&gt;生成证书结束后，将证书相关文件放到 conf/ 下，目录结构：&lt;/p&gt;</description>
    </item>
    <item>
      <title>「连载三」gRPC Streaming, Client and Server</title>
      <link>https://note.lican.site/posts/posts/go/grpc/2018-09-24-stream-client-server/</link>
      <pubDate>Mon, 24 Sep 2018 12:00:00 +0000</pubDate>
      <guid>https://note.lican.site/posts/posts/go/grpc/2018-09-24-stream-client-server/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;&#xA;&lt;p&gt;本章节将介绍 gRPC 的流式，分为三种类型：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Server-side streaming RPC：服务器端流式 RPC&lt;/li&gt;&#xA;&lt;li&gt;Client-side streaming RPC：客户端流式 RPC&lt;/li&gt;&#xA;&lt;li&gt;Bidirectional streaming RPC：双向流式 RPC&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;流&#34;&gt;流&lt;/h2&gt;&#xA;&lt;p&gt;任何技术，因为有痛点，所以才有了存在的必要性。如果您想要了解 gRPC 的流式调用，请继续&lt;/p&gt;&#xA;&lt;h3 id=&#34;图&#34;&gt;图&lt;/h3&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://image.eddycjy.com/8812038d20ffece377c0e4901c9a9231.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;gRPC Streaming 是基于 HTTP/2 的，后续章节再进行详细讲解&lt;/p&gt;&#xA;&lt;h3 id=&#34;为什么不用-simple-rpc&#34;&gt;为什么不用 Simple RPC&lt;/h3&gt;&#xA;&lt;p&gt;流式为什么要存在呢，是 Simple RPC 有什么问题吗？通过模拟业务场景，可得知在使用 Simple RPC 时，有如下问题：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;数据包过大造成的瞬时压力&lt;/li&gt;&#xA;&lt;li&gt;接收数据包时，需要所有数据包都接受成功且正确后，才能够回调响应，进行业务处理（无法客户端边发送，服务端边处理）&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;为什么用-streaming-rpc&#34;&gt;为什么用 Streaming RPC&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;大规模数据包&lt;/li&gt;&#xA;&lt;li&gt;实时场景&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h4 id=&#34;模拟场景&#34;&gt;模拟场景&lt;/h4&gt;&#xA;&lt;p&gt;每天早上 6 点，都有一批百万级别的数据集要同从 A 同步到 B，在同步的时候，会做一系列操作（归档、数据分析、画像、日志等）。这一次性涉及的数据量确实大&lt;/p&gt;&#xA;&lt;p&gt;在同步完成后，也有人马上会去查阅数据，为了新的一天筹备。也符合实时性。&lt;/p&gt;&#xA;&lt;p&gt;两者相较下，这个场景下更适合使用 Streaming RPC&lt;/p&gt;&#xA;&lt;h2 id=&#34;grpc&#34;&gt;gRPC&lt;/h2&gt;&#xA;&lt;p&gt;在讲解具体的 gRPC 流式代码时，会&lt;strong&gt;着重在第一节讲解&lt;/strong&gt;，因为三种模式其实是不同的组合。希望你能够注重理解，举一反三，其实都是一样的知识点 👍&lt;/p&gt;&#xA;&lt;h3 id=&#34;目录结构&#34;&gt;目录结构&lt;/h3&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ tree go-grpc-example&#xA;go-grpc-example&#xA;├── client&#xA;│   ├── simple_client&#xA;│   │   └── client.go&#xA;│   └── stream_client&#xA;│       └── client.go&#xA;├── proto&#xA;│   ├── search.proto&#xA;│   └── stream.proto&#xA;└── server&#xA;    ├── simple_server&#xA;    │   └── server.go&#xA;    └── stream_server&#xA;        └── server.go&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;增加 stream_server、stream_client 存放服务端和客户端文件，proto/stream.proto 用于编写 IDL&lt;/p&gt;</description>
    </item>
    <item>
      <title>「连载二」gRPC Client and Server</title>
      <link>https://note.lican.site/posts/posts/go/grpc/2018-09-23-client-and-server/</link>
      <pubDate>Sun, 23 Sep 2018 12:30:00 +0000</pubDate>
      <guid>https://note.lican.site/posts/posts/go/grpc/2018-09-23-client-and-server/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;&#xA;&lt;p&gt;本章节将使用 Go 来编写 gRPC Server 和 Client，让其互相通讯。在此之上会使用到如下库：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;google.golang.org/grpc&lt;/li&gt;&#xA;&lt;li&gt;github.com/golang/protobuf/protoc-gen-go&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;安装&#34;&gt;安装&lt;/h2&gt;&#xA;&lt;h3 id=&#34;grpc&#34;&gt;gRPC&lt;/h3&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;go get -u google.golang.org/grpc&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;protocol-buffers-v3&#34;&gt;Protocol Buffers v3&lt;/h3&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;wget https://github.com/google/protobuf/releases/download/v3.5.1/protobuf-all-3.5.1.zip&#xA;unzip protobuf-all-3.5.1.zip&#xA;cd protobuf-3.5.1/&#xA;./configure&#xA;make&#xA;make install&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;检查是否安装成功&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;protoc --version&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;若出现以下错误，执行 &lt;code&gt;ldconfig&lt;/code&gt; 命名就能解决这问题&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;protoc: error while loading shared libraries: libprotobuf.so.15: cannot open shared object file: No such file or directory&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;protoc-plugin&#34;&gt;Protoc Plugin&lt;/h3&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;go get -u github.com/golang/protobuf/protoc-gen-go&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;安装环境若有问题，可参考我先前的文章 &lt;a href=&#34;https://segmentfault.com/a/1190000013339403&#34;&gt;《介绍与环境安装》&lt;/a&gt; 内有详细介绍，不再赘述&lt;/p&gt;&#xA;&lt;h2 id=&#34;grpc-1&#34;&gt;gRPC&lt;/h2&gt;&#xA;&lt;p&gt;本小节开始正式编写 gRPC 相关的程序，一起上车吧 😄&lt;/p&gt;&#xA;&lt;h3 id=&#34;图示&#34;&gt;图示&lt;/h3&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://image.eddycjy.com/415d9544fce1e774e1095ab99b6cc015.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;&#xA;&lt;h3 id=&#34;目录结构&#34;&gt;目录结构&lt;/h3&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ tree go-grpc-example&#xA;go-grpc-example&#xA;├── client&#xA;├── proto&#xA;│   └── search.proto&#xA;└── server.go&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;idl&#34;&gt;IDL&lt;/h3&gt;&#xA;&lt;h4 id=&#34;编写&#34;&gt;编写&lt;/h4&gt;&#xA;&lt;p&gt;在 proto 文件夹下的 search.proto 文件中，写入如下内容：&lt;/p&gt;</description>
    </item>
    <item>
      <title>「连载一」gRPC及相关介绍</title>
      <link>https://note.lican.site/posts/posts/go/grpc/2018-09-22-install/</link>
      <pubDate>Sat, 22 Sep 2018 12:00:00 +0000</pubDate>
      <guid>https://note.lican.site/posts/posts/go/grpc/2018-09-22-install/</guid>
      <description>&lt;p&gt;项目地址：https://github.com/EDDYCJY/go-grpc-example&lt;/p&gt;&#xA;&lt;p&gt;作为开篇章，将会介绍 gRPC 相关的一些知识。简单来讲 gRPC 是一个 基于 HTTP/2 协议设计的 RPC 框架，它采用了 Protobuf 作为 IDL&lt;/p&gt;&#xA;&lt;p&gt;你是否有过疑惑，它们都是些什么？本文将会介绍一些常用的知识和概念，更详细的会给出手册地址去深入&lt;/p&gt;&#xA;&lt;h2 id=&#34;一rpc&#34;&gt;一、RPC&lt;/h2&gt;&#xA;&lt;h3 id=&#34;什么是-rpc&#34;&gt;什么是 RPC&lt;/h3&gt;&#xA;&lt;p&gt;RPC 代指远程过程调用（Remote Procedure Call），它的调用包含了传输协议和编码（对象序列号）协议等等。允许运行于一台计算机的程序调用另一台计算机的子程序，而开发人员无需额外地为这个交互作用编程&lt;/p&gt;&#xA;&lt;h4 id=&#34;实际场景&#34;&gt;实际场景：&lt;/h4&gt;&#xA;&lt;p&gt;有两台服务器，分别是 A、B。在 A 上的应用 C 想要调用 B 服务器上的应用 D，它们可以直接本地调用吗？&lt;br&gt;&#xA;答案是不能的，但走 RPC 的话，十分方便。因此常有人称使用 RPC，就跟本地调用一个函数一样简单&lt;/p&gt;&#xA;&lt;h3 id=&#34;rpc-框架&#34;&gt;RPC 框架&lt;/h3&gt;&#xA;&lt;p&gt;我认为，一个完整的 RPC 框架，应包含负载均衡、服务注册和发现、服务治理等功能，并具有可拓展性便于流量监控系统等接入&lt;br&gt;&#xA;那么它才算完整的，当然了。有些较单一的 RPC 框架，通过组合多组件也能达到这个标准&lt;/p&gt;&#xA;&lt;p&gt;你认为呢？&lt;/p&gt;&#xA;&lt;h3 id=&#34;常见-rpc-框架&#34;&gt;常见 RPC 框架&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://grpc.io/&#34;&gt;gRPC&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/apache/thrift&#34;&gt;Thrift&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/smallnest/rpcx&#34;&gt;Rpcx&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/apache/incubator-dubbo&#34;&gt;Dubbo&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;比较一下&#34;&gt;比较一下&lt;/h3&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;多 IDL&lt;/th&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;gRPC&lt;/td&gt;&#xA;          &lt;td&gt;√&lt;/td&gt;&#xA;          &lt;td&gt;×&lt;/td&gt;&#xA;          &lt;td&gt;×&lt;/td&gt;&#xA;          &lt;td&gt;×&lt;/td&gt;&#xA;          &lt;td&gt;×&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Thrift&lt;/td&gt;&#xA;          &lt;td&gt;√&lt;/td&gt;&#xA;          &lt;td&gt;×&lt;/td&gt;&#xA;          &lt;td&gt;×&lt;/td&gt;&#xA;          &lt;td&gt;×&lt;/td&gt;&#xA;          &lt;td&gt;×&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Rpcx&lt;/td&gt;&#xA;          &lt;td&gt;×&lt;/td&gt;&#xA;          &lt;td&gt;√&lt;/td&gt;&#xA;          &lt;td&gt;√&lt;/td&gt;&#xA;          &lt;td&gt;√&lt;/td&gt;&#xA;          &lt;td&gt;√&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Dubbo&lt;/td&gt;&#xA;          &lt;td&gt;×&lt;/td&gt;&#xA;          &lt;td&gt;√&lt;/td&gt;&#xA;          &lt;td&gt;√&lt;/td&gt;&#xA;          &lt;td&gt;√&lt;/td&gt;&#xA;          &lt;td&gt;√&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h3 id=&#34;为什么要-rpc&#34;&gt;为什么要 RPC&lt;/h3&gt;&#xA;&lt;p&gt;简单、通用、安全、效率&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
