feat: add redis cache and fanout support

This commit is contained in:
richjohnrambo
2026-08-25 20:54:03 +08:00
parent 07376c70e3
commit f900a08a01
68 changed files with 3949 additions and 5696 deletions
+47 -14
View File
@@ -45,7 +45,7 @@ type Topic struct {
touched time.Time
// Server-side ID of the last data message
lastID int
lastID int64
// ID of the deletion operation. Not an ID of the message.
delID int
@@ -142,8 +142,8 @@ type perUserData struct {
online int
// Last t.lastId reported by user through {pres} as received or read
recvID int
readID int
recvID int64
readID int64
// ID of the latest Delete operation
delID int
@@ -646,14 +646,14 @@ func (t *Topic) handleServerMsg(msg *ServerComMessage) {
logs.Warn.Printf("topic[%s]: rejected cross-tenant server message: topic=%d message=%d", t.name, t.tenantID, msg.TenantID)
return
}
// Server-generated message: {info} or {pres}.
// Server-generated message: {data}, {info} or {pres}.
if t.isInactive() {
// Ignore message - the topic is paused or being deleted.
return
}
if msg.Pres != nil {
t.handlePresence(msg)
} else if msg.Info != nil {
} else if msg.Info != nil || msg.Data != nil {
t.broadcastToSessions(msg)
} else {
// TODO(gene): maybe remove this panic.
@@ -797,6 +797,11 @@ func (t *Topic) handleLeaveRequest(msg *ClientComMessage, sess *Session) {
if !sess.background {
pud.online--
t.perUser[uid] = pud
if t.cat == types.TopicCatGrp && pud.online == 0 {
if err := store.MarkTopicMemberOffline(t.tenantID, t.name, uid, currentNodeID()); err != nil {
logs.Warn.Printf("topic[%s]: failed to mark member offline in redis: %v", t.name, err)
}
}
}
} else if len(pssd.muids) > 0 {
// UID is zero: multiplexing session is dropped altogether.
@@ -805,6 +810,11 @@ func (t *Topic) handleLeaveRequest(msg *ClientComMessage, sess *Session) {
pud := t.perUser[uid]
pud.online--
t.perUser[uid] = pud
if t.cat == types.TopicCatGrp && pud.online == 0 {
if err := store.MarkTopicMemberOffline(t.tenantID, t.name, uid, currentNodeID()); err != nil {
logs.Warn.Printf("topic[%s]: failed to mark member offline in redis: %v", t.name, err)
}
}
}
} else if !sess.isCluster() {
logs.Warn.Panic("cannot determine uid: leave req", msg, sess)
@@ -1119,8 +1129,11 @@ func (t *Topic) saveAndBroadcastMessage(msg *ClientComMessage, asUid types.Uid,
return nil
}
markedReadBySender := saveResult != nil && saveResult.MarkedReadBySender
t.lastID++
if saveResult != nil && saveResult.SeqId > t.lastID {
t.lastID = saveResult.SeqId
} else {
t.lastID++
}
t.touched = msg.Timestamp
if userFound {
@@ -1163,6 +1176,11 @@ func (t *Topic) saveAndBroadcastMessage(msg *ClientComMessage, asUid types.Uid,
pluginMessage(t.tenantID, data.Data, plgActCreate)
t.broadcastToSessions(data)
if t.cat == types.TopicCatGrp {
if err := store.PublishGroupFanout(currentNodeID(), data); err != nil {
logs.Warn.Printf("topic[%s]: failed to publish redis group fanout: %v", t.name, err)
}
}
// sendPush will update unread message count and send push notification.
if pushRcpt := t.pushForData(asUid, data.Data, markedReadBySender); pushRcpt != nil {
@@ -1332,7 +1350,7 @@ func (t *Topic) handleNoteBroadcast(msg *ClientComMessage) {
return
}
var read, recv, unread, seq int
var read, recv, unread, seq int64
switch msg.Note.What {
case "read":
@@ -1593,8 +1611,14 @@ func (t *Topic) subscriptionReply(asChan bool, msg *ClientComMessage) error {
// The user is online in the topic. Increment the counter if notifications are not deferred.
if !msg.sess.background {
userData := t.perUser[asUid]
wasOffline := userData.online == 0
userData.online++
t.perUser[asUid] = userData
if t.cat == types.TopicCatGrp && wasOffline {
if err := store.MarkTopicMemberOnline(t.tenantID, t.name, asUid, currentNodeID()); err != nil {
logs.Warn.Printf("topic[%s]: failed to mark member online in redis: %v", t.name, err)
}
}
}
if t.cat == types.TopicCatGrp && msgsub.Newsub {
@@ -2342,7 +2366,7 @@ func (t *Topic) replyGetDesc(sess *Session, asUid types.Uid, _ bool, opts *MsgGe
// t.delID <= pud.delID; t.readID <= t.recvID <= t.lastID
desc.DelId = max(pud.delID, t.delID)
desc.ReadSeqId = pud.readID
desc.RecvSeqId = max(pud.recvID, pud.readID)
desc.RecvSeqId = max64(pud.recvID, pud.readID)
} else {
// Send some sane value of touched.
desc.TouchedAt = &t.updated
@@ -2755,12 +2779,18 @@ func (t *Topic) replyGetSub(sess *Session, asUid types.Uid, authLevel auth.Level
mts.Topic = with
canViewLastSeen = imCanViewLastSeen(t.tenantID, asUid, types.ParseUserId(with))
mts.Online = t.perSubs[with].online && !deleted && presencer
if !deleted && presencer {
mts.Online = redisUserOnline(t.tenantID, types.ParseUserId(with), mts.Online)
}
} else if strings.HasPrefix(sub.Topic, "slf") {
mts.Topic = "slf"
// Not reporting Online as it makes no sense for slf.
} else {
mts.Topic = sub.Topic
mts.Online = t.perSubs[sub.Topic].online && !deleted && presencer
if !deleted && presencer && strings.HasPrefix(sub.Topic, "usr") {
mts.Online = redisUserOnline(t.tenantID, types.ParseUserId(sub.Topic), mts.Online)
}
}
if !deleted && !banned {
@@ -2808,6 +2838,9 @@ func (t *Topic) replyGetSub(sess *Session, asUid types.Uid, authLevel auth.Level
if t.cat == types.TopicCatGrp {
pud := t.perUser[uid]
mts.Online = pud.online > 0 && presencer
if presencer {
mts.Online = redisUserOnline(t.tenantID, uid, mts.Online)
}
}
}
}
@@ -3392,7 +3425,7 @@ func (t *Topic) replyDelMsg(sess *Session, asUid types.Uid, asChan bool, msg *Cl
if dq.HiId == 0 {
count++
} else {
count += dq.HiId - dq.LowId
count += int(dq.HiId - dq.LowId)
}
ranges = append(ranges, types.Range{Low: dq.LowId, Hi: dq.HiId})
@@ -3440,7 +3473,7 @@ func (t *Topic) replyDelMsg(sess *Session, asUid types.Uid, asChan bool, msg *Cl
unreadDeleted := calculateUnreadInRanges(pud.readID, t.lastID, ranges)
if unreadDeleted > 0 {
// Decrease unread count (negative value)
usersUpdateUnread(t.tenantID, uid, -unreadDeleted, true)
usersUpdateUnread(t.tenantID, uid, -int64(unreadDeleted), true)
}
}
}
@@ -4183,7 +4216,7 @@ func topicNameForUser(name string, uid types.Uid, isChan bool) string {
// calculateUnreadInRanges calculates how many unread messages are within the given ranges.
// unreadStart is the first unread message SeqId (readID + 1), unreadEnd is the last possible message SeqId.
// Assumes ranges are sorted by Low ascending.
func calculateUnreadInRanges(readID, lastID int, ranges []types.Range) int {
func calculateUnreadInRanges(readID, lastID int64, ranges []types.Range) int {
if readID >= lastID {
// No unread messages
return 0
@@ -4207,11 +4240,11 @@ func calculateUnreadInRanges(readID, lastID int, ranges []types.Range) int {
}
// Find intersection of [unreadStart, unreadEnd] and [rangeStart, rangeEnd)
intersectionStart := max(unreadStart, rangeStart)
intersectionStart := max64(unreadStart, rangeStart)
intersectionEnd := min(unreadEnd+1, rangeEnd) // +1 because unreadEnd is inclusive
if intersectionStart < intersectionEnd {
count += intersectionEnd - intersectionStart
count += int(intersectionEnd - intersectionStart)
}
}