跳转至内容
  • 版块
  • 最新
  • 标签
  • 热门
  • 世界
  • 用户
  • 群组
皮肤
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • 默认(不使用皮肤)
  • 不使用皮肤
折叠

乐达

  1. 主页
  2. 技术路线图
  3. 框架进化
  4. TG-199:【性能优化】BCEL跳转支付状态变更延迟问题(付款后约30秒状态未更新)

TG-199:【性能优化】BCEL跳转支付状态变更延迟问题(付款后约30秒状态未更新)

已定时 已固定 已锁定 已移动 框架进化
10 帖子 2 发布者 25 浏览 1 关注中
  • 从旧到新
  • 从新到旧
  • 最多赞同
回复
  • 在新帖中回复
登录后回复
此主题已被删除。只有拥有主题管理权限的用户可以查看。
  • Z 离线
    Z 离线
    zhongfangxiong
    写于 最后由 编辑
    #1

    我先把问题拆成“支付成功 -> 订单状态更新”的链路,把你提到的三个仓库里相关的回调/轮询/状态刷新逻辑、以及图片里提到的 SemaphoreSlim 并发控制点找出来;然后定位为什么会出现 30s+ 的延迟(常见是:定时轮询间隔、分布式锁/信号量串行化导致排队、跨库事务/重试、缓存失效延迟或最终一致性队列积压)。我会先用代码搜索把 IPayService、IFinanceAmountsServices、ICustomerBalanceService、第三方支付回调入口、以及 SemaphoreSlim 的使用点全部定位出来,再读关键文件给出结论和可落地的修复方案(含代码改动点)。接下来我开始搜代码。
    我会先在你给的三个目录里做一次“定向全局搜索”:把 IPayService/IFinanceAmountsServices/ICustomerBalanceService/ICenterCacheService、第三方支付回调入口(Notify/Callback/Webhook)、以及 SemaphoreSlim 的使用点全部找出来,并锁定“支付成功后更新订单状态”的具体实现位置。随后再读关键文件分析为什么会>30秒。现在开始搜。

    我这边的“Fast Context”子检索刚才失败了(工具侧错误),我改用更稳的 grep_search 在你给的三个目录内直接搜关键字:回调入口(Notify/Callback/Webhook)、IPayService 等接口与实现、以及 SemaphoreSlim。先把命中位置列出来,再逐个读文件确认延迟点。

    我已经把命中点收敛到几类关键文件:finance-backend-core 里有明显的同步/Worker(SyncThirdPartyPaymentRecordsWorker)、核心支付服务(PayService/OnlinePayService);bank-api 里有 NotifyController/BoxOnlinePaymentController 等入口。下一步我会直接把这些文件读出来,找出是否存在 固定30秒轮询/延迟任务、或 SemaphoreSlim 导致的串行排队,以及回调后是否只是“落库记录”而不是“立即更新订单状态”。我先读取最关键的 6-8 个文件。

    我已经定位到两个关键事实:bank-api 里确实有 NotifyController 会在回调后立刻入账;但 finance-backend-core 里也存在 Worker 类(比如 SyncThirdPartyPaymentRecordsWorker)做“待确认订单”的异步确认/入账,且逻辑里还有“创建3分钟内不处理”的保护。接下来我会继续把 SemaphoreSlim 的使用点、以及“箱子在线支付/补款”对应的状态更新方法(比如 UpdateOrderStatusOfBalanceAduit、BalanceAudit2BoxesClosed 等)读出来,看看是否存在 固定30秒轮询 或 信号量导致串行排队。

    我现在基本能解释“为什么会慢”:你们这条链路里存在两套确认/入账机制——NotifyController(回调即时处理)和 OnlinePayService.AutoDealOnlineOrderStatus / SyncThirdPartyPaymentRecordsWorker(后台轮询处理)。其中 OnlinePayService 明确写了 _timeoutMinMinutes = 3,也就是订单创建后3分钟内即使支付成功也会被跳过,直到下一次定时任务才会入账/扣款/更新箱子状态;如果你们线上主要走的是“后台定时”而不是“回调即时”,体验就会出现 30s~数分钟的延迟。下一步我会把 AutoDealOnlineOrderStatus 在哪里被调度(Hangfire/定时器/Windows服务间隔)找出来,确认实际触发频率(你们反馈的30秒很像 job interval),并给出改法(缩短/取消3分钟保护、回调里直接触发 ConfirmPay 流程、以及加幂等防重复)。我先定位调度入口。
    我先把“30秒以上才更新”的最可能原因点出来:代码里有明确的最小等待 3 分钟(_timeoutMinMinutes = 3),如果你们依赖的是后台定时任务而非回调即时处理,肯定会慢;而你反馈“30秒+”更像是后台任务每 30 秒跑一次,但会被这条 3 分钟门槛挡住。接下来我会在整个 loda.framework.erp 里搜索 AutoDealOnlineOrderStatus 的真实调用方/调度器(Hangfire/Windows 服务/autorunner),确认实际触发间隔,然后给出可落地的修复方案(优先让回调触发即时确认/入账,后台只兜底)。现在开始全仓库搜索调用点。

    结论(为什么会“30秒以上才更新状态”)

    从你给的三个仓库代码来看,支付成功后订单/箱子状态更新“慢”并不是第三方支付本身慢,而是你们业务侧采用了“异步兜底 + 最小等待时间”的处理策略,导致更新不可能做到秒级。

    1) 核心阻塞点:硬编码的“最小等待 3 分钟”

    在 finance-backend-core 的:

    • GxHub.Pay.Services/OnlinePayService.cs

    里面明确写了:

    • _timeoutMinMinutes = 3;
    • AutoDealOnlineOrderStatus(...) 处理待确认单据时有逻辑:
      • (tempDate - item.CreatedOn).TotalMinutes < _timeoutMinMinutes) continue;

    也就是:订单创建后 3 分钟内,即使第三方已经支付成功,这个自动处理流程也会直接跳过。
    如果线上主要依赖该自动流程驱动“入账 + 扣款 + 箱子状态变更”,那体验必然是“慢”。

    你们反馈“30s+”的现象,非常符合:前端每隔一段时间轮询/刷新,但后台真正落库变更要等到过了 3 分钟门槛或下一轮处理。

    2) 同一业务存在“多套入口”:回调即时 vs 后台自动兜底

    我看到至少三条路径:

    • 回调即时入账(bank-api)
      web-api/bank-api/GxHub.FinanceAPI.Server/Controllers/NotifyController.cs
      这个 Post() 在 msgCd == SUCCESS 后会调用支付中心 api/ConfirmPayment,并在 presp.IsInAccount 时直接把 BalanceAduit 入账并生成 BalanceDetail/FinanceAmount...
      但注意:这里主要是“钱包入账”,并**不一定覆盖“箱子扣费/箱子状态更新”**那一整套流程。

    • 前端/业务触发的“确认支付”(bank-api)
      web-api/bank-api/GxHub.FinanceAPI.Server/Controllers/ConfirmPayController.cs
      这是一个显式确认入口:入账后如果是 在线包裹 会走 OnlinePayService.BoxPay(...) 做扣款/箱子相关更新。
      这条链路如果前端没有在支付完成后立即调用,状态就只能等后台兜底。

    • 后台自动兜底(finance-backend-core)
      OnlinePayService.AutoDealOnlineOrderStatus(...)
      这是“扫待确认订单 -> 调 ConfirmPayment -> 入账 -> 扣款 -> 更新业务状态”的兜底流程,但被 _timeoutMinMinutes=3 卡住。

    另外你们还有一个较老的 worker:

    • SyncThirdPartyPaymentRecordsWorker(10分钟跑一次,且也写了 checkTime.TotalMinutes < 3 跳过)
      它更像历史兜底,不太可能解释“30秒”,但同样体现了最小等待时间的策略。

    3) 你图片里提到的 SemaphoreSlim

    我在仓库内直接搜 SemaphoreSlim 没有命中,所以“信号量导致排队 30 秒”这个点在当前这三个目录里并不成立(至少不是用 SemaphoreSlim 实现的)。
    倒是 OnlinePayService.OrderPay 里用了 lock (locker) 做进程内串行,但这更可能造成并发下降,不太像固定 30 秒延迟。

    解决办法(按优先级,从“立刻可改善体验”到“体系化”)

    A. 最高优先级:支付回调成功后,直接触发“确认支付/业务更新”

    目标:做到 支付成功后 1-3 秒内更新箱子/订单状态。

    做法建议(二选一或组合):

    • 方案 A1(推荐):第三方支付系统(Third-partyPayment)回调落库后,主动回调 bank-api 的 ConfirmPay

      • Third-partyPayment 在接到支付渠道回调后,除了写 ThirdPartyPaymentRecords,再调用一次:
        • bank-api 的 ConfirmPayController.Get(balanceorderno, thirdpartyorderno)
      • 优点:
        • 完整走“入账 + 扣款 + 箱子状态更新”的主链路(你们当前代码里 ConfirmPay 已经覆盖在线包裹)
        • 后台 AutoDealOnlineOrderStatus 保留兜底,不影响稳定性
      • 风险点:
        • 需要保证 ConfirmPay 幂等(从代码看已有多处 BalanceAudit2Details.Any(...) 的幂等防重复,基本具备)
    • 方案 A2:前端在支付成功页/SDK成功回调后立即调用 ConfirmPay

      • 如果你们前端现在只是在支付页“等状态刷新”,那就是慢的直接原因之一
      • 适合快速上线(不改 Third-partyPayment),但对前端改造要求更高

    B. 去掉或缩短 _timeoutMinMinutes=3(只作为兜底策略,不要卡主链路)

    如果你们确认“支付成功后回调/ConfirmPay 已经可靠”,那 _timeoutMinMinutes=3 就应该仅用于“防止刚创建订单就频繁打第三方确认接口”的后台扫描场景。

    建议:

    • 将 _timeoutMinMinutes 改成 0~0.2 分钟(0~12秒),或者改为可配置(AppSettings)。
    • 或者保留 3 分钟,但只用于“没有回调信息/没有第三方记录”的场景;一旦 ThirdPartyPaymentRecords.CallbackJson 已存在,可立即处理。

    这条可以显著改善“补款后慢”:补款通常就是补一笔后希望立刻变更状态,但后台扫单被 3 分钟门槛挡住会非常糟。

    C. 增加观测:用日志/埋点快速定位到底卡在哪里

    建议在 ConfirmPayController 和 OnlinePayService.AutoDealOnlineOrderStatus 加上关键时间差日志(不改注释、不大重构):

    • 订单创建时间 CreatedOn
    • 第三方回调写入时间(ThirdPartyPaymentRecord.CreatedOn/CallbackJson更新时间,如果有)
    • 首次触发 ConfirmPay 的时间
    • 实际入账成功时间 AduitOn
    • 箱子 PayFreightOn/状态变更时间

    这样能一眼确认:

    • 是前端没调 ConfirmPay导致只能等后台
    • 还是后台任务调度频率过低/被最小等待卡住
    • 还是第三方回调本身来得晚(较少见)

    你需要补充的 2 个关键信息(用于最终定方案)

    为了把“30秒”精确对上,我需要你确认两点(你回我一句话即可):

    1. 前端支付完成后有没有调用 bank-api 的 ConfirmPay 接口?

      • 有:那还慢,说明可能卡在别处(例如业务状态刷新依赖另一个异步队列/缓存)
      • 没有:那基本就是主因
    2. OnlinePayService.AutoDealOnlineOrderStatus 在生产是怎么触发的?

      • Hangfire 定时?MQ(我看到 DealMQMessageService 可以消费 MQ 来触发)?还是某个 Windows 服务?
      • 触发间隔是多少?(30秒/1分钟/10分钟)

    当前完成状态

    • 已完成

      • 把“支付成功到订单/箱子状态更新”的关键代码链路定位清楚
      • 找到导致慢的关键逻辑(_timeoutMinMinutes=3 + 依赖后台兜底/未即时确认)
      • 给出可落地的解决方案路径(回调触发 ConfirmPay、缩短最小等待、加观测)
    • 待你确认后我可以继续

      • 如果你希望我直接落地改代码:我可以按你选的方案(A1/A2/B)在对应仓库里提交最小改动补丁,并标注改动点与回滚点
    1 条回复 最后回复
    0
    • Z 离线
      Z 离线
      zhongfangxiong
      写于 最后由 编辑
      #2

      上面的帖子是WindSurf第一轮答复。

      下边是我提的问题:

      涉及到的仓库:
      X:\gitlab\loda.net.cn\loda.framework.erp\web-api\bank-api
      X:\gitlab\loda.net.cn\loda.framework.erp\web-api\Third-partyPayment
      X:\gitlab\loda.net.cn\loda.framework.erp\Business\finance\finance-backend-core
      具体是:图片里。

      1fca7c5b-92c4-48f9-88ef-dbc30a9421d5-企业微信截图_17682151674041.png

      前方推广的人员反馈说,箱子在线支付后,状态变更慢。“补款”以后,状态变更也慢。就是走第三方支付,付款成功之后,要30秒以上才会更新订单的状态。你帮我分析一下原因并给出解决办法。图片是程序员发来的。[图片]

      1 条回复 最后回复
      0
      • Z 离线
        Z 离线
        zhongfangxiong
        写于 最后由 编辑
        #3

        我会用代码来“证伪/证实”你们的说法:

        1. 在 Third-partyPayment 里找到真正接收银行回调的控制器/服务,确认它回调你们业务侧时调用的是 ConfirmPayController 还是 NotifyController(或其它)。
        2. 在 bank-api 里查 ConfirmPayController 的调用方(是否被第三方服务主动调用、还是前端轮询/主动确认调用)。
        3. 同时核对是否完全“无队列/无后台任务”:ConfirmPayController 里我已经看到 centerCacheService.LPush(...)(Redis队列)痕迹,我会把涉及点标注在流程图里。
          接下来我先做全仓库搜索 + 读取 Third-partyPayment 的回调入口文件。

        我先用代码来“核验”两件事:

        1. 银行/第三方支付网关的回调到底打到 bank-api 的哪个入口(NotifyController 还是 ConfirmPayController,或两者皆有)。
        2. 你说“没有队列/后台延迟处理”——我会在 ConfirmPayController、OnlinePayService 等处检查是否存在 Redis 队列/后台兜底入口,并判断它们在“箱子支付/补款”这条链路里是否会影响状态实时性。

        刚才的搜索报错是因为 grep_search 默认把查询当正则,我用了 \/ 这种转义导致解析失败;我会改用 FixedStrings 或去掉转义重新搜。

        对“ConfirmPayController 是最终回调入口、全程同步、无后台延迟”的核验结论

        基于我已读到的代码,这个说法只能算“部分成立”,需要按“支付渠道/业务类型”拆开看。

        1) “银行支付成功后一定进入 ConfirmPayController”——不成立(至少对 ABA 不成立)

        在 web-api/Third-partyPayment 里,ABA 的真实“银行回调入口”是:

        • Yee.ThirdPartyPayment.Api.Server/Controllers/ThirdPay/ABAPayCallBackController.cs
          ABA 银行(或其网关)会调用这里:ABAPayCallBackController.Post(ABANotifyRequest request)
          里面转到:new ABABank(businessType).PayCallBack(request)

        而 ABABank.PayCallBack(Yee.ThirdPartyPayment.Core/ThirdPay/ABABank.cs)里做的事情是:

        • 先 ConfirmPayment(notifyRequest.TranId) 去 ABA 再确认一次(同步 HTTP)
        • 更新 ThirdPartyPaymentRecords(SqlClientUnits.UpdatePaymentRecord)
        • 如果支付确认成功,拼接:
          • geturl = "?balanceorderno={tranId}&businessNo={returnParams}"
        • 然后 HTTP GET 调用 basicConfig.LodaConfirmUrl + geturl(或 LodaPayConfirmUrl)

        也就是说:第三方支付系统回调到你们 ERP 的“哪个 Controller”,取决于 basicConfig.LodaConfirmUrl 配置的具体地址,并不是代码层面强制 ConfirmPayController。

        另外,在 bank-api 里我确认存在多个“ConfirmPay入口”:

        • ConfirmPayController(你们说的“统一入口”)
        • ThirdPay/ABAErpExpressConfirmPayController
        • ThirdPay/ABARiderConfirmPayController

        它们的 URL 都可能被 LodaConfirmUrl 配到;因此“最终一定到 ConfirmPayController”不成立。

        你要真正验证线上走哪一个入口:需要看生产 Third-partyPayment 的 ABA 配置表/配置文件里 LodaConfirmUrl 的值(或日志里打印的 URL)。ABABank.PayCallBack 已经 Info 打印了请求 URL。

        2) “技术上在 API 内及时处理,不用 hangfire/队列/后台任务”——不成立(存在异步/队列)

        你们可能想表达“箱子支付链路不依赖 Hangfire”,这一点有可能成立;但“完全不使用队列/后台异步”是不成立的:

        • bank-api/ConfirmPayController.cs:在默认订单分支里会 centerCacheService.LPush(Yee.Api.RedisConstants.SellerOrderPaymentQueue, ...)
          这就是 Redis 队列(异步处理下游商家单)。

        • bank-api/ThirdPay/ABAErpExpressConfirmPayController.cs:回调里 Task.Run(...) 推 RabbitMQ 消息(明确异步)

        • bank-api/ThirdPay/ABARiderConfirmPayController.cs:同样 Task.Run(...) 推 RabbitMQ 消息(明确异步)

        所以结论是:

        • 没有 Hangfire:在你指定的这几段代码里,我没看到 Hangfire 调度。
        • 没有队列/后台:不对,至少 Redis / RabbitMQ / Task.Run 都在使用。

        3) “箱子在线支付/补款:状态更新慢”这件事,与 ConfirmPay 是否同步的关系

        如果你们线上确实是 回调立即打到 ConfirmPayController.Get() 并执行 OnlinePayService.BoxPay(),那“30秒以上才更新箱子状态”通常不应该由 Hangfire 造成,而更可能来自:

        • 第三方回调本身并非实时
          ABA 这边是:银行回调 -> Third-partyPayment -> ConfirmPayment 再确认一次 -> 再 HTTP 回调到 ERP
          其中任一步慢都会延迟(网络、第三方接口、重试等)。

        • 回调到 ERP 后,ConfirmPay 内部耗时较长(同步写库 + 多表更新)
          OnlinePayService.BoxPay 会:

          • 遍历 boxIdList
          • 查 StockOutBoxes、StockInOrders
          • 写 PayFreightOn
          • 写 BoxFreightItem2FinanceDetails
          • PayInterFreight(涉及余额/明细/可能跨库)
          • 多次 db.SaveChanges()

          如果一次支付涉及箱子多、或数据库锁竞争/慢查询,会明显拖慢“状态落库”时间。

        • “前端看到状态更新”依赖的是另外的读库/缓存刷新
          即使箱子表 PayFreightOn 已写入,前端查询可能读了缓存、或走从库延迟(你截图里提到过 ERPDBSlave,代码里也确实用过),会造成“已写主库但读不到”。


        完整流程图(按 ABA + 你们“箱子在线支付”主链路)

        下面给你一个“代码级流程图”(Mermaid),你可以直接贴到支持 Mermaid 的地方渲染:

        flowchart TD
          A[客户发起箱子在线支付] --> B[bank-api: BoxOnlinePaymentController.Post]
          B --> B1[ERPDB: 插入 BalanceAduits(待确认)]
          B --> B2[ERPDB: 插入 BalanceAudit2Boxes(箱子关联)]
          B --> C[调用 OtherPayUrl: api/PreOrder 或 WingPayment 等 获取支付参数]
          C --> D[客户在银行/第三方完成支付]
        
          %% ABA 真实回调入口(Third-partyPayment)
          D --> E[Third-partyPayment: ABAPayCallBackController.Post]
          E --> F[ThirdPartyPayment.Core: ABABank.PayCallBack]
          F --> G[ABA接口: check-transaction-2 ConfirmPayment]
          G --> H[GXHub_Finance: Update ThirdPartyPaymentRecords 回调/结果]
          H --> I[HTTP GET: basicConfig.LodaConfirmUrl?balanceorderno=...&businessNo=...]
        
          %% ERP侧回调入口(可能多个controller)
          I --> J{bank-api 回调入口是哪一个?}
          J -->|配置为 ConfirmPay| K[bank-api: ConfirmPayController.Get]
          J -->|配置为 Rider/Express| K2[bank-api: ABARiderConfirmPayController / ABAErpExpressConfirmPayController]
          J -->|历史路径| K3[bank-api: NotifyController.Post (存在但不一定走)]
        
          %% ConfirmPayController 主处理
          K --> L[读取 GXHub_Finance: ThirdPartyPaymentRecords]
          K --> M[读取 ERPDB: BalanceAduits + BalanceAudit2Business]
          K --> N[入账: CustomerBalanceService.CreateRecharge + FinanceAmountsServices...]
          N --> O{业务类型? BalanceAudit2Business.BusinessType}
          O -->|在线包裹| P[OnlinePayService.BoxPay: 写箱子PayFreightOn等 + PayInterFreight扣费]
          O -->|默认订单| Q[PayService.PayForCustomerOrderList + Redis LPush SellerOrderPaymentQueue]
          O -->|其他| R[PayService.PayInterFreight 等]
        
          P --> S[ERPDB 提交 SaveChanges]
          Q --> S
          R --> S
        
          S --> T[前端查询箱子/订单状态]
          T --> U{读主库/从库/缓存?}
        

        基于上述核验,我的诊断(在你“无需 hangfire/队列”前提下)

        你们说“回调进入 ConfirmPayController 同步处理,所以不可能慢”,这在工程上不成立,原因是:

        1. 回调是否进入 ConfirmPayController 取决于配置 LodaConfirmUrl,不是代码强制;且 bank-api 里还有其它 ConfirmPay 入口。
        2. 即便进入 ConfirmPayController,同步链路也包含:
          • 外部 HTTP(Third-partyPayment 调 ABA ConfirmPayment)
          • 跨库读写(ERPDB / GXHub_Finance)
          • 多表写入 + 可能大循环(箱子数量)
          • 可能还有“写主读从/缓存”的可见性延迟
            任何一个点都会造成 30s+。

        进一步我需要你补充的“非常具体”的问题(用来锁定到底慢在哪)

        你现在给的信息还不足以定点到“到底卡 30 秒卡在哪一跳”,我建议你补充以下 6 个信息(最好带一条真实支付流水号):

        A. 回调入口与时间线

        • [问题1] 生产环境 Third-partyPayment 的 ABA 配置里 LodaConfirmUrl 的具体值是什么?(或从日志 ABABank.PayCallBack 打印的 URL 复制一条)
        • [问题2] 你说“支付成功后 30 秒才更新”,这 30 秒的起点是:
          • 银行页面显示成功的时间?
          • 还是 Third-partyPayment 收到回调的时间?
          • 还是 bank-api 收到回调的时间?

        B. 用一笔单做链路耗时分解(最关键)

        找一笔“慢”的 balanceorderno(例如 PYxxxx),分别在三边日志里搜:

        • [问题3] Third-partyPayment:ABAPayCallBackController 进入时间、ConfirmPayment 返回时间、HttpUtils.DoGet(LodaConfirmUrl...) 发起时间
        • [问题4] bank-api:ConfirmPayController 进入时间、入账完成时间、OnlinePayService.BoxPay 完成时间
        • [问题5] 客户端刷新看到状态变更的时间

        这样我们能明确 30 秒到底在:

        • 银行 -> Third-partyPayment
        • Third-partyPayment -> bank-api(HTTP 回调发起慢 / 网络慢)
        • bank-api 内部处理慢(DB慢/锁)
        • bank-api 已落库但前端读取慢(从库延迟/缓存)

        C. 数据侧确认

        • [问题6] 前端查询箱子状态的接口读的是主库还是从库?是否走缓存?(如果你不确定,把查询箱子状态的 API 路由/Controller 文件名给我,我可以再定位)

        当前完成状态

        • 已完成
          • 用代码证据核验:并非所有支付都必然进入 ConfirmPayController(ABA 由配置决定)
          • 用代码证据核验:并非“无队列/后台”,至少存在 Redis LPush、RabbitMQ Task.Run 异步
          • 输出了覆盖你给定文件/仓库的“完整流程图”(代码级节点)

        如果你把 一笔具体的慢单号 + LodaConfirmUrl 配置/日志 URL 给我,我可以继续把“30秒”精确定位到某个方法/某条 SQL/某个外部请求,并给出最小改动的修复建议(比如减少 DB 循环查询、合并 SaveChanges、避免读从库、或将关键状态写入与业务扣费拆分等)。

        1 条回复 最后回复
        0
        • Z 离线
          Z 离线
          zhongfangxiong
          写于 最后由 编辑
          #4

          我向WindSurf提问:
          依据现有你了解到的逻辑,我想顺便就给公司的分布式大流程的事务,实施一个便捷的追踪方案。达到如下目的:
          1)事务开始的时候,就有唯一ID;
          2)跨服务器流转的时候,这个唯一ID不变:
          3)是不是所有系统的日志,都归到一个终极的ES?
          4)然后在Kibana和ES里,很迅速就查出来全部的日志?

          1 条回复 最后回复
          0
          • Z 离线
            Z 离线
            zhongfangxiong
            写于 最后由 编辑
            #5

            目标:一套“便捷可追踪”的分布式事务/链路追踪方案

            你提的 4 点,本质就是做 分布式链路追踪(Trace)+ 结构化日志(Log)+ 集中检索(ES/Kibana)。我按“最小可落地”给一套公司级规范,结合你们现状(.NET WebApi + 多仓库 + Redis/RabbitMQ + Third-partyPayment 回调)。


            1)事务开始就有唯一 ID:怎么定义?

            建议把“事务唯一ID”分成两层(非常关键):

            A. TraceId(贯穿全链路、全系统不变)

            • 用途:你在 Kibana 一搜 trace.id: xxx 就能看到跨服务所有日志。
            • 生成时机:
              • 入口请求(用户发起、第三方回调、定时任务触发、MQ 消费触发)如果没有携带 TraceId,则生成新的。
            • 格式:
              • 推荐用 W3C Trace Context 标准(traceparent header),兼容 OpenTelemetry/Elastic APM。
              • 内部自己造也行,但建议至少是 16 字节/32 hex 这种强唯一(Guid 也可以)。

            B. BizTxnId(业务事务号,可读、可回放)

            • 例如你们的 balanceorderno(PY...)、ThirdPartyPaymentRecords.ID、businessNo(箱子/订单号)本身就是天然“业务主键”。
            • 用途:产品/客服/财务更喜欢用业务号查;工程排障更喜欢 TraceId 查。
            • 建议:日志里同时打 trace.id + biz.txn_id(或 biz.order_no/biz.balanceorderno)。

            结论:TraceId 用于技术链路;业务号用于业务语义。两者都要。


            2)跨服务器流转时 ID 不变:靠“传播协议”而不是靠“约定字符串”

            你们链路里至少有 3 类跨系统跳转:HTTP、RabbitMQ、Redis队列/任务。分别这样做:

            A. HTTP:用 W3C traceparent / tracestate

            • 服务收到请求:
              • 若 header 有 traceparent,就沿用(生成新 span)
              • 没有就新建(trace 开始)
            • 服务发起下游 HTTP:
              • 自动把 traceparent 带上

            如果你们暂时不引入全套 APM,也可以先手工传播一个 X-Trace-Id,但长期建议用 W3C 标准,避免以后迁移成本。

            B. RabbitMQ:把 Trace 信息放到 message headers

            • traceparent 放 header(字符串)
            • 也可以加上 x-trace-id、x-biz-txn-id 便于肉眼排查
            • 消费端:
              • 从 header 取出 trace context,继续链路(同 TraceId,新 Span)

            C. Redis(你们 LPush 的 queue):把 Trace 信息放到 payload

            你们现在 centerCacheService.LPush(queue, respOrder.SellerPaymentOrderList),如果 payload 是对象列表:

            • 在对象里加字段(如 TraceId)或包一层 envelope:
              • { traceparent, bizTxnId, payload }
            • 消费端把 trace 恢复出来

            这是你们当前“容易丢链路”的点:Redis list 往往没 headers,只能靠 payload 包装。


            3)是不是所有系统日志都归到一个终极 ES?

            建议:是,集中到一个 ES 集群,但按规范分索引/分数据流,避免“大杂烩一个索引”不可控。

            典型做法(Elastic 推荐):

            • logs-*:应用日志(结构化)
            • traces-*:链路追踪数据(span)
            • metrics-*:指标

            如果你们只想先做最便捷的一步:

            • 先把所有应用日志统一进 logs-*(或 data stream logs-app-default)
            • trace 数据(APM)可以后面再上;也可以先只做“log correlation”(日志带 trace.id,不一定先上 span 存储)

            4)在 Kibana 里一搜就拿到全链路日志:关键在“字段规范 + 结构化日志”

            A. 强制日志字段(最低成本但收益最高)

            无论你们现在用 Yee.Trace.ServerSide.Instance.Info/Error/Fatal 还是别的 logger,最终进 ES 的文档至少要包含:

            • @timestamp
            • service.name(如 bank-api、third-party-payment、finance-backend-core)
            • environment(prod/staging/dev)
            • trace.id(必须)
            • span.id(有 APM 时可用;没有也不强求)
            • transaction.id(可选)
            • biz.balanceorderno / biz.businessNo / biz.paymentRecordId(按业务)
            • log.level
            • message
            • error.stack_trace(异常时)

            B. Kibana 查询范式(你们想要的“很迅速”)

            • 按链路查:trace.id: "4bf92f3577b34da6a3ce929d0e0e4736"
            • 按业务号查:biz.balanceorderno: "PY202..."
              然后在结果里点开任意一条日志的 trace.id,再跳全链路。
            • 按服务收敛:service.name: "bank-api" and trace.id: ...

            结合你们现状:最小可落地改造点清单(不重构、不依赖 Hangfire)

            结合我看到的真实链路(ABA:Third-partyPayment -> HttpUtils.DoGet -> bank-api Controller;以及 bank-api 内部 Redis/RabbitMQ):

            1) HTTP 回调:Third-partyPayment 调用 bank-api 时带上 trace

            在 Yee.ThirdPartyPayment.Core/ThirdPay/ABABank.cs 的 PayCallBack 里:

            • 这里发起 HttpUtils.DoGet(basicConfig.LodaConfirmUrl + geturl)
            • 建议改为:
              • DoGet 支持传 header:traceparent / X-Trace-Id
              • 如果 HttpUtils 暂不支持 header,至少把 traceId 拼到 query(短期权宜,但不推荐长期)

            2) bank-api:所有 Controller 入口统一“提取/生成 TraceId”,并写入日志上下文

            尤其是:

            • ConfirmPayController.Get
            • NotifyController.Post
            • ABAErpExpressConfirmPayController.Get
            • ABARiderConfirmPayController.Get

            入口规则:

            • 有 traceparent / X-Trace-Id 就沿用
            • 没有就生成新的
            • 在整个请求处理期间,把 traceId 注入到日志上下文字段里(后续每条日志自动带上)

            3) RabbitMQ:在 Task.Run 推消息前把 trace 写入 headers / payload

            你们现在:

            • PublishMessage(JsonConvert.SerializeObject(new ...Eto()))

            建议:

            • Eto 增加 TraceId 字段,或 envelope:{ TraceId, Payload }
            • RabbitProducerBuilder 支持 headers 的话,更推荐 headers 带 traceparent

            4) Redis LPush:SellerOrderPaymentQueue 的 payload 必须带 trace

            ConfirmPayController 里 centerCacheService.LPush(...):

            • 给 SellerPaymentOrderList 的元素补 TraceId(如果你们能改 DTO)
            • 或在写入 Redis 前 envelope 一层(成本更小,对消费端要配套改造)

            我需要你确认的 3 个关键点(用来给出“最适合你们的落地方案”)

            你回复这 3 个问题,我就能把方案收敛到“你们公司能最快上线的版本”:

            1. 你们现在日志是怎么进 ES 的?
            • Filebeat/Logstash 收文件?
            • 还是应用直接写 ES?
            • 还是有自研日志平台代理?
            1. Yee.Trace.ServerSide.Instance 底层是什么?
            • log4net / NLog / Serilog / 自研?
              这决定了我们能否“无侵入地”把 trace.id 做成全局字段。
            1. 你们希望“只做日志关联”还是“日志 + APM span 全链路”?
            • 只做日志关联:最快(几天内可上线),Kibana 搜 trace.id 即可。
            • 上 APM span(OpenTelemetry/Elastic APM):更强(能看到耗时瀑布图),但改造和运维更重。

            当前完成状态

            • 已给出:TraceId 生成与传播规则、ES 归集建议、Kibana 快速检索方式、以及结合你们 HTTP/RabbitMQ/Redis 场景的最小改造点清单。
            • 下一步:你回答上面 3 个问题后,我可以把“落地版本 v1/v2”拆成清晰的实施步骤(包括字段规范、索引命名、Kibana dashboard/查询模板)。
            1 条回复 最后回复
            0
            • Z 离线
              Z 离线
              zhongfangxiong
              写于 最后由 zhongfangxiong 编辑
              #6

              日志的底层,在这里:X:\gitlab\loda.net.cn\loda.framework.erp\framework\trace
              1)根据 X:\gitlab\loda.net.cn\loda.framework.erp\framework\trace\日志跟踪\Yee.Trace.Services.Register\ServiceRegister.cs 可知,日志先是由 X:\gitlab\loda.net.cn\loda.framework.erp\framework\trace\日志跟踪\Yee.Trace.Services\RabbitMQLogger.cs 写入了消息队列;
              2)然后由X:\gitlab\loda.net.cn\loda.framework.erp\ui\trace-web\LogsServer.Client 这个控制台程序收集到ES
              3)程序员登入ERP后台,访问X:\gitlab\loda.net.cn\loda.framework.erp\ui\trace-web\Yee.Trace.WebSite\Events\EventsList.aspx,查询ES的日志记录,分页显示。
              请为我核实和提出改进意见。
              比如:
              1)我希望放弃控制台程序,改造成HangFire任务
              2)把这个HangFire作业,集成到X:\gitlab\loda.net.cn\loda.framework.erp\Business\autorunner HangFire的Web的Dashboard里。

              1 条回复 最后回复
              0
              • Z 离线
                Z 离线
                zhongfangxiong
                写于 最后由 编辑
                #7

                我会先按你给的三个文件/目录把现有链路“证据化”核实:ServiceRegister 怎么注册 logger、RabbitMQLogger 发到哪个 exchange/queue、LogsServer.Client 是否在消费并写 ES、trace-web 查询 ES 的实现。确认之后我再给两部分改进建议:

                • 架构层面:控制台消费者改 Hangfire 是否合适(长连接消费 vs 轮询),我会明确推荐方案。
                • 工程落地:如何把作业集成进 Business/autorunner 的 Hangfire Web Dashboard(需要改哪些项目/配置)。
                  我现在先读你点名的关键文件,并补充读取 LogsServer.Client 的入口代码。

                我先把你描述的链路用代码证据逐条核实(目前已基本核实到 RabbitMQ -> ES -> Web 查询这一整条),然后针对你提出的两个改造点给出结论:“控制台消费者”是否适合改成 Hangfire Job、以及如何把它并入 autorunner 的 Hangfire Dashboard(需要看 autorunner Hangfire Host 的技术栈/启动方式)。我接下来会补读 LogQueueNames、LogsServer.Client 的配置文件,以及定位 Business/autorunner 里 Hangfire Web 的入口/Registrar。随后给你一份可落地的改造清单与推荐架构。

                核实结果:你描述的日志链路基本属实(并补上关键细节)

                1) “ServiceRegister -> RabbitMQLogger 写 MQ” ✅属实

                framework/trace/日志跟踪/Yee.Trace.Services.Register/ServiceRegister.cs 明确注册:

                • Kernel.AddService<Framework.Trace.ILogger, RabbitMQLogger>();

                Yee.Trace.Services/RabbitMQLogger.cs 中所有 Info/Error/Fatal/Warn/Debug 都调用:

                • LoggerProducerBuilder.GetProducerBuilder(LogQueueNames.ProgramLogQueue)
                • 最终 pub.PublishMessage(loggerJson) 写入 RabbitMQ。

                同时还有一个 ApiAuditLog 专用队列:

                • ApiAuditInfo(...) 用 LogQueueNames.ApiAuditLogQueue。

                并且 LoggerProducerBuilder.cs 固定了 exchange:

                • Exchange:Yee.Trace.Log(Direct)
                • RoutingKey/QueueName:ProgramLogQueue / ApiAuditLogQueue

                LogQueueNames.cs 也确认为两个队列常量。

                2) “LogsServer.Client 控制台消费 MQ 并写 ES” ✅属实

                ui/trace-web/LogsServer.Client:

                • Program.cs 读取 DefaultTaskCount(App.config 默认 6)
                • 启动:
                  • MQConsumerBuilder.InitProgramLogClient(threadCount)
                  • MQConsumerBuilder.InitAuditLogClient(threadCount)
                • 然后 Console.ReadLine() 常驻

                MQConsumerBuilder.cs 里每个线程都 Task.Run 拉起一个 RabbitMQ consumer,并在回调里:

                • JsonConvert.DeserializeObject<ProgramLog>(message)
                • ElasticFactory.Instance.ProgramLogClient.InsertAsync(logModel) 写入 ES
                • ApiAuditLog 同理

                也就是说它本质是 常驻消费者(长连接消费),不是“定时任务轮询”。

                3) “trace-web 从 ES 查、分页显示” ✅属实

                ui/trace-web/Yee.Trace.WebSite/Events/EventsList.aspx.cs:

                • 用 ElasticFactory.Instance.ProgramLogClient
                • 构造 SearchDescriptor<ProgramLog>,按时间/用户/关键字/级别/CRUD/appId/pageId/uniqueId 过滤
                • esClient.Search(search) 然后分页展示

                另外你们 trace-web/Web.config 里已配置了 index name(目前是 test):

                • ProgramLogIndexName = logs.programlog.test
                • ApiAuditLogIndexName = logs.apiauditlog.test

                但注意:Yee.Trace.WebSite/ElasticFactory.cs 里又把 index name 写死成 "ProgramLog"/"ApiAuditLog",是否真正使用 web.config 的 *IndexName,要看 Loda.Framework.Elasticsearch.ElasticInitClient 内部是否会读取 appSettings 覆盖(这里我还没读到它的实现)。


                改进意见:关于“放弃控制台程序改 Hangfire”——结论与推荐

                结论:不建议把 MQ 实时消费本身改成 Hangfire 周期 Job

                原因很工程化:你当前的 LogsServer.Client 是 持续消费队列的 daemon;而 Hangfire 的 Recurring Job 更适合:

                • 定时扫描/补偿
                • 定时清理/统计/健康检查
                • 触发一次性批处理

                如果硬改成 Hangfire,有两种实现方式,分别都有明显问题:

                • 方式1:Hangfire Job 内启动一个长期阻塞消费循环
                  • Job 不会结束,占用 worker
                  • 重启/伸缩/并发都不符合 Hangfire 的模型
                • 方式2:Hangfire 每隔 N 秒拉一次队列(轮询 Pull)
                  • RabbitMQ 默认是 push/消费模型,你要实现 pull 反而复杂
                  • 延迟、吞吐、错误处理都会退化

                所以:消费 MQ -> 写 ES 这件事,应该继续保持“常驻服务”形态。

                推荐方案(满足你“进 Dashboard 可视化、可运维”的诉求)

                你想要的两个目的其实是:

                • 不想再手工跑一个 console
                • 想统一在 autorunner 的 Hangfire Dashboard 里管理/观察

                建议分层解决:

                A. 把 LogsServer.Client 改造成 Web Host / Worker Service(常驻),不要用 console

                推荐两条路线(二选一):

                • A1:直接做成 ASP.NET Core Web(像 LodaErpConsumer.Web 那样)
                  • 好处:
                    • 可以挂健康检查、metrics、管理页
                    • 可以用同一套部署方式(IIS/Kestrel/Windows Service)
                • A2:Worker Service(Windows Service / systemd)
                  • 好处:
                    • 更轻量,纯后台

                你们其实已经有样板:Business/autorunner/Loda消息队列消费工具/LodaErpConsumer.Web
                它是 ASP.NET Core,内部用 AddHostedService<...Consumer> 常驻消费,然后再用 Hangfire 做“监控任务”。

                B. 用 Hangfire 做“监控/自愈/补偿”,并集成进 Dashboard

                你们 LodaErpConsumer.Web 已经在用的模式是正确的:

                • 真正消费:AddHostedService<...Consumer>()(常驻)
                • Hangfire 任务:RabbitMQConsumerMonitorTask 定时检查消费者是否断连,不行就重启(consumer.IsActive() / StopAsync / StartAsync)
                • Dashboard:app.UseLodaHangfireDashboard()

                你想把 LogsServer.Client 的能力融入 autorunner Dashboard,建议复用这个成熟模式。


                你提出的两个改造点:可落地方案

                1)“放弃控制台程序,改造成 HangFire 任务”

                我建议改成:

                • 常驻消费者(HostedService):负责实时从 ProgramLogQueue、ApiAuditLogQueue 消费并写 ES
                • Hangfire 任务:负责
                  • 消费者健康检查/断线重连
                  • ES 写入失败重试/死信队列扫描(如果你们做 DLQ)
                  • 日志清理(你 Program.cs 里那个 15 天定时器逻辑,应该改成 Hangfire Job)

                也就是:“消费”不进 Hangfire,“运维”进 Hangfire。

                2)“把这个 HangFire 作业集成到 autorunner 的 HangFire Dashboard”

                从代码看,你们 Business/autorunner 已经有 Hangfire Web Host:LodaErpConsumer.Web,并且 Dashboard 已经存在。

                两种集成路径:

                • 路径B1(最快):直接把“日志入 ES 消费者”加到 LodaErpConsumer.Web 这个项目里

                  • 新增两个 HostedService:
                    • ProgramLogQueueConsumerHostedService
                    • ApiAuditLogQueueConsumerHostedService
                  • 配置 RabbitMQ 指向 Yee.Trace.Log exchange + 两个 queue
                  • 复用现有 Hangfire Dashboard + MonitorTask 模式
                  • 结果:日志链路可视化/重启都在同一个 Dashboard 里
                • 路径B2:做一个独立的 TraceLogConsumer.Web,但复用同一套 Hangfire Storage

                  • Dashboard 仍然可以统一看(前提是你们多个应用共用同一个 Hangfire storage,并且 Dashboard 显示你想要的 job)
                  • 但运维上还是多一个站点要部署

                我更推荐 B1,因为你们 LodaErpConsumer.Web 已经是“消费工具合集”,语义上也契合。


                额外的工程改进点(你们现在这套链路的真实风险点)

                这些点不改也能跑,但会影响可靠性/排障效率:

                • [ES 写入吞吐] 现在是一条消息 InsertAsync 一次请求

                  • 建议改为批量 Bulk(按 100~1000 条/或按 1~5MB flush)
                  • 否则 ES 压力大时会显著延迟/丢重试
                • [消费幂等/ACK语义] 我没看到 ConsumeMessageAsync 的 ack 策略

                  • 要确认:写 ES 成功才 ack;失败要 nack/requeue 或进入 DLQ
                  • 否则“写失败但消息已确认”会丢日志
                • [索引命名一致性] Web.config 配了 logs.programlog.test,但 ElasticIndex.ProgramLogIndexName 写死 "ProgramLog"

                  • 建议统一为“可配置的 index name / data stream”,避免环境混乱
                • [追踪字段] ProgramLog 当前有 PageId、Id、UniqueId(实际是 Id)但缺少 trace.id

                  • 下一步做分布式追踪时,建议在 ProgramLog 增加 TraceId/SpanId/BizTxnId(至少 TraceId)并贯穿传播

                我需要你确认 2 个问题,才能给出“最终落地实施清单(包含要改哪些文件/项目)”

                1. 你说的“集成到 Business/autorunner HangFire 的Web Dashboard”,这个 Dashboard 指的是:
                  • Business/autorunner/Loda消息队列消费工具/LodaErpConsumer.Web 这个站点吗?
                2. 你希望 LogsServer 的部署形态是什么?
                  • Windows Service(最像现在 console,但更稳)
                  • IIS/Kestrel Web 站点(带健康检查 + dashboard)

                只要你确认这两点,我就能把改造拆成 3-5 个明确的 PR 级别改动(包括:新增消费者 HostedService、把定时清理改 Hangfire、统一 index 配置、加入监控任务等)。


                当前完成状态

                • 已核实:RabbitMQLogger -> RabbitMQ -> LogsServer.Client -> ES -> trace-web UI 全链路成立,并补全 exchange/queue/index 等关键细节。
                • 已给出:是否替换为 Hangfire 的结论与推荐架构,以及如何并入 autorunner Dashboard 的两条落地路径。
                1 条回复 最后回复
                0
                • Z 离线
                  Z 离线
                  zhongfangxiong
                  写于 最后由 编辑
                  #8

                  图片中“消息”这一列,是我们的C#代码记录的时间。从现有的日志来看是银行发送消息太慢 。 具体还是要发到 正式站上试下看看 。因为正式站的 回调是在国外的服务器。按道理来说 不会像国内这么慢。图片中是我们深圳的测试服务器。涉及的仓库你都可以执行git pull[图片]
                  792cb66f-bbaa-459b-88d2-5fa0f9f31afb.jpg

                  1 条回复 最后回复
                  0
                  • Z 离线
                    Z 离线
                    zhongfangxiong
                    写于 最后由 编辑
                    #9

                    分析与结论

                    1. 图中“消息”列是你们 ConfirmPayController.Get 里 Logger.Info 写入的时间,代表 ConfirmPayController 收到并处理第三方回调的时刻(Business/finance/.../ConfirmPayController.cs 已确认该方法同步完成状态更新后立即记录日志)。因此这列时间延迟表明确实是银行回调到达晚,不是我们内部异步处理造成的[@x:\gitlab\loda.net.cn\loda.framework.erp\web-api\bank-api\GxHub.FinanceAPI.Server\Controllers\ConfirmPayController.cs#1-120]。

                    2. 你提到的“正式站”回调在国外服务器,理论上网络/第三方差异可能更好。当前测试服务器(深圳)表现慢只能说明该环境下银行发起回调慢,需要在正式环境重跑一次确认:尽量让正式银行回调到国外 URL,观察 Message 日期与订单状态的时间差,确认生产环境延迟是否仍然出现。

                    3. 由于你也提到所有仓库都可以 git pull,建议部署前先同步最新代码(以确保 ConfirmPayController 以及其它日志链路与现网一致),然后在正式站监控 trace-web(EventsList.aspx)里的相同日志,看是否有更短的回调间隔。

                    下一步建议(按优先级)

                    1. 在正式环境用对应的回调地址跑一次真实银行支付流程,观察 事件ID/消息 时间是否仍然落后,用 trace-web 查询 ConfirmPay、balanceorderno 关联记录。
                    2. 如果正式环境也慢,记录 traceId/BizTxnId(已有日志模型可以扩展)用于跨系统追踪;否则就说明测试环境网络或银行配置导致慢。
                    3. 如果继续怀疑网络问题,可让银行直接在正式环境回调国外地址,顺便在 ConfirmPayController 前加入额外日志(如收到 NotifyRequest 的时间戳)以便对比。

                    需要我进一步帮你分析 ConfirmPayController 中的具体日志字段、或者准备部署到正式站后的验证步骤吗?

                    1 条回复 最后回复
                    0
                    • L 离线
                      L 离线
                      linqipeng
                      写于 最后由 编辑
                      #10

                      在线支付状态变更耗时总结:应该是连接的服务器有差异,之前慢的时候连接的服务器是 api.dollar.la ;最新连接的服务器是 api.loda.net;
                      下图是最新 域名发布之后的 监控日志截图
                      [图片]
                      企业微信截图_17690627679170.png

                      1 条回复 最后回复
                      0
                      回复
                      • 在新帖中回复
                      登录后回复
                      • 从旧到新
                      • 从新到旧
                      • 最多赞同


                      • 登录

                      • 登录或注册以进行搜索。
                      • 第一个帖子
                        最后一个帖子
                      0
                      • 版块
                      • 最新
                      • 标签
                      • 热门
                      • 世界
                      • 用户
                      • 群组