chao 10a577c310
feat: Supports launching multiple monolithic instances, load-balanced by nginx, requiring only MongoDB and Redis at a minimum (#3759)
* fix: performance issues with Kafka caused by encapsulating the MQ interface

* fix: admin token in standalone mode

* fix: full id version

* fix: resolve deadlock in cache eviction and improve GetBatch implementation

* refactor: replace LongConn with ClientConn interface and simplify message handling

* refactor: replace LongConn with ClientConn interface and simplify message handling

* fix: seq use $setOnInsert for min_seq in conversation update

* feat: add error code for handled friend requests and improve error handling in friend operations

* refactor(msg): update regex pattern for conversationID to include a trailing colon

* refactor: streamline cache initialization and enhance queue engine configuration

* refactor: streamline cache initialization and enhance queue engine configuration

* feat: add RegisterIP to API config and implement Redis server registration

* feat(redis): implement standalone gateway registration with Redis

* chore: update openimsdk/tools dependency to v0.0.50-alpha.121

* fix: change timer to ticker for improved periodic execution

* refactor: replace Kafka topic configuration with mqbuild constants for improved readability

* feat(redis): implement Redis-based locking mechanism for cron tasks
2026-07-03 10:28:06 +00:00

174 lines
5.1 KiB
Go

package mqbuild
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/tools/mq"
"github.com/openimsdk/tools/mq/kafka"
"github.com/openimsdk/tools/mq/redismq"
"github.com/openimsdk/tools/mq/simmq"
)
type Builder interface {
GetTopicProducer(ctx context.Context, topic string) (mq.Producer, error)
GetTopicConsumer(ctx context.Context, topic string) (mq.Consumer, error)
}
const (
TopicToRedis = "toRedis"
TopicToMongo = "toMongo"
TopicToPush = "toPush"
TopicToOfflinePush = "toOfflinePush"
)
const (
GroupRedis = "redis"
GroupMongo = "mongo"
GroupPush = "push"
GroupOfflinePush = "offlinePush"
)
func NewBuilder(queue config.EngineSelector, kafka *config.Kafka, redis redis.UniversalClient) (Builder, error) {
engine, err := config.ValidateQueueEngine(queue.Engine, config.Standalone())
if err != nil {
return nil, err
}
switch engine {
case config.QueueEngineKafka:
if kafka == nil {
return nil, fmt.Errorf("nil kafka config")
}
return newKafkaBuilder(kafka), nil
case config.QueueEngineRedis:
return newRedisBuilder(redis), nil
case config.QueueEngineMemory:
return standaloneBuilder{}, nil
default:
return nil, fmt.Errorf("unsupported queue engine %s", queue.Engine)
}
}
func newKafkaBuilder(kafka *config.Kafka) Builder {
topics := MergeTopics(KafkaTopics(kafka))
return &kafkaBuilder{
addr: kafka.Address,
config: kafka.Build(),
logicalTopic: LogicalTopicNames(topics),
topicGroupID: TopicGroupID(topics),
}
}
func newRedisBuilder(redis redis.UniversalClient) Builder {
return redismq.NewBuilder(redis, TopicGroupID(DefaultTopics()), redismq.Config{StreamPrefix: "mq:"})
}
type QueueTopicsConfig struct {
ToRedis QueueTopicConfig
ToMongo QueueTopicConfig
ToPush QueueTopicConfig
ToOfflinePush QueueTopicConfig
}
type QueueTopicConfig struct {
Topic string
GroupID string
}
func DefaultTopics() QueueTopicsConfig {
return QueueTopicsConfig{
ToRedis: QueueTopicConfig{Topic: TopicToRedis, GroupID: GroupRedis},
ToMongo: QueueTopicConfig{Topic: TopicToMongo, GroupID: GroupMongo},
ToPush: QueueTopicConfig{Topic: TopicToPush, GroupID: GroupPush},
ToOfflinePush: QueueTopicConfig{Topic: TopicToOfflinePush, GroupID: GroupOfflinePush},
}
}
func KafkaTopics(kafka *config.Kafka) QueueTopicsConfig {
if kafka == nil {
return QueueTopicsConfig{}
}
return QueueTopicsConfig{
ToRedis: QueueTopicConfig{Topic: kafka.ToRedisTopic, GroupID: kafka.ToRedisGroupID},
ToMongo: QueueTopicConfig{Topic: kafka.ToMongoTopic, GroupID: kafka.ToMongoGroupID},
ToPush: QueueTopicConfig{Topic: kafka.ToPushTopic, GroupID: kafka.ToPushGroupID},
ToOfflinePush: QueueTopicConfig{Topic: kafka.ToOfflinePushTopic, GroupID: kafka.ToOfflineGroupID},
}
}
func MergeTopics(topics QueueTopicsConfig) QueueTopicsConfig {
defaults := DefaultTopics()
fillTopic(&topics.ToRedis, defaults.ToRedis)
fillTopic(&topics.ToMongo, defaults.ToMongo)
fillTopic(&topics.ToPush, defaults.ToPush)
fillTopic(&topics.ToOfflinePush, defaults.ToOfflinePush)
return topics
}
func fillTopic(topic *QueueTopicConfig, defaultTopic QueueTopicConfig) {
if topic.Topic == "" {
topic.Topic = defaultTopic.Topic
}
if topic.GroupID == "" {
topic.GroupID = defaultTopic.GroupID
}
}
func LogicalTopicNames(topics QueueTopicsConfig) map[string]string {
return map[string]string{
TopicToRedis: topics.ToRedis.Topic,
TopicToMongo: topics.ToMongo.Topic,
TopicToPush: topics.ToPush.Topic,
TopicToOfflinePush: topics.ToOfflinePush.Topic,
}
}
func TopicGroupID(topics QueueTopicsConfig) map[string]string {
return map[string]string{
topics.ToRedis.Topic: topics.ToRedis.GroupID,
topics.ToMongo.Topic: topics.ToMongo.GroupID,
topics.ToPush.Topic: topics.ToPush.GroupID,
topics.ToOfflinePush.Topic: topics.ToOfflinePush.GroupID,
}
}
type standaloneBuilder struct{}
func (standaloneBuilder) GetTopicProducer(ctx context.Context, topic string) (mq.Producer, error) {
return simmq.GetTopicProducer(topic), nil
}
func (standaloneBuilder) GetTopicConsumer(ctx context.Context, topic string) (mq.Consumer, error) {
return simmq.GetTopicConsumer(topic), nil
}
type kafkaBuilder struct {
addr []string
config *kafka.Config
logicalTopic map[string]string
topicGroupID map[string]string
}
func (x *kafkaBuilder) GetTopicProducer(ctx context.Context, topic string) (mq.Producer, error) {
realTopic, ok := x.logicalTopic[topic]
if !ok {
return nil, fmt.Errorf("topic %s not found", topic)
}
return kafka.NewKafkaProducerV2(x.config, x.addr, realTopic)
}
func (x *kafkaBuilder) GetTopicConsumer(ctx context.Context, topic string) (mq.Consumer, error) {
realTopic, ok := x.logicalTopic[topic]
if !ok {
return nil, fmt.Errorf("topic %s not found", topic)
}
groupID, ok := x.topicGroupID[realTopic]
if !ok {
return nil, fmt.Errorf("topic %s groupID not found", realTopic)
}
return kafka.NewMConsumerGroupV2(ctx, x.config, groupID, []string{realTopic}, true)
}