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 基本数据类型
| 类别 | 类型 | 说明 | 压测相关 |
|---|---|---|---|
| 布尔 | bool | true/false | 控制开关、健康检查 |
| 整型 | int/int8~int64/uint | 平台相关长度 | 消息计数、偏移量、分区号 |
| 浮点 | float32/float64 | IEEE-754 标准 | 吞吐率、延迟百分位 |
| 字符串 | string | UTF-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)
}
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 停止压测。
3.8 本章小结
本章覆盖了 Go 语言在 Kafka 压测项目中最高频使用的语法要素:
- 变量与常量:短声明用于局部变量,
iota用于枚举状态。 - 数据类型:切片和映射是消息批处理的核心容器,结构体用于组织压测配置与报告。
- 控制结构:
for range遍历分区,select多路复用,switch状态分发。 - 函数与错误:多返回值 + 显式错误检查是 Go 的基石,压测代码中每个可能失败的操作都必须处理 error。