基于Redis?Set輕松實現(xiàn)簡單的抽獎系統(tǒng)
基于 Redis Set 輕松搞定高并發(fā)抽獎系統(tǒng)
想要從零手搓一個高性能的抽獎系統(tǒng)?Redis 的 Set (集合)數(shù)據(jù)結(jié)構(gòu)絕對是你的不二之選。
它的特性和 Java 中的 HashSet 極其相似,天生自帶去重光環(huán)。這就意味著,無論一個用戶手速多快、瘋狂點擊了多少次參與,抽獎池里也永遠只有他的一個名字,完美避免了重復報名的問題。更棒的是,它底層隨機彈出元素的時間復雜度僅為 O(1)O(1)O(1),即使面對海量用戶的并發(fā)抽獎,也能輕松扛住壓力。
利用 Set 實現(xiàn)抽獎系統(tǒng)的核心邏輯非常輕量,熟練掌握以下三個命令即可:
SADD key member1 member2 ...:向獎池中添加一個或多個參與者。SPOP key count:隨機從獎池中抽出并移除指定數(shù)量的元素。非常適合“一等獎”、“二等獎”這種不允許重復中獎的核心業(yè)務場景。SRANDMEMBER key count:隨機從獎池中獲取指定數(shù)量的元素,但不移除它們。適合“陽光普照獎”、“參與獎”這種允許重復中獎的場景。
核心代碼實現(xiàn)
下面我們結(jié)合 Java (Spring Boot) 與 Redis,來落地這個抽獎系統(tǒng)。
1. Controller 層:定義抽獎接口
在這里我們定義了加入獎池、抽取大獎(不放回)以及抽取陽光獎(可放回)的 API。
package com.example.redissetrandomget.lottery;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/api/lottery")
public class LotteryController {
private final LotteryService lotteryService;
public LotteryController(LotteryService lotteryService) {
this.lotteryService = lotteryService;
}
// 加入抽獎者(支持批量)
@RequestMapping(path = "/add", method = {RequestMethod.GET, RequestMethod.POST})
public String add(@RequestParam String activityId, @RequestParam String[] userIds) {
lotteryService.addParticipants(activityId, userIds);
long remainCount = lotteryService.getRemainCount(activityId);
return "成功加入獎池!當前獎池總?cè)藬?shù):" + remainCount;
}
// 抽核心大獎(抽完即踢出獎池,絕對不重復中獎)
@GetMapping("/drawGrand")
public List<String> drawGrand(@RequestParam String activityId, @RequestParam long count) {
return lotteryService.drawGrandPrize(activityId, count);
}
// 抽幸運參與獎(抽完保留在獎池,下次還有機會)
@GetMapping("/drawSunshine")
public List<String> drawSunshine(@RequestParam String activityId, @RequestParam long count) {
return lotteryService.drawSunshinePrize(activityId, count);
}
// 查詢獎池剩余人數(shù)
@GetMapping("/remain")
public long remain(@RequestParam String activityId) {
return lotteryService.getRemainCount(activityId);
}
}
2. Service 層:封裝 Redis 操作
Service 層主要負責與 Redis 進行交互,并做了一些基礎(chǔ)的參數(shù)校驗和清理工作,保證數(shù)據(jù)的健壯性。
package com.example.redissetrandomget.lottery;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import java.util.Arrays;
import java.util.List;
@Service
public class LotteryService {
private static final String LOTTERY_KEY_PREFIX = "lottery:activity:";
private final StringRedisTemplate redisTemplate;
public LotteryService(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void addParticipants(String activityId, String... userIds) {
redisTemplate.opsForSet().add(buildKey(activityId), normalizeUserIds(userIds));
}
// 使用 pop:隨機抽取并移除(適用于大獎)
public List<String> drawGrandPrize(String activityId, long count) {
validateCount(count);
List<String> winners = redisTemplate.opsForSet().pop(buildKey(activityId), count);
return winners != null ? winners : List.of();
}
// 使用 randomMembers:隨機抽取但不移除(適用于陽光普照獎)
public List<String> drawSunshinePrize(String activityId, long count) {
validateCount(count);
List<String> winners = redisTemplate.opsForSet().randomMembers(buildKey(activityId), count);
return winners != null ? winners : List.of();
}
public long getRemainCount(String activityId) {
Long size = redisTemplate.opsForSet().size(buildKey(activityId));
return size != null ? size : 0L;
}
public void joinLottery(String activityId, String... userIds) {
addParticipants(activityId, userIds);
}
public List<String> drawWithoutRepeat(String activityId, long count) {
return drawGrandPrize(activityId, count);
}
public List<String> drawWithRepeat(String activityId, long count) {
return drawSunshinePrize(activityId, count);
}
public long participantCount(String activityId) {
return getRemainCount(activityId);
}
// --- 私有輔助方法 ---
private void validateCount(long count) {
Assert.isTrue(count > 0, "抽獎人數(shù)必須大于 0");
}
private String buildKey(String activityId) {
Assert.hasText(activityId, "活動 ID 不能為空");
return LOTTERY_KEY_PREFIX + activityId.trim();
}
private String[] normalizeUserIds(String[] userIds) {
Assert.notEmpty(userIds, "用戶列表不能為空");
String[] normalizedUserIds = Arrays.stream(userIds)
.filter(StringUtils::hasText)
.map(String::trim)
.distinct()
.toArray(String[]::new);
Assert.notEmpty(normalizedUserIds, "過濾后沒有合法的用戶 ID");
return normalizedUserIds;
}
}
接口測試與驗證
代碼準備就緒,我們來模擬一次真實的抽獎流程。
首先,我們通過接口向活動 2026 的獎池中加入 5 名測試用戶。你可以在 Redis 客戶端中使用 SCARD lottery:activity:2026 命令來驗證獎池內(nèi)的人數(shù),確認 5 人已成功入場:
測試一:抽取大獎(不放回)
我們先來測試一下抽取 2 名一等獎用戶。調(diào)用 drawGrand 接口:
HTTP
GET http://localhost:8080/api/lottery/drawGrand?activityId=2026&count=2
接口成功返回了 3 號和 5 號用戶。由于使用的是 SPOP 命令,這兩個幸運兒已經(jīng)被移出獎池,后續(xù)的抽獎中絕不會再出現(xiàn)他們的身影。
HTTP/1.1 200 Content-Type: application/json Date: Fri, 13 Mar 2026 08:54:20 GMT [ "3", "5" ]
測試二:抽取幸運參與獎(可放回)
接下來,我們測試抽取 2 名陽光普照獎。調(diào)用 drawSunshine 接口:
HTTP
GET http://localhost:8080/api/lottery/drawSunshine?activityId=2026&count=2
查看返回結(jié)果,我們發(fā)現(xiàn) 2 號用戶被抽中了兩次!這正是 SRANDMEMBER 的特性:隨機抽取元素但保留在原集合中,因此同一個用戶在同一輪或不同輪次中都有可能重復中獎。
JSON
HTTP/1.1 200 Content-Type: application/json Date: Fri, 13 Mar 2026 08:56:28 GMT [ "2", "2" ]
總結(jié)
到此這篇關(guān)于基于Redis Set輕松實現(xiàn)簡單的抽獎系統(tǒng)的文章就介紹到這了,更多相關(guān)Redis Set抽獎系統(tǒng)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis+threading實現(xiàn)多線程消息隊列的使用示例
Redis多線程消息隊列是一種使用Redis作為存儲后端的消息隊列實現(xiàn),它利用Redis的線程并發(fā)處理能力來提高消息隊列的處理效率,本文主要介紹了Redis+threading實現(xiàn)多線程消息隊列的使用示例,感興趣的可以了解一下2023-12-12
使用Redis實現(xiàn)記錄訪問次數(shù)的三種方案
這篇文章主要介紹了使用Redis實現(xiàn)記錄訪問次數(shù)的三種方案,文中通過代碼示例和圖文講解的非常詳細,對大家的學習或工作有一定的幫助,需要的朋友可以參考下2024-09-09
springboot項目redis緩存異常實戰(zhàn)案例詳解(提供解決方案)
redis基本上是高并發(fā)場景上會用到的一個高性能的key-value數(shù)據(jù)庫,屬于nosql類型,一般用作于緩存,一般是結(jié)合數(shù)據(jù)庫一塊使用的,但是在使用的過程中可能會出現(xiàn)異常的問題,這篇文章主要介紹了springboot項目redis緩存異常實戰(zhàn)案例詳解(提供解決方案),需要的朋友可以參考下2025-05-05
如何保證Redis與數(shù)據(jù)庫的數(shù)據(jù)一致性
這篇文章主要介紹了如何保證Redis與數(shù)據(jù)庫的數(shù)據(jù)一致性,文中舉了兩個場景例子介紹的非常詳細,需要的朋友可以參考下2023-05-05

