150 lines
4.0 KiB
Go
150 lines
4.0 KiB
Go
package redis
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/cache/cachekey"
|
||
"git.imall.cloud/openim/protocol/constant"
|
||
"github.com/openimsdk/tools/errs"
|
||
"github.com/redis/go-redis/v9"
|
||
)
|
||
|
||
const onlineUserCountHistorySeparator = ":"
|
||
|
||
// OnlineUserCountSample 在线人数历史采样点
|
||
type OnlineUserCountSample struct {
|
||
// Timestamp 采样时间(毫秒时间戳)
|
||
Timestamp int64
|
||
// Count 采样在线人数
|
||
Count int64
|
||
}
|
||
|
||
// GetOnlineUserCount 读取在线人数缓存
|
||
func GetOnlineUserCount(ctx context.Context, rdb redis.UniversalClient) (int64, error) {
|
||
if rdb == nil {
|
||
return 0, errs.ErrInternalServer.WrapMsg("redis client is nil")
|
||
}
|
||
val, err := rdb.Get(ctx, cachekey.OnlineUserCountKey).Result()
|
||
if err != nil {
|
||
if errors.Is(err, redis.Nil) {
|
||
return 0, err
|
||
}
|
||
return 0, errs.Wrap(err)
|
||
}
|
||
count, err := strconv.ParseInt(val, 10, 64)
|
||
if err != nil {
|
||
return 0, errs.WrapMsg(err, "parse online user count failed")
|
||
}
|
||
return count, nil
|
||
}
|
||
|
||
// RefreshOnlineUserCount 刷新在线人数缓存
|
||
func RefreshOnlineUserCount(ctx context.Context, rdb redis.UniversalClient) (int64, error) {
|
||
if rdb == nil {
|
||
return 0, errs.ErrInternalServer.WrapMsg("redis client is nil")
|
||
}
|
||
var (
|
||
cursor uint64
|
||
total int64
|
||
)
|
||
now := strconv.FormatInt(time.Now().Unix(), 10)
|
||
for {
|
||
keys, nextCursor, err := rdb.Scan(ctx, cursor, fmt.Sprintf("%s*", cachekey.OnlineKey), constant.ParamMaxLength).Result()
|
||
if err != nil {
|
||
return 0, errs.Wrap(err)
|
||
}
|
||
for _, key := range keys {
|
||
count, err := rdb.ZCount(ctx, key, now, "+inf").Result()
|
||
if err != nil {
|
||
return 0, errs.Wrap(err)
|
||
}
|
||
if count > 0 {
|
||
total++
|
||
}
|
||
}
|
||
cursor = nextCursor
|
||
if cursor == 0 {
|
||
break
|
||
}
|
||
}
|
||
if err := rdb.Set(ctx, cachekey.OnlineUserCountKey, total, 0).Err(); err != nil {
|
||
return 0, errs.Wrap(err)
|
||
}
|
||
return total, nil
|
||
}
|
||
|
||
// AppendOnlineUserCountHistory 写入在线人数历史采样
|
||
func AppendOnlineUserCountHistory(ctx context.Context, rdb redis.UniversalClient, timestamp int64, count int64) error {
|
||
if rdb == nil {
|
||
return errs.ErrInternalServer.WrapMsg("redis client is nil")
|
||
}
|
||
if timestamp <= 0 {
|
||
return errs.ErrArgs.WrapMsg("invalid timestamp")
|
||
}
|
||
member := fmt.Sprintf("%d%s%d", timestamp, onlineUserCountHistorySeparator, count)
|
||
if err := rdb.ZAdd(ctx, cachekey.OnlineUserCountHistoryKey, redis.Z{
|
||
Score: float64(timestamp),
|
||
Member: member,
|
||
}).Err(); err != nil {
|
||
return errs.Wrap(err)
|
||
}
|
||
// 清理历史数据,避免无界增长
|
||
retentionMs := int64(cachekey.OnlineUserCountHistoryRetention / time.Millisecond)
|
||
cutoff := timestamp - retentionMs
|
||
if cutoff > 0 {
|
||
if err := rdb.ZRemRangeByScore(ctx, cachekey.OnlineUserCountHistoryKey, "0", strconv.FormatInt(cutoff, 10)).Err(); err != nil {
|
||
return errs.Wrap(err)
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// GetOnlineUserCountHistory 读取在线人数历史采样
|
||
func GetOnlineUserCountHistory(ctx context.Context, rdb redis.UniversalClient, startTime int64, endTime int64) ([]OnlineUserCountSample, error) {
|
||
if rdb == nil {
|
||
return nil, errs.ErrInternalServer.WrapMsg("redis client is nil")
|
||
}
|
||
if startTime <= 0 || endTime <= 0 || endTime <= startTime {
|
||
return nil, nil
|
||
}
|
||
// 包含endTime的数据,使用endTime作为最大值
|
||
values, err := rdb.ZRangeByScore(ctx, cachekey.OnlineUserCountHistoryKey, &redis.ZRangeBy{
|
||
Min: strconv.FormatInt(startTime, 10),
|
||
Max: strconv.FormatInt(endTime, 10),
|
||
}).Result()
|
||
if err != nil {
|
||
if errors.Is(err, redis.Nil) {
|
||
return nil, nil
|
||
}
|
||
return nil, errs.Wrap(err)
|
||
}
|
||
if len(values) == 0 {
|
||
return nil, nil
|
||
}
|
||
samples := make([]OnlineUserCountSample, 0, len(values))
|
||
for _, val := range values {
|
||
parts := strings.SplitN(val, onlineUserCountHistorySeparator, 2)
|
||
if len(parts) != 2 {
|
||
continue
|
||
}
|
||
ts, err := strconv.ParseInt(parts[0], 10, 64)
|
||
if err != nil {
|
||
continue
|
||
}
|
||
cnt, err := strconv.ParseInt(parts[1], 10, 64)
|
||
if err != nil {
|
||
continue
|
||
}
|
||
samples = append(samples, OnlineUserCountSample{
|
||
Timestamp: ts,
|
||
Count: cnt,
|
||
})
|
||
}
|
||
return samples, nil
|
||
}
|