Django+React+uWSGI应用:数据库变更触发前端重载的最优方案
原轮询方案是否合理?
短期应急可以用,但非常不推荐长期使用。每秒轮询会带来几个严重问题:
- 服务器承受大量无效请求,用户量上升后,API接口和数据库会被频繁空查询拖垮;
- 最多1秒的延迟是硬伤,没法做到真正的实时;
- 前端频繁发起请求会增加客户端资源消耗,影响用户体验。
更优实现方案
结合你的Django+React+uWSGI架构,推荐以下几种实时推送方案,从易到难排序:
1. Server-Sent Events (SSE):最简单向推送
SSE是基于HTTP的单向推送协议,服务器主动给前端发消息,不需要额外协议,uWSGI也能支持(只需配置长连接)。
实现要点:
Django端:写一个返回
text/event-stream类型的视图,保持长连接,当数据库有变更时向客户端发送事件:from django.http import StreamingHttpResponse import time from myapp.models import MyTable from django.db.models.signals import post_save from django.dispatch import receiver # 生产环境建议用Redis/消息队列替代全局变量,避免多进程数据不一致 has_change = False @receiver(post_save, sender=MyTable) def on_table_change(sender, instance, created, **kwargs): global has_change has_change = True def sse_stream(request): def event_generator(): global has_change while True: if has_change: # 自定义事件内容,比如触发指定JS函数的指令 yield f"data: reload\n\n" has_change = False time.sleep(0.5) # 降低轮询标记的频率 return StreamingHttpResponse( event_generator(), content_type='text/event-stream' )uWSGI配置需开启
http-keepalive,避免长连接被强制断开。React端:用
EventSource监听SSE流,收到事件后执行对应操作:useEffect(() => { const eventSource = new EventSource('/sse-stream/'); eventSource.onmessage = (event) => { if (event.data === 'reload') { // 执行重载或指定JS函数 window.location.reload(); // 或者调用自定义函数:myCustomFunction(); } }; return () => eventSource.close(); }, []);处理直接MySQL命令的场景:如果管理员绕开Django直接操作数据库,给
mytable加MySQL触发器,当有INSERT/UPDATE/DELETE时调用Django接口标记变更:DELIMITER // CREATE TRIGGER after_mytable_change AFTER INSERT ON mytable FOR EACH ROW BEGIN -- 生产环境建议用消息队列替代直接HTTP请求,避免触发器阻塞 CALL sys.http_get('http://your-django-server/api/mark-change/'); END // DELIMITER ;
2. WebSocket:双向实时通信
如果未来需要前端给服务器发消息的交互场景,WebSocket更合适。Django可以用Channels库实现WebSocket服务,搭配ASGI服务器(比如Daphne)与uWSGI共存(uWSGI处理HTTP请求,Daphne处理WebSocket)。
实现要点:
Django端:配置Channels,定义WebSocket消费者,监听数据库变更信号并推送消息:
# consumers.py from channels.generic.websocket import AsyncWebsocketConsumer import json from myapp.models import MyTable from django.db.models.signals import post_save from django.dispatch import receiver from channels.layers import get_channel_layer from asgiref.sync import async_to_sync channel_layer = get_channel_layer() @receiver(post_save, sender=MyTable) def send_change_notification(sender, instance, created, **kwargs): async_to_sync(channel_layer.group_send)( 'table_updates', {'type': 'send_update', 'message': 'reload'} ) class TableUpdateConsumer(AsyncWebsocketConsumer): async def connect(self): await self.channel_layer.group_add('table_updates', self.channel_name) await self.accept() async def disconnect(self, close_code): await self.channel_layer.group_discard('table_updates', self.channel_name) async def send_update(self, event): message = event['message'] await self.send(text_data=json.dumps({'message': message}))React端:用WebSocket连接,收到消息后执行操作:
useEffect(() => { const socket = new WebSocket('ws://your-django-server/ws/table-updates/'); socket.onmessage = (event) => { const data = JSON.parse(event.data); if (data.message === 'reload') { window.location.reload(); // 或调用自定义函数 } }; return () => socket.close(); }, []);直接MySQL命令的处理:同样用MySQL触发器调用Django接口,触发信号推送WebSocket消息。
3. 基于Redis Pub/Sub的中转方案
如果不想改动Django的Web服务架构,可以用Redis的发布订阅功能:
- 数据库变更时(Django信号或MySQL触发器),向Redis的指定频道发布消息;
- Django写一个后台进程(或用Celery)订阅该频道,收到消息后通过SSE/WebSocket推给前端;
- 这种方案适合多实例部署的场景,避免单服务器的推送局限。
方案选择建议
- 优先选SSE:配置最简单,不需要额外服务,适合单纯的服务器推送场景;
- 如果需要双向交互,选WebSocket + Channels;
- 多实例部署选Redis Pub/Sub + SSE/WebSocket。
内容的提问来源于stack exchange,提问作者whitebear

