flask服务端推送

本文用以记录在flask-sse使用中踩到的坑及解决方案。

当前主流的服务端推送方案

  1. 客户端短连接轮询
  2. websocket
  3. Server Send Event(SSE)

相较于轮询的方式,websocket和sse会稍显高级而不是无脑的轮询浪费网络资源。
在最近一次的方案选型中,我选择了sse,理由是websocket是双工,且需要单独的服务,而项目的需求仅是向客户端进行简单的推送信息,对比之下sse更显轻量且开发量更少。


使用过程中遇到的困难

  • flask-sse redis连接释放问题
    该库目前存在缺陷,当浏览器刷新或者网关断开重连时会重新new EventSource,然而服务端并无法得知连接的断开,导致redis连接数会一直增加。
    唯一释放的机会在于当再次收到推送信息,协程被唤醒并产生GeneratorExit异常
    因此代码中未对异常捕获,将错过最后一次释放reids连接的机会:

    1
    2
    3
    4
    5
    @stream_with_context
    def generator():
    for message in self.messages(channel=channel):
    yield str(message)
    self.redis.connection_pool.disconnect()

    修改的代码已上传github,并提交merge,但是由于没有通过python2的测试用例导致未被合并(懒得适配),有兴趣的童鞋可以直接查看我fork提交的代码。

    redis连接数查看

    1
    redis-cli -a pwd -h *.*.*.* info | grep client
  • 程序阻塞问题
    由于最开始使用之前未仔细看官方的实用说明和源码,发现uwsgi实用进程线程的启动模式,程序运行几次之后就阻塞了,最后发现正确的打开方式是使用协程的方式驱动http服务。

    另外还有一点现象,当使用flask自带的http服务器进行多线程threaded=True调试时redis连接能一次性释放掉,而当使用gunicorn+gevent方式运行时却没有一步到位的效果,原因是协程是异步的,一次最多能唤醒对应进程数量的协程,所以redis的释放问题最终还是需要通过定时推送心跳包的方式解决,推荐使用celery

  • 网关超时问题
    网关基本都会有超时设置,而浏览器的断线重连貌似对于504的异常并不起作用。
    因此需要针对超时异常进行特殊处理。
    这里给出nginx配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    location /eventsource {
    include uwsgi_params;
    uwsgi_pass eventsource-botbot-backend;
    uwsgi_buffering off;
    chunked_transfer_encoding off;
    proxy_cache off;
    access_log /var/log/nginx/eventsource_botbot.access.log;
    error_page 504 =200 @eventsource-close-graceful;
    }

    location @eventsource-close-graceful {
    add_header Content-Type text/event-stream;
    return 200;
    }

    注意每个nginx网关就需要配置


参考资料

-------------The End-------------
坚持原创技术分享,您的支持将鼓励我继续创作!