You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

异步SOAP响应等待优化:避免系统阻塞的最佳实践问询

异步SOAP请求响应的最优解决方案

针对你当前用轮询数据库导致高并发下进程阻塞的问题,结合Ruby/Rails生态,这里给出几个落地性强的解决方案,按性能和推荐度排序:

1. 外部SOAP系统回调 + Sidekiq处理(最优)

如果外部SOAP服务支持回调,这是彻底解决轮询问题的最佳方式,完全避免资源浪费。

具体做法:

  • 发起SOAP请求时,把你的回调URL(比如https://your-domain.com/soap_callback)和correlation ID一起传给外部系统
  • 外部系统处理完成后,会主动调用这个回调URL,携带correlation ID和响应内容
  • 后端回调接口验证请求合法性后,把响应存入数据库,还可以用Sidekiq触发后续通知逻辑(比如推送给前端)

代码示例:

# 发起SOAP请求,附带回调信息
def soap_request(request)
  client = Savon.client(wsdl: 'http://external-soap-service.com/wsdl')
  client.call(:submit_request, message: {
    request_data: request.data,
    correlation_id: request.correlation,
    callback_url: 'https://your-domain.com/soap_callback'
  })
end

# 回调接收接口
def soap_callback
  # 这里要加签名验证,防止恶意请求
  correlation_id = params[:correlation_id]
  response_content = params[:response_content]
  
  Income.create!(ident: correlation_id, content: response_content)
  
  # 用Sidekiq推送给前端(比如ActionCable)
  SoapResultNotifierWorker.perform_async(correlation_id, response_content)
  
  render plain: 'OK'
end

# Sidekiq Worker:推送结果到前端
class SoapResultNotifierWorker
  include Sidekiq::Worker

  def perform(correlation_id, content)
    parsed_data = Nokogiri::XML(content).to_hash
    ActionCable.server.broadcast("soap_result_#{correlation_id}", {
      status: 'success',
      data: parsed_data
    })
  end
end

优缺点:

  • ✅ 完全消除轮询,资源占用极低,高并发场景下表现最好
  • ❌ 依赖外部SOAP系统支持回调,需要额外做请求合法性验证(比如签名)

2. Server-Sent Events (SSE) 实时推送(次优)

如果外部系统不支持回调,用SSE让后端主动推送结果,替代前端高频轮询,减少请求数。

具体做法:

  • 前端发起请求后,后端立即触发SOAP请求,同时和前端建立SSE长连接
  • 后端在SSE连接中轮询数据库(或用Sidekiq异步监听),拿到结果或超时后推送给前端

代码示例(Rails + ActionController::Live):

class SoapRequestsController < ApplicationController
  include ActionController::Live

  def stream_result
    response.headers['Content-Type'] = 'text/event-stream'
    sse = SSE.new(response.stream, retry: 3000)
    correlation_id = params[:correlation_id]
    timeout_at = 180.seconds.from_now

    begin
      loop do
        income = Income.find_by(ident: correlation_id)
        if income&.content.present?
          parsed_data = Nokogiri::XML(income.content).to_hash
          sse.write({ status: 'success', data: parsed_data }, event: 'result')
          break
        elsif Time.now >= timeout_at
          sse.write({ status: 'timeout' }, event: 'error')
          break
        end
        sleep(1) # 1秒查一次,比原0.5秒更友好
      end
    rescue IOError
      # 前端断开连接,清理资源
    ensure
      sse.close
    end
  end
end

前端JS:

const eventSource = new EventSource(`/soap/stream_result?correlation_id=${correlationId}`);

eventSource.addEventListener('result', (event) => {
  const data = JSON.parse(event.data);
  // 处理返回结果
  eventSource.close();
});

eventSource.addEventListener('error', (event) => {
  alert('请求超时或出错');
  eventSource.close();
});

优缺点:

  • ✅ 后端主动推送,请求数少,实时性好
  • ❌ 长连接会占用服务器连接数,高并发下需要调整服务器配置(比如Nginx的worker_connections)

3. 前端轮询 + 后端状态接口(快速落地)

这是最容易实现的方案,把原来后端的轮询转移到前端,释放web进程。

具体做法:

  • 后端接收请求后,立即发起SOAP请求,返回correlation ID和超时时间给前端
  • 前端用这个ID定期调用后端的状态查询接口,直到拿到结果或超时

代码示例:

# 初始请求接口:触发SOAP请求并返回correlation ID
def initiate_request
  request = build_soap_request(params)
  soap_request(request)
  # 建议把请求发起时间存在Redis或单独表中,用于后续超时判断
  Redis.current.setex("soap_request_start:#{request.correlation}", 180, Time.now.to_i)
  render json: { correlation_id: request.correlation, timeout: 180 }
end

# 状态查询接口:返回当前请求状态
def check_status
  correlation_id = params[:correlation_id]
  start_time = Redis.current.get("soap_request_start:#{correlation_id}")&.to_i
  income = Income.find_by(ident: correlation_id)

  if income&.content.present?
    parsed_data = Nokogiri::XML(income.content).to_hash
    render json: { status: 'success', data: parsed_data }
  elsif start_time && Time.now.to_i - start_time > 180
    render json: { status: 'timeout' }, status: 408
  else
    render json: { status: 'pending' }
  end
end

前端JS:

async function getSoapResult(correlationId, totalTimeout) {
  const startTime = Date.now();
  while (Date.now() - startTime < totalTimeout * 1000) {
    const res = await fetch(`/soap/check_status?correlation_id=${correlationId}`);
    const data = await res.json();
    
    if (data.status === 'success') return data.data;
    if (data.status === 'timeout') throw new Error('请求超时');
    
    await new Promise(resolve => setTimeout(resolve, 1000)); // 1秒轮询一次
  }
  throw new Error('请求超时');
}

优缺点:

  • ✅ 实现简单,无需额外依赖,兼容所有浏览器
  • ❌ 会产生较多HTTP请求,适合响应时间较短的场景

4. Sidekiq + Redis 临时存储(折中方案)

如果一定要用Sidekiq处理等待逻辑,可以结合Redis做临时结果存储,避免阻塞web进程。

具体做法:

  • 后端发起SOAP请求后,启动Sidekiq Worker轮询数据库
  • Worker拿到结果后,把结果存入Redis并设置过期时间
  • 后端初始接口做短时间轮询尝试拿结果,没拿到就返回correlation ID让前端后续查询

代码示例:

# 初始请求接口
def initiate_request
  request = build_soap_request(params)
  soap_request(request)
  WaitForSoapResultWorker.perform_async(request.correlation)
  
  # 短轮询尝试拿结果,避免直接阻塞
  5.times do
    result = Redis.current.get("soap_result:#{request.correlation}")
    if result
      if result == 'timeout'
        render json: { status: 'timeout' }, status: 408
      else
        render json: { data: Nokogiri::XML(result).to_hash }
      end
      return
    end
    sleep(0.5)
  end

  # 短轮询没拿到,返回correlation ID让前端自己查
  render json: { correlation_id: request.correlation }
end

# Sidekiq Worker:轮询数据库并缓存结果到Redis
class WaitForSoapResultWorker
  include Sidekiq::Worker
  sidekiq_options retry: false, timeout: 180

  def perform(correlation_id)
    timeout_at = 180.seconds.from_now
    loop do
      income = Income.find_by(ident: correlation_id)
      if income&.content.present?
        Redis.current.setex("soap_result:#{correlation_id}", 180, income.content)
        break
      elsif Time.now >= timeout_at
        Redis.current.setex("soap_result:#{correlation_id}", 180, 'timeout')
        break
      end
      sleep(1)
    end
  end
end

优缺点:

  • ✅ 把轮询逻辑放到Sidekiq进程,不占用web进程
  • ❌ 还是存在轮询,只是转移了位置

总结

  • 优先选回调方案,性能最优;
  • 不支持回调的话,高并发场景选SSE;
  • 快速上线选前端轮询;
  • 非要用Sidekiq的话,选Redis缓存折中方案。

内容的提问来源于stack exchange,提问作者Archer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 20:39:55