一、主要内容
本文主要描述Eclipse Ditto 的节流机制。
二、日志解析
(1)数据流展示如下:
tian@hang:~/ditto/deployment/docker$ docker compose logs -f
gateway-1 | 2026-07-04 05:29:21,304 INFO [85fe8a2a-7316-449c-a014-fea56f9389cc][] o.e.d.g.s.s.a.p.PreAuthenticatedAuthenticationProvider - Pre-authentication has been applied resulting in AuthorizationContext <ImmutableAuthorizationContext [type=pre-authenticated-http, authorizationSubjects=[nginx:ditto]]>.
gateway-1 | 2026-07-04 05:29:21,309 INFO [85fe8a2a-7316-449c-a014-fea56f9389cc][] o.e.d.e.s.d.EdgeCommandForwarderActor pekko://ditto-cluster/user/gatewayRoot/edgeCommandForwarder - Forwarding thing signal with ID <my-demo:device001> and type <things.commands:retrieveThing> to 'things' shard region
things-1 | 2026-07-04 05:29:21,317 INFO [85fe8a2a-7316-449c-a014-fea56f9389cc][] o.e.d.t.s.e.ThingEnforcerActor pekko://ditto-cluster/system/sharding/thing/9/my-demo%3Adevice001/en - Completed enforcement of message type <things.commands:retrieveThing> with outcome 'success'
things-1 | 2026-07-04 05:29:21,319 INFO [85fe8a2a-7316-449c-a014-fea56f9389cc][] o.e.d.t.s.p.a.ThingSupervisorActor pekko://ditto-cluster/system/sharding/thing/9/my-demo%3Adevice001 - Received DittoRuntimeException during enforcement or forwarding to target actor, telling sender: ThingPreconditionNotModifiedException [message='The comparison of precondition header 'if-none-match' for the requested Thing resource evaluated to false. Expected: '"rev:17"' not to match actual: '"rev:17"'.', errorCode=things:precondition.notmodified, httpStatus=HttpStatus [code=304, category=REDIRECTION], description='The comparison of the provided precondition header ''if-none-match'' with the current ETag value of the requested Thing resource evaluated to false. Check the value of your conditional header value.', href=null, dittoHeaders=ImmutableDittoHeaders [{correlation-id=85fe8a2a-7316-449c-a014-fea56f9389cc, sec-fetch-mode=cors, referer=http://localhost:5173/, if-none-match="rev:17", x-ditto-pre-authenticated=nginx:ditto, sec-fetch-site=same-origin, accept-language=zh-CN, zh;q=0.9, zh-TW;q=0.8, zh-HK;q=0.7, en-US;q=0.6, en;q=0.5, x-forwarded-for=172.18.0.1, priority=u=4, accept=*/*, authorization=***, x-real-ip=172.18.0.1, x-forwarded-user=ditto, host=localhost:8080, sec-fetch-dest=empty, user-agent=Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:152.0) Gecko/20100101 Firefox/152.0, ditto-auth-context={"type":"pre-authenticated-http","subjects":["nginx:ditto"]}, ditto-originator=nginx:ditto, ditto-read-subjects=["nginx:ditto"], ditto-entity-id=thing:my-demo:device001, etag="rev:17"}]]
gateway-1 | 2026-07-04 05:29:21,323 INFO [85fe8a2a-7316-449c-a014-fea56f9389cc][] o.e.d.i.u.c.AskWithRetryCommandForwarder - ThingPreconditionNotModifiedException: The comparison of precondition header 'if-none-match' for the requested Thing resource evaluated to false. Expected: '"rev:17"' not to match actual: '"rev:17"'.
gateway-1 | 2026-07-04 05:29:21,328 INFO [85fe8a2a-7316-449c-a014-fea56f9389cc][] o.e.d.g.s.e.a.HttpRequestActor pekko://ditto-cluster/user/$+K - DittoRuntimeException <things:precondition.notmodified>: <The comparison of precondition header 'if-none-match' for the requested Thing resource evaluated to false. Expected: '"rev:17"' not to match actual: '"rev:17"'.>.
gateway-1 | 2026-07-04 05:29:21,328 DEBUG [][] o.a.p.a.CoordinatedShutdown CoordinatedShutdown(pekko://ditto-cluster) - Successfully cancelled CoordinatedShutdown task [service-requests-done-http-request-actor] from phase [service-requests-done].
gateway-1 | 2026-07-04 05:29:21,328 INFO [85fe8a2a-7316-449c-a014-fea56f9389cc][] o.e.d.g.s.e.d.RequestResultLoggingDirective - StatusCode of request GET '/api/2/things/my-demo:device001' was: 304
nginx-1 | 172.18.0.1 - ditto [04/Jul/2026:03:29:21 +0000] "GET /api/2/things/my-demo:device001 HTTP/1.1" 304 0 "http://localhost:5173/" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:152.0) Gecko/20100101 Firefox/152.0"
(2)数据流解析
数据流转:gateway-1 ➔ gateway-1 ➔ things-1 ➔ things-1 ➔ gateway-1 ➔ gateway-1 ➔ gateway-1 ➔ gateway-1 ➔ nginx-1
第 1 步:gateway-1(鉴权拦截)
时间:05:29:21,304
动作:Ditto 网关收到请求,执行预认证(PreAuthenticatedAuthenticationProvider),确认 Vite 代理传过来的 Authorization 头有效,赋予了 nginx:ditto 身份。
第 2 步:gateway-1(转发命令)
时间:05:29:21,309
动作:网关将 HTTP 请求转换成内部的 retrieveThing 指令,通过 EdgeCommandForwarderActor 转发给负责存储这个设备数据的"Things 分片节点(shard)"。
第 3 步:things-1(执行查询)
时间:05:29:21,317
动作:负责本分片的 ThingEnforcerActor 收到命令,根据权限校验,成功读取到了该设备的当前数据,内部执行状态为 success。
第 4 步:things-1(触发 304 异常拦截)
时间:05:29:21,319
动作:ThingSupervisorActor 准备返回数据时,对比了前端发来的 If-None-Match: "rev:17" 和服务端当前实际的版本 rev:17。因为完全一致,它直接抛出了一个 ThingPreconditionNotModifiedException 异常,拦截了数据的下发。
第 5 步:gateway-1(捕获异常)
时间:05:29:21,323
动作:AskWithRetryCommandForwarder 负责把这一步的异常结果带回给网关层,并打印出了这条提示日志。
第 6 步:gateway-1(翻译异常)
时间:05:29:21,328
动作:HttpRequestActor 接收到了这个 304 异常,将它翻译成符合 HTTP 规范的标准响应:状态码为 304,且不携带任何 JSON 响应体。
第 7 步:gateway-1(记录结果状态)
时间:05:29:21,328
动作:RequestResultLoggingDirective 记录下了本次请求的最终处理结果:状态码为 304。
第 8 步:gateway-1(旁路无关日志)
时间:05:29:21,328 (顺便说一下,你的日志里紧接着有一个 DEBUG [][] o.a.p.a.CoordinatedShutdown。这个是 Ditto 底层 Akka 集群的"协调关闭"任务被取消的日志,跟你的这个请求毫无关系,是系统后台的杂音,可以直接忽略,不影响你的交互过程。)
第 9 步:nginx-1(访问日志落地)
时间:05:29:21,328
动作:Nginx 作为反向代理,在最后的链路端输出一行访问日志:GET ... 304 0。这里的 0 代表响应体大小为 0 字节,完美印证了上面第 6 步没有返回 JSON 数据的结论。
三、内容总结
(1)认证与鉴权
Vite 代理配置里写了 headers: { Authorization: 'Basic ZGl0dG86ZGl0dG8=' }。Nginx(作为反向代理)把这个请求头传给 Ditto 网关。Ditto 的 PreAuthenticatedAuthenticationProvider(预认证提供者)率先接管这个请求,读取了这个 Header 并验证通过。
(2)版本控制
在第二步拿到 rev:17 后,Ditto 并没有马上把 JSON 数据发返回,而是先看了一眼你发来的请求头 。
你的浏览器(或 fetch 请求)在内部携带了一个 If-None-Match: "rev:17" 。Ditto 拿这个值和设备的实际版本 rev:17 做比较,发现一模一样 。
根据 HTTP 标准,Ditto 认为:"既然前端(浏览器)手里已经有这个 rev:17 的旧数据了,而我的数据也没变,那我就不用把完整的 JSON 再发一遍了,告诉你'没变'即可。"
于是它扔出一个 ThingPreconditionNotModifiedException(异常),网关截获这个异常,直接将其翻译成 HTTP 状态码 304 返回给前端,不附带任何 JSON 响应体。
四、流程梳理
🌐 你的浏览器 (地址栏: localhost:5173)
⬇️ 请求 /api/2/things/...
🔄 Vite 开发服务器 (Node.js 进程, 监听 5173)
(读取 vite.config.ts 中的 proxy 配置)
⬇️ 转发请求到 http://localhost:8080
🛡️ Nginx (反向代理, 监听 8080)
(向外界暴露 Ditto 的接口)
⬇️ 转发到 Ditto Gateway
... (此后进入你刚才看到的那一长串 Ditto 内部处理流程)
⬆️ 最终结果原路返回给浏览器
五、名词解释
vite:
Vite 是打包工具,但在开发阶段,它同时也是一个"开发服务器(Dev Server)"。
当你运行 npm run dev(或 pnpm dev)时,Vite 并没有真正打包你的代码,而是启动了一个基于 Node.js 的本地 HTTP 服务器。这个服务器不仅负责实时编译 Vue 组件并提供热更新(HMR),它最核心的另一个功能就是充当"反向代理"。
nginx-1:Ditto 的反向代理器,
转发外部向 ditto 传递过来的信息。nginx是结果输出类型,在通讯完成后打印日志。
MongoDB:数据库
-
关键日志 :
mongodb-1服务输出的Connection accepted和client metadata。 -
这意味着什么 :Ditto 内部真正存东西的地方是 MongoDB。刚才的
things.commands:retrieveThing命令执行成功后,Ditto 的things-1服务必须去连接 MongoDB 查取设备最新数据。 -
注意细节 :日志里写了
Connection not authenticating(未进行身份认证)。这说明你的 MongoDB 容器目前是免密连接的,这在本地 Docker 开发环境中是非常正常和标准的配置。
gateway-1:Ditto 原生 API 网关
-
它的角色 :它是 Ditto 微服务架构中的一个核心组件(Ditto Gateway 服务),通常运行在 Akka 集群的最前端。
-
它的核心工作 :它接收到 Nginx 转发过来的 HTTP 请求后,不直接读数据库,而是做两件事:
-
执行鉴权 :比如你日志里看到的
PreAuthenticatedAuthenticationProvider,它读取Authorization头,把访问者认证为nginx:ditto身份。 -
命令翻译与路由 :它把 HTTP 的
GET /api/2/things/...,翻译成 Ditto 集群内部能听懂的内部消息指令------也就是你看到的things.commands:retrieveThing,然后把指令路由给负责存储这个设备的things-1节点去真正干活。
-
Thing:物模型
Policy 权限缺失 :创建 Thing 前必须先建 Policy,否则 403 无权限