fix(go): protect kafka message processing during shutdown

This commit is contained in:
lingniu
2026-07-02 13:20:46 +08:00
parent a80eb2eab4
commit 640f70636d
6 changed files with 311 additions and 39 deletions

View File

@@ -150,19 +150,39 @@ func consumeKafka(ctx context.Context, logger interface {
logger.Error("kafka fetch failed", "error", err)
continue
}
var env envelope.FrameEnvelope
if err := json.Unmarshal(message.Value, &env); err != nil {
logger.Warn("skip invalid envelope json", "topic", message.Topic, "partition", message.Partition, "offset", message.Offset, "error", err)
_ = reader.CommitMessages(ctx, message)
continue
}
if err := repository.Update(ctx, env); err != nil {
logger.Error("redis realtime update failed", "topic", message.Topic, "partition", message.Partition, "offset", message.Offset, "event_id", env.StableEventID(), "error", err)
continue
}
if err := reader.CommitMessages(ctx, message); err != nil {
logger.Error("kafka commit failed", "topic", message.Topic, "partition", message.Partition, "offset", message.Offset, "error", err)
}
processRealtimeMessage(ctx, logger, repository, reader, message)
}
}
const kafkaMessageOperationTimeout = 30 * time.Second
type realtimeUpdater interface {
Update(context.Context, envelope.FrameEnvelope) error
}
type kafkaMessageCommitter interface {
CommitMessages(context.Context, ...kafka.Message) error
}
func processRealtimeMessage(ctx context.Context, logger interface {
Error(string, ...any)
Warn(string, ...any)
}, updater realtimeUpdater, committer kafkaMessageCommitter, message kafka.Message) {
messageCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), kafkaMessageOperationTimeout)
defer cancel()
var env envelope.FrameEnvelope
if err := json.Unmarshal(message.Value, &env); err != nil {
logger.Warn("skip invalid envelope json", "topic", message.Topic, "partition", message.Partition, "offset", message.Offset, "error", err)
_ = committer.CommitMessages(messageCtx, message)
return
}
if err := updater.Update(messageCtx, env); err != nil {
logger.Error("redis realtime update failed", "topic", message.Topic, "partition", message.Partition, "offset", message.Offset, "event_id", env.StableEventID(), "error", err)
return
}
if err := committer.CommitMessages(messageCtx, message); err != nil {
logger.Error("kafka commit failed", "topic", message.Topic, "partition", message.Partition, "offset", message.Offset, "error", err)
}
}

View File

@@ -0,0 +1,71 @@
package main
import (
"context"
"encoding/json"
"testing"
"github.com/segmentio/kafka-go"
"lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope"
)
func TestProcessRealtimeMessageUsesUncancelledContextForUpdateAndCommit(t *testing.T) {
parent, cancel := context.WithCancel(context.Background())
cancel()
env := envelope.FrameEnvelope{
Protocol: envelope.ProtocolJT808,
MessageID: "0x0200",
Phone: "13307795425",
EventTimeMS: 1782918600000,
ReceivedAtMS: 1782918600000,
ParseStatus: envelope.ParseOK,
}
payload, err := json.Marshal(env)
if err != nil {
t.Fatal(err)
}
updater := &contextCheckingRealtimeUpdater{}
committer := &contextCheckingMessageCommitter{}
processRealtimeMessage(parent, discardRealtimeLogger{}, updater, committer, kafka.Message{Value: payload})
if updater.ctxErr != nil {
t.Fatalf("updater saw cancelled context: %v", updater.ctxErr)
}
if committer.ctxErr != nil {
t.Fatalf("committer saw cancelled context: %v", committer.ctxErr)
}
if updater.count != 1 || committer.count != 1 {
t.Fatalf("updates=%d commits=%d", updater.count, committer.count)
}
}
type contextCheckingRealtimeUpdater struct {
ctxErr error
count int
}
func (u *contextCheckingRealtimeUpdater) Update(ctx context.Context, _ envelope.FrameEnvelope) error {
u.ctxErr = ctx.Err()
u.count++
return u.ctxErr
}
type contextCheckingMessageCommitter struct {
ctxErr error
count int
}
func (c *contextCheckingMessageCommitter) CommitMessages(ctx context.Context, _ ...kafka.Message) error {
c.ctxErr = ctx.Err()
c.count++
return c.ctxErr
}
type discardRealtimeLogger struct{}
func (discardRealtimeLogger) Info(string, ...any) {}
func (discardRealtimeLogger) Error(string, ...any) {}
func (discardRealtimeLogger) Warn(string, ...any) {}