Springboot3 如何打造一個(gè)能承載百萬(wàn)用戶同時(shí)在線的超強(qiáng)直播評(píng)論系統(tǒng)?
隨著直播行業(yè)的迅猛發(fā)展,越來(lái)越多的平臺(tái)開(kāi)始承載著數(shù)百萬(wàn)甚至上千萬(wàn)用戶的同時(shí)在線互動(dòng)。尤其在直播評(píng)論系統(tǒng)中,如何保持高并發(fā)、大規(guī)模的用戶參與,成為了一個(gè)不可回避的技術(shù)挑戰(zhàn)。類(lèi)似于抖音、B站的實(shí)時(shí)彈幕系統(tǒng),它們能夠處理每秒成千上萬(wàn)的評(píng)論請(qǐng)求,同時(shí)又要確保低延遲、穩(wěn)定性以及系統(tǒng)的高可用性。
在本篇文章中,我們將通過(guò)系統(tǒng)設(shè)計(jì)的思路,逐步解析如何用 Spring Boot 3 打造一個(gè)支持百萬(wàn)用戶同時(shí)在線的超強(qiáng)直播評(píng)論系統(tǒng)。我們將通過(guò)面試官與候選人的對(duì)話模式,結(jié)合實(shí)際應(yīng)用場(chǎng)景,一步步搭建起這個(gè)系統(tǒng)架構(gòu)。希望通過(guò)這篇文章,能幫助你在技術(shù)面試中應(yīng)對(duì)類(lèi)似的系統(tǒng)設(shè)計(jì)問(wèn)題時(shí),展示出更加清晰和理性的思維。
需求梳理與邊界界定
面試官: “如果要你設(shè)計(jì)一個(gè)類(lèi)似 B站 的直播評(píng)論系統(tǒng),你會(huì)怎么著手?”
候選人: “首先,我們需要明確直播評(píng)論系統(tǒng)的核心功能和非功能需求?!?/span>
功能需求
- 核心需求:
a.發(fā)布評(píng)論:用戶能夠在觀看直播時(shí)發(fā)布實(shí)時(shí)評(píng)論。
b.實(shí)時(shí)查看評(píng)論:所有觀眾能夠?qū)崟r(shí)看到新發(fā)布的評(píng)論。
c.歷史評(píng)論展示:新加入的觀眾能夠看到他們加入直播前已發(fā)布的評(píng)論。
- 非核心需求:
a.評(píng)論回復(fù)功能:允許評(píng)論之間進(jìn)行嵌套回復(fù)。
b.評(píng)論點(diǎn)贊與表情回應(yīng):增加互動(dòng)性。
非功能需求
明確了核心功能需求后,我們需要確認(rèn)系統(tǒng)在百萬(wàn)級(jí)并發(fā)情況下的非功能性要求:
- 高擴(kuò)展性:支持水平擴(kuò)展,處理數(shù)百萬(wàn)觀眾的并發(fā)評(píng)論。
- 高可用性:即使在分布式環(huán)境下也能保證系統(tǒng)高可用,最終一致性可接受。
- 低延遲:評(píng)論廣播的端到端延遲應(yīng)控制在200毫秒以內(nèi)。
底層設(shè)計(jì)
面試官: “需求很清晰。接下來(lái)你打算如何進(jìn)行底層設(shè)計(jì)?”
候選人: “我傾向于從宏觀到微觀,先定義系統(tǒng)的核心實(shí)體和接口,這樣能夠幫助我們理清系統(tǒng)的基本構(gòu)成。”
核心實(shí)體定義
我們需要關(guān)注三個(gè)核心實(shí)體:
- 用戶(User):發(fā)布評(píng)論的用戶,可以是觀眾或主播。
- 直播視頻(LiveVideo):評(píng)論依附的直播視頻。
- 評(píng)論(Comment):用戶發(fā)布的評(píng)論。
系統(tǒng)接口設(shè)計(jì)
我們可以為每個(gè)功能設(shè)計(jì)接口,確保清晰的功能實(shí)現(xiàn)。
- 發(fā)布評(píng)論接口:
@PostMapping("/comments/{liveVideoId}")
public ResponseEntity<Void> createComment(@PathVariable String liveVideoId,
@RequestBody CommentRequest commentRequest,
@RequestHeader("Authorization") String token) {
commentService.createComment(liveVideoId, commentRequest, token);
return ResponseEntity.ok().build();
}- 獲取歷史評(píng)論接口:
@GetMapping("/comments/{liveVideoId}")
public ResponseEntity<List<CommentResponse>> getComments(@PathVariable String liveVideoId,
@RequestParam Long cursor,
@RequestParam int pageSize) {
List<CommentResponse> comments = commentService.getComments(liveVideoId, cursor, pageSize);
return ResponseEntity.ok(comments);
}高層架構(gòu)設(shè)計(jì)
面試官: “從架構(gòu)層面看,如何實(shí)現(xiàn)高并發(fā)評(píng)論發(fā)布?”
候選人: “我們可以采用三層架構(gòu):客戶端、服務(wù)端、數(shù)據(jù)庫(kù)?!?/span>
- 客戶端:負(fù)責(zé)向后端發(fā)送評(píng)論請(qǐng)求并進(jìn)行用戶認(rèn)證。
- 服務(wù)端:處理評(píng)論的接收、校驗(yàn)及持久化操作。
- 數(shù)據(jù)庫(kù):使用如 AWS DynamoDB 或阿里云 Table Store 這樣的 NoSQL 數(shù)據(jù)庫(kù)來(lái)存儲(chǔ)評(píng)論數(shù)據(jù)。
實(shí)現(xiàn)評(píng)論發(fā)布
在客戶端提交評(píng)論后,通過(guò) RESTful 接口 POST /comments/{liveVideoId} 將評(píng)論發(fā)送到服務(wù)端,服務(wù)端校驗(yàn)并將評(píng)論存儲(chǔ)到數(shù)據(jù)庫(kù)中。
@Service
public class CommentService {
@Autowired
private CommentRepository commentRepository;
@Autowired
private RedisService redisService;
public void createComment(String liveVideoId, CommentRequest commentRequest, String token) {
User user = userService.getUserFromToken(token);
Comment comment = new Comment(liveVideoId, user.getId(), commentRequest.getMessage(), new Date());
commentRepository.save(comment);
// 使用Redis進(jìn)行實(shí)時(shí)評(píng)論推送
redisService.pushToChannel(liveVideoId, comment);
}
}高性能設(shè)計(jì)
面試官: “如何解決實(shí)時(shí)評(píng)論的廣播問(wèn)題?”
候選人: “要解決實(shí)時(shí)廣播的問(wèn)題,我們可以采用 WebSocket 或服務(wù)器發(fā)送事件(SSE)。考慮到直播場(chǎng)景下讀寫(xiě)不平衡,SSE 更為合適?!?/span>
實(shí)時(shí)廣播評(píng)論
- WebSocket:適用于雙向通信,確??蛻舳藢?shí)時(shí)接收到新評(píng)論,但對(duì)于高并發(fā)場(chǎng)景,維護(hù)大量的 WebSocket 連接開(kāi)銷(xiāo)較大。
- SSE(推薦方案):通過(guò)建立持久化的 HTTP 連接,服務(wù)端可以主動(dòng)推送數(shù)據(jù),適合大規(guī)模閱讀場(chǎng)景。
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new CommentWebSocketHandler(), "/comments/{liveVideoId}")
.setAllowedOrigins("*");
}
}如何水平擴(kuò)展 SSE 服務(wù)?
要支持百萬(wàn)級(jí)并發(fā),SSE 服務(wù)必須能夠水平擴(kuò)展,處理大量的并發(fā)連接。單臺(tái)服務(wù)器無(wú)法承載百萬(wàn)個(gè)長(zhǎng)連接,必須采用分布式架構(gòu)和負(fù)載均衡方案。
負(fù)載均衡與分布式 SSE
為確保高并發(fā)時(shí)服務(wù)端可以支撐數(shù)百萬(wàn)觀眾,我們需要引入 負(fù)載均衡 和 分布式 SSE 服務(wù)。具體步驟如下:
- 負(fù)載均衡:通過(guò)負(fù)載均衡器(如 Nginx 或 HAProxy)將客戶端的 SSE 請(qǐng)求分配到不同的后端服務(wù)器。
- 發(fā)布/訂閱機(jī)制(Pub/Sub):使用 Redis 或 Kafka 等消息隊(duì)列系統(tǒng)進(jìn)行實(shí)時(shí)的評(píng)論推送,確保所有的服務(wù)器都能夠獲取到新的評(píng)論并推送到觀眾客戶端。
分布式 SSE 服務(wù)實(shí)現(xiàn)
我們可以通過(guò) Redis 來(lái)實(shí)現(xiàn)分布式 SSE 服務(wù)。在 Redis 中設(shè)置一個(gè)頻道,當(dāng)有新的評(píng)論時(shí),服務(wù)器將其發(fā)布到該頻道,所有訂閱了該頻道的 SSE 服務(wù)實(shí)例會(huì)收到新評(píng)論并推送給客戶端。
- Redis Pub/Sub 實(shí)現(xiàn):
@Service
public class RedisService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void pushToChannel(String liveVideoId, Comment comment) {
redisTemplate.convertAndSend("live:" + liveVideoId, comment);
}
}- SSE 服務(wù)端推送實(shí)現(xiàn):
@Service
public class SSEService {
private final Map<String, List<SseEmitter>> emittersMap = new ConcurrentHashMap<>();
public SseEmitter connect(String liveVideoId) {
SseEmitter sseEmitter = new SseEmitter();
emittersMap.computeIfAbsent(liveVideoId, k -> new ArrayList<>()).add(sseEmitter);
sseEmitter.onTimeout(() -> emittersMap.get(liveVideoId).remove(sseEmitter));
sseEmitter.onCompletion(() -> emittersMap.get(liveVideoId).remove(sseEmitter));
return sseEmitter;
}
public void pushComment(String liveVideoId, Comment comment) throws IOException {
List<SseEmitter> emitters = emittersMap.get(liveVideoId);
if (emitters != null) {
for (SseEmitter emitter : emitters) {
emitter.send(comment);
}
}
}
}- SSE Controller 實(shí)現(xiàn):
@RestController
public class CommentController {
@Autowired
private SSEService sseService;
@GetMapping("/sse/comments/{liveVideoId}")
public SseEmitter getComments(@PathVariable String liveVideoId) {
return sseService.connect(liveVideoId);
}
@PostMapping("/comments/{liveVideoId}")
public ResponseEntity<Void> createComment(@PathVariable String liveVideoId,
@RequestBody CommentRequest commentRequest,
@RequestHeader("Authorization") String token) throws IOException {
commentService.createComment(liveVideoId, commentRequest, token);
sseService.pushComment(liveVideoId, new Comment(liveVideoId, commentRequest.getMessage()));
return ResponseEntity.ok().build();
}
}系統(tǒng)擴(kuò)展與優(yōu)化
面試官: “如何進(jìn)一步優(yōu)化系統(tǒng)性能并保證高可用性?”
候選人: “我們可以采用分區(qū)發(fā)布/訂閱機(jī)制(Partitioned Pub/Sub)和 L7 負(fù)載均衡器(如 NGINX 或 Envoy)來(lái)實(shí)現(xiàn)?!?/span>
- 分區(qū)發(fā)布/訂閱:將評(píng)論流分為多個(gè)頻道,減少單個(gè)服務(wù)器的負(fù)載。
- L7 負(fù)載均衡:智能地將同一視頻的觀眾路由到同一服務(wù)器,避免資源浪費(fèi)。
小結(jié)
至此,我們完成了一個(gè)高并發(fā)、低延遲的直播評(píng)論系統(tǒng)的設(shè)計(jì)?;仡櫸覀兊脑O(shè)計(jì)過(guò)程:
- 需求驅(qū)動(dòng):明確系統(tǒng)的核心功能需求。
- 迭代演進(jìn):從簡(jiǎn)單的 MVP 開(kāi)始,逐步優(yōu)化系統(tǒng)架構(gòu)。
- 技術(shù)權(quán)衡:在每個(gè)關(guān)鍵點(diǎn)上做出合理的技術(shù)選型。
本篇文章不僅展示了如何設(shè)計(jì)一個(gè)百萬(wàn)級(jí)并發(fā)的直播評(píng)論系統(tǒng),還強(qiáng)調(diào)了在系統(tǒng)設(shè)計(jì)過(guò)程中結(jié)構(gòu)化思考的重要性。通過(guò)不斷迭代、權(quán)衡不同方案,最終實(shí)現(xiàn)了高效且穩(wěn)定的架構(gòu)設(shè)計(jì)。





























