<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>sse on </title>
    <link>/tags/sse/</link>
    <description>Recent content in sse on </description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <lastBuildDate>Fri, 02 Oct 2026 15:00:00 +0800</lastBuildDate><atom:link href="/tags/sse/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>虚拟线程下的 Spring MVC SSE：Tomcat、Jetty、WebFlux 的单机上限</title>
      <link>/posts/spring-mvc-sse-tomcat-jetty-webflux/</link>
      <pubDate>Fri, 02 Oct 2026 15:00:00 +0800</pubDate>
      
      <guid>/posts/spring-mvc-sse-tomcat-jetty-webflux/</guid>
      <description>同一份 Spring MVC 代码：SseEmitter 推送，每条连接一个容量 256 的 ArrayBlockingQueue，加一个跑在虚拟线程上的阻塞写循环。在 4 vCPU / 8 GB 的容器里挂 2 万条 SSE 连接，跑在 Tomcat 上每条连接常驻 112.3 KB 存活堆（socket 缓冲还在堆内那一档），换成 Jetty 是 18.3 KB。两边的平台线程数却差不多，Tomcat 24 个，Jetty 19 个（各次 run 的 server-final.json 里的 platformThreads），虚拟线程在两边都生效了，内存的差距出在容器本身。
数据来自 sse-demo（commit 850a668，Java 25、Spring Boot 4.1.1，对应 Tomcat 11.0.24、Jetty 12.1.12），负载是服务端单向推送，1 Hz，每帧 265 字节。一档连接数算通过，要同时满足建连成功率 ≥ 99%、保持期间基本不掉线、投递率 ≥ 0.99、p99 &amp;lt; 1000 ms；p99 读数是直方图桶的上界，接近判据的档位要按区间理解。测试跑在单机 OrbStack 容器里，没有用物理机，其余前提见《SSE 还是 WebSocket》的「这些数字成立的前提」。那篇比较两种 transport，这里只比较同一份 SSE 代码放在三种容器里的差别。
容器 编程模型 每连接存活堆（2 万） 每连接 RSS（2 万） 单机上限 先到的限制 Tomcat，socket 缓冲在堆内 Spring MVC + 虚拟线程 112.</description>
    </item>
    
    <item>
      <title>SSE 多实例部署的跨节点路由与断线补发</title>
      <link>/posts/sse-scalability-relay-fanout-architecture/</link>
      <pubDate>Wed, 30 Sep 2026 11:30:00 +0800</pubDate>
      
      <guid>/posts/sse-scalability-relay-fanout-architecture/</guid>
      <description>上一篇《SSE 还是 WebSocket：每连接内存、单机容量上限与多实例路由》在 4 核 8 GB 的压测环境里测了单机容量。2 万条 SSE 连接时，每条连接的 RSS 在 Netty、Go、Rust 上都在 35 KB 左右，而 Tomcat 即使全调优后 RSS 仍达 148.1 KB；在 Tomcat 路径上，单机先后受限于 maxConnections 计数器、堆和扇出耗时。那篇最后一章写到「加第二台机器后，最先出问题的是路由」。这一篇接着讲多实例怎么设计，内容来自公开资料和设计推演，没有压测。
SSE 是一条一直不结束的 HTTP 响应，连接对象只存在于接受这条连接的那台机器的内存里。浏览器端的 EventSource 断线后会自己重连：服务端结束响应或者网络出错，它都会等一个重连间隔再发起请求；之前收到过带 id 字段的事件，重连请求就在 Last-Event-ID 头里带上最后一个 ID（见 WHATWG 的 Server-sent events）。部署成多实例后，这两个特性各带出一个问题。一是触发推送的请求常常落在另一台机器上：用户连在 Node A，更新却由 Node B 触发，Node B 在本地的连接列表里找不到这个用户，消息就发不出去（见 AYA 的 Scaling Pains: Why Your SSE App Fails Under Load Balancers）。二是重连请求经过负载均衡，也可能落到另一台机器上，这台机器手里没有这条连接之前收到的事件。
推送怎么到达持有连接的节点 flowchart LR P[&#34;发布请求目标：用户 A&#34;] --&gt; N1[&#34;Node 1没有用户 A 的连接&#34;</description>
    </item>
    
    <item>
      <title>H5 应用容器与奖池的架构</title>
      <link>/posts/h5-container-prize-pool-architecture/</link>
      <pubDate>Thu, 24 Sep 2026 02:17:27 +0800</pubDate>
      
      <guid>/posts/h5-container-prize-pool-architecture/</guid>
      <description>背景 合作方 App 的后台调一次 /app/url，拿到一个地址，交给用户的浏览器打开。原来这个地址就是第三方服务商的 H5 页面，平台在这中间没有自己的页面，所以加不了防护，各服务商的加载体验不统一，也拿不到加载过程的埋点。
要做的是在中间放一个平台自己的页面：/app/url 改成返回容器页（前端代码里叫 container shell，下文简称 shell）的地址，shell 再把服务商的 H5 应用装进 iframe。防护、统一的加载体验和埋点都可以放在 shell 上做。
后来平台还要在 shell 页面底部挂一条奖池活动条（下文叫 overlay），显示四档奖池的实时金额，用户可以报名、退出，中奖时会弹出通知。报名之后，用户每结算一笔订单，系统就从钱包里扣一张抽奖券的钱，按比例分进四档奖池，再为这笔订单抽一次奖。订单和钱包都在交易平台，也就是平台里负责下单和钱包的服务。
本文只讲从打开应用到推送的运行时链路，以及抽奖券从扣款到派奖的过程；后台管理、计费、抽奖随机数的审计、各服务商的适配器和埋点管道不在范围内。
整体架构 浏览器这一侧只有一个用户域名，请求先到 Cloudflare，再按路径分给 Worker 或 ALB。合作方后台到 container-service 是服务端之间的调用，不经过浏览器：
flowchart LR subgraph BROWSER[&#34;用户浏览器里的容器页&#34;] direction TB SHELL[&#34;container shell&#34;] APP[&#34;服务商 H5 iframe&#34;] OVL[&#34;奖池 overlay&#34;] end CF[&#34;Cloudflare唯一的用户域名&#34;] WK[&#34;Worker 路由 /open/*shell 静态资源&#34;] ALB[&#34;ALB IngressGroup其余路径按前缀分流&#34;] subgraph EKS[&#34;EKS&#34;] direction TB CS[&#34;container-service/v1/app /v1/container /v1/overlay&#34;] OB[&#34;overlay 静态包/widget/prize&#34;] PS[&#34;prize-service/v1/prize&#34;] SS[&#34;stream-service/stream/prize&#34;] end PB[&#34;合作方后台&#34;] SHELL --&gt; CF OVL --&gt; CF CF --&gt; WK CF --&gt; ALB ALB --&gt; CS ALB --&gt; OB ALB --&gt; PS ALB --&gt; SS PB --&gt;|&#34;</description>
    </item>
    
    <item>
      <title>SSE 还是 WebSocket：每连接内存、单机容量上限与多实例路由</title>
      <link>/posts/sse-vs-websocket-long-connection-capacity/</link>
      <pubDate>Mon, 21 Sep 2026 05:00:00 +0800</pubDate>
      
      <guid>/posts/sse-vs-websocket-long-connection-capacity/</guid>
      <description>10,000 个用户每秒轮询一次价格，一个月是 259 亿次请求。换成长连接后，就是 10,000 条 TCP 连接，服务端每秒推一万帧。省下的请求费用容易算，要定的是另外两件事：选 SSE 还是 WebSocket，以及一台机器上各自先受哪个限制。
轮询的成本是用户数乘更新频率 先算轮询的成本。这 259 亿次请求按 AWS API Gateway 的公开价格（us-east-1）算，REST API 每月大约 5.7 万美元：前 3.33 亿次每百万 3.50 美元，超过 200 亿次的部分降到每百万 1.51 美元。这个费用和数据变没变无关：价格十分钟不动，这十分钟里的六百万次请求照样计费。
服务端每处理一次请求，都要新建一批对象。具体是哪些对象取决于技术栈，本文压测用的是 Spring Boot + Tomcat，每个 HTTP 请求要创建 Http11Processor、coyote.Request / coyote.Response，以及它们背后的 header 和 chunk 缓冲。轮询每秒要把这些对象创建再回收一万次，开销体现在分配率上。长连接省掉了这部分分配，代价是这些对象随连接一直留在内存里。
客户端也有上限。浏览器对每个域名最多开 6 条 HTTP/1.1 连接，单次请求按 100 到 200 ms 算（DEV Community 上那篇轮询成本分析 Why 1-second polling doesn&amp;rsquo;t scale 给的区间，取 150 ms；下文简称轮询那篇），按 Little&amp;rsquo;s Law，一个浏览器实例最多约 40 req/s（OceanBase 与 TiDB 那轮容量压测里算吞吐用的也是这个公式）。6 条不是规范规定的，RFC 9112 §9.</description>
    </item>
    
  </channel>
</rss>
