batcher

package
v0.12.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 1, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

README

batcher(攒批域)

高频小写的攒批出口——把逐条落库合并成批量事务,抑制写放大

这个域干什么

batcher 承接五类高频小写场景:消息状态回报、消息记录落库(write-ahead outbox)、消息收发统计、心跳统计、观察者通知。逐条写库在千万级连接下会 打爆后端,本域用统一的 syncx.BatchProcessor 攒批模型(攒满或到期 → flush)把 N 次写合并为 1 次批量操作

核心概念

概念 说明
Manager 域管理器:持有五个攒批组件,统一构造(nil/零值参数兜底)与分段停机
MessageStatusUpdater 消息状态攒批:按 status+reason+errMsg 分组,合并同组 SQL
MessageRecordOutbox 消息记录攒批 outbox:write-ahead INSERT 攒批化,早到状态更新合并进内存
MessageStatsBatcher 消息统计攒批:按 connectionID 聚合收发条数与字节数
HeartbeatStatsUpdater 心跳统计攒批:按 clientID 去重保留最新,批量刷连接质量表
ObserverNotificationBatcher 观察者通知攒批:合并同窗口的观察者回调
BatchProcessor go-toolbox 泛型攒批骨架:Submit 入队、条件触发 flush、Stop 冲刷余量
Submit(item) ──► 队列(有界) ──► 攒满 batchSize / 到期 flushInterval ──► flush(聚合分组) ──► 批量落库
     │
     └─ 队列满返回 false:调用方自行降级(记日志/丢弃),不阻塞热路径

怎么用

package main

import (
    "github.com/kamalyes/go-config/pkg/wsc"
    "github.com/kamalyes/go-wsc/batcher"
)

// 编排层组装:一个 Manager 持有五个组件,参数 nil/零值时默认值兜底
mgr := batcher.NewManager(host, cfg.Batcher)

// 业务侧通常只感知 Submit
if !mgr.StatusUpdater().Submit(&batcher.StatusUpdateItem{
    MessageID: msgID,
    Receiver:  receiver,
    Status:    status,
}) {
    // 队列满:降级处理(日志 + 丢弃)
}

mgr.StopTracking() // 连接清理前:心跳/消息统计/观察者通知冲刷余量
mgr.StopRecords()  // 连接清理后:先 outbox 后状态更新,保 INSERT→UPDATE 顺序

端口与依赖

  • 消费者端口:Host(Manager 聚合构造所需能力面,hub 实现)、 StorageBatchWriter(消息状态/消息统计/心跳统计共用)、 ObserverNotifier(观察者通知)、MessageRecordSinkProvider(记录 outbox)
  • 落库目标经 spi 契约注入:MessageSink(消息记录)、ConnectionStore(连接记录)、 ConnectionQualityStore(连接质量)
  • 依赖 go-toolbox syncx.BatchProcessor(攒批骨架)与 mathx(分片计算)

性能与陷阱

  • flush 必须用 context.WithTimeout(context.Background(), 5s): 带 cancel 的 ctx 会把 Hub 关闭时最后一批落库截断
  • 队列有界:Submit 返回 false 即满,调用方必须处理满载语义,禁止阻塞等待
  • 心跳攒批按 clientID 去重保留最新——重连期间的旧心跳不会覆盖新连接的统计
  • 攒批参数(batchSize/worker/interval)与后端写入能力匹配:批过大触发后端 锁等待,批过小退化为逐条写

后端绑定

  • RDBMS(GORM 方言:MySQL / PG / CRDB):消息状态、消息统计、心跳统计攒批落库
  • Redis:心跳统计的 64 分片变更集刷写(时间戳续期路径)

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type DisconnectionBatcher

type DisconnectionBatcher struct {
	// contains filtered or unexported fields
}

DisconnectionBatcher 断连终态批量更新器

func NewDisconnectionBatcher

func NewDisconnectionBatcher(hub StorageBatchWriter) *DisconnectionBatcher

NewDisconnectionBatcher 创建断连终态批量更新器

func (*DisconnectionBatcher) Stop

func (b *DisconnectionBatcher) Stop()

Stop 停止更新器,flush 剩余数据后退出

func (*DisconnectionBatcher) Submit

Submit 非阻塞提交断连终态条目 队列满时返回 false(上层记录池本就是可丢弃语义,风暴极端场景下 丢弃终态只影响审计统计精度,不影响连接清理与消息投递主流程)

type ErrorStatsBatcher

type ErrorStatsBatcher struct {
	// contains filtered or unexported fields
}

ErrorStatsBatcher 连接错误统计批量更新器

func NewErrorStatsBatcher

func NewErrorStatsBatcher(hub StorageBatchWriter, queueSize, batchSize int, flushInterval time.Duration) *ErrorStatsBatcher

NewErrorStatsBatcher 创建连接错误统计批量更新器

func (*ErrorStatsBatcher) Stop

func (u *ErrorStatsBatcher) Stop()

Stop 停止更新器,flush 剩余数据后退出

func (*ErrorStatsBatcher) Submit

func (u *ErrorStatsBatcher) Submit(item *ErrorStatsItem) bool

Submit 非阻塞提交连接错误 队列满时返回 false(错误统计可丢弃,非核心路径)

type ErrorStatsItem

type ErrorStatsItem struct {
	ConnectionID string
	Error        error
	ErrorAt      time.Time
}

ErrorStatsItem 连接错误条目(Hub 侧构造)

type HeartbeatStatsEntry

type HeartbeatStatsEntry struct {
	ClientID string
	PingTime time.Time
	PingMs   float64
}

HeartbeatStatsEntry 心跳统计条目

type HeartbeatStatsUpdater

type HeartbeatStatsUpdater struct {
	// contains filtered or unexported fields
}

HeartbeatStatsUpdater 心跳统计批量更新器

func NewHeartbeatStatsUpdater

func NewHeartbeatStatsUpdater(hub StorageBatchWriter, queueSize, batchSize int, flushInterval time.Duration) *HeartbeatStatsUpdater

NewHeartbeatStatsUpdater 创建心跳统计批量更新器

func (*HeartbeatStatsUpdater) Stop

func (u *HeartbeatStatsUpdater) Stop()

Stop 停止更新器,flush 剩余数据后退出

func (*HeartbeatStatsUpdater) Submit

Submit 非阻塞提交心跳统计 队列满时返回 false(心跳数据可丢弃,下次心跳会补上)

type Host

type Host interface {
	StorageBatchWriter
	GetMessageSink() spi.MessageSink
}

Host 批处理器域宿主端口(Manager 聚合构造五个批处理器所需的能力面)

由 hub 编排层实现:StorageBatchWriter 覆盖状态/消息统计/心跳统计三个更新器, GetMessageSink 覆盖记录 outbox(MessageRecordSinkProvider 能力面); 观察者直投(ObserverNotifier)由消息域 Manager 实现并单独注入—— 观察者投递语义归 messaging 域,batcher flush 直连域组件不再绕编排层

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager 批处理器域管理器:持有七个攒批组件并提供域内访问器

func NewManager

func NewManager(host Host, observerNotify ObserverNotifier, cfg *wscconfig.BatcherConfig) *Manager

NewManager 构造全部批处理器并启动后台 flush 协程 observerNotify 为观察者直投端口(消息域 Manager 实现,flush 回调直连域组件); cfg 为 nil 或子项零值时,各组件内部使用默认参数兜底(Get*Params 已处理)

func (*Manager) DisconnectionBatcher

func (m *Manager) DisconnectionBatcher() *DisconnectionBatcher

DisconnectionBatcher 断连终态批量更新器

func (*Manager) ErrorStats

func (m *Manager) ErrorStats() *ErrorStatsBatcher

ErrorStats 连接错误统计批量更新器

func (*Manager) HeartbeatStats

func (m *Manager) HeartbeatStats() *HeartbeatStatsUpdater

HeartbeatStats 心跳统计批量更新器

func (*Manager) MessageStats

func (m *Manager) MessageStats() *MessageStatsBatcher

MessageStats 消息统计批量聚合器

func (*Manager) ObserverNotify

func (m *Manager) ObserverNotify() *ObserverNotificationBatcher

ObserverNotify 观察者通知批量处理器

func (*Manager) RecordOutbox

func (m *Manager) RecordOutbox() *MessageRecordOutbox

RecordOutbox 消息记录攒批 outbox

func (*Manager) StatusUpdater

func (m *Manager) StatusUpdater() *MessageStatusUpdater

StatusUpdater 消息状态批量更新器

func (*Manager) StopRecords

func (m *Manager) StopRecords()

StopRecords 停止断连终态 / 记录 outbox 与状态更新器(连接清理后调用) 断连终态须待本段:unregister 风暴的 Submit 发生在连接清理期间(StopTracking 之后); 先 outbox 后 statusUpdater:保 INSERT→UPDATE 落库顺序,避免 UPDATE 扑空; 在 Hub cancel 之前调用,确保 flush 时 h.ctx 仍然有效

func (*Manager) StopTracking

func (m *Manager) StopTracking()

StopTracking 停止心跳统计 / 消息统计 / 错误统计 / 观察者通知批处理器 连接清理前调用,Stop 内部 flush 剩余数据并等待完成

type MessageRecordOutbox

type MessageRecordOutbox struct {
	// contains filtered or unexported fields
}

MessageRecordOutbox 消息发送记录攒批 outbox

func NewMessageRecordOutbox

func NewMessageRecordOutbox(provider MessageRecordSinkProvider, queueSize, batchSize int, flushInterval time.Duration) *MessageRecordOutbox

NewMessageRecordOutbox 创建消息记录攒批 outbox 并启动后台 flush 协程

func (*MessageRecordOutbox) DroppedCount

func (o *MessageRecordOutbox) DroppedCount() int64

DroppedCount 返回累计丢弃条目数(队列满时丢弃,监控背压用)

func (*MessageRecordOutbox) OnFlushed

func (o *MessageRecordOutbox) OnFlushed(fn func(keys []models.MessageRecordKey))

OnFlushed 注册 flush 成功回调(每批仅对状态仍为 sending 的记录回调,供 ACK 超时注册)

func (*MessageRecordOutbox) Stop

func (o *MessageRecordOutbox) Stop()

Stop 停止后台协程并 flush 剩余记录(SafeShutdown 在 Hub cancel 前调用)

func (*MessageRecordOutbox) Submit

func (o *MessageRecordOutbox) Submit(record *models.MessageSendRecord) bool

Submit 提交一条待落库记录(同 key 重复提交以最新为准:重试路径复用同一 messageID) 队列满时该条丢弃并告警(极端背压下的最终一致性语义,与 statusUpdater 对齐)

func (*MessageRecordOutbox) UpdateStatusIfPending

func (o *MessageRecordOutbox) UpdateStatusIfPending(key models.MessageRecordKey, status models.MessageSendStatus, reason models.FailureReason, errMsg string) bool

UpdateStatusIfPending 记录尚未落库时直接合并状态更新(消除攒批竞态) 返回 true 表示已合并(调用方跳过 statusUpdater 的 UPDATE 提交)

type MessageRecordSinkProvider

type MessageRecordSinkProvider interface {
	GetMessageSink() spi.MessageSink
	GetLogger() spi.Logger
}

MessageRecordSinkProvider outbox 依赖端口(窄接口:sink + logger,hub 与测试桩均可实现)

type MessageStatsBatcher

type MessageStatsBatcher struct {
	// contains filtered or unexported fields
}

MessageStatsBatcher 消息统计批量更新器

func NewMessageStatsBatcher

func NewMessageStatsBatcher(hub StorageBatchWriter, queueSize, batchSize int, flushInterval time.Duration) *MessageStatsBatcher

NewMessageStatsBatcher 创建消息统计批量更新器

func (*MessageStatsBatcher) Acquire

Acquire 获取池化统计条目(已清零,填充字段后经 Submit 入队) 高频路径(广播扇出逐接收者统计)以 Acquire+Submit 替代字面量构造,消除逐条堆分配; 清零在 Acquire 时执行(归还时不触碰字段),防上一轮残留计数串扰 所有权契约:Submit 后条目归批量器所有(flush 后回池复用),调用方不得继续持有使用, 同一指针仅可 Submit 一次(重复入队会破坏池内唯一性)

func (*MessageStatsBatcher) Stop

func (u *MessageStatsBatcher) Stop()

Stop 停止更新器,flush 剩余数据后退出

func (*MessageStatsBatcher) Submit

func (u *MessageStatsBatcher) Submit(item *StatsIncrementItem) bool

Submit 非阻塞提交统计增量 队列满时返回 false(统计数据可丢失,非核心路径),条目即时归还池

type MessageStatusUpdater

type MessageStatusUpdater struct {
	// contains filtered or unexported fields
}

MessageStatusUpdater 消息状态批量更新器

func NewMessageStatusUpdater

func NewMessageStatusUpdater(hub StorageBatchWriter, queueSize, batchSize int, flushInterval time.Duration) *MessageStatusUpdater

NewMessageStatusUpdater 创建消息状态批量更新器

func (*MessageStatusUpdater) Stop

func (u *MessageStatusUpdater) Stop()

Stop 停止更新器,flush 剩余数据后退出

func (*MessageStatusUpdater) Submit

func (u *MessageStatusUpdater) Submit(item *StatusUpdateItem) bool

Submit 非阻塞提交状态更新 队列满时返回 false(调用方可记 metric 或降级处理)

type ObserverNotificationBatcher

type ObserverNotificationBatcher struct {
	// contains filtered or unexported fields
}

ObserverNotificationBatcher 观察者通知批量处理器

func NewObserverNotificationBatcher

func NewObserverNotificationBatcher(hub ObserverNotifier, queueSize, batchSize int, flushInterval time.Duration) *ObserverNotificationBatcher

NewObserverNotificationBatcher 创建观察者通知批量处理器

func (*ObserverNotificationBatcher) DroppedCount

func (b *ObserverNotificationBatcher) DroppedCount() int64

DroppedCount 返回累计丢弃的观察者通知数(队列满时丢弃)

func (*ObserverNotificationBatcher) Flush

Flush 批量处理观察者通知 逐条执行本地观察者投递 + 跨节点广播,消除 per-message goroutine

func (*ObserverNotificationBatcher) Hub

Hub 返回本批处理器绑定的通知端口(观测/测试断言用)

func (*ObserverNotificationBatcher) Processor

Processor 返回底层批处理器(观测/测试断言用)

func (*ObserverNotificationBatcher) Stop

func (b *ObserverNotificationBatcher) Stop()

Stop 停止处理器,flush 剩余数据后退出

func (*ObserverNotificationBatcher) Submit

func (b *ObserverNotificationBatcher) Submit(msg *models.HubMessage, namespace string, groupIDs []string) bool

Submit 非阻塞提交观察者通知 msg 由 BatchProcessor.WithClone 自动深拷贝,调用方无需关心数据隔离 队列满时返回 false(观察者通知可丢失,非核心路径)

type ObserverNotifier

type ObserverNotifier interface {
	// NotifyObserversDirect 直接通知观察者(不经批处理队列,避免递归入队)
	NotifyObserversDirect(msg *models.HubMessage, namespace string, groupIDs []string)
}

ObserverNotifier 观察者通知能力(ObserverNotificationBatcher 所需)

type ObserverNotifyItem

type ObserverNotifyItem struct {
	Msg       *models.HubMessage
	Namespace string
	GroupIDs  []string
}

ObserverNotifyItem 观察者通知条目(内部使用) ObserverNotifyItem 观察者通知条目

type StatsIncrementItem

type StatsIncrementItem struct {
	ConnectionID     string
	MessagesSent     int64
	MessagesReceived int64
	BytesSent        int64
	BytesReceived    int64
}

StatsIncrementItem 统计增量条目(Hub 侧构造)

type StatusUpdateItem

type StatusUpdateItem struct {
	MessageID string
	Receiver  string                   // 接收者ID(广播类记录为空串)
	Status    models.MessageSendStatus // 消息发送状态
	Reason    models.FailureReason     // 失败原因
	ErrMsg    string                   // 错误信息
}

StatusUpdateItem 消息状态更新项(Hub 侧构造) Receiver 参与 MessageSink 复合键定位:P2P 场景下同一 message_id 为每个 receiver 各建一条记录,缺少 receiver 会造成多 receiver 状态相互覆盖

type StorageBatchWriter

type StorageBatchWriter interface {
	// Context 返回 Hub 生命周期上下文(仅用于日志关联)
	Context() context.Context
	// GetLogger 日志器
	GetLogger() spi.Logger

	// GetConnectionRecordRepo 连接记录仓储(心跳时间戳落 connect 表)
	GetConnectionRecordRepo() spi.ConnectionStore
	// GetConnectionQualityRepository 连接质量仓储(心跳统计 / 消息统计)
	GetConnectionQualityRepository() spi.ConnectionQualityStore
	// GetMessageRecordRepo 消息记录仓储(消息状态更新)
	GetMessageRecordRepo() spi.MessageSink
}

StorageBatchWriter 存储批量写入能力 (HeartbeatStatsUpdater / MessageStatsBatcher / MessageStatusUpdater 共同所需)

三者都是「攒批 → 单事务落库」的写放大抑制剂,能力面完全一致,故共用一个端口。

flush 刻意不使用 Context():三个更新器都用 context.WithTimeout(context.Background(), 5s),避免 Hub 关闭时最后一批 落库被 cancel 截断。Context() 仅供日志关联。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL