Files
backend/internal/cache/keys.go

260 lines
8.5 KiB
Go
Raw Normal View History

package cache
import (
"fmt"
)
// 缓存键前缀常量
const (
// 帖子相关
PrefixPostList = "posts:list"
PrefixPost = "posts:detail"
// HotRankZSet 热门榜有序集合member=post_idscore=归一化热度,仅存 TopN
HotRankZSet = "posts:hot:zset"
// HotRankTopIDs 热门榜有序 post_id 列表JSON []string供分层缓存本地层命中
HotRankTopIDs = "posts:hot:top_ids"
// 会话相关
PrefixConversationList = "conversations:list"
PrefixConversationDetail = "conversations:detail"
// 群组相关
PrefixGroupMembers = "groups:members"
PrefixGroupInfo = "groups:info"
// 未读数相关
PrefixUnreadSystem = "unread:system"
PrefixUnreadConversation = "unread:conversation"
PrefixUnreadDetail = "unread:detail"
feat(core): optimize performance and reliability through batching and redis-backed unread counts This commit introduces several significant architectural improvements to enhance system performance, scalability, and reliability: - **Performance Optimization (N+1 Problem Resolution)**: Implemented batching mechanisms across `MessageHandler`, `ChatService`, `GroupService`, `MessageService`, and `UserService`. This replaces multiple individual database/cache queries with single batch operations for fetching unread counts, last messages, participants, and user information. - **Enhanced Unread Count Management**: Migrated unread count tracking to a Redis Hash-based approach (`unread:hash:{userID}`). This allows for atomic increments/decrements and efficient retrieval of both individual conversation unread counts and total unread counts for a user. - **Improved Message Sequencing**: Integrated Redis-based sequence (`seq`) pre-allocation for messages to ensure strict ordering and reduce database contention during high-concurrency message creation. - **Push Service Reliability**: Refactored the push notification system to include persistent `PushRecord` tracking. Added a recovery mechanism (`recoverPendingPushes`) to reload pending notifications from the database upon service startup, ensuring better delivery guarantees. - **WebSocket Reliability**: Updated the WebSocket hub to move away from in-memory history replay in favor of a client-driven synchronization model (`sync_required` event and `ack` handling), reducing memory overhead and improving connection stability. - **Cache Layer Enhancements**: Added `HIncrBy` and `IncrBySeq` to the `Cache` interface and its implementations (`RedisCache`, `LayeredCache`) to support the new unread and sequence management logic.
2026-05-04 18:31:03 +08:00
PrefixUnreadHash = "unread:hash"
PrefixUnreadTotal = "unread:total"
// 已读位置相关OpenIM 风格:缓存 hasReadSeq用于 O(1) 未读数计算)
PrefixUserReadSeq = "user_read_seq"
// 用户相关
PrefixUserInfo = "users:info"
PrefixUserMe = "users:me"
// 频道相关(全量列表一条缓存,供 /channels、帖子 channel 填充共用)
PrefixChannelsAllList = "channels:all_list"
// 消息缓存相关
refactor: improve system stability, performance, and code structure This commit introduces several architectural improvements and optimizations across the codebase: - **Performance & Reliability**: - Implemented Redis pipelining in `ConversationCache.CacheMessage` to reduce network round-trips. - Added a circuit breaker to the JPush client to prevent cascading failures. - Introduced batch deletion and batch member addition capabilities in repositories. - Added message idempotency support using `client_msg_id` and a Redis-based cache. - Optimized WebSocket handling with connection limits (total and per-user) and improved error logging. - **Code Refactoring**: - Refactored `Router` to use a `RouterDeps` struct, simplifying the constructor and improving maintainability. - Unified model ID generation logic using new `id_helper.go` (supporting UUID and Snowflake). - Standardized JSON serialization/deserialization in models using `json_helper.go`. - Refactored DTO conversion logic, specifically for `UserResponse` (using functional options) and `Report` responses. - Removed redundant/deprecated DTOs like `PostDetailResponse` and `TradeItemDetailResponse`. - **Cache Improvements**: - Enhanced `LayeredCache` with `SetRaw` to avoid double-encoding when promoting values from Redis to local cache. - Added `DeleteBatch` support to the cache interface. - **Other Changes**: - Cleaned up `config.go` by removing redundant default values and explicit environment variable overrides. - Improved WebSocket registration flow to handle connection limits gracefully.
2026-05-04 13:07:03 +08:00
keyPrefixMsgHash = "msg_hash" // 消息详情 Hash
keyPrefixMsgIndex = "msg_index" // 消息索引 Sorted Set
keyPrefixMsgCount = "msg_count" // 消息计数
keyPrefixMsgSeq = "msg_seq" // Seq 计数器
keyPrefixMsgPage = "msg_page" // 分页缓存
keyPrefixMsgIdempotent = "msg_idem" // 消息幂等键
)
// PostListKey 生成帖子列表缓存键
// postType: 帖子类型 (hot, follow, latest)
// page: 页码
// pageSize: 每页数量
// userID: 用户维度(仅在个性化列表如 follow 场景使用)
// HotRankZSetKey 热门榜 Redis ZSET 逻辑键(仅存 TopN会经 normalizeKey 加前缀)
func HotRankZSetKey() string {
return HotRankZSet
}
// HotRankTopIDsKey 热门榜 Top 顺序 ID 列表键(会经 normalizeKey 加前缀)
func HotRankTopIDsKey() string {
return HotRankTopIDs
}
func HotRankZSetChannelKey(channelID string) string {
return fmt.Sprintf("%s:ch:%s", HotRankZSet, channelID)
}
func HotRankTopIDsChannelKey(channelID string) string {
return fmt.Sprintf("%s:ch:%s", HotRankTopIDs, channelID)
}
func PostListKey(postType string, userID string, page, pageSize int) string {
if userID == "" {
return fmt.Sprintf("%s:%s:%d:%d", PrefixPostList, postType, page, pageSize)
}
return fmt.Sprintf("%s:%s:%s:%d:%d", PrefixPostList, postType, userID, page, pageSize)
}
// PostDetailKey 生成帖子详情缓存键
func PostDetailKey(postID string) string {
return fmt.Sprintf("%s:%s", PrefixPost, postID)
}
// ConversationListKey 生成会话列表缓存键
func ConversationListKey(userID string, page, pageSize int) string {
return fmt.Sprintf("%s:%s:%d:%d", PrefixConversationList, userID, page, pageSize)
}
// ConversationDetailKey 生成会话详情缓存键
func ConversationDetailKey(conversationID, userID string) string {
return fmt.Sprintf("%s:%s:%s", PrefixConversationDetail, conversationID, userID)
}
// GroupMembersKey 生成群组成员缓存键
func GroupMembersKey(groupID string, page, pageSize int) string {
return fmt.Sprintf("%s:%s:page:%d:size:%d", PrefixGroupMembers, groupID, page, pageSize)
}
// GroupMembersAllKey 生成群组全量成员ID列表缓存键
func GroupMembersAllKey(groupID string) string {
return fmt.Sprintf("%s:all:%s", PrefixGroupMembers, groupID)
}
// GroupInfoKey 生成群组信息缓存键
func GroupInfoKey(groupID string) string {
return fmt.Sprintf("%s:%s", PrefixGroupInfo, groupID)
}
// UnreadSystemKey 生成系统消息未读数缓存键
func UnreadSystemKey(userID string) string {
return fmt.Sprintf("%s:%s", PrefixUnreadSystem, userID)
}
// UnreadConversationKey 生成会话未读总数缓存键
func UnreadConversationKey(userID string) string {
return fmt.Sprintf("%s:%s", PrefixUnreadConversation, userID)
}
// UnreadDetailKey 生成单个会话未读数缓存键
func UnreadDetailKey(userID, conversationID string) string {
return fmt.Sprintf("%s:%s:%s", PrefixUnreadDetail, userID, conversationID)
}
feat(core): optimize performance and reliability through batching and redis-backed unread counts This commit introduces several significant architectural improvements to enhance system performance, scalability, and reliability: - **Performance Optimization (N+1 Problem Resolution)**: Implemented batching mechanisms across `MessageHandler`, `ChatService`, `GroupService`, `MessageService`, and `UserService`. This replaces multiple individual database/cache queries with single batch operations for fetching unread counts, last messages, participants, and user information. - **Enhanced Unread Count Management**: Migrated unread count tracking to a Redis Hash-based approach (`unread:hash:{userID}`). This allows for atomic increments/decrements and efficient retrieval of both individual conversation unread counts and total unread counts for a user. - **Improved Message Sequencing**: Integrated Redis-based sequence (`seq`) pre-allocation for messages to ensure strict ordering and reduce database contention during high-concurrency message creation. - **Push Service Reliability**: Refactored the push notification system to include persistent `PushRecord` tracking. Added a recovery mechanism (`recoverPendingPushes`) to reload pending notifications from the database upon service startup, ensuring better delivery guarantees. - **WebSocket Reliability**: Updated the WebSocket hub to move away from in-memory history replay in favor of a client-driven synchronization model (`sync_required` event and `ack` handling), reducing memory overhead and improving connection stability. - **Cache Layer Enhancements**: Added `HIncrBy` and `IncrBySeq` to the `Cache` interface and its implementations (`RedisCache`, `LayeredCache`) to support the new unread and sequence management logic.
2026-05-04 18:31:03 +08:00
// UnreadHashKey 生成用户未读数 Hash 缓存键
func UnreadHashKey(userID string) string {
return fmt.Sprintf("%s:%s", PrefixUnreadHash, userID)
}
// UnreadTotalKey 生成用户总未读数计数器键
func UnreadTotalKey(userID string) string {
return fmt.Sprintf("%s:%s", PrefixUnreadTotal, userID)
}
// UserInfoKey 生成用户信息缓存键
func UserInfoKey(userID string) string {
return fmt.Sprintf("%s:%s", PrefixUserInfo, userID)
}
// UserMeKey 生成当前用户信息缓存键
func UserMeKey(userID string) string {
return fmt.Sprintf("%s:%s", PrefixUserMe, userID)
}
// ChannelsAllListKey 频道全量列表缓存键(含未启用,供 MapByIDs 解析历史帖子 channel
func ChannelsAllListKey() string {
return PrefixChannelsAllList
}
// InvalidateChannelList 失效频道列表缓存(管理端增删改频道后调用)
func InvalidateChannelList(c Cache) {
c.Delete(ChannelsAllListKey())
}
// InvalidatePostList 失效帖子列表缓存
func InvalidatePostList(cache Cache) {
cache.DeleteByPrefix(PrefixPostList)
}
// InvalidatePostDetail 失效帖子详情缓存
func InvalidatePostDetail(cache Cache, postID string) {
cache.Delete(PostDetailKey(postID))
}
// InvalidateConversationList 失效会话列表缓存
func InvalidateConversationList(cache Cache, userID string) {
cache.DeleteByPrefix(PrefixConversationList + ":" + userID + ":")
}
// InvalidateConversationDetail 失效会话详情缓存
func InvalidateConversationDetail(cache Cache, conversationID, userID string) {
cache.Delete(ConversationDetailKey(conversationID, userID))
}
// InvalidateGroupMembers 失效群组成员缓存
func InvalidateGroupMembers(cache Cache, groupID string) {
cache.DeleteByPrefix(PrefixGroupMembers + ":" + groupID)
}
// InvalidateGroupInfo 失效群组信息缓存
func InvalidateGroupInfo(cache Cache, groupID string) {
cache.Delete(GroupInfoKey(groupID))
}
// InvalidateUnreadSystem 失效系统消息未读数缓存
func InvalidateUnreadSystem(cache Cache, userID string) {
cache.Delete(UnreadSystemKey(userID))
}
// InvalidateUnreadConversation 失效会话未读数缓存
func InvalidateUnreadConversation(cache Cache, userID string) {
cache.Delete(UnreadConversationKey(userID))
}
// InvalidateUnreadDetail 失效单个会话未读数缓存
func InvalidateUnreadDetail(cache Cache, userID, conversationID string) {
cache.Delete(UnreadDetailKey(userID, conversationID))
}
// InvalidateUserInfo 失效用户信息缓存
func InvalidateUserInfo(cache Cache, userID string) {
cache.Delete(UserInfoKey(userID))
cache.Delete(UserMeKey(userID))
}
// ============================================================
// 消息缓存 Key 生成函数
// ============================================================
// MessageHashKey 消息详情 Hash key
func MessageHashKey(convID string) string {
return fmt.Sprintf("%s:%s", keyPrefixMsgHash, convID)
}
// MessageIndexKey 消息索引 Sorted Set key
func MessageIndexKey(convID string) string {
return fmt.Sprintf("%s:%s", keyPrefixMsgIndex, convID)
}
// MessageCountKey 消息计数 key
func MessageCountKey(convID string) string {
return fmt.Sprintf("%s:%s", keyPrefixMsgCount, convID)
}
// MessageSeqKey Seq 计数器 key
func MessageSeqKey(convID string) string {
return fmt.Sprintf("%s:%s", keyPrefixMsgSeq, convID)
}
// MessagePageKey 分页缓存 key
func MessagePageKey(convID string, page, pageSize int) string {
return fmt.Sprintf("%s:%s:%d:%d", keyPrefixMsgPage, convID, page, pageSize)
}
// InvalidateMessagePages 失效会话消息分页缓存
func InvalidateMessagePages(cache Cache, conversationID string) {
cache.DeleteByPrefix(fmt.Sprintf("%s:%s:", keyPrefixMsgPage, conversationID))
}
refactor: improve system stability, performance, and code structure This commit introduces several architectural improvements and optimizations across the codebase: - **Performance & Reliability**: - Implemented Redis pipelining in `ConversationCache.CacheMessage` to reduce network round-trips. - Added a circuit breaker to the JPush client to prevent cascading failures. - Introduced batch deletion and batch member addition capabilities in repositories. - Added message idempotency support using `client_msg_id` and a Redis-based cache. - Optimized WebSocket handling with connection limits (total and per-user) and improved error logging. - **Code Refactoring**: - Refactored `Router` to use a `RouterDeps` struct, simplifying the constructor and improving maintainability. - Unified model ID generation logic using new `id_helper.go` (supporting UUID and Snowflake). - Standardized JSON serialization/deserialization in models using `json_helper.go`. - Refactored DTO conversion logic, specifically for `UserResponse` (using functional options) and `Report` responses. - Removed redundant/deprecated DTOs like `PostDetailResponse` and `TradeItemDetailResponse`. - **Cache Improvements**: - Enhanced `LayeredCache` with `SetRaw` to avoid double-encoding when promoting values from Redis to local cache. - Added `DeleteBatch` support to the cache interface. - **Other Changes**: - Cleaned up `config.go` by removing redundant default values and explicit environment variable overrides. - Improved WebSocket registration flow to handle connection limits gracefully.
2026-05-04 13:07:03 +08:00
// MessageIdempotentKey 消息幂等键 (clientMsgID -> server MsgID)
func MessageIdempotentKey(senderID, clientMsgID string) string {
return fmt.Sprintf("%s:%s:%s", keyPrefixMsgIdempotent, senderID, clientMsgID)
}
// PushDedupKey 推送去重键,防止同一消息重复推送给同一用户
func PushDedupKey(userID, messageID string) string {
return fmt.Sprintf("push_dedup:%s:%s", userID, messageID)
}
// UserReadSeqKey 用户已读位置缓存键OpenIM 风格 SEQ_USER_READ:{convID}:{userID}
func UserReadSeqKey(convID, userID string) string {
return fmt.Sprintf("%s:%s:%s", PrefixUserReadSeq, convID, userID)
}
// ConvMaxSeqKey 会话最大 seq 缓存键
func ConvMaxSeqKey(convID string) string {
return fmt.Sprintf("%s:%s", keyPrefixMsgSeq, convID)
}