V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
pzhyy
V2EX  ›  程序员

记一次部署 SSE 消息推送到 Cloudflare 遇到的问题

  •  
  •   pzhyy · 2023-08-17 10:33:11 +08:00 · 1426 次点击
    这是一个创建于 449 天前的主题,其中的信息可能已经有所发展或是发生改变。

    前言

    情况是这样的,在开发 MagicBee 时,需要支持实时消息通知和任务分发功能。

    在做了一番技术方案的对比之后,决定使用 SSE (Server Send Events) 来实现。

    历经一个星期加班加点地开发之后,在本地测试一切都很完美,忍不住想来一波自夸:我真是个天才!😄

    终于到了部署上线的环节,跟往常一样,这个项目依然采用单机服务,然后使用 Cloudflare DNS 解析,再通过它的边缘网络分发到世界各地。

    为了安全,我决定先上到预发布环境测一测,再发布,以确保万无一失。

    拷贝了一份 Nginx 的配置,改改,-t 测试通过,然后重启 Nginx 使配置生效。

    最后,在 Cloudflare 的配置面板,将域名解析指向自己的服务器 IP 。

    这样,后端的部署就完成了。

    遇到的问题

    我迫不及待地打开 MagicBee 插件,心里有点慌,又有点期待...

    打开控制台,切换到 Network 面板,映入眼帘的是一秒闪一下的 401 错误,心想 🤔 完了。

    愣住 3 秒,突然回过来神来的我,这是访问受限,嗷 ~~ 嗐,我还没登录呀!

    于是,不慌不忙地打开登录页,噼里啪啦地输入账号&密码,回车。

    回到 Network 面板,F5 刷新一下,没有错误了,暗自欣喜,继续观察中...

    在 1.1min 后,SSE 连接断开了,可能是网络波动,更何况还有重试机制兜底,不慌,继续观察...

    在下一个 1.1min 后,连接又断开了,看来这不是一个意外!

    首先想到的是 Nginx 的配置不对,赶紧检查了下配置。

    发现 proxy_read_timeout 1m; 设置为 1 分钟,肯定是它在搞鬼,于是改为 8h ,一天断开 3 次连接,还可以接受吧!

    继续观察,在 1.7min 后,又有序的断开连接了。

    啊 ~~ 😵 这次又是什么原因呢???

    疯狂检索后,试了一遍又一遍,各种解决方案,可惜对我都不管用!

    比如,更改 Nginx 配置:

    proxy_http_version 1.1;
    proxy_set_header Connection '';
    chunked_transfer_encoding off;
    
    proxy_buffering off;
    proxy_cache off;
    

    在源(上游)服务的响应头添加:

    X-Accel-Buffering: no
    

    简直要疯了,为了确定这是 Cloudflare 导致的问题,我不得不在本地装了个 Nginx ,用同样的配置测试却没问题。

    好,既然知道问题出在哪里,那就有解决的方法!

    又一波疯狂地检索后,得知 Cloudflare 对 SSE 支持并不好,但能很好的支持 WebSocket 。

    啊 ~~ 这?难道要我更换技术方案?

    • 使用传统的轮询?
    • 还是长连接?
    • 还是 WebSocket 呢?
    • 还是不用 Cloudflare 了?

    想想那漫漫重构路,我头大了!😥

    解决方案

    就在即将放弃 SSE 方案之际,决定再好好翻阅 Cloudflare 官方文档,终于功夫不负有心人,发现了这么一个描述。

    意思是 Cloudflare 已成功连接到源服务器,但在 100 秒内没有响应,所以就发生超时了。

    100 秒?换算起来不就是 1.666... 分钟,四舍五入恰好就是 1.7 分钟嘛?这与我遇到的问题十分吻合啊!

    继续往下看,对于这种耗时任务,Cloudflare 也提供了几种解决方案:

    • 使用短轮询来避免发生这个错误
    • 企业用户可以设置 proxy_read_timeout 到 6000 秒
    • 关闭 Cloudflare 的边缘网络分发,请求直接打到源服务器上

    这些都不是我想要的解决方案,仔细想想,是不是在 100 秒内有响应就好了?

    于是,我赶紧改造消息推送的代码,启动一个时钟来每隔 30 秒发送一个 keepalive 的空消息

    部署上去后,继续观察,嗯 ~ 这一次,稳了!

    连接在 1.5 小时后才断开,还是由于电脑休眠了的原因,不过这已经足够了。

    只要能达到小时级别,再加上重连机制,这是可接受的结果。

    总结

    这是一次部署 SSE 消息推送到 Cloudflare 的经历,中途遇到很多很多问题,做了很多测试,再一一排除。

    每一次都到了濒临崩溃,更改技术方案的局面,好在最终克服了重重困难,得偿所愿。

    如果你的应用场景与我相似,请求链路如下:

    Client -> Cloudflare -> Nginx -> Server
    
    1. 请在 Server 设置响应头 X-Accel-Buffering: no 来关闭 Nginx 响应缓存
    2. 每隔 30 秒做一次响应来避免 Cloudflare 100 秒未响应的超时错误

    好啦!今天就分享到这里,有任何问题,欢迎在评论区交流。

    4 条回复    2023-08-17 21:14:58 +08:00
    GTim
        1
    GTim  
       2023-08-17 13:46:03 +08:00
    所以,看到最后,你没搞心跳就是了
    xieqiqiang00
        2
    xieqiqiang00  
       2023-08-17 17:29:51 +08:00 via Android
    cloudflare 免费的压根不支持 sse 吧?踩过坑
    pzhyy
        3
    pzhyy  
    OP
       2023-08-17 17:51:09 +08:00
    @GTim 跳了,每 30s 跳一下,要不然 Cloudflare 就以为链接死了 🤣

    @xieqiqiang00 支持!只是之前没搞过,这一次踩坑,记录一下
    RangerWolf
        4
    RangerWolf  
       2023-08-17 21:14:58 +08:00
    感觉很干货,很多我没见过的东西!感谢楼主分享
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2849 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 02:12 · PVG 10:12 · LAX 18:12 · JFK 21:12
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.