3. Go 基础语法速览:变量、常量、数据类型、控制结构与错误处理

在进入 Kafka 压测项目之前,必须掌握 Go 语言的核心语法骨架。本章以“压测工具开发”为场景,快速梳理变量、常量、数据类型、控制结构与错误处理。所有示例均可在后续项目代码中找到对应实现。

3.1 变量声明与初始化

Go 是静态强类型语言,但提供类型推断以简化代码。变量声明有三种主要形式:

声明方式示例适用场景
var 完整声明var count int = 100需要显式指定类型或零值初始化
var 多变量var x, y int = 1, 2同类型批量声明
短变量声明 :=msg := "hello"函数内部局部变量,类型由右值推断

压测场景示例:

// 压测配置参数
var (
    topic     = "test-topic"       // 字符串类型推断
    partition = 0                  // int 类型推断
    msgCount  int64 = 1_000_000    // 显式指定 int64
    batchSize int   = 500          // 批量发送大小
)

// 短变量声明常用于循环与临时变量
for i := 0; i < 10; i++ {
    // ...
}

3.2 常量与枚举模式

常量使用 const 关键字,编译期确定值。Go 没有原生枚举,但通过 iota 实现自增常量枚举:

const (
    StatusPending = iota // 0
    StatusRunning        // 1
    StatusSuccess        // 2
    StatusFailed         // 3
)

const (
    KB = 1 << (10 * iota) // 1 << 10 = 1024
    MB                     // 1 << 20
    GB                     // 1 << 30
)

压测用途:使用 iota 定义消息发送状态、消费者阶段标记、配置级别等,避免魔法数字。

3.3 基本数据类型

类别类型说明压测相关
布尔booltrue/false控制开关、健康检查
整型int/int8~int64/uint平台相关长度消息计数、偏移量、分区号
浮点float32/float64IEEE-754 标准吞吐率、延迟百分位
字符串stringUTF-8 不可变字节序列Topic 名称、消息体、配置键
字节byte (=uint8)二进制数据消息序列化、网络缓冲区
错误error内置接口类型所有可能失败的操作

3.4 复合数据类型

Go 提供四种内置复合类型,在压测项目中频繁使用:

  • 数组:固定长度,值类型。var ids [3]int
  • 切片:动态数组,引用类型。msgs := []string{}
  • 映射:哈希表。config := map[string]string{"bootstrap.servers": "localhost:9092"}
  • 结构体:自定义聚合类型。
// 压测报告结构体
type Report struct {
    TotalMessages int64
    Duration      time.Duration
    Throughput    float64
    P99Latency    time.Duration
    Errors        int
}

注意:切片作为函数参数时传递的是引用语义,修改会影响原切片。映射在并发读写时必须加锁或使用 sync.Map。

3.5 控制结构:if / for / switch

Go 的控制结构简洁且无括号包裹条件,所有控制语句都支持在条件前执行一个简单语句。

3.5.1 if 语句

// 带初始化语句的 if
if err := produceMessage(topic, msg); err != nil {
    log.Printf("发送失败: %v", err)
    return err
}

// 标准 if-else
if latency > 100*time.Millisecond {
    fmt.Println("延迟过高")
} else if latency > 50*time.Millisecond {
    fmt.Println("延迟中等")
} else {
    fmt.Println("延迟正常")
}

3.5.2 for 循环

Go 只有 for 关键字,覆盖所有循环模式:

模式语法压测示例
经典三段式for i := 0; i < n; i++循环发送 N 条消息
条件循环for running {持续消费直到信号中断
无限循环for {后台心跳协程
range 迭代for k, v := range map遍历分区分配结果
// 压测中典型的 range 循环
partitions := []int{0, 1, 2, 3}
for _, p := range partitions {
    go consumePartition(topic, p)
}

// 无限循环 + select 多路复用
for {
    select {
    case msg := <-msgChan:
        process(msg)
    case <-ctx.Done():
        return
    }
}

3.5.3 switch 语句

Go 的 switch 默认不穿透(无需 break),且支持表达式和类型判断:

// 值匹配
switch status {
case StatusPending:
    fmt.Println("等待发送")
case StatusRunning:
    fmt.Println("发送中")
case StatusSuccess:
    fmt.Println("发送成功")
default:
    fmt.Println("未知状态")
}

// 类型断言 switch
var v interface{} = getMetricValue()
switch t := v.(type) {
case int64:
    fmt.Printf("整型指标: %d\n", t)
case float64:
    fmt.Printf("浮点指标: %f\n", t)
case string:
    fmt.Printf("字符串指标: %s\n", t)
}
Go 控制结构执行流程(压测上下文) 压测主循环入口 if err := send(msg); err != nil 错误处理分支 记录错误日志 更新成功计数 for i := 0; i < batchSize; i++ 批量发送循环 switch msg.Status 状态分发 StatusFailed StatusSuccess 批次发送完成

3.6 函数定义与多返回值

Go 函数支持多返回值,这是错误处理的基础模式:

// 标准函数:多返回值 + 命名返回值
func produceBatch(topic string, messages []string) (success int, failed int, err error) {
    for _, msg := range messages {
        if err := send(topic, msg); err != nil {
            failed++
            continue
        }
        success++
    }
    return // 裸返回,使用命名返回值
}

// 函数作为参数(回调)
func withTimeout(fn func() error, timeout time.Duration) error {
    done := make(chan error, 1)
    go func() {
        done <- fn()
    }()
    select {
    case err := <-done:
        return err
    case <-time.After(timeout):
        return fmt.Errorf("操作超时")
    }
}

3.7 错误处理哲学

Go 没有异常机制,错误通过返回值显式传递。这是压测代码中必须严格遵守的范式:

  • 总是检查错误:忽略错误会导致静默失败,压测结果失真。
  • 尽早返回:错误发生后立即处理或向上传播。
  • 包装错误:使用 fmt.Errorf("context: %w", err) 保留原始错误链。
  • 哨兵错误:定义可比较的错误变量。
// 哨兵错误定义
var ErrTopicNotFound = errors.New("topic not found")

// 错误包装与传播
func getPartitionLeader(topic string, partition int) (string, error) {
    leader, err := metadata.Lookup(topic, partition)
    if err != nil {
        return "", fmt.Errorf("获取分区 %d 的 leader 失败: %w", partition, err)
    }
    if leader == "" {
        return "", ErrTopicNotFound
    }
    return leader, nil
}

// 调用方处理
leader, err := getPartitionLeader("test-topic", 0)
if err != nil {
    if errors.Is(err, ErrTopicNotFound) {
        log.Fatal("Topic 不存在,请先创建")
    }
    log.Fatalf("严重错误: %v", err)
}

压测最佳实践:在压测主循环中,对非致命错误(如单条消息发送失败)使用计数器累加,而非立即终止;对致命错误(如连接断开)使用 log.Fatal 或 panic 停止压测。

Go 错误处理流程(压测场景) 调用方(压测主循环) result, err := sendMessage(msg) 多返回值调用 if err != nil 错误检查分支 错误处理 记录/重试/计数 正常处理 更新成功指标

3.8 本章小结

本章覆盖了 Go 语言在 Kafka 压测项目中最高频使用的语法要素:

  • 变量与常量:短声明用于局部变量,iota 用于枚举状态。
  • 数据类型:切片和映射是消息批处理的核心容器,结构体用于组织压测配置与报告。
  • 控制结构:for range 遍历分区,select 多路复用,switch 状态分发。
  • 函数与错误:多返回值 + 显式错误检查是 Go 的基石,压测代码中每个可能失败的操作都必须处理 error。