2016-08-15 41 views
14

我有一個Player類與score屬性:如何跟蹤球員的排名?

class Player(game_engine.Player): 

    def __init__(self, id): 
     super().__init__(id) 
     self.score = 0 

這個分數增加/減少爲玩家成功/失敗做目標。現在我需要告訴玩家他的軍銜從玩家的東西總額的像

print('Your rank is {0} out of {1}') 

首先我想爲所有的球員名單中,每當有什麼事情發生球員:

  1. 我檢查,如果他的分數增加或減少
  2. 發現他在列表
  3. 動他,直到他的得分是在正確的位置

但這會是極其慢。可以有成千上萬的玩家,並且玩家可以將他自己的分數重置爲0,這意味着我必須將他之後的所有人移動到堆疊中。即使找到球員將是O(n)。

我在尋找的是一款高性能解決方案。儘管應該使用常識,但RAM的使用並不那麼重要。我怎樣才能改善系統速度?

更新信息:每次他離開遊戲服務器時,我都會使用SQLAlchemy將玩家數據存儲到MySQL數據庫中,並且每次他加入服務器時都會加載它。這些通過'player_join''player_leave'事件處理:

@Event('player_join') 
def load_player(id): 
    """Load player into the global players dict.""" 
    session = Session() 
    query = session.query(Player).filter_by(id=id) 
    players[id] = query.one_or_none() or Player(id=id) 

@Event('player_leave') 
def save_player(id): 
    """Save player into the database.""" 
    session = Session() 
    session.add(players[id]) 
    session.commit() 

而且,玩家的分數是在'player_kill'事件更新:

@Event('player_kill') 
def update_score(id, target_id): 
    """Update players' scores upon a kill.""" 
    players[id].score += 2 
    players[target_id].score -= 2 
+0

您使用的數據庫? –

+0

@ r-m-n我在某些數據庫中使用MySQL –

+0

,它可以用DENSE_RANK窗口函數完成,但MySQL不支持這個函數。你可以嘗試這樣的事情http://dukesoftware00.blogspot.ru/2012/11/calculate-denserank-for-mysql.html –

回答

6

Redis的分類設置與此確切情況幫助(文檔使用排行榜作爲例子使用)http://redis.io/topics/data-types-intro#redis-sorted-sets

  • 您所關心的關鍵命令是ZADD(更新玩家等級)和ZRANK(取得名次具體播放器)。兩種操作都是O(log(N))複雜度。

Redis可以用作玩家排名的緩存。當您的應用程序啓動時,從SQL數據填充redis。在mysql中更新玩家分數時也會更新redis。

如果你有多個服務器進程/線程,他們可能會引發玩家比分更新的同時,你也應該考慮爲MySQL/redis的更新競爭條件,例如:

  • 從數據庫觸發器只更新Redis的;或
  • serialise球員比分更新;或
  • 讓數據暫時失去同步並在延遲後執行另一個緩存更新;或
  • 讓數據得到暫時不同步,並做了充分的緩存重建在固定時間間隔
4

你的問題是要對數據庫的實時更新,這需要每次db查詢。如果你在內存中保留一個分數列表,並以更合理的頻率更新它(例如每小時一次,甚至每分鐘一次,如果你的玩家真的關心他們的排名),那麼玩家仍然會體驗到真實的分數,時間進度與評分等級相關,並且他們無法確定更新中是否存在短暫的滯後。

通過內存中的分數排序列表,您可以立即以緩存內存爲代價獲取玩家的排名(即時,我的意思是在內存中進行O(lg n)查找),當然還有時間在需要時更新緩存。每次有人想要瀏覽他們的排名時,與每100k記錄的db查詢相比,這是一個更好的選擇。

在排序列表上進行詳細說明,您必須查詢db以獲取它,但您可以繼續使用它一段時間。也許你存儲last_update,並且只有當這個列表「太舊」時才重新查詢db。因此,您不必一直嘗試進行更新,而是快速更新,而只是覺得實時。

爲了近乎即時地找到某人的等級,您可以使用支持在排序列表中進行二分搜索的bisect模塊。當你得到它們時,分數會被排序。

from bisect import bisect_left 

# suppose scores are 1 through 10 
scores = range(1, 11) 

# get the insertion index for score 7 
# subtract it from len(scores) because bisect expects ascending sort 
# but you want a descending rank 
print len(scores) - bisect_left(scores, 7) 

這就是說7分是4級,這是正確的。

+0

'「通過內存中的分數排序列表,您可以立即獲得玩家的排名(即時,我的意思是O( lg n)在內存中查找)以緩存內存爲代價「這在我的主要職位中幾乎是問題(而不是數據庫問題,我在文章中幾乎沒有提到數據庫)。如何通過列表更新和快速查找,同時保持排序? –

+0

我更新了該部分的示例代碼。如果緩存信息不是100%最新的數據庫是一個經營者,讓我知道,所以我可以刪除這個。我不確定這一點,這就是爲什麼我剛纔將它作爲評論發佈的原因。 –

0

這種信息可以使用SQLAlchemy的sort_by函數來提取。如果您執行的查詢如下:

leaderboard = session.query(Player).order_by(Player.score).all() 

您將擁有按其得分排序的玩家列表。請記住,每次執行此操作時,都會對數據庫執行I/O操作,而不是保存數據python變量,這可能會很慢。