超大型组和用户缓存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);
    }
}
相关推荐
流星白龙41 分钟前
【Redis】8.List列表
redis
NWU_白杨1 小时前
三种常用的数据存储技术
数据库·redis·mysql·sqlite
我叫黑大帅1 小时前
我为什么单一消费者的场景下,要用 Redis List 当消息队列?
redis·后端·面试
正儿八经的少年2 小时前
redis 的大 key 和热 key 详解
数据库·redis·缓存
X-⃢_⃢-X3 小时前
十、Redis之布隆过滤器
数据库·redis·缓存
Database_Cool_4 小时前
阿里云 Tair(企业级内存数据库)vs 腾讯云 Redis 云缓存深度对比:性能/数据结构/成本全维度 Benchmark
数据库·阿里云·缓存
流星白龙4 小时前
【Redis】1.Redis特性
数据库·redis·缓存
chexus4 小时前
21. 深入 Nginx HTTP 缓存源码:CDN功能
nginx·http·缓存
二宝哥17 小时前
CentOS 7.9 系统下 Redis 5.0.4 单机安装与源码编译安装指南
redis·centos
时空无限1 天前
vllm 缓存对模型启动时间的影响
缓存·vllm