关于云原生:MoE-系列四|Go-扩展的异步模式

5次阅读

共计 2916 个字符,预计需要花费 8 分钟才能阅读完成。

《MoE 系列(三)|应用 Istio 动静更新 Go 扩大配置》 中咱们体验了用 Istio 做管制面,给 Go 扩大推送配置,这次咱们来体验一下,在 Go 扩大的异步模式下,对 Goroutine 等全副 Go 个性的反对。

异步模式

之前,咱们实现了一个简略的 Basic Auth[1],然而,那个实现是同步的,也就是说,Go 扩大会阻塞,直到 Basic Auth 验证实现,才会返回给 Envoy。

因为 Basic Auth 是一个非常简单的场景,用户名明码曾经解析在 Go 内存中了,整个过程只是纯 CPU 计算,所以,这种同步的实现形式是没问题的。

然而,如果咱们要实现一个更简单的需要,比方,咱们要将用户名明码调用近程接口查问,波及网络操作,这个时候,同步的实现形式就不太适合了。因为同步模式下,如果咱们要期待近程接口返回,Go 扩大就会阻塞,Envoy 也就无奈解决其余申请了。

所以,咱们须要一种异步模式:

  • 咱们在 Go 扩大中,启动一个 Goroutine,而后立刻返回给 Envoy,以后正在解决的申请会被挂起,Envoy 则能够持续解决其余申请。
  • Goroutine 在后盾异步执行,当 Goroutine 中的工作实现之后,再回调告诉 Envoy,挂起的申请能够持续解决了。

留神:尽管 Goroutine 是异步执行,然而 Goroutine 中的代码,与同步模式下的代码,简直是一样的,并不需要特地的解决。

为什么须要

为什么须要反对 Goroutine 等全副 Go 的个性呢?

有两方面的起因:

  • 有了 Full-feature supported Go,咱们能够实现十分弱小、简单的扩大。
  • 能够十分不便的集成现有 Go 世界的代码,享受 Go 生态的红利。

如果不反对全副的 Go 个性,那么在集成现有 Go 代码的时候,会有诸多限度,导致须要重写大量的代码,这样,就享受不到 Go 生态的红利了。

实现

接下来,咱们还是通过一个示例来体验,这次咱们实现 Basic Auth 的近程校验版本,要害代码如下:

func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api.StatusType {go func() {
    // verify 中的代码,能够不须要感知是否异步
    // 同时,verify 中是能够应用全副的 Go 个性,比方,http.Post
    if ok, msg := f.verify(header); !ok {f.callbacks.SendLocalReply(401, msg, map[string]string{}, 0, "bad-request")
      return
    }
    // 这里是惟一的 API 区别,异步回调,告诉 Envoy,能够持续解决以后申请了
    f.callbacks.Continue(api.Continue)
  }()
  // Running 示意 Go 还在解决中,Envoy 会挂起以后申请,持续解决其余申请
  return api.Running
}

再来看 verify 的代码,重点是,咱们能够在这里应用全副的 Go 个性:

// 这里应用了 http.Post
func checkRemote(config *config, username, password string) bool {body := fmt.Sprintf(`{"username": "%s", "password": "%s"}`, username, password)
  remoteAddr := "http://" + config.host + ":" + strconv.Itoa(int(config.port)) + "/check"
  resp, err := http.Post(remoteAddr, "application/json", strings.NewReader(body))
  if err != nil {fmt.Printf("check error: %v\n", err)
    return false
  }
  if resp.StatusCode != 200 {return false}
  return true
}
// 这里操作 header 这个 interface,与同步模式齐全一样
func (f *filter) verify(header api.RequestHeaderMap) (bool, string) {auth, ok := header.Get("authorization")
  if !ok {return false, "no Authorization"}
  username, password, ok := parseBasicAuth(auth)
  if !ok {return false, "invalid Authorization format"}
  fmt.Printf("got username: %v, password: %v\n", username, password)
  if ok := checkRemote(f.config, username, password); !ok {return false, "invalid username or password"}
  return true, ""
}

另外,咱们还须要实现一个简略的 HTTP 服务,用来校验用户名明码,这里就不开展了,用户名明码还是 foo:bar

残缺的代码,请移步 Github[2]。

测试

老规矩,启动之后,咱们应用 curl 来测试一下:

$ curl -s -I -HHost:httpbin.example.com "http://$INGRESS_HOST:$INGRESS_PORT/status/200"
HTTP/1.1 401 Unauthorized
# valid foo:bar
$ curl -s -I -HHost:httpbin.example.com "http://$INGRESS_HOST:$INGRESS_PORT/status/200" -H 'Authorization: basic Zm9vOmJhcg=='
HTTP/1.1 200 OK

仍旧合乎预期。

总结

在同步模式下,Go 代码中惯例的异步非阻塞也会变成阻塞执行,这是因为 Go 和 Envoy 是两套事件循环体系。

而通过异步模式,Go 能够在后盾异步执行,不会阻塞 Envoy 的事件循环,这样,就能够用上全副的 Go 个性了。

因为 Envoy Go 裸露的是底层的 API,所以实现 Go 扩大的时候,须要关怀同步和异步的区别。

当然,这对于一般的扩大开发而言,并不是一个敌对的设计,之所以这么设计,更多是为了极致性能的考量。

大多数场景下,其实并不需要到这么极致,所以,咱们会在更下层提供一种默认异步的模式。这样,Go 扩大的开发者,就不须要关怀同步和异步的区别了。

下一篇咱们将介绍 Envoy Go 扩大之内存平安。欢送感兴趣的继续关注~

敬请期待:MoE 系列(五)|Envoy Go 扩大之内存平安

[1]Basic Auth:

https://uncledou.site/2023/moe-extend-envoy-using-golang-2/

[2]Github:

https://github.com/doujiang24/envoy-go-filter-example/tree/master/example-remote-basic-auth

正文完
 0