超大型组和用户缓存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);
    }
}
相关推荐
阿维的博客日记11 小时前
zset实现延迟队列
redis·zset
无小道12 小时前
Redis——string类型相关指令
redis·指令·string
один but you13 小时前
unorder_map 和unorder_set
算法·哈希算法·散列表
码云骑士13 小时前
Redis 入门实战:从 NoSQL 概念到安装与基础操作详解(一)
数据库·redis·缓存
Tirzano15 小时前
批量查询在线成员对应节点redis
数据库·redis·junit
高翔·权衡之境15 小时前
主题9:DMA与零拷贝——让CPU从数据搬运中解放
驱动开发·安全·缓存·系统安全·信息与通信
Hello--_--World15 小时前
为什么 用vite进行分包后,可以通过 浏览器强制缓存 提高性能?路由懒加载进行的分包与 vite进行的分包有什么不同?
前端·javascript·缓存·vite
wljt16 小时前
Redis的5种数据类型
数据库·redis·缓存
敖正炀16 小时前
分布式锁与 Redisson 深度:续期、红锁与无锁化
redis