@Service
@Slf4j
public class RankingServiceImpl implements RankingService {
public CommonVO gameRanking(String userNo, String gameId, Integer leaderboardType, Long topN) {
RankingInfo rankingInfo = new RankingInfo();
try {
List gameRankingList = doGameRanking(topN);
GameRanking gameRankingSelf = doGameRankingSelf(userNo);
rankingInfo.setScoreList(gameRankingList);
rankingInfo.setUserSelf(gameRankingSelf);
} catch (Exception e) {
log.error(“gameRanking exception”, e);
return CommonVO.error(CommonVOCode.SERVER_ERROR, “gameRanking exception”);
}
return CommonVO.success(rankingInfo);
}
public List doGameRanking(Long topN) {
List<Map<String, Object>> dataMapList = new ArrayList<>();
JSONArray jsonArray = JSONArray.parseArray(ConfigManager.get(GameConstant.USER_SCORE_RANKING_INTERVAL));
int size = jsonArray.size();
long totalNum = 0;
for (int i = size - 1; i >= 0; i–) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
String bucketName = jsonObject.getString(“bucketName”);
long unitBucketNum = redisUtil.zCard(bucketName);
totalNum += unitBucketNum;
if (totalNum <= topN && unitBucketNum != 0) {
List<Map<String,Object>> one = commonScoreList(bucketName, topN);
dataMapList.addAll(one);
} else if (totalNum >= topN) {
List<Map<String,Object>> two = commonScoreList(bucketName, unitBucketNum);
dataMapList.addAll(two);
break;
}
}
if (CollectionUtils.isEmpty(dataMapList)) {
return Collections.emptyList();
}
Set<ZSetOperations.TypedTuple> vals = dataMapList.stream().map(
en -> new DefaultTypedTuple<>((String) en.get(“userId”),
(Double) en.get(“score”))).collect(Collectors.toSet());
// 计算排行榜前先将topN桶删除,防止之前进入桶的用户干扰
redisUtil.delAndZaddExec(GameConstant.USER_SCORE_RANKING_TOPN, vals);
return doTopNScoreList(topN);
}
public List<Map<String, Object>> commonScoreList(String bucketValue, Long topN) {
Set<ZSetOperations.TypedTuple> rangeWithScores
= redisUtil.zRevrangeWithScore(bucketValue, 0L, topN - 1);
List<ZSetOperations.TypedTuple> userScoreTuples = new ArrayList<>(rangeWithScores);
return userScoreTuples.stream().map(tuple -> {
String userId = tuple.getValue();
Double score = tuple.getScore();
Map<String,Object> map = new HashMap<>();
map.put(“userId”, userId);
map.put(“score”, score);
return map;
}).collect(Collectors.toList());
}
public List doTopNScoreList(Long topN) {
List userIdList = new ArrayList<>();
Set<ZSetOperations.TypedTuple> rangeWithScores
= redisUtil.zRevrangeWithScore(GameConstant.USER_SCORE_GENERAL_RANKING_TOPN, 0L, topN - 1);
List<ZSetOperations.TypedTuple> userScoreTuples = new ArrayList<>(rangeWithScores);
List collect = userScoreTuples.stream().map(tuple -> {
String userId = tuple.getValue();
Double score = tuple.getScore();
userIdList.add(userId);
return GameRanking.builder()
.userNo(userId)
.leaderboardScore(score)
.ranking((long) (userScoreTuples.indexOf(tuple) + 1))
.build();
}).collect(Collectors.toList());
List<Map<String,String>> nickNameList = gameRankingMapper.selectBatchByUserNo(userIdList);
collect.stream().forEach(gameRanking -> {
Map<String,String> entity = nickNameList.stream()
.filter(map -> map.get(“userNo”).equals(gameRanking.getUserNo())).findFirst().orElse(null);
if (entity != null) {
gameRanking.setNickName(entity.get(“nickName”));
}
});
// 增加段位功能
long count = 0;
for (int i = 0; i < collect.size(); i++) {
count++;
collect.get(i).setGrade(getUserGrade(count));
}
return collect;
}
public GameRanking doGameRankingSelf(String userNo) {
Long selfRank = null;
Double score = null;
String nickName = null;
try {
GameRanking gameRanking = gameRankingMapper.selectOneByUserNo(userNo);
if (Objects.isNull(gameRanking)) {
nickName = getNickName(userNo);
} else {
nickName = gameRanking.getNickName();
}
score = gameRanking.getLeaderboardScore();
// 看该用户是否在topN的排行里
GameRanking rankingSelf = rankingSelfInTopN(userNo);
if (rankingSelf != null) {
return rankingSelf;
}
String bucketName = getBucketNameParseFromConfigCenter(score);
Map<String, Object> map = Collections.synchronizedMap(new LinkedHashMap());
Map<String, String> rankingIntervalMap = getRankingIntervalMapFromConfigCenter();
// 桶位置比较
for (Map.Entry<String, String> entry : rankingIntervalMap.entrySet()) {
if (entry.getValue().compareTo(bucketName) >= 0) {
Long perBucketSize = redisUtil.zCard(entry.getValue());
map.put(entry.getValue(), perBucketSize);
}
}
Long totalNum = 0L;
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (Objects.isNull(entry.getValue())) {
continue;
}
if (bucketName.equals(entry.getKey())) {
// 自身桶的排名
Long selfNum = redisUtil.zRevrank(bucketName, userNo) + 1;
// 自身桶排名与自身桶前面的总人数相加
totalNum += selfNum;
} else {
totalNum += Long.parseLong(entry.getValue().toString());
}
}
selfRank = totalNum;
} catch (NullPointerException e) {
selfRank = null;
score = null;
log.warn(“gameRanking userNo:{”+userNo+“} score is null”, e);
}
return GameRanking.builder()
.userNo(userNo)
.leaderboardScore(score)
.nickName(nickName)
.ranking(selfRank)
.grade(getUserGrade(selfRank))
.build();
}
public GameRanking rankingSelfInTopN(String userNo) {
Double score = redisUtil.zScore(GameConstant.USER_SCORE_GENERAL_RANKING_TOPN, userNo);
if (score == null) {
return null;
} else {
Long rank = redisUtil.zRevrank(GameConstant.USER_SCORE_GENERAL_RANKING_TOPN, userNo);
return GameRanking.builder()
.userNo(userNo)
.leaderboardScore(score)
.ranking(rank + 1)
.nickName(getNickName(userNo))
.grade(getUserGrade(rank + 1))
.build();
}
}
public String getBucketNameParseFromConfigCenter(Double score) {
JSONArray jsonArray = JSONArray.parseArray(ConfigManager.get(GameConstant.USER_SCORE_GENERAL_RANKING_INTERVAL));
int size = jsonArray.size();
boolean flag = false;
for (int i = 0; i < size; i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
String bucketInterval = jsonObject.getString(“bucketInterval”);
String bucketName = jsonObject.getString(“bucketName”);
String[] split = bucketInterval.substring(1, bucketInterval.length() - 1).split(“,”);
if ((score >= Double.parseDouble(split[0]) && “+endless”.equals(split[1])) ||
(score >= Double.parseDouble(split[0]) && score < Double.parseDouble(split[1]))) {
flag = true;
} else {
flag = false;
}
if (flag) {
return bucketName;
}
}
return “”;
}
}
4、原子性操作导致并发安全问题
redisUtil.delAndZaddExec(GameConstant.USER_SCORE_RANKING_TOPN, vals);
通过lua脚本保证原子一致性,解决并发安全问题。
public class RedisUtil {
@Autowired
private StringRedisTemplate stringRedisTemplate;
private static final String DELANDZADDSCRIPT =
“if redis.call(‘zcard’, KEYS[1]) > 0 then\n” +
" redis.call(‘del’, KEYS[1])\n" +
" for i, v in pairs(ARGV) do\n" +
" if i > (table.getn(ARGV)) / 2 then\n" +
" break\n" +
" end\n" +
" redis.call(‘zadd’, KEYS[1], ARGV[2i - 1], ARGV[2i])\n" +
" end\n" +
" return 1\n" +
“else\n” +
" for i, v in pairs(ARGV) do\n" +
" if i > (table.getn(ARGV)) / 2 then\n" +
" break\n" +
" end\n" +
" redis.call(‘zadd’,KEYS[1], ARGV[2i - 1], ARGV[2i])\n" +
" end\n" +
" return 1\n" +
“end”;
private RedisScript redisDelAndZaddScript = new DefaultRedisScript<>(DELANDZADDSCRIPT, Long.class);
/**
-
刪除及插入
-
@param key 键
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
最后
由于篇幅限制,小编在此截出几张知识讲解的图解
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
ttps://img-community.csdnimg.cn/images/e5c14a7895254671a72faed303032d36.jpg" alt=“img” style=“zoom: 33%;” />
最后
由于篇幅限制,小编在此截出几张知识讲解的图解
[外链图片转存中…(img-bKkxzKQT-1713571728819)]
[外链图片转存中…(img-HScPjk94-1713571728820)]
[外链图片转存中…(img-C3y2Ub3t-1713571728822)]
[外链图片转存中…(img-YWacwQgJ-1713571728823)]
[外链图片转存中…(img-vVoTzYvk-1713571728825)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!