直播必備!Spring Boot 實現高并發觀眾統計功能的技術方案揭秘
隨著直播平臺用戶數量激增,實時觀眾統計功能成為衡量直播間熱度和服務質量的核心指標之一。然而,在高并發場景下,如何保證統計數據的準確性與系統性能,是一個關鍵技術挑戰。本文將深入探討如何基于 Spring Boot 框架,構建一個支持高并發、低延遲、可擴展的觀眾統計系統。
系統需求分析
功能需求
- 實時統計每個直播間的在線觀眾人數。
- 支持觀眾進入、退出時的即時更新。
- 提供 REST API 接口供前端定時拉取或后端推送使用。
- 支持 WebSocket 實時推送觀眾人數變化。
技術挑戰
- 并發量高:直播高峰期間同一直播間可能存在上萬并發連接。
- 數據一致性:進入、退出直播間事件頻繁,需精準計算。
- 系統性能:避免頻繁操作數據庫導致性能瓶頸。
系統架構設計
+----------------+ +------------------+ +------------------+
| Web Frontend | <-----> | Spring Boot API | <-----> | Redis In-Memory |
| (WebSocket) | | + WebSocket | | Store |
+----------------+ +------------------+ +------------------+
|
v
+---------------+
| MySQL (持久化) |
+---------------+
核心組件
- Spring Boot API:提供 REST 接口與 WebSocket 服務。
- Redis:作為高性能計數器,用于記錄各直播間實時觀眾數。
- MySQL:作為歷史數據的持久化存儲。
技術實現方案(含代碼)
Redis 結構設計
- 使用 Hash + Set 結構保存直播間觀眾信息。
Key: live:room:{roomId}:viewers
Type: Set
Value: 用戶唯一標識(userId 或 sessionId)
Key: live:room:{roomId}:viewerCount
Type: String
Value: 當前觀眾人數
用戶進入/退出直播間邏輯
我們通過 Redis 的 Set 來記錄唯一用戶 ID,同時通過計數器確保人數準確。
@Service
public class LiveViewerService {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String VIEWER_SET_KEY = "live:room:%s:viewers";
private static final String COUNT_KEY = "live:room:%s:viewerCount";
/**
* 用戶進入直播間
*/
public void userEnter(String roomId, String userId) {
String viewerSetKey = String.format(VIEWER_SET_KEY, roomId);
String countKey = String.format(COUNT_KEY, roomId);
// 添加用戶到觀眾集合
Boolean isNew = redisTemplate.opsForSet().add(viewerSetKey, userId) == 1;
if (isNew) {
// 若是新用戶,人數 +1
redisTemplate.opsForValue().increment(countKey);
}
}
/**
* 用戶退出直播間
*/
public void userExit(String roomId, String userId) {
String viewerSetKey = String.format(VIEWER_SET_KEY, roomId);
String countKey = String.format(COUNT_KEY, roomId);
// 移除用戶
Boolean removed = redisTemplate.opsForSet().remove(viewerSetKey, userId) == 1;
if (removed) {
redisTemplate.opsForValue().decrement(countKey);
}
}
/**
* 獲取直播間當前觀眾人數
*/
public Long getViewerCount(String roomId) {
String countKey = String.format(COUNT_KEY, roomId);
String count = redisTemplate.opsForValue().get(countKey);
return count == null ? 0 : Long.parseLong(count);
}
}
REST 接口示例
@RestController
@RequestMapping("/api/live")
public class LiveController {
@Autowired
private LiveViewerService viewerService;
@PostMapping("/enter")
public ResponseEntity<String> enter(@RequestParam String roomId, @RequestParam String userId) {
viewerService.userEnter(roomId, userId);
return ResponseEntity.ok("entered");
}
@PostMapping("/exit")
public ResponseEntity<String> exit(@RequestParam String roomId, @RequestParam String userId) {
viewerService.userExit(roomId, userId);
return ResponseEntity.ok("exited");
}
@GetMapping("/count")
public ResponseEntity<Long> getCount(@RequestParam String roomId) {
return ResponseEntity.ok(viewerService.getViewerCount(roomId));
}
}
WebSocket 實時推送實現
@ServerEndpoint("/ws/viewerCount/{roomId}")
@Component
public class ViewerCountWebSocket {
private static Map<String, Set<Session>> roomSessions = new ConcurrentHashMap<>();
@OnOpen
public void onOpen(Session session, @PathParam("roomId") String roomId) {
roomSessions.computeIfAbsent(roomId, k -> ConcurrentHashMap.newKeySet()).add(session);
}
@OnClose
public void onClose(Session session, @PathParam("roomId") String roomId) {
Set<Session> sessions = roomSessions.get(roomId);
if (sessions != null) {
sessions.remove(session);
}
}
public static void broadcastViewerCount(String roomId, Long count) {
Set<Session> sessions = roomSessions.get(roomId);
if (sessions != null) {
for (Session session : sessions) {
session.getAsyncRemote().sendText(count.toString());
}
}
}
}
在 LiveViewerService
中用戶每次進入或退出后,調用:
ViewerCountWebSocket.broadcastViewerCount(roomId, getViewerCount(roomId));
即可實時推送更新。
前端集成示例 (Thymeleaf + JavaScript)
<script>
const roomId = "10001";
const socket = new WebSocket("ws://localhost:8080/ws/viewerCount/" + roomId);
socket.onmessage = function(event) {
document.getElementById("viewerCount").innerText = event.data;
};
</script>
<span>當前觀眾人數:<strong id="viewerCount">0</strong></span>
系統優化建議
- Redis 原子性保障:使用 Lua 腳本處理計數與集合操作,避免競態。
- 斷開連接處理:使用心跳機制或定時任務清理無效連接。
- Redis 過期策略:為每個 Set 設置過期時間,避免垃圾數據堆積。
- 數據持久化:定期將統計數據寫入數據庫,供歷史分析使用。
總結
通過 Redis 的高并發處理能力,結合 Spring Boot 提供的 REST 接口和 WebSocket 推送機制,我們可以實現一個既輕量又高效的直播觀眾統計系統。該方案不僅能應對大量用戶同時在線的場景,還具備良好的可擴展性與實時性,是現代直播平臺不可或缺的基礎能力。