亿级用户游戏排行榜设计方案

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> commonScoreList(String bucketValue, Long topN) {

Set rangeWithScores

= redisUtil.zRevrangeWithScore(bucketValue, 0L, topN - 1);

List userScoreTuples = new ArrayList<>(rangeWithScores);

return userScoreTuples.stream().map(tuple -> {

String userId = tuple.getValue();

Double score = tuple.getScore();

Map 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 rangeWithScores

= redisUtil.zRevrangeWithScore(GameConstant.USER_SCORE_GENERAL_RANKING_TOPN, 0L, topN - 1);

List 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> nickNameList = gameRankingMapper.selectBatchByUserNo(userIdList);

collect.stream().forEach(gameRanking -> {

Map 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 map = Collections.synchronizedMap(new LinkedHashMap());

Map rankingIntervalMap = getRankingIntervalMapFromConf 《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 igCenter();

// 桶位置比较

for (Map.Entry 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 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 键

  • @param val 批量值

*/

public void delAndZaddExec(String key, Set val) {

if (StringUtils.isEmpty(key)) {

throw new IllegalArgumentException();

}

Object[] args = new Object[val.size()*2];

int i= 0;

for (ZSetOperations.TypedTuple it : val ) {

args[2*i] = String.valueOf(it.getScore());

args[2*i + 1] = it.getValue();

i++;

你可能感兴趣的:(Java,经验分享,架构,java)