mirror of
https://github.com/openimsdk/open-im-server.git
synced 2025-11-04 19:32:17 +08:00
seq
This commit is contained in:
parent
d55d416f68
commit
a2a28b43c5
@ -87,7 +87,12 @@ func Start(ctx context.Context, index int, config *Config) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
msgDatabase, err := controller.NewCommonMsgDatabase(msgDocModel, msgModel, seqModel, &config.KafkaConfig)
|
seqConversation, err := mgo.NewSeqConversationMongo(mgocli.GetDB())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
seqConversationCache := redis.NewSeqConversationCacheRedis(rdb, seqConversation)
|
||||||
|
msgDatabase, err := controller.NewCommonMsgDatabase(msgDocModel, msgModel, seqModel, seqConversationCache, &config.KafkaConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -91,7 +91,12 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
|
|||||||
userRpcClient := rpcclient.NewUserRpcClient(client, config.Share.RpcRegisterName.User, config.Share.IMAdminUserID)
|
userRpcClient := rpcclient.NewUserRpcClient(client, config.Share.RpcRegisterName.User, config.Share.IMAdminUserID)
|
||||||
groupRpcClient := rpcclient.NewGroupRpcClient(client, config.Share.RpcRegisterName.Group)
|
groupRpcClient := rpcclient.NewGroupRpcClient(client, config.Share.RpcRegisterName.Group)
|
||||||
friendRpcClient := rpcclient.NewFriendRpcClient(client, config.Share.RpcRegisterName.Friend)
|
friendRpcClient := rpcclient.NewFriendRpcClient(client, config.Share.RpcRegisterName.Friend)
|
||||||
msgDatabase, err := controller.NewCommonMsgDatabase(msgDocModel, msgModel, seqModel, &config.KafkaConfig)
|
seqConversation, err := mgo.NewSeqConversationMongo(mgocli.GetDB())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
seqConversationCache := redis.NewSeqConversationCacheRedis(rdb, seqConversation)
|
||||||
|
msgDatabase, err := controller.NewCommonMsgDatabase(msgDocModel, msgModel, seqModel, seqConversationCache, &config.KafkaConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
16
pkg/common/storage/cache/cachekey/seq.go
vendored
16
pkg/common/storage/cache/cachekey/seq.go
vendored
@ -15,24 +15,24 @@
|
|||||||
package cachekey
|
package cachekey
|
||||||
|
|
||||||
const (
|
const (
|
||||||
maxSeq = "MAX_SEQ:"
|
MaxSeq = "MAX_SEQ:"
|
||||||
minSeq = "MIN_SEQ:"
|
MinSeq = "MIN_SEQ:"
|
||||||
conversationUserMinSeq = "CON_USER_MIN_SEQ:"
|
ConversationUserMinSeq = "CON_USER_MIN_SEQ:"
|
||||||
hasReadSeq = "HAS_READ_SEQ:"
|
HasReadSeq = "HAS_READ_SEQ:"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetMaxSeqKey(conversationID string) string {
|
func GetMaxSeqKey(conversationID string) string {
|
||||||
return maxSeq + conversationID
|
return MaxSeq + conversationID
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetMinSeqKey(conversationID string) string {
|
func GetMinSeqKey(conversationID string) string {
|
||||||
return minSeq + conversationID
|
return MinSeq + conversationID
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetHasReadSeqKey(conversationID string, userID string) string {
|
func GetHasReadSeqKey(conversationID string, userID string) string {
|
||||||
return hasReadSeq + userID + ":" + conversationID
|
return HasReadSeq + userID + ":" + conversationID
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetConversationUserMinSeqKey(conversationID, userID string) string {
|
func GetConversationUserMinSeqKey(conversationID, userID string) string {
|
||||||
return conversationUserMinSeq + conversationID + "u:" + userID
|
return ConversationUserMinSeq + conversationID + "u:" + userID
|
||||||
}
|
}
|
||||||
|
|||||||
5
pkg/common/storage/cache/cachekey/seq1.go
vendored
5
pkg/common/storage/cache/cachekey/seq1.go
vendored
@ -2,7 +2,6 @@ package cachekey
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
MallocSeq = "MALLOC_SEQ:"
|
MallocSeq = "MALLOC_SEQ:"
|
||||||
MallocSeqLock = "MALLOC_SEQ_LOCK:"
|
|
||||||
MallocMinSeqLock = "MALLOC_MIN_SEQ:"
|
MallocMinSeqLock = "MALLOC_MIN_SEQ:"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -10,10 +9,6 @@ func GetMallocSeqKey(conversationID string) string {
|
|||||||
return MallocSeq + conversationID
|
return MallocSeq + conversationID
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetMallocSeqLockKey(conversationID string) string {
|
|
||||||
return MallocSeqLock + conversationID
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetMallocMinSeqKey(conversationID string) string {
|
func GetMallocMinSeqKey(conversationID string) string {
|
||||||
return MallocMinSeqLock + conversationID
|
return MallocMinSeqLock + conversationID
|
||||||
}
|
}
|
||||||
|
|||||||
36
pkg/common/storage/cache/redis/seq.md
vendored
36
pkg/common/storage/cache/redis/seq.md
vendored
@ -1,36 +0,0 @@
|
|||||||
|
|
||||||
### mongo
|
|
||||||
```go
|
|
||||||
type Seq struct {
|
|
||||||
ConversationID string `bson:"conversation_id"`
|
|
||||||
MaxSeq int64 `bson:"max_seq"`
|
|
||||||
MinSeq int64 `bson:"min_seq"`
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Seq interface {
|
|
||||||
Malloc(ctx context.Context, conversationID string, size int64) (int64, error)
|
|
||||||
GetMaxSeq(ctx context.Context, conversationID string) (int64, error)
|
|
||||||
GetMinSeq(ctx context.Context, conversationID string) (int64, error)
|
|
||||||
SetMinSeq(ctx context.Context, conversationID string, seq int64) error
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
1. Malloc 申请seq数量,返回的已有seq的最大值,消息用的第一个seq是返回值+1
|
|
||||||
2. GetMaxSeq 获取申请的seq的最大值,在发消息的seq小于这个值
|
|
||||||
3. GetMinSeq 获取最小的seq,用于拉取历史消息
|
|
||||||
4. SetMinSeq 设置最小的seq,用于拉取历史消息
|
|
||||||
|
|
||||||
### redis
|
|
||||||
```go
|
|
||||||
type RedisSeq struct {
|
|
||||||
Curr int64 // 当前的最大seq
|
|
||||||
Last int64 // mongodb中申请的最大seq
|
|
||||||
Lock *int64 // 锁,用于在mongodb中申请seq
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
1. Malloc 申请seq数量,返回的已有seq的最大值,消息用的第一个seq是返回值+1,如果redis中申请数量够用,直接返回,并自增对应数量。如果redis中申请数量不够用,加锁,从mongodb中申请seq。
|
|
||||||
2. GetMaxSeq 获取已发消息的最大seq就是Curr的值。如果redis中缓存不存在就通过mongodb获取最大seq。存储在redis中。其中Curr和Last都是这个seq值。
|
|
||||||
3. GetMinSeq, SetMinSeq用之前rockscache的方案。
|
|
||||||
246
pkg/common/storage/cache/redis/seq1.go.txt
vendored
246
pkg/common/storage/cache/redis/seq1.go.txt
vendored
@ -1,246 +0,0 @@
|
|||||||
package redis
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/dtm-labs/rockscache"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
|
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
|
|
||||||
"github.com/openimsdk/tools/errs"
|
|
||||||
"github.com/redis/go-redis/v9"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var errLock = errors.New("lock failed")
|
|
||||||
|
|
||||||
type MallocSeq interface {
|
|
||||||
Malloc(ctx context.Context, conversationID string, size int64) ([]int64, error)
|
|
||||||
GetMaxSeq(ctx context.Context, conversationID string) (int64, error)
|
|
||||||
GetMinSeq(ctx context.Context, conversationID string) (int64, error)
|
|
||||||
SetMinSeq(ctx context.Context, conversationID string, seq int64) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSeqCache1(rdb redis.UniversalClient, mgo database.Seq) MallocSeq {
|
|
||||||
opt := rockscache.NewDefaultOptions()
|
|
||||||
opt.EmptyExpire = time.Second * 3
|
|
||||||
opt.Delay = time.Second / 2
|
|
||||||
return &seqCache1{
|
|
||||||
rdb: rdb,
|
|
||||||
mgo: mgo,
|
|
||||||
rocks: rockscache.NewClient(rdb, opt),
|
|
||||||
lockExpire: time.Minute * 1,
|
|
||||||
seqExpire: time.Hour * 24 * 7,
|
|
||||||
minSeqExpire: time.Hour * 1,
|
|
||||||
groupMinNum: 1000,
|
|
||||||
userMinNum: 100,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type seqCache1 struct {
|
|
||||||
rdb redis.UniversalClient
|
|
||||||
rocks *rockscache.Client
|
|
||||||
mgo database.Seq
|
|
||||||
lockExpire time.Duration
|
|
||||||
seqExpire time.Duration
|
|
||||||
minSeqExpire time.Duration
|
|
||||||
groupMinNum int64
|
|
||||||
userMinNum int64
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1
|
|
||||||
2
|
|
||||||
3
|
|
||||||
4
|
|
||||||
5
|
|
||||||
6
|
|
||||||
7
|
|
||||||
8
|
|
||||||
9
|
|
||||||
10
|
|
||||||
*/
|
|
||||||
|
|
||||||
func (s *seqCache1) GetMaxSeq(ctx context.Context, conversationID string) (int64, error) {
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
res, err := s.rdb.LIndex(ctx, cachekey.GetMallocSeqKey(conversationID), 0).Int64()
|
|
||||||
if err == nil {
|
|
||||||
return res, nil
|
|
||||||
} else if !errors.Is(err, redis.Nil) {
|
|
||||||
return 0, errs.Wrap(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.mallocSeq(ctx, conversationID, 0, nil); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, errs.New("get max seq timeout")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *seqCache1) unlock(ctx context.Context, key string, owner string) error {
|
|
||||||
script := `
|
|
||||||
local value = redis.call("GET", KEYS[1])
|
|
||||||
if value == false then
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
if value == ARGV[1] then
|
|
||||||
redis.call("DEL", KEYS[1])
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
return 2
|
|
||||||
`
|
|
||||||
state, err := s.rdb.Eval(ctx, script, []string{key}, owner).Int()
|
|
||||||
if err != nil {
|
|
||||||
return errs.Wrap(err)
|
|
||||||
}
|
|
||||||
switch state {
|
|
||||||
case 0:
|
|
||||||
return errs.Wrap(redis.Nil)
|
|
||||||
case 1:
|
|
||||||
return nil
|
|
||||||
case 2:
|
|
||||||
return errs.New("not the lock holder")
|
|
||||||
default:
|
|
||||||
return errs.New(fmt.Sprintf("unknown state: %d", state))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *seqCache1) initMallocSeq(ctx context.Context, conversationID string, size int64) ([]int64, error) {
|
|
||||||
owner := uuid.New().String()
|
|
||||||
ok, err := s.rdb.SetNX(ctx, cachekey.GetMallocSeqLockKey(conversationID), owner, s.lockExpire).Result()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
seq, err := s.mgo.Malloc(ctx, conversationID, size)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
seqs := make([]int64, 0, size)
|
|
||||||
for i := seq - size + 1; i <= seq; i++ {
|
|
||||||
seqs = append(seqs, i)
|
|
||||||
}
|
|
||||||
return seqs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *seqCache1) GetMinSeq(ctx context.Context, conversationID string) (int64, error) {
|
|
||||||
return getCache[int64](ctx, s.rocks, cachekey.GetMallocMinSeqKey(conversationID), s.minSeqExpire, func(ctx context.Context) (int64, error) {
|
|
||||||
return s.mgo.GetMinSeq(ctx, conversationID)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *seqCache1) SetMinSeq(ctx context.Context, conversationID string, seq int64) error {
|
|
||||||
if err := s.mgo.SetMinSeq(ctx, conversationID, seq); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return s.deleteMinSeqCache(ctx, conversationID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *seqCache1) Malloc(ctx context.Context, conversationID string, size int64) ([]int64, error) {
|
|
||||||
if size <= 0 {
|
|
||||||
return nil, errs.Wrap(errors.New("size must be greater than 0"))
|
|
||||||
}
|
|
||||||
seqKey := cachekey.GetMallocSeqKey(conversationID)
|
|
||||||
lockKey := cachekey.GetMallocSeqLockKey(conversationID)
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
seqs, err := s.lpop(ctx, seqKey, lockKey, size)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(seqs) < int(size) {
|
|
||||||
if err := s.mallocSeq(ctx, conversationID, size, &seqs); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(seqs) >= int(size) {
|
|
||||||
return seqs, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, errs.ErrInternalServer.WrapMsg("malloc seq failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *seqCache1) push(ctx context.Context, seqKey string, seqs []int64) error {
|
|
||||||
script := `
|
|
||||||
redis.call("DEL", KEYS[1])
|
|
||||||
for i = 2, #ARGV do
|
|
||||||
redis.call("RPUSH", KEYS[1], ARGV[i])
|
|
||||||
end
|
|
||||||
redis.call("EXPIRE", KEYS[1], ARGV[1])
|
|
||||||
return 1
|
|
||||||
`
|
|
||||||
argv := make([]any, 0, 1+len(seqs))
|
|
||||||
argv = append(argv, s.seqExpire.Seconds())
|
|
||||||
for _, seq := range seqs {
|
|
||||||
argv = append(argv, seq)
|
|
||||||
}
|
|
||||||
err := s.rdb.Eval(ctx, script, []string{seqKey}, argv...).Err()
|
|
||||||
return errs.Wrap(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *seqCache1) lpop(ctx context.Context, seqKey, lockKey string, size int64) ([]int64, error) {
|
|
||||||
script := `
|
|
||||||
local result = redis.call("LRANGE", KEYS[1], 0, ARGV[1]-1)
|
|
||||||
if #result == 0 then
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
redis.call("LTRIM", KEYS[1], #result, -1)
|
|
||||||
if redis.call("LLEN", KEYS[1]) == 0 then
|
|
||||||
redis.call("DEL", KEYS[2])
|
|
||||||
end
|
|
||||||
return result
|
|
||||||
`
|
|
||||||
res, err := s.rdb.Eval(ctx, script, []string{seqKey, lockKey}, size).Int64Slice()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errs.Wrap(err)
|
|
||||||
}
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *seqCache1) getMongoStepSize(conversationID string, size int64) int64 {
|
|
||||||
var num int64
|
|
||||||
if msgprocessor.IsGroupConversationID(conversationID) {
|
|
||||||
num = s.groupMinNum
|
|
||||||
} else {
|
|
||||||
num = s.userMinNum
|
|
||||||
}
|
|
||||||
if size > num {
|
|
||||||
num += size
|
|
||||||
}
|
|
||||||
return num
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *seqCache1) mallocSeq(ctx context.Context, conversationID string, size int64, seqs *[]int64) error {
|
|
||||||
var delMinSeqKey bool
|
|
||||||
_, err := getCache[string](ctx, s.rocks, cachekey.GetMallocSeqLockKey(conversationID), s.lockExpire, func(ctx context.Context) (string, error) {
|
|
||||||
res, err := s.mgo.Malloc(ctx, conversationID, s.getMongoStepSize(conversationID, size))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
delMinSeqKey = res[0] == 1
|
|
||||||
if seqs != nil && size > 0 {
|
|
||||||
if len(*seqs) > 0 && (*seqs)[len(*seqs)-1]+1 == res[0] {
|
|
||||||
n := size - int64(len(*seqs))
|
|
||||||
*seqs = append(*seqs, res[:n]...)
|
|
||||||
res = res[n:]
|
|
||||||
} else {
|
|
||||||
*seqs = res[:size]
|
|
||||||
res = res[size:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := s.push(ctx, cachekey.GetMallocSeqKey(conversationID), res); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return strconv.Itoa(int(time.Now().UnixMicro())), nil
|
|
||||||
})
|
|
||||||
if delMinSeqKey {
|
|
||||||
s.deleteMinSeqCache(ctx, conversationID)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *seqCache1) deleteMinSeqCache(ctx context.Context, conversationID string) error {
|
|
||||||
return s.rocks.TagAsDeleted2(ctx, cachekey.GetMallocMinSeqKey(conversationID))
|
|
||||||
}
|
|
||||||
@ -3,74 +3,68 @@ package redis
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/dtm-labs/rockscache"
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo"
|
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
|
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
|
||||||
"github.com/openimsdk/tools/errs"
|
"github.com/openimsdk/tools/errs"
|
||||||
"github.com/openimsdk/tools/log"
|
"github.com/openimsdk/tools/log"
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
|
||||||
"go.mongodb.org/mongo-driver/mongo/options"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RedisHash struct {
|
func NewSeqConversationCacheRedis(rdb redis.UniversalClient, mgo database.SeqConversation) cache.SeqConversationCache {
|
||||||
NextSeq int64
|
return &seqConversationCacheRedis{
|
||||||
LastSeq int64
|
rdb: rdb,
|
||||||
}
|
mgo: mgo,
|
||||||
|
lockTime: time.Second * 3,
|
||||||
func NewTestSeq() *SeqMalloc {
|
dataTime: time.Hour * 24 * 365,
|
||||||
mgocli, err := mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://openIM:openIM123@172.16.8.48:37017/openim_v3?maxPoolSize=100").SetConnectTimeout(5*time.Second))
|
minSeqExpireTime: time.Hour,
|
||||||
if err != nil {
|
rocks: rockscache.NewClient(rdb, *GetRocksCacheOptions()),
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
model, err := mgo.NewSeqMongo(mgocli.Database("openim_v3"))
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
opt := &redis.Options{
|
|
||||||
Addr: "172.16.8.48:16379",
|
|
||||||
Password: "openIM123",
|
|
||||||
DB: 1,
|
|
||||||
}
|
|
||||||
rdb := redis.NewClient(opt)
|
|
||||||
if err := rdb.Ping(context.Background()).Err(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return &SeqMalloc{
|
|
||||||
rdb: rdb,
|
|
||||||
mgo: model,
|
|
||||||
//lockTime: time.Second * 30,
|
|
||||||
lockTime: time.Second * 60 * 60 * 24 * 1,
|
|
||||||
dataTime: time.Second * 60 * 60 * 24 * 7,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type RedisSeq struct {
|
type seqConversationCacheRedis struct {
|
||||||
Curr int64
|
rdb redis.UniversalClient
|
||||||
Last int64
|
mgo database.SeqConversation
|
||||||
Lock *int64
|
rocks *rockscache.Client
|
||||||
|
lockTime time.Duration
|
||||||
|
dataTime time.Duration
|
||||||
|
minSeqExpireTime time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
type SeqMalloc struct {
|
func (s *seqConversationCacheRedis) getMinSeqKey(conversationID string) string {
|
||||||
rdb redis.UniversalClient
|
return cachekey.GetMallocMinSeqKey(conversationID)
|
||||||
mgo database.Seq
|
|
||||||
lockTime time.Duration
|
|
||||||
dataTime time.Duration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SeqMalloc) getSeqMallocKey(conversationID string) string {
|
func (s *seqConversationCacheRedis) SetMinSeq(ctx context.Context, conversationID string, seq int64) error {
|
||||||
|
if err := s.mgo.SetMinSeq(ctx, conversationID, seq); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.rocks.TagAsDeleted2(ctx, s.getMinSeqKey(conversationID)); err != nil {
|
||||||
|
return errs.Wrap(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *seqConversationCacheRedis) GetMinSeq(ctx context.Context, conversationID string) (int64, error) {
|
||||||
|
return getCache(ctx, s.rocks, s.getMinSeqKey(conversationID), s.minSeqExpireTime, func(ctx context.Context) (int64, error) {
|
||||||
|
return s.mgo.GetMinSeq(ctx, conversationID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *seqConversationCacheRedis) getSeqMallocKey(conversationID string) string {
|
||||||
return cachekey.GetMallocSeqKey(conversationID)
|
return cachekey.GetMallocSeqKey(conversationID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SeqMalloc) setSeq(ctx context.Context, key string, owner int64, currSeq int64, lastSeq int64) (int64, error) {
|
func (s *seqConversationCacheRedis) setSeq(ctx context.Context, key string, owner int64, currSeq int64, lastSeq int64) (int64, error) {
|
||||||
if lastSeq < currSeq {
|
if lastSeq < currSeq {
|
||||||
return 0, errs.New("lastSeq must be greater than currSeq")
|
return 0, errs.New("lastSeq must be greater than currSeq")
|
||||||
}
|
}
|
||||||
// 0: 成功
|
// 0: success
|
||||||
// 1: 成功 锁过期,但未被其他人锁
|
// 1: success the lock has expired, but has not been locked by anyone else
|
||||||
// 2: 已经被锁,但是锁的不是自己
|
// 2: already locked, but not by yourself
|
||||||
script := `
|
script := `
|
||||||
local key = KEYS[1]
|
local key = KEYS[1]
|
||||||
local lockValue = ARGV[1]
|
local lockValue = ARGV[1]
|
||||||
@ -97,12 +91,12 @@ return 0
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// malloc size=0为获取当前seq size>0为分配seq
|
// malloc size=0 is to get the current seq size>0 is to allocate seq
|
||||||
func (s *SeqMalloc) malloc(ctx context.Context, key string, size int64) ([]int64, error) {
|
func (s *seqConversationCacheRedis) malloc(ctx context.Context, key string, size int64) ([]int64, error) {
|
||||||
// 0: 成功
|
// 0: success
|
||||||
// 1: 需要获取,并加锁
|
// 1: need to obtain and lock
|
||||||
// 2: 已经被锁
|
// 2: already locked
|
||||||
// 3: 超过最大值,并加锁
|
// 3: exceeded the maximum value and locked
|
||||||
script := `
|
script := `
|
||||||
local key = KEYS[1]
|
local key = KEYS[1]
|
||||||
local size = tonumber(ARGV[1])
|
local size = tonumber(ARGV[1])
|
||||||
@ -156,7 +150,7 @@ return result
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SeqMalloc) wait(ctx context.Context) error {
|
func (s *seqConversationCacheRedis) wait(ctx context.Context) error {
|
||||||
timer := time.NewTimer(time.Second / 4)
|
timer := time.NewTimer(time.Second / 4)
|
||||||
defer timer.Stop()
|
defer timer.Stop()
|
||||||
select {
|
select {
|
||||||
@ -167,7 +161,7 @@ func (s *SeqMalloc) wait(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SeqMalloc) setSeqRetry(ctx context.Context, key string, owner int64, currSeq int64, lastSeq int64) {
|
func (s *seqConversationCacheRedis) setSeqRetry(ctx context.Context, key string, owner int64, currSeq int64, lastSeq int64) {
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
state, err := s.setSeq(ctx, key, owner, currSeq, lastSeq)
|
state, err := s.setSeq(ctx, key, owner, currSeq, lastSeq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -191,13 +185,13 @@ func (s *SeqMalloc) setSeqRetry(ctx context.Context, key string, owner int64, cu
|
|||||||
log.ZError(ctx, "set seq cache retrying still failed", nil, "key", key, "owner", owner, "currSeq", currSeq, "lastSeq", lastSeq)
|
log.ZError(ctx, "set seq cache retrying still failed", nil, "key", key, "owner", owner, "currSeq", currSeq, "lastSeq", lastSeq)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SeqMalloc) getMallocSize(conversationID string, size int64) int64 {
|
func (s *seqConversationCacheRedis) getMallocSize(conversationID string, size int64) int64 {
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
var basicSize int64
|
var basicSize int64
|
||||||
if msgprocessor.IsGroupConversationID(conversationID) {
|
if msgprocessor.IsGroupConversationID(conversationID) {
|
||||||
basicSize = 200
|
basicSize = 100
|
||||||
} else {
|
} else {
|
||||||
basicSize = 50
|
basicSize = 50
|
||||||
}
|
}
|
||||||
@ -205,7 +199,7 @@ func (s *SeqMalloc) getMallocSize(conversationID string, size int64) int64 {
|
|||||||
return basicSize
|
return basicSize
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SeqMalloc) Malloc(ctx context.Context, conversationID string, size int64) (int64, error) {
|
func (s *seqConversationCacheRedis) Malloc(ctx context.Context, conversationID string, size int64) (int64, error) {
|
||||||
if size < 0 {
|
if size < 0 {
|
||||||
return 0, errs.New("size must be greater than 0")
|
return 0, errs.New("size must be greater than 0")
|
||||||
}
|
}
|
||||||
@ -227,7 +221,6 @@ func (s *SeqMalloc) Malloc(ctx context.Context, conversationID string, size int6
|
|||||||
s.setSeqRetry(ctx, key, states[1], seq+size, seq+mallocSize)
|
s.setSeqRetry(ctx, key, states[1], seq+size, seq+mallocSize)
|
||||||
return seq, nil
|
return seq, nil
|
||||||
case 2: // locked
|
case 2: // locked
|
||||||
fmt.Println("locked----->", "conversationID", conversationID, "size", size)
|
|
||||||
if err := s.wait(ctx); err != nil {
|
if err := s.wait(ctx); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@ -257,6 +250,6 @@ func (s *SeqMalloc) Malloc(ctx context.Context, conversationID string, size int6
|
|||||||
return 0, errs.New("malloc seq waiting for lock timeout", "conversationID", conversationID, "size", size)
|
return 0, errs.New("malloc seq waiting for lock timeout", "conversationID", conversationID, "size", size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SeqMalloc) GetMaxSeq(ctx context.Context, conversationID string) (int64, error) {
|
func (s *seqConversationCacheRedis) GetMaxSeq(ctx context.Context, conversationID string) (int64, error) {
|
||||||
return s.Malloc(ctx, conversationID, 0)
|
return s.Malloc(ctx, conversationID, 0)
|
||||||
}
|
}
|
||||||
109
pkg/common/storage/cache/redis/seq_conversation_test.go
vendored
Normal file
109
pkg/common/storage/cache/redis/seq_conversation_test.go
vendored
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newTestSeq() *seqConversationCacheRedis {
|
||||||
|
mgocli, err := mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://openIM:openIM123@172.16.8.48:37017/openim_v3?maxPoolSize=100").SetConnectTimeout(5*time.Second))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
model, err := mgo.NewSeqConversationMongo(mgocli.Database("openim_v3"))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
opt := &redis.Options{
|
||||||
|
Addr: "172.16.8.48:16379",
|
||||||
|
Password: "openIM123",
|
||||||
|
DB: 1,
|
||||||
|
}
|
||||||
|
rdb := redis.NewClient(opt)
|
||||||
|
if err := rdb.Ping(context.Background()).Err(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return NewSeqConversationCacheRedis(rdb, model).(*seqConversationCacheRedis)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSeq(t *testing.T) {
|
||||||
|
ts := newTestSeq()
|
||||||
|
var (
|
||||||
|
wg sync.WaitGroup
|
||||||
|
speed atomic.Int64
|
||||||
|
)
|
||||||
|
|
||||||
|
const count = 128
|
||||||
|
wg.Add(count)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
index := i + 1
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
var size int64 = 10
|
||||||
|
cID := strconv.Itoa(index * 1)
|
||||||
|
for i := 1; ; i++ {
|
||||||
|
//first, err := ts.mgo.Malloc(context.Background(), cID, size) // mongo
|
||||||
|
first, err := ts.Malloc(context.Background(), cID, size) // redis
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("[%d-%d] %s %s", index, i, cID, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
speed.Add(size)
|
||||||
|
_ = first
|
||||||
|
//t.Logf("[%d] %d -> %d", i, first+1, first+size)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
|
||||||
|
ticker := time.NewTicker(time.Second)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
ticker.Stop()
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
value := speed.Swap(0)
|
||||||
|
t.Logf("speed: %d/s", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDel(t *testing.T) {
|
||||||
|
ts := newTestSeq()
|
||||||
|
for i := 1; i < 100; i++ {
|
||||||
|
var size int64 = 100
|
||||||
|
first, err := ts.Malloc(context.Background(), "100", size)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("[%d] %s", i, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Logf("[%d] %d -> %d", i, first+1, first+size)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSeqMalloc(t *testing.T) {
|
||||||
|
ts := newTestSeq()
|
||||||
|
t.Log(ts.GetMaxSeq(context.Background(), "100"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMinSeq(t *testing.T) {
|
||||||
|
ts := newTestSeq()
|
||||||
|
t.Log(ts.GetMinSeq(context.Background(), "10000000"))
|
||||||
|
}
|
||||||
77
pkg/common/storage/cache/redis/seq_test.go
vendored
77
pkg/common/storage/cache/redis/seq_test.go
vendored
@ -1,77 +0,0 @@
|
|||||||
package redis
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSeq(t *testing.T) {
|
|
||||||
ts := NewTestSeq()
|
|
||||||
var (
|
|
||||||
wg sync.WaitGroup
|
|
||||||
speed atomic.Int64
|
|
||||||
)
|
|
||||||
|
|
||||||
const count = 256
|
|
||||||
wg.Add(count)
|
|
||||||
for i := 0; i < count; i++ {
|
|
||||||
index := i + 1
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
var size int64 = 1
|
|
||||||
cID := strconv.Itoa(index * 100)
|
|
||||||
for i := 1; ; i++ {
|
|
||||||
first, err := ts.mgo.Malloc(context.Background(), cID, size) // mongo
|
|
||||||
//first, err := ts.Malloc(context.Background(), cID, size) // redis
|
|
||||||
if err != nil {
|
|
||||||
t.Logf("[%d-%d] %s %s", index, i, cID, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
speed.Add(size)
|
|
||||||
_ = first
|
|
||||||
//t.Logf("[%d] %d -> %d", i, first+1, first+size)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
done := make(chan struct{})
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
wg.Wait()
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
|
|
||||||
ticker := time.NewTicker(time.Second)
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
ticker.Stop()
|
|
||||||
return
|
|
||||||
case <-ticker.C:
|
|
||||||
value := speed.Swap(0)
|
|
||||||
t.Logf("speed: %d/s", value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//for i := 1; i < 1000000; i++ {
|
|
||||||
// var size int64 = 100
|
|
||||||
// first, err := ts.Malloc(context.Background(), "1", size)
|
|
||||||
// if err != nil {
|
|
||||||
// t.Logf("[%d] %s", i, err)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// t.Logf("[%d] %d -> %d", i, first+1, first+size)
|
|
||||||
// time.Sleep(time.Second / 4)
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDel(t *testing.T) {
|
|
||||||
ts := NewTestSeq()
|
|
||||||
t.Log(ts.GetMaxSeq(context.Background(), "1"))
|
|
||||||
|
|
||||||
}
|
|
||||||
14
pkg/common/storage/cache/seq.go
vendored
14
pkg/common/storage/cache/seq.go
vendored
@ -5,13 +5,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type SeqCache interface {
|
type SeqCache interface {
|
||||||
SetMaxSeq(ctx context.Context, conversationID string, maxSeq int64) error
|
//SetMaxSeq(ctx context.Context, conversationID string, maxSeq int64) error
|
||||||
GetMaxSeqs(ctx context.Context, conversationIDs []string) (map[string]int64, error)
|
//GetMaxSeqs(ctx context.Context, conversationIDs []string) (map[string]int64, error)
|
||||||
GetMaxSeq(ctx context.Context, conversationID string) (int64, error)
|
//GetMaxSeq(ctx context.Context, conversationID string) (int64, error)
|
||||||
SetMinSeq(ctx context.Context, conversationID string, minSeq int64) error
|
//SetMinSeq(ctx context.Context, conversationID string, minSeq int64) error
|
||||||
SetMinSeqs(ctx context.Context, seqs map[string]int64) error
|
//SetMinSeqs(ctx context.Context, seqs map[string]int64) error
|
||||||
GetMinSeqs(ctx context.Context, conversationIDs []string) (map[string]int64, error)
|
//GetMinSeqs(ctx context.Context, conversationIDs []string) (map[string]int64, error)
|
||||||
GetMinSeq(ctx context.Context, conversationID string) (int64, error)
|
//GetMinSeq(ctx context.Context, conversationID string) (int64, error)
|
||||||
GetConversationUserMinSeq(ctx context.Context, conversationID string, userID string) (int64, error)
|
GetConversationUserMinSeq(ctx context.Context, conversationID string, userID string) (int64, error)
|
||||||
GetConversationUserMinSeqs(ctx context.Context, conversationID string, userIDs []string) (map[string]int64, error)
|
GetConversationUserMinSeqs(ctx context.Context, conversationID string, userIDs []string) (map[string]int64, error)
|
||||||
SetConversationUserMinSeq(ctx context.Context, conversationID string, userID string, minSeq int64) error
|
SetConversationUserMinSeq(ctx context.Context, conversationID string, userID string, minSeq int64) error
|
||||||
|
|||||||
10
pkg/common/storage/cache/seq_conversation.go
vendored
Normal file
10
pkg/common/storage/cache/seq_conversation.go
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
type SeqConversationCache interface {
|
||||||
|
Malloc(ctx context.Context, conversationID string, size int64) (int64, error)
|
||||||
|
GetMaxSeq(ctx context.Context, conversationID string) (int64, error)
|
||||||
|
SetMinSeq(ctx context.Context, conversationID string, seq int64) error
|
||||||
|
GetMinSeq(ctx context.Context, conversationID string) (int64, error)
|
||||||
|
}
|
||||||
@ -108,7 +108,7 @@ type CommonMsgDatabase interface {
|
|||||||
DeleteDocMsgBefore(ctx context.Context, ts int64, doc *model.MsgDocModel) ([]int, error)
|
DeleteDocMsgBefore(ctx context.Context, ts int64, doc *model.MsgDocModel) ([]int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCommonMsgDatabase(msgDocModel database.Msg, msg cache.MsgCache, seq cache.SeqCache, kafkaConf *config.Kafka) (CommonMsgDatabase, error) {
|
func NewCommonMsgDatabase(msgDocModel database.Msg, msg cache.MsgCache, seq cache.SeqCache, seqConversation cache.SeqConversationCache, kafkaConf *config.Kafka) (CommonMsgDatabase, error) {
|
||||||
conf, err := kafka.BuildProducerConfig(*kafkaConf.Build())
|
conf, err := kafka.BuildProducerConfig(*kafkaConf.Build())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -129,28 +129,19 @@ func NewCommonMsgDatabase(msgDocModel database.Msg, msg cache.MsgCache, seq cach
|
|||||||
msgDocDatabase: msgDocModel,
|
msgDocDatabase: msgDocModel,
|
||||||
msg: msg,
|
msg: msg,
|
||||||
seq: seq,
|
seq: seq,
|
||||||
|
seqConversation: seqConversation,
|
||||||
producer: producerToRedis,
|
producer: producerToRedis,
|
||||||
producerToMongo: producerToMongo,
|
producerToMongo: producerToMongo,
|
||||||
producerToPush: producerToPush,
|
producerToPush: producerToPush,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//func InitCommonMsgDatabase(rdb redis.UniversalClient, database *mongo.Database, config *tools.CronTaskConfig) (CommonMsgDatabase, error) {
|
|
||||||
// msgDocModel, err := database.NewMsgMongo(database)
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
// //todo MsgCacheTimeout
|
|
||||||
// msg := cache.NewMsgCache(rdb, 86400, config.RedisConfig.EnablePipeline)
|
|
||||||
// seq := cache.NewSeqCache(rdb)
|
|
||||||
// return NewCommonMsgDatabase(msgDocModel, msg, seq, &config.KafkaConfig)
|
|
||||||
//}
|
|
||||||
|
|
||||||
type commonMsgDatabase struct {
|
type commonMsgDatabase struct {
|
||||||
msgDocDatabase database.Msg
|
msgDocDatabase database.Msg
|
||||||
msgTable model.MsgDocModel
|
msgTable model.MsgDocModel
|
||||||
msg cache.MsgCache
|
msg cache.MsgCache
|
||||||
seq cache.SeqCache
|
seq cache.SeqCache
|
||||||
|
seqConversation cache.SeqConversationCache
|
||||||
producer *kafka.Producer
|
producer *kafka.Producer
|
||||||
producerToMongo *kafka.Producer
|
producerToMongo *kafka.Producer
|
||||||
producerToPush *kafka.Producer
|
producerToPush *kafka.Producer
|
||||||
@ -349,10 +340,9 @@ func (db *commonMsgDatabase) DeleteMessagesFromCache(ctx context.Context, conver
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (db *commonMsgDatabase) BatchInsertChat2Cache(ctx context.Context, conversationID string, msgs []*sdkws.MsgData) (seq int64, isNew bool, err error) {
|
func (db *commonMsgDatabase) BatchInsertChat2Cache(ctx context.Context, conversationID string, msgs []*sdkws.MsgData) (seq int64, isNew bool, err error) {
|
||||||
// TODO set SEQ
|
currentMaxSeq, err := db.seqConversation.Malloc(ctx, conversationID, int64(len(msgs)))
|
||||||
currentMaxSeq, err := db.seq.GetMaxSeq(ctx, conversationID)
|
if err != nil {
|
||||||
if err != nil && errs.Unwrap(err) != redis.Nil {
|
log.ZError(ctx, "storage.seq.Malloc", err)
|
||||||
log.ZError(ctx, "storage.seq.GetMaxSeq", err)
|
|
||||||
return 0, false, err
|
return 0, false, err
|
||||||
}
|
}
|
||||||
lenList := len(msgs)
|
lenList := len(msgs)
|
||||||
@ -362,9 +352,6 @@ func (db *commonMsgDatabase) BatchInsertChat2Cache(ctx context.Context, conversa
|
|||||||
if lenList < 1 {
|
if lenList < 1 {
|
||||||
return 0, false, errs.New("no messages to insert", "minCount", 1).Wrap()
|
return 0, false, errs.New("no messages to insert", "minCount", 1).Wrap()
|
||||||
}
|
}
|
||||||
if errs.Unwrap(err) == redis.Nil {
|
|
||||||
isNew = true
|
|
||||||
}
|
|
||||||
lastMaxSeq := currentMaxSeq
|
lastMaxSeq := currentMaxSeq
|
||||||
userSeqMap := make(map[string]int64)
|
userSeqMap := make(map[string]int64)
|
||||||
for _, m := range msgs {
|
for _, m := range msgs {
|
||||||
@ -380,13 +367,6 @@ func (db *commonMsgDatabase) BatchInsertChat2Cache(ctx context.Context, conversa
|
|||||||
} else {
|
} else {
|
||||||
prommetrics.MsgInsertRedisSuccessCounter.Inc()
|
prommetrics.MsgInsertRedisSuccessCounter.Inc()
|
||||||
}
|
}
|
||||||
|
|
||||||
err = db.seq.SetMaxSeq(ctx, conversationID, currentMaxSeq)
|
|
||||||
if err != nil {
|
|
||||||
log.ZError(ctx, "storage.seq.SetMaxSeq error", err, "conversationID", conversationID)
|
|
||||||
prommetrics.SeqSetFailedCounter.Inc()
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.seq.SetHasReadSeqs(ctx, conversationID, userSeqMap)
|
err = db.seq.SetHasReadSeqs(ctx, conversationID, userSeqMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ZError(ctx, "SetHasReadSeqs error", err, "userSeqMap", userSeqMap, "conversationID", conversationID)
|
log.ZError(ctx, "SetHasReadSeqs error", err, "userSeqMap", userSeqMap, "conversationID", conversationID)
|
||||||
@ -519,8 +499,8 @@ func (db *commonMsgDatabase) GetMsgBySeqsRange(ctx context.Context, userID strin
|
|||||||
if err != nil && errs.Unwrap(err) != redis.Nil {
|
if err != nil && errs.Unwrap(err) != redis.Nil {
|
||||||
return 0, 0, nil, err
|
return 0, 0, nil, err
|
||||||
}
|
}
|
||||||
minSeq, err := db.seq.GetMinSeq(ctx, conversationID)
|
minSeq, err := db.seqConversation.GetMinSeq(ctx, conversationID)
|
||||||
if err != nil && errs.Unwrap(err) != redis.Nil {
|
if err != nil {
|
||||||
return 0, 0, nil, err
|
return 0, 0, nil, err
|
||||||
}
|
}
|
||||||
if userMinSeq > minSeq {
|
if userMinSeq > minSeq {
|
||||||
@ -531,8 +511,8 @@ func (db *commonMsgDatabase) GetMsgBySeqsRange(ctx context.Context, userID strin
|
|||||||
log.ZWarn(ctx, "minSeq > end", errs.New("minSeq>end"), "minSeq", minSeq, "end", end)
|
log.ZWarn(ctx, "minSeq > end", errs.New("minSeq>end"), "minSeq", minSeq, "end", end)
|
||||||
return 0, 0, nil, nil
|
return 0, 0, nil, nil
|
||||||
}
|
}
|
||||||
maxSeq, err := db.seq.GetMaxSeq(ctx, conversationID)
|
maxSeq, err := db.seqConversation.GetMaxSeq(ctx, conversationID)
|
||||||
if err != nil && errs.Unwrap(err) != redis.Nil {
|
if err != nil {
|
||||||
return 0, 0, nil, err
|
return 0, 0, nil, err
|
||||||
}
|
}
|
||||||
log.ZDebug(ctx, "GetMsgBySeqsRange", "userMinSeq", userMinSeq, "conMinSeq", minSeq, "conMaxSeq", maxSeq, "userMaxSeq", userMaxSeq)
|
log.ZDebug(ctx, "GetMsgBySeqsRange", "userMinSeq", userMinSeq, "conMinSeq", minSeq, "conMaxSeq", maxSeq, "userMaxSeq", userMaxSeq)
|
||||||
@ -572,11 +552,8 @@ func (db *commonMsgDatabase) GetMsgBySeqsRange(ctx context.Context, userID strin
|
|||||||
var successMsgs []*sdkws.MsgData
|
var successMsgs []*sdkws.MsgData
|
||||||
log.ZDebug(ctx, "GetMsgBySeqsRange", "first seqs", seqs, "newBegin", newBegin, "newEnd", newEnd)
|
log.ZDebug(ctx, "GetMsgBySeqsRange", "first seqs", seqs, "newBegin", newBegin, "newEnd", newEnd)
|
||||||
cachedMsgs, failedSeqs, err := db.msg.GetMessagesBySeq(ctx, conversationID, seqs)
|
cachedMsgs, failedSeqs, err := db.msg.GetMessagesBySeq(ctx, conversationID, seqs)
|
||||||
if err != nil {
|
if err != nil && !errors.Is(err, redis.Nil) {
|
||||||
if err != redis.Nil {
|
log.ZError(ctx, "get message from redis exception", err, "conversationID", conversationID, "seqs", seqs)
|
||||||
|
|
||||||
log.ZError(ctx, "get message from redis exception", err, "conversationID", conversationID, "seqs", seqs)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
successMsgs = append(successMsgs, cachedMsgs...)
|
successMsgs = append(successMsgs, cachedMsgs...)
|
||||||
log.ZDebug(ctx, "get msgs from cache", "cachedMsgs", cachedMsgs)
|
log.ZDebug(ctx, "get msgs from cache", "cachedMsgs", cachedMsgs)
|
||||||
@ -600,12 +577,12 @@ func (db *commonMsgDatabase) GetMsgBySeqs(ctx context.Context, userID string, co
|
|||||||
if err != nil && errs.Unwrap(err) != redis.Nil {
|
if err != nil && errs.Unwrap(err) != redis.Nil {
|
||||||
return 0, 0, nil, err
|
return 0, 0, nil, err
|
||||||
}
|
}
|
||||||
minSeq, err := db.seq.GetMinSeq(ctx, conversationID)
|
minSeq, err := db.seqConversation.GetMinSeq(ctx, conversationID)
|
||||||
if err != nil && errs.Unwrap(err) != redis.Nil {
|
if err != nil {
|
||||||
return 0, 0, nil, err
|
return 0, 0, nil, err
|
||||||
}
|
}
|
||||||
maxSeq, err := db.seq.GetMaxSeq(ctx, conversationID)
|
maxSeq, err := db.seqConversation.GetMaxSeq(ctx, conversationID)
|
||||||
if err != nil && errs.Unwrap(err) != redis.Nil {
|
if err != nil {
|
||||||
return 0, 0, nil, err
|
return 0, 0, nil, err
|
||||||
}
|
}
|
||||||
if userMinSeq < minSeq {
|
if userMinSeq < minSeq {
|
||||||
@ -649,7 +626,7 @@ func (db *commonMsgDatabase) DeleteConversationMsgsAndSetMinSeq(ctx context.Cont
|
|||||||
if minSeq == 0 {
|
if minSeq == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return db.seq.SetMinSeq(ctx, conversationID, minSeq)
|
return db.seqConversation.SetMinSeq(ctx, conversationID, minSeq)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *commonMsgDatabase) UserMsgsDestruct(ctx context.Context, userID string, conversationID string, destructTime int64, lastMsgDestructTime time.Time) (seqs []int64, err error) {
|
func (db *commonMsgDatabase) UserMsgsDestruct(ctx context.Context, userID string, conversationID string, destructTime int64, lastMsgDestructTime time.Time) (seqs []int64, err error) {
|
||||||
@ -803,7 +780,7 @@ func (db *commonMsgDatabase) DeleteMsgsBySeqs(ctx context.Context, conversationI
|
|||||||
|
|
||||||
func (db *commonMsgDatabase) CleanUpUserConversationsMsgs(ctx context.Context, user string, conversationIDs []string) {
|
func (db *commonMsgDatabase) CleanUpUserConversationsMsgs(ctx context.Context, user string, conversationIDs []string) {
|
||||||
for _, conversationID := range conversationIDs {
|
for _, conversationID := range conversationIDs {
|
||||||
maxSeq, err := db.seq.GetMaxSeq(ctx, conversationID)
|
maxSeq, err := db.seqConversation.GetMaxSeq(ctx, conversationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == redis.Nil {
|
if err == redis.Nil {
|
||||||
log.ZDebug(ctx, "max seq is nil", "conversationID", conversationID)
|
log.ZDebug(ctx, "max seq is nil", "conversationID", conversationID)
|
||||||
@ -812,7 +789,7 @@ func (db *commonMsgDatabase) CleanUpUserConversationsMsgs(ctx context.Context, u
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := db.seq.SetMinSeq(ctx, conversationID, maxSeq+1); err != nil {
|
if err := db.seqConversation.SetMinSeq(ctx, conversationID, maxSeq+1); err != nil {
|
||||||
log.ZError(ctx, "set min seq failed", err, "conversationID", conversationID, "minSeq", maxSeq+1)
|
log.ZError(ctx, "set min seq failed", err, "conversationID", conversationID, "minSeq", maxSeq+1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -823,29 +800,37 @@ func (db *commonMsgDatabase) CleanUpUserConversationsMsgs(ctx context.Context, u
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
func (db *commonMsgDatabase) GetMaxSeqs(ctx context.Context, conversationIDs []string) (map[string]int64, error) {
|
func (db *commonMsgDatabase) GetMaxSeqs(ctx context.Context, conversationIDs []string) (map[string]int64, error) {
|
||||||
return db.seq.GetMaxSeqs(ctx, conversationIDs)
|
result := make(map[string]int64)
|
||||||
|
for _, conversationID := range conversationIDs {
|
||||||
|
if result[conversationID] != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seq, err := db.seqConversation.GetMaxSeq(ctx, conversationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result[conversationID] = seq
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *commonMsgDatabase) GetMaxSeq(ctx context.Context, conversationID string) (int64, error) {
|
func (db *commonMsgDatabase) GetMaxSeq(ctx context.Context, conversationID string) (int64, error) {
|
||||||
return db.seq.GetMaxSeq(ctx, conversationID)
|
return db.seqConversation.GetMaxSeq(ctx, conversationID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *commonMsgDatabase) SetMinSeq(ctx context.Context, conversationID string, minSeq int64) error {
|
func (db *commonMsgDatabase) SetMinSeq(ctx context.Context, conversationID string, minSeq int64) error {
|
||||||
return db.seq.SetMinSeq(ctx, conversationID, minSeq)
|
return db.seqConversation.SetMinSeq(ctx, conversationID, minSeq)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *commonMsgDatabase) SetMinSeqs(ctx context.Context, seqs map[string]int64) error {
|
func (db *commonMsgDatabase) SetMinSeqs(ctx context.Context, seqs map[string]int64) error {
|
||||||
return db.seq.SetMinSeqs(ctx, seqs)
|
for conversationID, seq := range seqs {
|
||||||
|
if err := db.seqConversation.SetMinSeq(ctx, conversationID, seq); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//func (db *commonMsgDatabase) GetMinSeqs(ctx context.Context, conversationIDs []string) (map[string]int64, error) {
|
|
||||||
// return db.seq.GetMinSeqs(ctx, conversationIDs)
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//func (db *commonMsgDatabase) GetMinSeq(ctx context.Context, conversationID string) (int64, error) {
|
|
||||||
// return db.seq.GetMinSeq(ctx, conversationID)
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (db *commonMsgDatabase) GetConversationUserMinSeq(ctx context.Context, conversationID string, userID string) (int64, error) {
|
func (db *commonMsgDatabase) GetConversationUserMinSeq(ctx context.Context, conversationID string, userID string) (int64, error) {
|
||||||
return db.seq.GetConversationUserMinSeq(ctx, conversationID, userID)
|
return db.seq.GetConversationUserMinSeq(ctx, conversationID, userID)
|
||||||
}
|
}
|
||||||
@ -895,11 +880,11 @@ func (db *commonMsgDatabase) GetConversationMinMaxSeqInMongoAndCache(ctx context
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
minSeqCache, err = db.seq.GetMinSeq(ctx, conversationID)
|
minSeqCache, err = db.seqConversation.GetMinSeq(ctx, conversationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
maxSeqCache, err = db.seq.GetMaxSeq(ctx, conversationID)
|
maxSeqCache, err = db.seqConversation.GetMaxSeq(ctx, conversationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1011,33 +996,8 @@ func (db *commonMsgDatabase) DeleteDocMsgBefore(ctx context.Context, ts int64, d
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//func (db *commonMsgDatabase) ClearMsg(ctx context.Context, ts int64) (err error) {
|
|
||||||
// var (
|
|
||||||
// docNum int
|
|
||||||
// msgNum int
|
|
||||||
// start = time.Now()
|
|
||||||
// )
|
|
||||||
// for {
|
|
||||||
// msgs, err := db.msgDocDatabase.GetBeforeMsg(ctx, ts, 100)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// if len(msgs) == 0 {
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
// for _, msg := range msgs {
|
|
||||||
// num, err := db.deleteOneMsg(ctx, ts, msg)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// docNum++
|
|
||||||
// msgNum += num
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (db *commonMsgDatabase) setMinSeq(ctx context.Context, conversationID string, seq int64) error {
|
func (db *commonMsgDatabase) setMinSeq(ctx context.Context, conversationID string, seq int64) error {
|
||||||
dbSeq, err := db.seq.GetMinSeq(ctx, conversationID)
|
dbSeq, err := db.seqConversation.GetMinSeq(ctx, conversationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(errs.Unwrap(err), redis.Nil) {
|
if errors.Is(errs.Unwrap(err), redis.Nil) {
|
||||||
return nil
|
return nil
|
||||||
@ -1047,5 +1007,5 @@ func (db *commonMsgDatabase) setMinSeq(ctx context.Context, conversationID strin
|
|||||||
if dbSeq >= seq {
|
if dbSeq >= seq {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return db.seq.SetMinSeq(ctx, conversationID, seq)
|
return db.seqConversation.SetMinSeq(ctx, conversationID, seq)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package mgo
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||||
"github.com/openimsdk/tools/db/mongoutil"
|
"github.com/openimsdk/tools/db/mongoutil"
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
@ -10,16 +11,24 @@ import (
|
|||||||
"go.mongodb.org/mongo-driver/mongo/options"
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewSeqMongo(db *mongo.Database) (*SeqMongo, error) {
|
func NewSeqConversationMongo(db *mongo.Database) (database.SeqConversation, error) {
|
||||||
coll := db.Collection("seq")
|
coll := db.Collection(database.SeqConversationName)
|
||||||
return &SeqMongo{coll: coll}, nil
|
_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
|
||||||
|
Keys: bson.D{
|
||||||
|
{Key: "conversation_id", Value: 1},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &seqConversationMongo{coll: coll}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type SeqMongo struct {
|
type seqConversationMongo struct {
|
||||||
coll *mongo.Collection
|
coll *mongo.Collection
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SeqMongo) Malloc(ctx context.Context, conversationID string, size int64) (int64, error) {
|
func (s *seqConversationMongo) Malloc(ctx context.Context, conversationID string, size int64) (int64, error) {
|
||||||
if size < 0 {
|
if size < 0 {
|
||||||
return 0, errors.New("size must be greater than 0")
|
return 0, errors.New("size must be greater than 0")
|
||||||
}
|
}
|
||||||
@ -29,7 +38,7 @@ func (s *SeqMongo) Malloc(ctx context.Context, conversationID string, size int64
|
|||||||
filter := map[string]any{"conversation_id": conversationID}
|
filter := map[string]any{"conversation_id": conversationID}
|
||||||
update := map[string]any{
|
update := map[string]any{
|
||||||
"$inc": map[string]any{"max_seq": size},
|
"$inc": map[string]any{"max_seq": size},
|
||||||
"$set": map[string]any{"min_seq": 1},
|
"$set": map[string]any{"min_seq": int64(0)},
|
||||||
}
|
}
|
||||||
opt := options.FindOneAndUpdate().SetUpsert(true).SetReturnDocument(options.After).SetProjection(map[string]any{"_id": 0, "max_seq": 1})
|
opt := options.FindOneAndUpdate().SetUpsert(true).SetReturnDocument(options.After).SetProjection(map[string]any{"_id": 0, "max_seq": 1})
|
||||||
lastSeq, err := mongoutil.FindOneAndUpdate[int64](ctx, s.coll, filter, update, opt)
|
lastSeq, err := mongoutil.FindOneAndUpdate[int64](ctx, s.coll, filter, update, opt)
|
||||||
@ -39,7 +48,19 @@ func (s *SeqMongo) Malloc(ctx context.Context, conversationID string, size int64
|
|||||||
return lastSeq - size, nil
|
return lastSeq - size, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SeqMongo) GetMaxSeq(ctx context.Context, conversationID string) (int64, error) {
|
func (s *seqConversationMongo) MallocSeq(ctx context.Context, conversationID string, size int64) ([]int64, error) {
|
||||||
|
first, err := s.Malloc(ctx, conversationID, size)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
seqs := make([]int64, 0, size)
|
||||||
|
for i := int64(0); i < size; i++ {
|
||||||
|
seqs = append(seqs, first+i+1)
|
||||||
|
}
|
||||||
|
return seqs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *seqConversationMongo) GetMaxSeq(ctx context.Context, conversationID string) (int64, error) {
|
||||||
seq, err := mongoutil.FindOne[int64](ctx, s.coll, bson.M{"conversation_id": conversationID}, options.FindOne().SetProjection(map[string]any{"_id": 0, "max_seq": 1}))
|
seq, err := mongoutil.FindOne[int64](ctx, s.coll, bson.M{"conversation_id": conversationID}, options.FindOne().SetProjection(map[string]any{"_id": 0, "max_seq": 1}))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return seq, nil
|
return seq, nil
|
||||||
@ -50,7 +71,7 @@ func (s *SeqMongo) GetMaxSeq(ctx context.Context, conversationID string) (int64,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SeqMongo) GetMinSeq(ctx context.Context, conversationID string) (int64, error) {
|
func (s *seqConversationMongo) GetMinSeq(ctx context.Context, conversationID string) (int64, error) {
|
||||||
seq, err := mongoutil.FindOne[int64](ctx, s.coll, bson.M{"conversation_id": conversationID}, options.FindOne().SetProjection(map[string]any{"_id": 0, "min_seq": 1}))
|
seq, err := mongoutil.FindOne[int64](ctx, s.coll, bson.M{"conversation_id": conversationID}, options.FindOne().SetProjection(map[string]any{"_id": 0, "min_seq": 1}))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return seq, nil
|
return seq, nil
|
||||||
@ -61,10 +82,10 @@ func (s *SeqMongo) GetMinSeq(ctx context.Context, conversationID string) (int64,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SeqMongo) SetMinSeq(ctx context.Context, conversationID string, seq int64) error {
|
func (s *seqConversationMongo) SetMinSeq(ctx context.Context, conversationID string, seq int64) error {
|
||||||
return mongoutil.UpdateOne(ctx, s.coll, bson.M{"conversation_id": conversationID}, bson.M{"$set": bson.M{"min_seq": seq}}, false)
|
return mongoutil.UpdateOne(ctx, s.coll, bson.M{"conversation_id": conversationID}, bson.M{"$set": bson.M{"min_seq": seq}}, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SeqMongo) GetConversation(ctx context.Context, conversationID string) (*model.Seq, error) {
|
func (s *seqConversationMongo) GetConversation(ctx context.Context, conversationID string) (*model.SeqConversation, error) {
|
||||||
return mongoutil.FindOne[*model.Seq](ctx, s.coll, bson.M{"conversation_id": conversationID})
|
return mongoutil.FindOne[*model.SeqConversation](ctx, s.coll, bson.M{"conversation_id": conversationID})
|
||||||
}
|
}
|
||||||
@ -14,5 +14,5 @@ const (
|
|||||||
LogName = "log"
|
LogName = "log"
|
||||||
ObjectName = "s3"
|
ObjectName = "s3"
|
||||||
UserName = "user"
|
UserName = "user"
|
||||||
SeqName = "seq"
|
SeqConversationName = "seq"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -2,7 +2,7 @@ package database
|
|||||||
|
|
||||||
import "context"
|
import "context"
|
||||||
|
|
||||||
type Seq interface {
|
type SeqConversation interface {
|
||||||
Malloc(ctx context.Context, conversationID string, size int64) (int64, error)
|
Malloc(ctx context.Context, conversationID string, size int64) (int64, error)
|
||||||
GetMaxSeq(ctx context.Context, conversationID string) (int64, error)
|
GetMaxSeq(ctx context.Context, conversationID string) (int64, error)
|
||||||
GetMinSeq(ctx context.Context, conversationID string) (int64, error)
|
GetMinSeq(ctx context.Context, conversationID string) (int64, error)
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
type Seq struct {
|
type SeqConversation struct {
|
||||||
ConversationID string `bson:"conversation_id"`
|
ConversationID string `bson:"conversation_id"`
|
||||||
MaxSeq int64 `bson:"max_seq"`
|
MaxSeq int64 `bson:"max_seq"`
|
||||||
MinSeq int64 `bson:"min_seq"`
|
MinSeq int64 `bson:"min_seq"`
|
||||||
|
|||||||
152
tools/seq/internal/main.go
Normal file
152
tools/seq/internal/main.go
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/common/cmd"
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo"
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||||
|
"github.com/openimsdk/tools/db/mongoutil"
|
||||||
|
"github.com/openimsdk/tools/db/redisutil"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const batchSize = 5
|
||||||
|
|
||||||
|
func readConfig[T any](dir string, name string) (*T, error) {
|
||||||
|
data, err := os.ReadFile(filepath.Join(dir, name))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var conf T
|
||||||
|
if err := yaml.Unmarshal(data, &conf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &conf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func redisKey(rdb redis.UniversalClient, prefix string, fn func(ctx context.Context, key string, delKey map[string]struct{}) error) error {
|
||||||
|
var (
|
||||||
|
cursor uint64
|
||||||
|
keys []string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
ctx := context.Background()
|
||||||
|
for {
|
||||||
|
keys, cursor, err = rdb.Scan(ctx, cursor, prefix+"*", batchSize).Result()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
delKey := make(map[string]struct{})
|
||||||
|
if len(keys) > 0 {
|
||||||
|
for _, key := range keys {
|
||||||
|
if err := fn(ctx, key, delKey); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(delKey) > 0 {
|
||||||
|
//if err := rdb.Del(ctx, datautil.Keys(delKey)...).Err(); err != nil {
|
||||||
|
// return err
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
if cursor == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main(conf string) error {
|
||||||
|
redisConfig, err := readConfig[config.Redis](conf, cmd.RedisConfigFileName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mongodbConfig, err := readConfig[config.Mongo](conf, cmd.MongodbConfigFileName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||||
|
defer cancel()
|
||||||
|
rdb, err := redisutil.NewRedisClient(ctx, redisConfig.Build())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mgocli, err := mongoutil.NewMongoDB(ctx, mongodbConfig.Build())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := mgo.NewSeqConversationMongo(mgocli.GetDB()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
coll := mgocli.GetDB().Collection(database.SeqConversationName)
|
||||||
|
const prefix = cachekey.MaxSeq
|
||||||
|
return redisKey(rdb, prefix, func(ctx context.Context, key string, delKey map[string]struct{}) error {
|
||||||
|
conversationId := strings.TrimPrefix(key, prefix)
|
||||||
|
delKey[key] = struct{}{}
|
||||||
|
maxValue, err := rdb.Get(ctx, key).Result()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
seq, err := strconv.Atoi(maxValue)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid max seq %s", maxValue)
|
||||||
|
}
|
||||||
|
if seq == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if seq < 0 {
|
||||||
|
return fmt.Errorf("invalid max seq %s", maxValue)
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
minSeq int64
|
||||||
|
maxSeq = int64(seq)
|
||||||
|
)
|
||||||
|
minKey := cachekey.MinSeq + conversationId
|
||||||
|
delKey[minKey] = struct{}{}
|
||||||
|
minValue, err := rdb.Get(ctx, minKey).Result()
|
||||||
|
if err == nil {
|
||||||
|
seq, err := strconv.Atoi(minValue)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid min seq %s", minValue)
|
||||||
|
}
|
||||||
|
if seq < 0 {
|
||||||
|
return fmt.Errorf("invalid min seq %s", minValue)
|
||||||
|
}
|
||||||
|
minSeq = int64(seq)
|
||||||
|
} else if !errors.Is(err, redis.Nil) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if maxSeq < minSeq {
|
||||||
|
return fmt.Errorf("invalid max seq %d < min seq %d", maxSeq, minSeq)
|
||||||
|
}
|
||||||
|
res, err := mongoutil.FindOne[*model.SeqConversation](ctx, coll, bson.M{"conversation_id": conversationId}, nil)
|
||||||
|
if err == nil {
|
||||||
|
if res.MaxSeq < int64(seq) {
|
||||||
|
_, err = coll.UpdateOne(ctx, bson.M{"conversation_id": conversationId}, bson.M{"$set": bson.M{"max_seq": maxSeq, "min_seq": minSeq}})
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
} else if errors.Is(err, mongo.ErrNoDocuments) {
|
||||||
|
res = &model.SeqConversation{
|
||||||
|
ConversationID: conversationId,
|
||||||
|
MaxSeq: maxSeq,
|
||||||
|
MinSeq: minSeq,
|
||||||
|
}
|
||||||
|
_, err := coll.InsertOne(ctx, res)
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
16
tools/seq/main.go
Normal file
16
tools/seq/main.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"github.com/openimsdk/open-im-server/v3/tools/seq/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var config string
|
||||||
|
flag.StringVar(&config, "redis", "/Users/chao/Desktop/project/open-im-server/config", "config directory")
|
||||||
|
flag.Parse()
|
||||||
|
if err := internal.Main(config); err != nil {
|
||||||
|
fmt.Println("seq task", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user