我已经玩了一段时间了,我一直在使用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
}

基本上,我只是设置两个回声服务器,一个用于每个组件。

对于它的value,这是我的配置:

[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" }

当我在一个过程中运行它时,一切似乎都在起作用as-expected。我看到Web请求的日志条目,表明事情正常工作。当我以部署模式运行它(即实际上执行多个过程魔术)时,我只能在看到如下的日志条目之前提出1-2请求,然后响应不经常工作:

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

看来我在做错事,但似乎这是一种用例以某种方式支持的用例,所以我想知道是否有适当的方法可以解决这个问题。

谢谢!

分析解答

TL; DR Service Weaver当前没有一个从non-main组件运行HTTP服务器的好方法。我建议您将两个侦听器移至Server结构,并在serve功能中运行两个HTTP服务器。 HTTP服务器可以在其他组件上调用方法。

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

细节

weaver multi Deployer将每个组件复制两次,每个副本在其自己的OS进程中运行。考虑当APIServer组件在端口12345上请求网络侦听器时会发生什么。只有一个操作系统可以在端口上收听。为了解决这个问题,这两个复制品在随机选择的端口上聆听,例如8000和9000。然后,weaver multi部署者在端口12345上运行HTTP代理,该代理将请求转发到端口8000和9000。

在您的应用程序中,主Server组件被复制两次,并且serve功能运行两次,一次在每个副本上一次。在serve内部,当您调用apiServer.Get().Serve(context.Background())时,随机选择APIServer的复制品以执行Serve方法。如果幸运的是,两个方法调用将发送到两个不同的副本,那么一切都应该顺利运行。但是,如果两个方法调用都发送到同一副本,则APIServer的一个副本正在运行HTTP服务器,而另一个则没有。

在这种情况下,代理将所有请求的一半转发到运行的HTTP服务器,而另一半请求将其转发给不聆听的听众。这导致您看到的代理错误。

最后,请注意,当您使用go runweaver single deploy运行应用程序时,事情可以正常工作,因为没有代理和组件未复制。