PHP不直接处理弹幕渲染与动画,仅负责数据存储、校验及API分发;前端JS+CSS实现显示效果,WebSocket(如Swoole)负责实时推送;高频场景推荐Redis主写(LPUSH+LTRIM)+MySQL异步持久化;API仅需/pull(游标分页)和/send(带校验)两个轻量接口;推送用broadcast而非遍历连接;内容须过滤控制字符并截断;最终渲染由前端Canvas或requestAnimationFrame完成。
PHP 本身不直接处理弹幕的实时渲染和客户端动画,它只负责弹幕数据的存储、读取、校验与接口分发。真正的弹幕显示(飞入、碰撞、透明度、定位)由前端 JavaScript + CSS 实现;实时推送依赖 WebSocket(如 Swoole 或第三方服务),而非 PHP-FPM 的 HTTP 请求。
高频写入(用户发弹幕)、高并发读取(所有观众同时拉取最新弹幕)场景下,MySQL 容易成为瓶颈。推荐「双写策略」:
Redis 作为主写入和短时缓存:用 LPUSH 
LTRIM 限制每房间最多 500 条,过期时间设为 3600 秒MySQL 作为持久化备份:异步写入(如通过 Swoole task 进程或消息队列),字段至少包含 video_id、content、user_id、time_offset(视频播放秒数)、style(如 "top"/"bottom"/"scroll")SELECT * FROM danmaku WHERE video_id = ? ORDER BY created_at DESC LIMIT 20 —— 这会随观看人数上升而雪崩后端只需暴露两个轻量接口,全部走 JSON,禁用 session 和 cookie:
/api/danmaku/pull?video_id=123&last_id=45678:返回最近 20 条新弹幕(last_id 用于游标分页,不是时间戳),响应结构示例:{"code":0,"data":[{"id":45679,"content":"太强了","time":12.5,"style":"scroll","color":"#ff6b6b"},{"id":45680,"content":"666","time":13.2,"style":"top","color":"#4ecdc4"}]}/api/danmaku/send:POST 接收 video_id、content、time_offset,需做基础校验:mb_strlen($content) 、is_numeric($time_offset)、$time_offset >= 0 && $time_offset
user_id 在 5 秒内最多发 1 条,用 Redis INCR + EXPIRE 实现,键名如 danmaku:rate_limit:uid_12345
如果不用第三方 IM 服务(如 LeanCloud、Socket.IO),Swoole 是最贴近 PHP 生态的方案。关键不在“怎么连”,而在“怎么广播不卡”:
room_id(如 "video_123"),连接建立时将 $fd 加入该房间:$server->join($fd, 'video_123')
foreach($server->connections as $fd) 遍历推送 —— 改用 $server->broadcast('video_123', $json)
open_websocket_protocol 日志('log_file' => '/dev/null'),否则磁盘 I/O 会拖垮吞吐strip_tags()、mb_substr($content, 0, 30)、禁止 \x00-\x08 等控制字符(防止客户端解析异常)真正难的不是存和推,而是当 2000 人同时看一个视频时,如何让每条弹幕在各自浏览器里以相同速度、不重叠、不卡顿地飞过屏幕——这部分完全交给前端 Canvas 或 requestAnimationFrame 控制,PHP 只管别把脏数据或超频请求放过去。