思想就是用循环数组来实现整个结构。其中numSlots用来表示滑动窗口的持续时间,比如30s,间隔时间是1s执行一次,这个可以用goroutine起一个定时器实现。计数就用golang atmic 实现,这个具有原子性。每一次步进就是
(curIndex+1)%numSlots
下边就是相关代码。
package service
import (
"fmt"
"sync/atomic"
"time"
)
// 滑动窗口实时 QPS 可视化示例
type QpsService struct {
Slots []uint64
NumSlots int
Interval time.Duration
CurrIndex int
}
/*
时间段,间隔时间
*/
func NewQPSCounter(duration, interval time.Duration) *QpsService {
numSlots := int(duration / interval)
counter := &QpsService{
Slots: make([]uint64, numSlots),
NumSlots: numSlots,
Interval: interval,
}
//每个1s 往下一个槽
go func() {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for range ticker.C {
counter.nextSlot()
}
}()
return counter
}
// 把指针移动到下一个时间槽,如果到末尾就从头开始。
// 因为我们已经切换到新的时间槽了,开始统计新的时间段的请求数
func (q *QpsService) nextSlot() {
q.CurrIndex = (q.CurrIndex + 1) % q.NumSlots
atomic.StoreUint64(&q.Slots[q.CurrIndex], 0)
}
// 对当前分片值加1
func (q *QpsService) Add() {
atomic.AddUint64(&q.Slots[q.CurrIndex], 1)
}
// 每一个分片中的数量
func (q *QpsService) Count() (sum uint64) {
for i := 0; i < q.NumSlots; i++ {
sum += atomic.LoadUint64(&q.Slots[i])
}
return sum
}
// 快照
func (q *QpsService) Snapshot() []uint64 {
snapshot := make([]uint64, q.NumSlots)
for i := 0; i < q.NumSlots; i++ {
snapshot[i] = atomic.LoadUint64(&q.Slots[i])
}
return snapshot
}
// 绘制 ASCII QPS 图
func (q *QpsService) drawQPSChart(slots []uint64) {
// 计算最大槽数量
maxSlots := uint64(1)
for _, v := range slots {
if v > maxSlots {
maxSlots = v
}
}
//顶部 h=6 → 只看到槽3满了水 → 打印 █
//h=5 → 槽3仍然有水 → █
//h=4 → 槽3仍然有水 → █
//h=3 → 槽2和槽3有水 → █ █
//h=2 → 槽2、槽3、槽4有水 → █ █ █
//h=1 → 所有槽都有水 → █ █ █ █
height := 10 // 图表高度
for h := height; h >= 1; h-- {
//每一个slots
for _, v := range slots {
//把请求数映射到柱状图高度
barHeight := v * uint64(height) / maxSlots
if barHeight >= uint64(h) {
fmt.Print("█")
} else {
fmt.Print(" ")
}
}
fmt.Println()
}
fmt.Println(fmt.Sprint(slots)) // 打印 slot 数值
fmt.Println()
}
评论