手遊服務端架構之使用Redis實現跨服熱門排行榜

來源:互聯網
上載者:User
實現跨服熱門排行榜的常規方法

遊戲裡為了刺激玩家的攀比心理,經常有各種各樣的熱門排行榜。熱門排行榜又可以分為本服熱門排行榜以及跨服熱門排行榜。

簡單說來,本服熱門排行榜上的記錄來自本服的玩家,而跨服熱門排行榜上的記錄是來自所有伺服器前N名玩家。通常,跨服熱門排行榜含金量更大,獎勵也更為豐富。從技術上而言,實現起來也更為麻煩。

典型地,實現跨服熱門排行榜有一下幾種思路。

取其中某個伺服器作為中心服,用來收集各服熱門排行榜資料並進行廣播; 使用獨立進程,例如web後台,向各個服務拉取熱門排行榜資料; 利用Redis的SortedSet,由Redis自己實現排序

本文詳細介紹如何使用Redis實現跨服熱門排行榜 Redis叢集的簡單用法

Redis是一個Key-Value的快取資料庫。這裡不做過多介紹。為了提高IO效率,最新的Redis支援叢集服務。官方的Redis是不支援Windows環境,所以本文開發環境是在Linux Ubuntu上。Redis的java用戶端實現是Jedis。下面的對RedisCluster的簡單封裝,包括對Redis的各種資料操作。

public  enum RedisCluster {INSTANCE;private JedisCluster cluster;public  void init() {String url = "127.0.0.1:8001";HashSet<HostAndPort> hostAndPorts = new HashSet<>();String[] hostPort = url.split(":");HostAndPort hostAndPort = new HostAndPort(hostPort[0], Integer.parseInt(hostPort[1]));hostAndPorts.add(hostAndPort);JedisPoolConfig poolConfig = new JedisPoolConfig();poolConfig.setMaxTotal(50);poolConfig.setMinIdle(1);poolConfig.setMaxIdle(10);this.cluster = new JedisCluster(hostAndPorts, 2000, poolConfig);}public  void destory() {try {cluster.close();} catch (IOException e) {e.printStackTrace();}}public Double zscore(String key, String member) {try {return cluster.zscore(key, member);} catch (JedisException e) {LoggerUtils.error("", e);throw new JedisException(e);}}public Set<Tuple> zrangeWithScores(String key, long start, long end) {try {return cluster.zrangeWithScores(key, start, end);} catch (JedisException e) {LoggerUtils.error("", e);throw new JedisException(e);}}public Set<Tuple> zrevrangeWithScores(String key, long start, long end) {try {return cluster.zrevrangeWithScores(key, start, end);} catch (JedisException e) {LoggerUtils.error("", e);return new HashSet<>(0);}}public Double zincrby(String key, double score, String member) {try {return cluster.zincrby(key, score, member);} catch (JedisException e) {LoggerUtils.error("", e);return null;}}public Long zrank(String key, String member) {try {return cluster.zrank(key, member);} catch (JedisException e) {LoggerUtils.error("", e);return -1L;}}public long hset(String key, String field, String value) {try {return cluster.hset(key, field, value);} catch (JedisException e) {LoggerUtils.error("", e);}return -1L;}public String hget(String key, String field) {try {return cluster.hget(key, field);} catch (JedisException e) {LoggerUtils.error("", e);return null;}}}
Redis實現跨服熱門排行榜的技術要點

有了Redis的SortedSet,可以輕易實現角色id與分數的有序映射。而對於具體的熱門排行榜記錄,則可以利用Redis的hashmap資料結構進行儲存。

由於Redis的SortedSet的score類型為double,只有52位的整數精度。而業務上的熱門排行榜經常需要多級排行。比如說,玩家等級熱門排行榜需要實現等級高的玩家排在前面,當玩家等級相同,先達到高等級的需要排前面。

為了實現多級排行,我們需要將多維因素映射到一維因素。在52位精度,我們可以把低32位表示記錄建立時間,高20位表示等級值。20位最大值為100多萬,如果超過這個值,那麼就要重新考慮位元的劃分或者排行因素了。為了易於拓展,產生一維分數的方法必須允許子類修改。

跨服熱門排行榜的代碼實現

父級介面CrossRank.java代碼熱門排行榜抽象,包括一級排行指標,二級排行指標,產生時間,構建Redis資料key等抽象方法。

public interface CrossRank {int getRankType();/** * local server id * @return */int getServerId();long getCreateTime() ;long getPlayerId();/**  *  first level rank score * @return */int getScore() ;/**  *  second level rank score * @return */int getAid() ;/** redis rank type key */String buildRankKey();/** redis rank record key */String buildResultKey();/** redis rank score */long buildRankScore();}
AbstractCrossRank是CrossRank的骨架實現,儘可能提供更多方法的預設實現
second level rank score */@Protobufprivate int aid;/** 32位時間戳記 */protected  long TIME_MAX_VALUE = 0xFFFFFFFFL; public AbstractCrossRank(long playerId, int score, int aid) {this.playerId = playerId;this.score = score;this.aid  = aid;this.serverId = ServerConfig.getInstance().getServerId();this.createTime = System.currentTimeMillis();}public AbstractCrossRank(long playerId, int score) {this(playerId, score, 0);}public AbstractCrossRank() {}public int getServerId() {return serverId;}public long getPlayerId() {return this.playerId;}public long getCreateTime() {return createTime;}public int getScore() {return score;}public int getAid() {return aid;}@Overridepublic String buildRankKey() {return "CrossRank_" + getRankType();}@Overridepublic String buildResultKey() {return getClass().getSimpleName() ;}@Overridepublic double buildRankScore() {//default rank score // score      |     createtime//  20bits            32bits  long timePart = (TIME_MAX_VALUE - getCreateTime()/1000) & TIME_MAX_VALUE;long result  = (long)score << 32 | timePart;//System.err.println(( (long)score << 32)+"|"+timePart+"|"+result);return  result;}@Overridepublic String toString() {return "AbstractCrossRank [serverId=" + serverId+ ", createTime=" + createTime+ ", playerId=" + playerId+ ", score=" + score + ", aid="+ aid + "]";}}

CrossLevelRank是一個樣本實現,代表玩家等級資料

/** *  cross server level rank  * @author kingston * */public class CrossLevelRank extends AbstractCrossRank {// just for jprotobufpublic CrossLevelRank() {}public CrossLevelRank(long playerId, int score) {super(playerId, score);}public int getRankType() {return  CrossRankKinds.LEVEL;}}
CrossRankService是熱門排行榜邏輯操作工具,提供熱門排行榜資料的更新與查詢
public class CrossRankService {private static CrossRankService instance;private RedisCluster cluster = RedisCluster.INSTANCE;private Map<Integer, Class<? extends AbstractCrossRank>> rank2Class = new HashMap<>();public static CrossRankService getInstance() {if (instance != null) {return instance;}synchronized (CrossRankService.class) {if (instance == null) {instance = new CrossRankService();instance.init();}}return instance;}private void init() {rank2Class.put(CrossRankKinds.FIGHTING, CrossLevelRank.class);}public void addRank(CrossRank rank) {String key = rank.buildRankKey();String member = buildRankMember(rank.getPlayerId());double score = rank.buildRankScore();cluster.zincrby(key, score, member); // add challenge result data.String data = RedisCodecHelper.serialize(rank);cluster.hset(rank.buildResultKey(), member, data);}private String buildRankMember(long  playerId) {return String.valueOf(playerId);}public List<CrossRank> queryRank(int rankType, int start, int end) {List<CrossRank> ranks = new ArrayList<>();Set<Tuple> tupleSet = cluster.zrevrangeWithScores("CrossRank_"  + rankType, start , end );Class<? extends AbstractCrossRank> rankClazz = rank2Class.get(rankType);for (Tuple record:tupleSet) {try{String element = record.getElement();AbstractCrossRank rankProto = rankClazz.newInstance();String resultKey = rankProto.buildResultKey();String data = cluster.hget(resultKey, element);CrossRank rank = unserialize(data, rankClazz);ranks.add(rank);}catch(Exception e) {e.printStackTrace();}}return ranks;}public <T extends CrossRank>  T unserialize(String rankData, Class<T> clazz) {return RedisCodecHelper.deserialize(rankData, clazz);}}
測試代碼,開啟Redis叢集服務後,執行RedisRankTest類單元測試
public class RedisRankTest {@Testpublic void test() {RedisCluster cluster = RedisCluster.INSTANCE;cluster.init();cluster.clearAllData();CrossRankService rankService = CrossRankService.getInstance();final int N_RECORD =  10;for (int i=1;i<N_RECORD*2;i++) {rankService.addRank(new CrossLevelRank(i, 100+i));}List<CrossRank> ranks = rankService.queryRank(CrossRankKinds.FIGHTING, 1, N_RECORD);for (CrossRank rank:ranks) {System.err.println(rank);}assertTrue(ranks.size() == N_RECORD);assertTrue(ranks.get(0).getScore() >= ranks.get(1).getScore());}}


手遊服務端開源架構系列完整的代碼請移步github ->>  jforgame





相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.