Golang中TCP I/O超时及无法建立TCP连接的问题排查

Golang中TCP I/O超时及无法建立TCP连接的问题排查 目前正在开发一个Go应用程序,该程序每秒读取大量消息,并通过database/sql包将这些消息处理到SQL Server中。据我理解,sql.DB是一个连接池,它会管理所有连接。我已经尝试调整SetMaxOpenConns、SetMaxIdleConns和SetConnMaxLifetime的参数,但至今没有成功。我的问题是,我遇到了大量与TCP连接相关的错误:“read tcp 10.0.223.183:50788->10.0.222.8:1433: i/o timeout”。我已经花费了很长时间,但仍然没有找到解决方案。我只有一个全局的sql.DB实例,它只创建一次,并被多个goroutine同时访问。有人能帮我找到解决这个问题的方法吗?我以为*sql.DB会处理并发问题,但肯定是我哪里做得不对!我已经尝试使用互斥锁和上下文,但仍然没有成功,请参考以下部分代码:

提前感谢!

// 全局sql实例
var dgsData *sql.DB
var DoLineChangePrep *sql.Stmt
.
.
.
func InitPreparedStatements() error {
if DoLineChangePrep, err = dgsData.Prepare(ExecMoveLine); err != nil {
return err
}
}

func DgsDoLineChange(…){
err := DoLineChangePrep.QueryRow(…)
.
.
.
}

更多关于Golang中TCP I/O超时及无法建立TCP连接的问题排查的实战教程也可以访问 https://www.itying.com/category-94-b0.html

10 回复

感谢您的回复,关于SQL Server,我已经在使用“http://github.com/denisenkom/go-mssqldb”了

更多关于Golang中TCP I/O超时及无法建立TCP连接的问题排查的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


嗯,也许它支持那么多,但实际配置了多少呢?

另外,它们能同时提供服务吗?还是只能同时服务10个,剩下的必须等待有空闲的工作线程?

您使用的是哪种SQL数据库?您可能忘记导入驱动程序了?

https://github.com/golang/go/wiki/SQLDrivers#sql-database-drivers

你可以用其他语言编写一个虚拟脚本,启动与你的Go程序相同数量的连接,看看它是否也会崩溃。至少这样可以确保不是数据库的问题。

感谢您的回复,抱歉耽搁了,之前外出了……当然,我会创建一个小脚本来尝试这个方法,稍后向您反馈,谢谢

你是否检查过你的数据库能否处理这样的负载,或者是否能够同时接受你抛给它的这么多连接?

数据库本身可能有一个连接上限,它可能有连接池,可能有工作线程池,可能存在锁阻止并发查询同时运行,可能还有…

你好 @NobbZ,感谢你抽空回复。我不清楚 SQL Server 允许多少连接,但在网上稍微查了一下,据说它支持最多 32767 个并发连接。我的应用程序记录的是数百而非数千个连接,所以不确定 SQL Server 是否是问题所在。

感谢大家的帮助,确实这与数据库服务器的硬件限制有关,另外我将最大打开连接数限制为20以便复用,同时也限制了最大空闲连接数。再次感谢你们的帮助,我非常感激!

// 代码示例
func main() {
    fmt.Println("hello world")
}

我认为问题不在于连接限制,这种情况下你会收到连接过多错误或类似的提示。根据错误信息显示,你很可能是因为查询超时而遇到这个问题。我会首先检查SQL服务器的连接超时配置以及你在程序中使用的连接字符串(DSN)。

如果仍然找不到问题,建议你检查SQL服务器日志。

编辑:我发现了这个:too many read tcp xxx> xxx i/o timeout · Issue #331 · denisenkom/go-mssqldb · GitHub 你可以通过在DSN中添加以下内容进行测试:

;Connection Timeout=0

在您的场景中,TCP I/O超时和连接建立问题通常与数据库连接池配置、网络环境或数据库服务器负载有关。以下是一些具体的排查步骤和代码示例:

1. 优化数据库连接池配置

func initDB() (*sql.DB, error) {
    connString := "server=10.0.222.8;port=1433;database=yourdb;user id=user;password=pass"
    db, err := sql.Open("sqlserver", connString)
    if err != nil {
        return nil, err
    }
    
    // 根据您的负载调整这些参数
    db.SetMaxOpenConns(100)           // 最大打开连接数
    db.SetMaxIdleConns(20)            // 最大空闲连接数
    db.SetConnMaxLifetime(30 * time.Minute) // 连接最大生命周期
    db.SetConnMaxIdleTime(10 * time.Minute) // 连接最大空闲时间
    
    // 验证连接
    if err := db.Ping(); err != nil {
        return nil, err
    }
    
    return db, nil
}

2. 实现带超时的上下文控制

func DgsDoLineChange(ctx context.Context, params ...interface{}) error {
    // 设置查询超时
    queryCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
    defer cancel()
    
    err := DoLineChangePrep.QueryRowContext(queryCtx, params...).Scan(&result)
    if err != nil {
        if errors.Is(err, context.DeadlineExceeded) {
            log.Printf("Query timeout: %v", err)
        }
        return err
    }
    return nil
}

3. 添加连接健康检查

func checkDBHealth(db *sql.DB) bool {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    if err := db.PingContext(ctx); err != nil {
        log.Printf("Database health check failed: %v", err)
        return false
    }
    return true
}

// 定期执行健康检查
func startHealthCheck(db *sql.DB) {
    ticker := time.NewTicker(1 * time.Minute)
    defer ticker.Stop()
    
    for range ticker.C {
        if !checkDBHealth(db) {
            log.Println("Database connection unhealthy")
        }
    }
}

4. 实现重试机制

func executeWithRetry(ctx context.Context, maxRetries int, fn func() error) error {
    var lastErr error
    
    for i := 0; i < maxRetries; i++ {
        select {
        case <-ctx.Done():
            return ctx.Err()
        default:
        }
        
        if err := fn(); err != nil {
            lastErr = err
            if isRetryableError(err) {
                time.Sleep(time.Duration(i+1) * time.Second) // 指数退避
                continue
            }
            return err
        }
        return nil
    }
    return lastErr
}

func isRetryableError(err error) bool {
    // 检查是否为可重试的错误类型
    if err == nil {
        return false
    }
    
    errorStr := err.Error()
    return strings.Contains(errorStr, "i/o timeout") ||
           strings.Contains(errorStr, "connection reset") ||
           strings.Contains(errorStr, "broken pipe")
}

5. 监控连接池状态

func monitorConnectionPool(db *sql.DB) {
    stats := db.Stats()
    log.Printf("Connection Pool Stats - Open: %d, InUse: %d, Idle: %d, WaitCount: %d, WaitDuration: %v",
        stats.OpenConnections,
        stats.InUse,
        stats.Idle,
        stats.WaitCount,
        stats.WaitDuration)
}

// 定期记录连接池状态
go func() {
    ticker := time.NewTicker(30 * time.Second)
    defer ticker.Stop()
    for range ticker.C {
        monitorConnectionPool(dgsData)
    }
}()

6. 网络层面排查

检查网络配置和防火墙规则:

func testNetworkConnectivity() {
    timeout := 5 * time.Second
    conn, err := net.DialTimeout("tcp", "10.0.222.8:1433", timeout)
    if err != nil {
        log.Printf("Network connectivity test failed: %v", err)
        return
    }
    defer conn.Close()
    log.Println("Network connectivity test passed")
}

这些措施应该能帮助您识别和解决TCP连接问题。重点关注连接池配置、超时设置和重试逻辑,这些通常是导致I/O超时的主要原因。

回到顶部