超大型组和用户缓存redis

java 复制代码
package com.kongjs.im.transfer.service;

import jakarta.annotation.Resource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@EnableScheduling
@Service
public class ImGroupService {

    private static final int SHARD_COUNT = 128; // 分片数,可配置
    private static final String GROUP_MEMBERS = "group:{%s}:members:{%s}";

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    // 计算用户属于哪个分片
    private int getShardId(Long groupId, Long userId) {
        // 混合 groupId 和 userId 使分布更均匀
        long hash = (groupId.hashCode() * 31L + userId.hashCode()) & 0x7fffffff;
        return (int) (hash % SHARD_COUNT);
    }

    private String getShardKey(Long groupId, int shardId) {
        return String.format(GROUP_MEMBERS, groupId, shardId);
    }

    // 添加成员
    public void addMember(Long groupId, Long userId) {
        int shardId = getShardId(groupId, userId);
        String key = getShardKey(groupId, shardId);
        stringRedisTemplate.opsForSet().add(key, userId.toString());
    }

    // 移除成员
    public void removeMember(Long groupId, Long userId) {
        int shardId = getShardId(groupId, userId);
        String key = getShardKey(groupId, shardId);
        stringRedisTemplate.opsForSet().remove(key, userId.toString());
    }

    // 判断成员是否存在
    public boolean isMember(Long groupId, Long userId) {
        int shardId = getShardId(groupId, userId);
        String key = getShardKey(groupId, shardId);
        Boolean exists = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
        return Boolean.TRUE.equals(exists);
    }

    // 获取群所有成员数量(跨分片求和)
    public long getMemberCount(Long groupId) {
        // 构建所有分片 key
        List<String> keys = new ArrayList<>();
        for (int i = 0; i < SHARD_COUNT; i++) {
            keys.add(getShardKey(groupId, i));
        }
        // 定义 Lua 脚本
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText("""
                local total = 0
                for i, key in ipairs(KEYS) do
                    local size = redis.call('scard', key)
                    if size then total = total + size end
                end
                return total
                """);
        //script.setResultType(Long.class);
        Long count = stringRedisTemplate.execute(script, keys);
        return count != null ? count : 0L;
    }

    public List<Long> getAllGroupMembers(Long groupId) {
        // 构造 128 个分片 key
        List<String> keys = new ArrayList<>();
        for (int i = 0; i < SHARD_COUNT; i++) {
            keys.add(getShardKey(groupId, i));
        }
        DefaultRedisScript<List<Long>> script = new DefaultRedisScript<>();
        script.setScriptText("""
                    local result = {}
                    for i, key in ipairs(KEYS) do
                        local members = redis.call('smembers', key)
                        for _, uid in ipairs(members) do
                            table.insert(result, uid)
                        end
                    end
                    return result
                """);
        //script.setResultType(List.class);
        List<Long> list = stringRedisTemplate.execute(script, keys);
        return CollectionUtils.isEmpty(list) ? Collections.emptyList() : list;
    }

    //    @Scheduled(fixedDelay = 1000)
    public void syncGroupMembers() {
        long groupId = 1001;
        /*for (int i = 0; i < 1000000; i++) {
            addMember(groupId, (1L + i) * 2);
        }*/
        long memberCount = getMemberCount(groupId);
        List<Long> allGroupMembers = getAllGroupMembers(groupId);
        System.out.println(memberCount);
        System.out.println(allGroupMembers);
    }
}
相关推荐
用户3074596982072 天前
Redis 延时队列详解
redis
烤代码的吐司君2 天前
Redis 数据结构 ZSet, BIT, HyperLogLog,Geo 空间数据
redis·后端
leeyi4 天前
Checkpoint 机制:Agent 怎么在断电后接着跑
redis·aigc·agent
云技纵横5 天前
一个 @Async 让循环依赖暴雷:Spring 代理的暗坑
redis
犯困蛋挞yy6 天前
用Claude快速解决Redis代码报错反复无解的问题
redis
小七-七牛开发者7 天前
TokenPilot:让 LLM Agent 长会话成本降 60%+ 的上下文管理
缓存·agent·token·context·上下文·推理成本
用户31693538118312 天前
Java连接Redis
redis
小小工匠14 天前
Redis - 事务机制:能实现 ACID 属性吗
数据结构·redis·性能优化·并发·持久化
ofoxcoding14 天前
在AI API聚合平台配置DeepSeek V3.2提示词缓存实战:快速接入与成本优化指南
人工智能·spring·缓存·ai
NeilYuen14 天前
gRPC结合FAISS构建AI助手语义缓存模块(一):设计
人工智能·缓存·faiss