超大型组和用户缓存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);
    }
}
相关推荐
thesky1234562 小时前
智能体面试准备(二十六):Agent 成本工程——模型路由、缓存、预算控制与大小模型分工
缓存·llmops·智能体·模型路由·成本工程·预算控制·小模型分工
cfm_29145 小时前
高并发系统缓存全解
java·缓存
Crazy________5 小时前
Redis03:持久化存储,大key分析,主从复制及哨兵模式
数据库·redis·容器
要开心吖ZSH6 小时前
本地缓存方案选择指南:volatile、ConcurrentHashMap、Caffeine 怎么选?
缓存·caffeine·volatile·本地缓存
范什么特西7 小时前
redis题目面渣重点
数据库·redis·缓存
圣殿骑士-Khtangc7 小时前
Go-Map面试核心考点从哈希冲突到并发安全的完整解析
面试·golang·哈希算法
今天的砖头有点烫手啊7 小时前
接口太慢?Spring Boot 缓存体系 @Cacheable 全链路拆解
spring boot·后端·缓存
xcLeigh8 小时前
Go入门:短变量声明的陷阱与最佳实践
java·redis·golang·教程·变量
szephyr8 小时前
腾讯云 ADP 智能体的 Skills 版本回滚总是回到旧配置,是缓存没清还是版本管理没开?
java·缓存·腾讯云
油丶酸萝卜别吃12 小时前
Redis 布隆过滤器快速实现
数据库·redis·缓存