Refactor Kafka publishing without DB outbox
This commit is contained in:
@@ -44,20 +44,6 @@ func ensureIMHTTPInfrastructureTables(exec sqlExecutor) error {
|
||||
INDEX idx_im_kafka_consume_log_biz(biz_type, biz_id),
|
||||
INDEX idx_im_kafka_consume_log_topic(source_topic, id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
|
||||
`CREATE TABLE IF NOT EXISTS im_kafka_outbox (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT, event_id VARCHAR(96) NOT NULL,
|
||||
topic VARCHAR(128) NOT NULL, event_type VARCHAR(96) NOT NULL,
|
||||
biz_type VARCHAR(64) NOT NULL DEFAULT '', biz_id VARCHAR(128) NOT NULL DEFAULT '',
|
||||
key_value VARCHAR(256) NOT NULL DEFAULT '', envelope JSON NOT NULL,
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'pending', attempts INT NOT NULL DEFAULT 0,
|
||||
next_retry_at DATETIME(3), sent_at DATETIME(3), last_reason VARCHAR(64) NOT NULL DEFAULT '',
|
||||
last_error VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(id), UNIQUE INDEX uk_im_kafka_outbox_event(event_id),
|
||||
INDEX idx_im_kafka_outbox_pending(status, next_retry_at, id),
|
||||
INDEX idx_im_kafka_outbox_biz(biz_type, biz_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
|
||||
`CREATE TABLE IF NOT EXISTS im_kafka_dead_letters (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT, dead_id VARCHAR(96) NOT NULL,
|
||||
event_id VARCHAR(96) NOT NULL, parent_event_id VARCHAR(96) NOT NULL DEFAULT '',
|
||||
|
||||
@@ -12,15 +12,14 @@ func TestEnsureIMHTTPInfrastructureTablesUsesSelectedTenantDatabase(t *testing.T
|
||||
if err := ensureIMHTTPInfrastructureTables(exec); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(exec.statements) != 8 {
|
||||
t.Fatalf("created %d infrastructure tables, want 8", len(exec.statements))
|
||||
if len(exec.statements) != 7 {
|
||||
t.Fatalf("created %d infrastructure tables, want 7", len(exec.statements))
|
||||
}
|
||||
joined := strings.ToLower(strings.Join(exec.statements, "\n"))
|
||||
for _, table := range []string{
|
||||
"im_http_idempotency",
|
||||
"im_business_events_outbox",
|
||||
"im_kafka_consume_log",
|
||||
"im_kafka_outbox",
|
||||
"im_kafka_dead_letters",
|
||||
"im_realname_appeals",
|
||||
"im_conversation_settings",
|
||||
@@ -33,4 +32,7 @@ func TestEnsureIMHTTPInfrastructureTablesUsesSelectedTenantDatabase(t *testing.T
|
||||
if strings.Contains(joined, "tinode.") || strings.Contains(joined, "create database") || strings.Contains(joined, "tenant_id") {
|
||||
t.Fatalf("infrastructure schema must stay inside the selected tenant database: %s", joined)
|
||||
}
|
||||
if strings.Contains(joined, "im_kafka_outbox") {
|
||||
t.Fatalf("kafka outbox table must not be created: %s", joined)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,140 +512,6 @@ func (a *adapter) TenantKafkaDeadLetterMarkReplay(id t.TenantID, deadID, resolve
|
||||
return a.TenantKafkaDeadLetterGet(id, deadID)
|
||||
}
|
||||
|
||||
func (a *adapter) TenantKafkaOutboxCreate(id t.TenantID, record *t.KafkaOutboxRecord) error {
|
||||
tenantAdp, err := a.infrastructureAdapter(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if record == nil || record.EventID == "" || record.Topic == "" || record.EventType == "" || len(record.Envelope) == 0 {
|
||||
return t.ErrMalformed
|
||||
}
|
||||
if tenantAdp != a {
|
||||
return tenantAdp.TenantKafkaOutboxCreate(id, record)
|
||||
}
|
||||
now := t.TimeNow()
|
||||
_, err = a.db.Exec(`INSERT INTO im_kafka_outbox
|
||||
(event_id,topic,event_type,biz_type,biz_id,key_value,envelope,status,next_retry_at,created_at,updated_at)
|
||||
VALUES(?,?,?,?,?,?,?,?,?,?,?)
|
||||
ON DUPLICATE KEY UPDATE updated_at=updated_at`,
|
||||
record.EventID, record.Topic, record.EventType, record.BizType, record.BizID, record.Key,
|
||||
record.Envelope, t.KafkaOutboxPending, record.NextRetryAt, now, now)
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *adapter) TenantKafkaOutboxListPending(id t.TenantID, limit int, now time.Time) ([]t.KafkaOutboxRecord, error) {
|
||||
tenantAdp, err := a.infrastructureAdapter(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if limit <= 0 {
|
||||
limit = 100
|
||||
}
|
||||
if tenantAdp != a {
|
||||
return tenantAdp.TenantKafkaOutboxListPending(id, limit, now)
|
||||
}
|
||||
rows := []struct {
|
||||
ID int64 `db:"id"`
|
||||
EventID string `db:"event_id"`
|
||||
Topic string `db:"topic"`
|
||||
EventType string `db:"event_type"`
|
||||
BizType string `db:"biz_type"`
|
||||
BizID string `db:"biz_id"`
|
||||
Key string `db:"key_value"`
|
||||
Envelope []byte `db:"envelope"`
|
||||
Status string `db:"status"`
|
||||
Attempts int `db:"attempts"`
|
||||
NextRetryAt sql.NullTime `db:"next_retry_at"`
|
||||
SentAt sql.NullTime `db:"sent_at"`
|
||||
LastReason string `db:"last_reason"`
|
||||
LastError string `db:"last_error"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}{}
|
||||
err = a.db.Select(&rows, `SELECT id,event_id,topic,event_type,biz_type,biz_id,key_value,envelope,status,attempts,
|
||||
next_retry_at,sent_at,last_reason,last_error,created_at,updated_at
|
||||
FROM im_kafka_outbox
|
||||
WHERE status IN (?,?) AND (next_retry_at IS NULL OR next_retry_at<=?)
|
||||
ORDER BY id ASC LIMIT ?`, t.KafkaOutboxPending, t.KafkaOutboxFailed, now, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]t.KafkaOutboxRecord, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
rec := t.KafkaOutboxRecord{
|
||||
ID: row.ID,
|
||||
EventID: row.EventID,
|
||||
Topic: row.Topic,
|
||||
EventType: row.EventType,
|
||||
BizType: row.BizType,
|
||||
BizID: row.BizID,
|
||||
Key: row.Key,
|
||||
Envelope: row.Envelope,
|
||||
Status: row.Status,
|
||||
Attempts: row.Attempts,
|
||||
LastReason: row.LastReason,
|
||||
LastError: row.LastError,
|
||||
CreatedAt: row.CreatedAt,
|
||||
UpdatedAt: row.UpdatedAt,
|
||||
}
|
||||
if row.NextRetryAt.Valid {
|
||||
rec.NextRetryAt = &row.NextRetryAt.Time
|
||||
}
|
||||
if row.SentAt.Valid {
|
||||
rec.SentAt = &row.SentAt.Time
|
||||
}
|
||||
out = append(out, rec)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (a *adapter) TenantKafkaOutboxMarkSent(id t.TenantID, eventID string, now time.Time) error {
|
||||
tenantAdp, err := a.infrastructureAdapter(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if eventID == "" {
|
||||
return t.ErrMalformed
|
||||
}
|
||||
if tenantAdp != a {
|
||||
return tenantAdp.TenantKafkaOutboxMarkSent(id, eventID, now)
|
||||
}
|
||||
res, err := a.db.Exec(`UPDATE im_kafka_outbox SET status=?,sent_at=?,attempts=attempts+1,last_reason='',
|
||||
last_error='',updated_at=? WHERE event_id=? AND status<>?`,
|
||||
t.KafkaOutboxSent, now, now, eventID, t.KafkaOutboxSent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if affected, _ := res.RowsAffected(); affected == 0 {
|
||||
return t.ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *adapter) TenantKafkaOutboxMarkFailed(id t.TenantID, eventID, reason, message string, nextRetryAt, now time.Time) error {
|
||||
tenantAdp, err := a.infrastructureAdapter(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if eventID == "" {
|
||||
return t.ErrMalformed
|
||||
}
|
||||
if tenantAdp != a {
|
||||
return tenantAdp.TenantKafkaOutboxMarkFailed(id, eventID, reason, message, nextRetryAt, now)
|
||||
}
|
||||
res, err := a.db.Exec(`UPDATE im_kafka_outbox SET status=?,attempts=attempts+1,next_retry_at=?,
|
||||
last_reason=?,last_error=?,updated_at=? WHERE event_id=? AND status<>?`,
|
||||
t.KafkaOutboxFailed, nextRetryAt, truncateKafkaString(reason, 64), truncateKafkaString(message, 1000),
|
||||
now, eventID, t.KafkaOutboxSent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if affected, _ := res.RowsAffected(); affected == 0 {
|
||||
return t.ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func scanKafkaDeadLetter(rows *sqlx.Rows) (*t.KafkaDeadLetter, error) {
|
||||
var row struct {
|
||||
ID int64 `db:"id"`
|
||||
|
||||
Reference in New Issue
Block a user