Golang实现多主机服务器实例并行运行的最佳实践

Golang实现多主机服务器实例并行运行的最佳实践 问题在于,我通过将主机端口结构中的值传递给 NewServerStart(conf.ScanConnect.ScanServerSec.GRPC) 函数来启动 gRPC 服务器,一切工作正常,但我无法弄清楚如何根据传递的主机值和端口值启动多个服务器实例。假设我在数组中传递了多个主机的值:

scanServerConn := [3]config.GRPCConfig{conf.ScanConnect.ScanServerFir.GRPC,
conf.ScanConnect.ScanServerSec.GRPC, conf.ScanConnect.ScanServerThi.GRPC}
for _, servConn := range scanServerConn {
fmt.Println(servConn)
if err := NewServerStart(conf.ScanConnect.ScanServerSec.GRPC); err != nil {
log.Fatalf("Can't run server: %s", err)
}
}

显然这不会起作用,循环只会执行一次迭代。我该如何确保所有操作都能执行?当然,有一个我不愿意做的选项,那就是重写多个服务器副本,并为每个副本硬编码主机和端口。请告诉我如何避免这种情况,使其更方便?并且用一个函数启动多个服务器。

这里是所有代码: https://github.com/8n0kkw1n/testcmd/pull/1/files


更多关于Golang实现多主机服务器实例并行运行的最佳实践的实战教程也可以访问 https://www.itying.com/category-94-b0.html

7 回复

感谢您的解释,我会照做的。

更多关于Golang实现多主机服务器实例并行运行的最佳实践的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在单独的 goroutine 中启动各个服务器,如果你确实需要返回的错误信息,可以使用通道将它们发送回启动它们的 goroutine。

NobbZ:

能否请你使用代码块来提高可读性?

抱歉,我本来可以,但不知为何 Markdown 无法正常工作,它把代码分成了两部分,一部分是纯文本,另一部分是代码。

看起来你现在似乎是在所有 goroutine 启动后就退出了。你需要在它们启动后进行阻塞。根据你的语义要求,通常的做法是使用 WaitGroup、返回错误的消息或其他方法。

感谢提供的信息,我会尝试使用 sync.WaitGroup。

能否请您使用代码块来让所有内容更具可读性?

不过看起来您现在似乎是在所有goroutine启动后就退出了。您需要在它们启动后进行阻塞。通常的做法是使用等待组(waitgroups)、带有返回错误的消息或其他方式。这取决于您的语义要求。

我可能会在主例程中休眠,并在各个goroutine中出错时重启。

func main() {
    fmt.Println("hello world")
}

我不太明白,我在一个goroutine中运行它,但什么都没发生,我是不是做错了什么

scanServerConn := [3]config.GRPCConfig{conf.ScanConnect.ScanServerFir.GRPC, conf.ScanConnect.ScanServerSec.GRPC, conf.ScanConnect.ScanServerThi.GRPC}
for _, servConn := range scanServerConn {
go func() {
err := NewServerStart(servConn)
if err != nil {
log.Fatalf(“Can’t run server: %s”, err)
}
}()
}

要解决多主机并行启动gRPC服务器的问题,可以使用goroutine配合WaitGroup来实现并发执行。以下是修改后的代码示例:

package main

import (
    "fmt"
    "log"
    "sync"
)

func main() {
    // 假设的配置结构
    scanServerConn := [3]config.GRPCConfig{
        conf.ScanConnect.ScanServerFir.GRPC,
        conf.ScanConnect.ScanServerSec.GRPC,
        conf.ScanConnect.ScanServerThi.GRPC,
    }

    var wg sync.WaitGroup
    var mu sync.Mutex
    var errors []error

    for _, servConn := range scanServerConn {
        wg.Add(1)
        go func(conn config.GRPCConfig) {
            defer wg.Done()
            
            fmt.Printf("Starting server on %s:%d\n", conn.Host, conn.Port)
            
            if err := NewServerStart(conn); err != nil {
                mu.Lock()
                errors = append(errors, fmt.Errorf("server %s:%d failed: %v", conn.Host, conn.Port, err))
                mu.Unlock()
                log.Printf("Can't run server on %s:%d: %s", conn.Host, conn.Port, err)
            }
        }(servConn)
    }

    wg.Wait()

    if len(errors) > 0 {
        log.Fatal("Some servers failed to start:", errors)
    }
    
    fmt.Println("All servers started successfully")
}

// NewServerStart 函数示例
func NewServerStart(conf config.GRPCConfig) error {
    // 实际的gRPC服务器启动逻辑
    lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", conf.Host, conf.Port))
    if err != nil {
        return err
    }
    
    s := grpc.NewServer()
    // 注册服务...
    
    go func() {
        if err := s.Serve(lis); err != nil {
            log.Printf("Server on %s:%d stopped: %v", conf.Host, conf.Port, err)
        }
    }()
    
    return nil
}

对于更复杂的场景,可以使用context来处理服务器生命周期:

func StartMultipleServers(configs []config.GRPCConfig) error {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    
    var wg sync.WaitGroup
    errCh := make(chan error, len(configs))
    
    for _, cfg := range configs {
        wg.Add(1)
        go func(c config.GRPCConfig) {
            defer wg.Done()
            
            server := grpc.NewServer()
            lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", c.Host, c.Port))
            if err != nil {
                errCh <- fmt.Errorf("failed to listen on %s:%d: %v", c.Host, c.Port, err)
                return
            }
            
            // 注册服务
            // pb.RegisterYourServiceServer(server, &yourService{})
            
            go func() {
                <-ctx.Done()
                server.GracefulStop()
            }()
            
            if err := server.Serve(lis); err != nil {
                errCh <- fmt.Errorf("server on %s:%d failed: %v", c.Host, c.Port, err)
            }
        }(cfg)
    }
    
    go func() {
        wg.Wait()
        close(errCh)
    }()
    
    for err := range errCh {
        if err != nil {
            cancel()
            return err
        }
    }
    
    return nil
}

在你的具体代码中,可以这样修改cmd/server.go

func runServer() error {
    // ... 配置加载代码
    
    scanServerConn := []config.GRPCConfig{
        conf.ScanConnect.ScanServerFir.GRPC,
        conf.ScanConnect.ScanServerSec.GRPC,
        conf.ScanConnect.ScanServerThi.GRPC,
    }
    
    var wg sync.WaitGroup
    for _, servConn := range scanServerConn {
        wg.Add(1)
        go func(conn config.GRPCConfig) {
            defer wg.Done()
            if err := NewServerStart(conn); err != nil {
                log.Printf("Failed to start server on %s:%d: %v", 
                    conn.Host, conn.Port, err)
            }
        }(servConn)
    }
    
    wg.Wait()
    return nil
}

这样修改后,所有gRPC服务器都会并行启动,每个服务器在独立的goroutine中运行,主goroutine会等待所有服务器启动完成。

回到顶部