替代在Rails的API包装器中使用Thread.current

我开发了一个应用程序,允许我们的客户创建自己的会员保护网站。 然后,我的应用程序连接到外部API服务(客户特定的api_key / api_url),以同步/更新/添加数据到此其他服务。 好吧,我已经为这个其他服务编写了一个API包装器。 但是,我现在看到连接为零的随机丢弃。 以下是我目前使用连接的方式:

我有一个xml / rpc连接类

class ApiConnection attr_accessor :api_url, :api_key, :retry_count def initialize(url, key) @api_url = url @api_key = key @retry_count = 1 end def api_perform(class_type, method, *args) server = XMLRPC::Client.new3({'host' => @api_url, 'path' => "/api/xmlrpc", 'port' => 443, 'use_ssl' => true}) result = server.call("#{class_type}.#{method}", @api_key, *args) return result end end 

我还有一个模块可以包含在我的模型中以访问和调用api方法

 module ApiService # Set account specific ApiConnection obj def self.set_account_api_conn(url, key) if ac = Thread.current[:api_conn] ac.api_url, ac.api_key = url, key else Thread.current[:api_conn] = ApiConnection.new(url, key) end end ######################## ### Email Service ### ######################## def api_email_optin(email, reason) # Enables you to opt contacts in Thread.current[:api_conn].api_perform('APIEmailService', 'optIn', email, reason) end ### more methods here ### end 

然后在应用程序控制器中,我使用beforefilter在每个请求上创建一个新的ApIConnection对象,该filter设置Thread.current [:api_conn]。 这是因为我有数百个客户,每个客户都有自己的api_key和api_url,同时使用该应用程序。

 # In before_filter of application controller def set_api_connection Thread.current[:api_conn] = ApiService.set_account_api_conn(url, key) end 

好吧,我的问题是我已经读过使用Thread.current并不是最理想的处理方式,我想知道这是否是ApiConnection在随机请求中为零的原因。 所以我想知道如何更好地设置这个包装器。

答案1

我希望问题是在连接完成之前发出的下一个请求,然后before_filter将覆盖仍在进行的连接的连接。 我试图远离线程。 fork_off更容易,但也有一些警告,特别是在性能方面。

我尝试将这样的逻辑转移到某种后台作业。 一个常见的解决方案是延迟作业https://github.com/collectiveidea/delayed_job ,这样你就不必乱用线程,它更健壮,更容易调试。 然后,只要有人登录,您就可以启动后台作业以异步同步服务。

 @account.delay.optin_via_email(email,user) 

这将序列化帐户,将其保存到作业队列,在那里它将被延迟的作业取消序列化,并且将调用延迟后的方法。 您可以拥有任意数量的后台作业,甚至可以使用一些专门用于某些类型操作的作业队列(通过使用作业优先级 – 假设两个bj用于高prio作业,一个用于低prio作业)

答案2

只需将其作为对象

 def before_filter @api_connection = ApiConnection.new(url, key) end 

那么你可以在控制器方法中使用该连接

 def show #just use it straight off @api_connection.api_perform('APIEmailService', 'optIn', email, reason) # or send the connection as a parameter to some other class ApiService.do_stuff(@api_connection) end 

答案3

最简单的解决方案可能就是在需要时创建api连接

 class User < ActiveRecord::Base def api_connection # added caching of the the connection in object # doing this makes taking a block a little pointless but making methods take blocks # makes the scope of incoming variables more explicit and looks better imho # might be just as good to not keep @conn as an instance variable @conn = ApiConnection.new(url, key) unless @conn if block_given? yield(@conn) else @conn end end end 

这样你就可以轻易忘记连接的创建,并有一个新的方便。 可能会有性能损失,但我怀疑它们是微不足道的,除非有额外的登录请求

 @user.api_connection do { |conn| conn.optin_via_email(email,user) }