接口容错机制

作者:周星 发布:2017-10-07

       这几天在做一个项目,要通过thrift接口和java进行数据传输,数据层的逻辑操作均由java完成,ruby端主要负责和前端交互和展示,其中有两个重要的问题,一是容错,要保证java接口在挂掉之后仍然不影响页面重要部分的展示,即保证页面不会超时不会挂掉;二是性能问题,调用接口次数要尽可能的少,把可以合并的接口合并成一个,并对所有的接口返回数据做缓存,这次我们主要描述系统的容错设计。

       既然是调用别人的接口,所以,一定要记录日志,并且越详细越好否则以后出现问题会难以定位。我这里的日志记录了接口调用时间、当前进程号、状态值、调用目的、方法名、传入参数、返回值。这样日志内容足够详细,可准确找出问题。

       首先,我们需要定义一个Flag,用来标记当前链接失败次数,如果这个次数大于我们定义的最大失败次数,我们就关闭链接,写入日志,返回一个默认值,因为这个Flag要递增、重置,操作较频繁,所以我们将它保存在Redis中(下面的RedisKey.message_fails_count)。

       我们把容错处理独立出来,尽量可以让其公用,我们在 lib 下定义一个模块来处理容错,先定义一个tolerance方法,在调用外部接口前先调用这个方法,如果这个方法没有抛出异常,没有超时,我们就调用这个接口获取数据,否则这个方法返回我们传入的默认值

def tolerance(key, opt = nil)
  options = { timeout: 0.8, max_failure: 5, failure_time: 300, block_time: 120, failure_return: nil, logger: nil, failure_callback: nil }.merge! opt 
  options.merge!(opt) if opt.present?
  if current_fail_count(key) >= options[:max_failure]
    write_log(key, options, "连续失败,停止调用")
    close_connection
    return options[:failure_return]
  else
    begin
      Timeout::timeout(options[:timeout]) do
        return yield
      end 
     rescue StandardError => e
      if options[:failure_callback].present?
        options[:failure_callback].call
      end 
      set_fail_counter(key, options, e)
      return options[:failure_return]
    end 
  end 
end

然后在调用接口的地方,我们这样调用这个方法

def forward_zhe_msg(user_id)
  result = tolerance(RedisKey.message_fails_count, tolerance_options.merge({ failure_return: { msgNum: 0, popMsg: "" } })) do
    client.getForwardZheMsg(user_id)                                                           end
  log("获取新消息数和弹出消息,方法名:getForwardZheMsg,参数为user_id: #{user_id} 返回值:result:#{result.inspect}")
  result
end

下面全面分析一下 tolerance 方法:

def tolerance(key, opt = nil)
  options = { timeout: 0.8, max_failure: 5, failure_time: 300, block_time: 120, failure_return: nil, logger: nil, failure_callback: nil }.merge! opt
end

    此方法接收两个参数,第一个参数是我们定义的 redis 的 key,第二个参数是一个 hash,接收传入的option,你可以指定超时时间,容忍失败最大次数等(后面会详细介绍各参数指标)。方法里面的第一行options 为各指标的默认值,如果调用者没传入对应参数,则会使用默认值。

再看下面一段重要代码:

if current_fail_count(key) >= options[:max_failure]
  write_log(key, options, "连续失败,停止调用")
  close_connection
  return options[:failure_return]
else
  begin
    Timeout::timeout(options[:timeout]) do
      return yield
    end
  rescue StandardError => e
    if options[:failure_callback].present?
      options[:failure_callback].call
    end
    set_fail_counter(key, options, e)
    return options[:failure_return]
  end
end

        如果当前失败次数大于容忍最大失败次数,我们就记录日志,然后关闭链接,返回指定的默认值(failure_return),否则,我们将做如下处理:

        调用ruby的Timeout模块的timeout方法,如果在指定时间(timeout)块内的代码未执行完,则抛出异常,在异常中执行可选的call_back(failure_callback),然后将失败次数+1(set_fail_counter),返回指定的默认值(failure_return)

我们接下来看一下 set_fail_counter 方法

def set_fail_counter(key, options, error)
  inrc_count = Redis.current.incr(key)
  if(inrc_count == 1)
    Redis.current.expire(key, options[:failure_time])
    write_log(key, options, "第一次失败,设置失败标识, #{error.to_s}\n\r#{error.backtrace.join("\n\r")}")                                                                 
  elsif(inrc_count >= options[:max_failure])
    Redis.current.expire(key, options[:block_time])
    write_log(key, options, "失败次数超过预设值#{options[:block_time]}, #{error.to_s}\n\r#{error.backtrace.join("\n\r")}")
  else
    write_log(key, options, "累计失败, #{error.to_s}\n\r#{error.backtrace.join("\n\r")}")
  end
end

       此方法接收三个参数,第一个参数为 Redis 的Key,第二个参数为容错参数指标,第三个参数为失败报错的信息。这里需要注意的是,我们为每一次失败的 Key 上赋予了一个超时时间,当超时时间已过,Key 则自动过期,第一次失败我们把这个时间设的稍微长一点,为 :failure_time ,之后的每次都为 :block_time,并在每次失败后记录日志。

容错抛出异常时的call_back 写在了一个公用方法里:

def tolerance_options
  { logger: logger, failure_callback: lambda { recreate_connection } }
end

即call 这个 lambda ,重新创建连接

以上功能设计、代码、指导均来自王鸿,在此感谢王鸿提供的技术支持        

 

支付宝扫码赞助博主


评论(0)