跪拜 Guibai
← All articles
Backend · Java

A Danmaku System from Standalone to Million-User Clusters

By 吃饱了得干活 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

The same push-broadcast-store pipeline underpins any real-time group chat, live-auction feed, or collaborative editing tool. The hot-cold storage pattern and the ban on synchronous DB writes are the two rules that keep latency flat and the database alive under spike traffic.

Summary

A danmaku system needs to push messages to every viewer in a live room with sub-100ms latency. HTTP polling collapses under load, so the architecture switches to WebSocket persistent connections via Netty, which keeps 50,000–100,000 connections alive on a single 4-core 8GB machine. When traffic outgrows one server, a message queue like RocketMQ broadcasts each comment to every cluster node, and Redis Pub/Sub handles smaller deployments where occasional message loss is acceptable.

Storage uses a hot-cold split: the most recent 500 messages per room sit in a Redis sorted set for instant retrieval, while all history flows asynchronously through a message queue into MySQL in batches of 1,000 rows. Synchronous database writes are avoided entirely to prevent the main push pipeline from blocking. Rate limiting is enforced with a Lua-scripted sliding window in Redis, and a DFA-based filter scans text in a single pass for sensitive words.

On the frontend, Canvas rendering replaces DOM manipulation once message volume climbs, recycling vertical tracks as text scrolls off-screen. The system also batches outbound pushes every 50ms, caches hot-room data locally with Caffeine, and isolates celebrity-stream traffic onto dedicated server resources. The roadmap moves from HTTP polling under 10,000 users to a multi-datacenter service mesh above one million.

Takeaways
HTTP polling burns 3.2× more CPU than WebSocket at ten-thousand-level concurrency and still adds 500ms+ latency.
A single Netty server (4 cores, 8 GB) holds 50,000–100,000 WebSocket connections before clustering is needed.
RocketMQ or Kafka in broadcast mode lets every cluster node receive every danmaku; Redis Pub/Sub works for smaller setups that tolerate message loss.
Never write danmaku to MySQL synchronously; push to Redis first, acknowledge the user, then batch-insert 1,000 rows at a time through a queue consumer.
Hot data stays in a Redis ZSet capped at 500 messages per room with a 1-hour TTL; cold data lands in MySQL for analytics.
A Lua-scripted sliding window in Redis enforces per-user rate limits atomically, and a DFA tree scans text for sensitive words in O(n).
Canvas rendering recycles vertical tracks as text exits the screen, avoiding DOM thrash when hundreds of messages are in flight.
Batch outbound pushes every 50ms and a Caffeine local cache with 5-second expiry cut Redis trips and IO overhead on hot rooms.
Hotspot isolation dedicates server resources to viral rooms and degrades non-critical features like colors and emojis under load.
Conclusions

The architecture is a textbook exercise in separating the fast path from the slow path: Redis acknowledges the user in ~1ms while MySQL catches up later, a pattern that applies to any write-heavy real-time feed.

Choosing between Redis Pub/Sub and a full message queue is framed as a reliability-vs-ops trade-off, not a purity test, which matches how teams actually decide under delivery pressure.

The DFA filter is a reminder that a well-known CS algorithm beats a regex loop when the word list is large and the text is short, a detail often missed in quick implementations.

The 50ms batch-push scheduler is a low-effort, high-impact optimization that turns per-message IO into a periodic bulk write, a trick that generalizes to any fan-out system.

Concepts & terms
Danmaku
Real-time comments that fly across a video or live-stream screen, visible to all viewers simultaneously. The system must push each message to every connected client in the same room with minimal latency.
WebSocket persistent connection
A TCP-based protocol that upgrades an HTTP connection to a full-duplex channel, letting the server push data to the client without a new request. It cuts end-to-end latency from 500ms+ to under 100ms compared to polling.
Hot-cold separation
A storage pattern where recent, frequently accessed data lives in a fast store (Redis) with a short TTL, while the full history is written asynchronously to a slower, durable store (MySQL). It keeps the write path fast and the database from becoming a bottleneck.
DFA (Deterministic Finite Automaton) for sensitive-word filtering
A tree-structured state machine built from a word list that scans input text character by character. It finds all matches in a single O(n) pass without backtracking, making it faster than regex for large keyword sets.
Sliding-window rate limiter (Redis + Lua)
A rate-limiting technique that stores request timestamps in a Redis sorted set. A Lua script atomically evicts expired entries, counts the remainder, and either admits or rejects the request, enforcing a cap like 5 messages per second per user.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗