跪拜 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 ↗