← 中文资源

简体中文版本

原子计数器与速率统计

用原子操作维护热路径计数器:无锁累加请求、错误与字节量,速率计算器按时间窗折算每秒值供监控消费。

阅读英文原文

示例说明

计数器是监控的最小单元,也是竞争最密集的共享状态——用互斥锁会让热路径串行化。atomic.Int64 的 Add 是单条 CPU 指令级操作,百个 goroutine 并发累加零锁竞争。Snapshot 用 Load 读一致性足够的近似值——监控指标本来就容忍微小滞后,不需要强一致快照。

技术要点

  • atomic.Add 无锁完成并发累加
  • Load 快照读容忍监控级滞后
  • 速率计算按时间差折算

代码解读

Counters 三类指标各自独立原子量,方法封装读写细节;RateCalculator 保存上次的时间与值,Rate 调用时按差值除以秒数得到速率,首次调用返回零并记录基线。main 里一百个协程并发递增验证无竞争正确性。

使用建议

复合读需多字段强一致时仍要锁或版本号。速率计算器非并发安全,采样应固定单协程。正式监控接 prometheus/client_golang。

代码示例

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"
)

type Counters struct {
	requests atomic.Int64
	errors   atomic.Int64
	bytes    atomic.Int64
}

func (c *Counters) IncRequest()   { c.requests.Add(1) }
func (c *Counters) IncError()     { c.errors.Add(1) }
func (c *Counters) AddBytes(n int64) { c.bytes.Add(n) }

func (c *Counters) Snapshot() (req, errs, transferred int64) {
	return c.requests.Load(), c.errors.Load(), c.bytes.Load()
}

type RateCalculator struct {
	lastValue atomic.Int64
	lastTime  atomic.Int64
}

func (r *RateCalculator) Rate(now time.Time, current int64) float64 {
	prevTime := r.lastTime.Load()
	prevValue := r.lastValue.Load()

	if prevTime == 0 {
		r.lastTime.Store(now.Unix())
		r.lastValue.Store(current)
		return 0
	}

	elapsed := now.Unix() - prevTime
	if elapsed <= 0 {
		return 0
	}
	rate := float64(current-prevValue) / float64(elapsed)

	r.lastTime.Store(now.Unix())
	r.lastValue.Store(current)
	return rate
}

func main() {
	counters := new(Counters)
	rate := new(RateCalculator)

	var wg sync.WaitGroup
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go func(n int) {
			defer wg.Done()
			counters.IncRequest()
			if n%50 == 0 {
				counters.IncError() // 模拟约百分之二的错误率
			}
			counters.AddBytes(512)
		}(i)
	}
	wg.Wait()

	req, errs, transferred := counters.Snapshot()
	fmt.Println(req, errs, transferred, rate.Rate(time.Now(), req))
}