Refactor Kafka publishing without DB outbox
This commit is contained in:
@@ -51,6 +51,7 @@ type groupMessageRuntime interface {
|
||||
MarkDone(tenantID types.TenantID, topic string, from types.Uid, clientMsgID string, seq int64, msgID string) error
|
||||
MarkFailed(tenantID types.TenantID, topic string, from types.Uid, clientMsgID string) error
|
||||
CacheHotMessage(tenantID types.TenantID, msg *types.Message) error
|
||||
CachePendingHotMessage(tenantID types.TenantID, msg *types.Message) error
|
||||
GetHotMessages(tenantID types.TenantID, topic string, opts *types.QueryOpt, maxLimit int) ([]types.Message, bool, error)
|
||||
InvalidateHotMessages(tenantID types.TenantID, topic string, ranges []types.Range) error
|
||||
MarkUserOnline(tenantID types.TenantID, uid types.Uid, sid, nodeID, device, userAgent string) error
|
||||
@@ -187,6 +188,27 @@ func DeleteCachedObject(tenantID types.TenantID, namespace, id string) error {
|
||||
return groupMsgRuntime.DeleteCachedObject(tenantID, namespace, id)
|
||||
}
|
||||
|
||||
func CacheHotMessage(tenantID types.TenantID, msg *types.Message) error {
|
||||
if groupMsgRuntime == nil || !tenantID.IsValid() || msg == nil || msg.Topic == "" || msg.SeqId <= 0 {
|
||||
return nil
|
||||
}
|
||||
return groupMsgRuntime.CacheHotMessage(tenantID, msg)
|
||||
}
|
||||
|
||||
func CachePendingHotMessage(tenantID types.TenantID, msg *types.Message) error {
|
||||
if groupMsgRuntime == nil || !tenantID.IsValid() || msg == nil || msg.Topic == "" || msg.SeqId <= 0 {
|
||||
return nil
|
||||
}
|
||||
return groupMsgRuntime.CachePendingHotMessage(tenantID, msg)
|
||||
}
|
||||
|
||||
func GetHotMessages(tenantID types.TenantID, topic string, opts *types.QueryOpt, maxLimit int) ([]types.Message, bool, error) {
|
||||
if groupMsgRuntime == nil || !tenantID.IsValid() || topic == "" {
|
||||
return nil, false, nil
|
||||
}
|
||||
return groupMsgRuntime.GetHotMessages(tenantID, topic, opts, maxLimit)
|
||||
}
|
||||
|
||||
func InvalidateHotMessages(tenantID types.TenantID, topic string, ranges []types.Range) error {
|
||||
if groupMsgRuntime == nil || !tenantID.IsValid() || topic == "" {
|
||||
return nil
|
||||
@@ -239,7 +261,7 @@ func initGroupMessageRuntime(raw json.RawMessage) error {
|
||||
cfg.KeyPrefix = "im"
|
||||
}
|
||||
if cfg.PendingTTL <= 0 {
|
||||
cfg.PendingTTL = 120
|
||||
cfg.PendingTTL = 300
|
||||
}
|
||||
if cfg.DoneTTL <= 0 {
|
||||
cfg.DoneTTL = 86400
|
||||
@@ -380,15 +402,25 @@ func (r *redisGroupMessageRuntime) MarkFailed(tenantID types.TenantID, topic str
|
||||
}
|
||||
|
||||
func (r *redisGroupMessageRuntime) CacheHotMessage(tenantID types.TenantID, msg *types.Message) error {
|
||||
return r.cacheHotMessage(tenantID, msg, time.Duration(r.cfg.HotMsgTTL)*time.Second)
|
||||
}
|
||||
|
||||
func (r *redisGroupMessageRuntime) CachePendingHotMessage(tenantID types.TenantID, msg *types.Message) error {
|
||||
return r.cacheHotMessage(tenantID, msg, time.Duration(r.cfg.PendingTTL)*time.Second)
|
||||
}
|
||||
|
||||
func (r *redisGroupMessageRuntime) cacheHotMessage(tenantID types.TenantID, msg *types.Message, ttl time.Duration) error {
|
||||
if msg == nil || msg.Topic == "" || msg.SeqId <= 0 {
|
||||
return types.ErrMalformed
|
||||
}
|
||||
if ttl <= 0 {
|
||||
return types.ErrMalformed
|
||||
}
|
||||
payload, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx := context.Background()
|
||||
ttl := time.Duration(r.cfg.HotMsgTTL) * time.Second
|
||||
seq := strconv.FormatInt(msg.SeqId, 10)
|
||||
pipe := r.client.Pipeline()
|
||||
pipe.Set(ctx, r.hotMsgKey(tenantID, msg.Topic, msg.SeqId), payload, ttl)
|
||||
|
||||
@@ -22,10 +22,6 @@ type HTTPInfrastructurePersistenceInterface interface {
|
||||
GetKafkaDeadLetter(types.TenantID, string) (*types.KafkaDeadLetter, error)
|
||||
MarkKafkaDeadLetter(types.TenantID, string, string, string, string, time.Time) error
|
||||
MarkKafkaDeadLetterReplay(types.TenantID, string, string, string, time.Time) (*types.KafkaDeadLetter, error)
|
||||
CreateKafkaOutbox(types.TenantID, *types.KafkaOutboxRecord) error
|
||||
ListPendingKafkaOutbox(types.TenantID, int, time.Time) ([]types.KafkaOutboxRecord, error)
|
||||
MarkKafkaOutboxSent(types.TenantID, string, time.Time) error
|
||||
MarkKafkaOutboxFailed(types.TenantID, string, string, string, time.Time, time.Time) error
|
||||
}
|
||||
|
||||
type httpInfrastructureMapper struct{}
|
||||
@@ -155,35 +151,3 @@ func (httpInfrastructureMapper) MarkKafkaDeadLetterReplay(tenantID types.TenantI
|
||||
}
|
||||
return infrastructure.TenantKafkaDeadLetterMarkReplay(tenantID, deadID, resolvedBy, note, now)
|
||||
}
|
||||
|
||||
func (httpInfrastructureMapper) CreateKafkaOutbox(tenantID types.TenantID, record *types.KafkaOutboxRecord) error {
|
||||
infrastructure, err := tenantHTTPInfrastructureAdapter(tenantID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return infrastructure.TenantKafkaOutboxCreate(tenantID, record)
|
||||
}
|
||||
|
||||
func (httpInfrastructureMapper) ListPendingKafkaOutbox(tenantID types.TenantID, limit int, now time.Time) ([]types.KafkaOutboxRecord, error) {
|
||||
infrastructure, err := tenantHTTPInfrastructureAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return infrastructure.TenantKafkaOutboxListPending(tenantID, limit, now)
|
||||
}
|
||||
|
||||
func (httpInfrastructureMapper) MarkKafkaOutboxSent(tenantID types.TenantID, eventID string, now time.Time) error {
|
||||
infrastructure, err := tenantHTTPInfrastructureAdapter(tenantID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return infrastructure.TenantKafkaOutboxMarkSent(tenantID, eventID, now)
|
||||
}
|
||||
|
||||
func (httpInfrastructureMapper) MarkKafkaOutboxFailed(tenantID types.TenantID, eventID, reason, message string, nextRetryAt, now time.Time) error {
|
||||
infrastructure, err := tenantHTTPInfrastructureAdapter(tenantID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return infrastructure.TenantKafkaOutboxMarkFailed(tenantID, eventID, reason, message, nextRetryAt, now)
|
||||
}
|
||||
|
||||
@@ -3443,6 +3443,10 @@ func PersistPreparedMessage(tenantID types.TenantID, msg *types.Message, attachm
|
||||
return Messages.PersistPrepared(tenantID, msg, attachmentURLs, readBySender)
|
||||
}
|
||||
|
||||
func NormalizeClientMsgID(head types.KVMap) (string, error) {
|
||||
return normalizeClientMsgID(head)
|
||||
}
|
||||
|
||||
func retryTenantMessageSaveAfterSeqConflict(tenantAdp adapter.TenantBusinessAdapter, tenantID types.TenantID, msg *types.Message) error {
|
||||
latest, err := tenantAdp.TenantMessageGetAll(tenantID, msg.Topic, types.ZeroUid, &types.QueryOpt{Before: 1 << 62, Limit: 1})
|
||||
if err != nil {
|
||||
|
||||
@@ -23,10 +23,6 @@ const (
|
||||
KafkaDeadStatusReplayed = "replayed"
|
||||
KafkaDeadStatusIgnored = "ignored"
|
||||
KafkaDeadStatusResolved = "resolved"
|
||||
|
||||
KafkaOutboxPending = "pending"
|
||||
KafkaOutboxSent = "sent"
|
||||
KafkaOutboxFailed = "failed"
|
||||
)
|
||||
|
||||
// HTTPIdempotencyRecord stores the first response for one authenticated write.
|
||||
@@ -115,26 +111,6 @@ type KafkaDeadLetterPage struct {
|
||||
NextCursor string
|
||||
}
|
||||
|
||||
// KafkaOutboxRecord stores a Kafka event waiting for reliable publication.
|
||||
type KafkaOutboxRecord struct {
|
||||
ID int64
|
||||
EventID string
|
||||
Topic string
|
||||
EventType string
|
||||
BizType string
|
||||
BizID string
|
||||
Key string
|
||||
Envelope []byte
|
||||
Status string
|
||||
Attempts int
|
||||
NextRetryAt *time.Time
|
||||
SentAt *time.Time
|
||||
LastReason string
|
||||
LastError string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
// StableEventID derives an idempotent event identifier from domain identity.
|
||||
func StableEventID(parts ...any) string {
|
||||
hash := sha256.New()
|
||||
|
||||
Reference in New Issue
Block a user