我一直在玩Service Weaver,因为它已经发布了一段时间,我很好奇我们应该如何用它设置多个监听器.我的意图是,当我们部署应用程序时,web的处理程序和api的处理程序是分开运行的(例如).我的代码目前如下:

package main

import (
    "context"
    "log"
    "sync"

    "github.com/ServiceWeaver/weaver"
    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
)

type Server struct {
    weaver.Implements[weaver.Main]
    apiServer weaver.Ref[APIServer]
    webServer weaver.Ref[WebServer]
}

type APIServer interface {
    Serve(context.Context) error
}

type apiServer struct {
    weaver.Implements[APIServer]
    api weaver.Listener
}

func (a apiServer) Serve(ctx context.Context) error {
    logger := a.Logger(ctx)
    e := echo.New()
    e.Listener = a.api

    e.Use(middleware.RequestID())

    e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
        LogStatus: true,
        LogURI:    true,
        LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error {
            logger.Info("Request", "id", v.RequestID, "uri", v.URI, "status", v.Status, "size", v.ResponseSize)
            return nil
        },
    }))

    return e.Start("")
}

type WebServer interface {
    Serve(context.Context) error
}

type webServer struct {
    weaver.Implements[WebServer]
    web weaver.Listener
}

func (w webServer) Serve(ctx context.Context) error {
    logger := w.Logger(ctx)
    e := echo.New()
    e.Listener = w.web

    e.Use(middleware.RequestID())

    e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
        LogStatus: true,
        LogURI:    true,
        LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error {
            logger.Info("Request", "id", v.RequestID, "uri", v.URI, "status", v.Status, "size", v.ResponseSize)
            return nil
        },
    }))

    return e.Start("")
}

func main() {
    if err := weaver.Run(context.Background(), serve); err != nil {
        log.Fatal(err)
    }
}

func serve(ctx context.Context, server *Server) error {
    wg := sync.WaitGroup{}
    wg.Add(2)

    go func() {
        defer wg.Done()
        server.apiServer.Get().Serve(context.Background())
    }()

    go func() {
        defer wg.Done()
        server.webServer.Get().Serve(context.Background())
    }()

    wg.Wait()

    return nil
}

基本上,我只是设置了两个Echo服务器,每个组件一个.

无论如何,以下是我的配置:

[serviceweaver]
binary = "./platform"

[multi]
listeners.api = { address = "localhost:12345" }
listeners.web = { address = "localhost:54321" }

[single]
listeners.api = { address = "localhost:12345" }
listeners.web = { address = "localhost:54321" }

当我在单个进程中运行它时,一切似乎都像预期的那样工作.我看到Web请求的日志(log)条目,表明一切正常.当我在部署模式下运行它时(即,实际上正在执行多进程魔术),在看到如下所示的日志(log)条目之前,我只能发出1-2个请求,然后响应通常不起作用:

2023/09/14 21:35:51 http: proxy error: context canceled

看起来我似乎做错了什么,但这似乎是一个会以某种方式得到支持的用例,所以我想知道是否有合适的方法来解决这一问题.

谢谢!

推荐答案

tl;dr Service Weaver目前没有从非主要组件运行HTTP服务器的好方法.我建议您将两个监听程序都移到Server struct ,并在serve函数中运行两个HTTP服务器.HTTP服务器可以调用其他组件上的方法.

type Server struct {
    weaver.Implements[weaver.Main]
    api weaver.Listener
    web weaver.Listener
}

细节

weaver multi部署人员将每个组件复制两次,每个副本都在其自己的操作系统进程中运行.考虑一下当组件请求端口12345上的网络侦听器时会发生什么.组件的两个副本不能同时侦听端口12345;只有一个操作系统进程可以侦听该端口.为了解决这个问题,这两个副本分别侦听随机 Select 的端口,比如8000和9000.然后,部署人员在端口12345上运行一个HTTP代理,将请求转发到端口8000和9000.

在您的应用程序中,主Server组件被复制两次,serve函数运行两次,每个副本上运行一次.在serve内部,当您调用apiServer.Get().Serve(context.Background())时,会随机 Select APIServer的复制品来执行Serve方法.如果幸运的是,这两个方法调用被发送到两个不同的副本,那么一切都应该顺利运行.然而,如果两个方法调用都被发送到相同的副本,则APIServer的一个副本正在运行HTTP服务器,而另一个不运行.

在这种情况下,代理将所有请求的一半转发到正在运行的HTTP服务器,并将另一半请求转发给没有侦听的侦听器.这会导致您看到的代理错误.

最后,请注意,当您使用go runweaver single deploy运行应用程序时,因为没有代理,并且组件不会被复制,所以可以正常工作.

Go相关问答推荐

包裹网.Conn导致挂起读取

Golang Gorm Fiber / argon2.Config 未定义

日志(log)文件不在 golang 的日志(log)目录中

正确的 shell 程序进入 golang alpine docker 容器的入口点?

Go 中的 YAML 自定义标签

最长连续重复的字符golang

Gorm 在自定义字符串类型上返回 scanner 错误

使用 LINQ 对内部数组进行排序

golang 上基于标头的版本控制

获取不带类型参数的泛型 struct 的类型名称

Go:从 ssl 证书中获取 'subject/unstructeredName' 的值

如何将一片 map 转换为一片具有不同属性的 struct

从数据库中带有 imageurl 的文件夹中获取图像,并在我的浏览器中用 golang 中的 echo 显示

在 Golang 模板中计算时间/持续时间

使用 Golang 在字符串中循环重复数据

将shell输出绑定到Go中的 struct 的最佳方法?

Golang:隐式 struct 匹配

Golang 数据库/sql 与 SetMaxOpenConns 挂起

Go GCP 同时模拟两个服务帐户

如何在程序退出时使用 golang 删除文件?