本文介紹如何在遊戲業務中使用Bloom Filter來實現營運活動的推送控制,避免向同一玩家重複推送。本文將結合代碼(以Jedis用戶端為例),展示如何使用Jedis串連Tair(企業版)並操作Bloom Filter資料。
背景資訊
在現代的遊戲營運中,遊戲開發人員和營運團隊常常會推出各種的活動以提高使用者的活躍度、參與度或付費率。通常以彈窗、站內信、NPC任務等形式將活動推送給玩家。然而,在一個複雜的遊戲環境中,開發人員需要確保這些活動資訊的發送頻率能夠被控制,並避免重複推送相同資訊而帶來使用者體驗下降的問題,同時又需要保持整個系統的高效性。
在上述情境中,使用Bloom Filter資料結構是一種高效的解決方案,可以高效地實現對彈窗的重複控制。Bloom Filter是一種空間效率極高的機率型資料結構,用於判斷某個元素是否在集合中。它可以很快地返回某個元素可能在集合中或者一定不在集合中,優點是具有較低的空間複雜度和查詢時間複雜度,適合處理大量資料的情境,缺點是可能存在誤判(在該情境中,誤判為漏推送給某使用者,不會重複推送)。
在該情境中使用Bloom Filter的優勢:
高效性:由於Bloom Filter使用的是位元組,操作極為快速,並且其空間複雜度較低,非常適合儲存大量的使用者資料。
低記憶體佔用:與傳統的集合結構相比,Bloom Filter佔用的空間要少得多,尤其在儲存數百萬個玩家的彈窗狀態時,這一優勢更加明顯。
可擴充性:由於Bloom Filter的擴充性非常好,它適用於大規模的分布式情境,比如Redis叢集。
Tair(企業版)提供的Bloom資料結構相容Redis Bloom Filter資料結構,使用方式也與Redis Bloom Filter一致。
方案概述
以下為範例程式碼的概述,具體實現請參見下方的範例程式碼。
串連Tair(企業版)執行個體。
建立名為
activity_popup
的Bloom Filter資料結構,具體實現可參見範例程式碼中的createBloom
函數。本樣本建立Bloom Filter預計儲存50000個元素,誤判率設定為1%(即0.01)。
當玩家登入時,打算向該玩家進行推送,具體實現可參見範例程式碼中的
handlePopup
函數。但推送前需要先檢查是否需要推送,具體實現可參見範例程式碼中的
shouldShowPopup
函數。若玩家ID不在Bloom Filter中,表示未進行推送,此時需要向該玩家推送,並更新玩家的推送狀態(
updatePopupState
函數)。若玩家ID已在Bloom Filter中,表示可能已推送,則不進行推送。
樣本代碼
Jedis的依賴如下:
完整程式碼範例如下:
import redis.clients.jedis.*;
import redis.clients.jedis.UnifiedJedis;
public class TairBloomFilterDemo {
static HostAndPort hostAndPort = new HostAndPort("r-bp1y****svonly41srpd.redis.rds.aliyuncs.com", 6379); // 您可以在控制台擷取執行個體串連地址與連接埠號碼。
static JedisClientConfig config = DefaultJedisClientConfig.builder().password("tw:Da***3").build(); // 執行個體帳號密碼。
static UnifiedJedis unifiedJedis = new UnifiedJedis(hostAndPort, config);
private static final String BLOOM_KEY = "activity_popup";
/**
* 建立一個Bloom Filter Key。
*/
public static void createBloom() {
try {
unifiedJedis.bfReserve(BLOOM_KEY, 0.01, 50000);
} catch (Exception e) {
e.printStackTrace(); // 逾時等異常情況
}
}
/**
* 查詢Bloom Filter中是否已經存在指定的玩家ID。
*/
public static boolean shouldShowPopup(String playerId) {
try {
return !unifiedJedis.bfExists(BLOOM_KEY, playerId);
} catch (Exception e) {
e.printStackTrace(); // 逾時等異常情況
return true;
}
}
/**
* 將玩家ID添加到Bloom Filter Key中。
*/
public static void updatePopupState(String playerId) {
try {
unifiedJedis.bfAdd(BLOOM_KEY, playerId);
} catch (Exception e) {
e.printStackTrace(); // 逾時等異常情況。
}
}
/**
* 向指定玩家ID進行推送。
*/
public static void handlePopup(String playerId) {
if (shouldShowPopup(playerId)) {
// 進行推送。
System.out.println("推送給玩家: " + playerId);
// 更新推送狀態。
updatePopupState(playerId);
} else {
System.out.println("玩家 " + playerId + " 已推送過");
}
}
public static void main(String[] args) {
createBloom();
// 假設玩家ID為player123。
String playerId = "player123";
// 第一次調用時,應該推送。
handlePopup(playerId);
// 第二次調用時,不應該再推送。
handlePopup(playerId);
}
}
本樣本的正確執行結果如下:
推送給玩家: player123
玩家 player123 已推送過