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);
}
}