Написать Hystrix вручную в 100 строк кода

Архитектура
Написать Hystrix вручную в 100 строк кода

Слияние и понижение версии

Недалеко от дома Сяояня открылся гастроном. В магазине есть две витрины, за которыми всегда можно выстроиться в очередь: одна — это выбранные холодные блюда для шеф-повара, а другая — тушеная курица, которую толстый повар может обрезать на месте. Нормальный поток клиентов примерно такой:

熔断.png

В жаркий летний день было бы прекрасно пригласить троих или пятерых друзей и выпить кружку пива, чтобы похвастаться. Может быть, все согласны с идеей маленьких глаз, и бизнес маленького магазина становится все более и более популярным. В этот день Сяоянь выбрала блюда, заплатила деньги и готовилась выстроиться в очередь к повару, чтобы откорректировать вкус и убрать косточки. Визуально было подсчитано, что время ожидания между двумя окнами будет не менее 20 минут.Вкупе с настоянием нескольких друзей они решительно сдались и пошли домой с посудой. Итак, мой процесс становится:

熔断-1.png

Когда последующая служба (приправы, обвалка) внезапно становится недоступной или по какой-то причине реагирует слишком медленно (3 минуты на покупку продуктов и полчаса в очереди), вышестоящая служба, чтобы обеспечить доступность своей общей услуги (может t ждать), больше не продолжать вызывать целевую службу, возвращаться напрямую и быстро освобождать ресурсы. Возобновите вызов, если целевой сервис улучшится. Это называется сервисным предохранителем.

Из-за долгой очереди Сяоянь решил отказаться от последующего процесса и предоставил блюда «пониженного качества». Это называется деградацией сервиса.

Есть много способов сплавить

Существует много способов снижения качества услуг, таких как ограничение тока, переключение и слияние.

Что касается автоматических выключателей, в Spring Cloud есть библиотека для понижения версии автоматических выключателей Hystrix, а Sentinel с открытым исходным кодом от Alibaba также можно использовать в распределенных проектах для достижения целей автоматического выключателя и понижения. И Hystrix, и Sentinel необходимо внедрять сторонние компоненты и понимать принцип реализации, что не подходит для использования в простых сценариях.

Использование рукописных предохранителей

В этой статье представлен метод автоматического выключателя, подходящий для простых приложений с основным кодом не более 100 строк. Метод использования примерно следующий:

// 初始化一个熔断器
private CircuitBreaker breaker = new CircuitBreaker(0.1, 10, true, "serviceDemo");

public void doSomething() {
    // 每次调用都检查服务状态
    breaker.checkStatus();
    // 如果熔断器返回 true 认为服务可用,继续执行逻辑
    if (breaker.isWorked()) {
        try {
            service.doSomething();
        } catch (Exception e) {
            e.printStackTrace();
            // 出现调用失败,记录失败次数
            breaker.addFailTimes();
        } finally {
            // 每一次调用,增加调用次数
            breaker.addInvokeTimes();
        }
    }
    // 服务不可用,执行降级逻辑
}

В этом псевдокоде автоматический выключатель выполняет три функции:

  1. Проверить состояние службы и вывести журнал статистики

  2. Возвращает статус службы breaker.isWorked()

  3. Запишите количество вызовов и отказов в качестве основы для слияния.

Реализация предохранителя

Конкретная реализация предохранителя выглядит следующим образом:

public class CircuitBreaker {
  /**
   * 记录失败次数
   */
  private AtomicLong failTimes =
          new AtomicLong(0);
  /**
   * 记录调用次数
   */
  private AtomicLong invokeTimes =
          new AtomicLong(0);
  /**
   * 降级阈值,比如 0.1
   * 请求失败次数/请求总次数的比例
   */
  private double failedRate = 0.1;
  /**
   * 降级最小条件,请求总次数大于该值
   * 才会执行阈值判断
   * 比如 设置为 10 ,
   * 当请求次数大于10次时才会执行判断
   */
  private double minTimes;
  /**
   * 熔断开关,默认关闭
   */
  private boolean enabled;
  /**
   * 熔断后是否发送邮件告警
   */
  private boolean mail;
  /**
   * 熔断后是否发送短信告警
   */
  private boolean sms;
  /**
   * 熔断器名字
   */
  private String name;
  /**
   * 保存上一次统计的时间戳,记录单位是分钟
   */
  private AtomicLong currentTime =
          new AtomicLong(
             System.currentTimeMillis() / 60000);
  /**
   * 记录服务是否是不可用状态
   */
  private AtomicBoolean isFailed =
          new AtomicBoolean(false);
  /**
   * 服务宕掉的状态放到线程容器中
   */
  private ThreadLocal<Boolean> fail =
          new ThreadLocal<Boolean>();


  private Logger log =
          LoggerFactory.getLogger(getClass());


  /**
   * 构造熔断器
   *
   * @param failedRate 熔断的阈值,
   *                   请求失败次数/请求总次数
   * @param minTimes   熔断的最小条件,
   *                   请求总次数大于该值才会根据阈值判断,
   *                   执行降级操作
   * @param enabled    是否需开启熔断操作
   */
  public CircuitBreaker(double failedRate,
                        double minTimes,
                        boolean enabled,
                        String name) {
    fail.set(false);
    this.failedRate = failedRate;
    this.minTimes = minTimes;
    this.enabled = enabled;
    this.name = name;
  }

  /**
   * 判断服务是否是失败状态
   *
   * @return
   */
  public boolean isFailed() {
    return isFailed.get();
  }

  /**
   * 增加错误次数
   */
  public void addFailTimes() {
    fail.set(true);
    if (enabled) {
      failTimes.incrementAndGet();
    }
  }

  /**
   * 增加一次调用次数
   */
  public void addInvokeTimes() {
    if (enabled) {
      invokeTimes.incrementAndGet();
    }
  }

  /**
   * 判断服务是否可用
   *
   * @return
   */
  public boolean isWorked() {
    if (!enabled) {
      return true;
    }
    // 当服务不可用时,牺牲掉 1% 的流量做探活请求
    if (isFailed.get() &&
        System.currentTimeMillis() % 100 == 0) {
      return true;
    }
    if (isFailed.get()) {
      fail.set(true);
      return false;
    }
    return true;
  }

  public void checkStatus() {
    if (!enabled) {
      return;
    }
    long newTime =
       System.currentTimeMillis() / 60000;
    if ((newTime > currentTime.get())
       && (invokeTimes.get() > minTimes)) {

      double percent =
              failTimes.get() * 1.0 /
                      invokeTimes.get();

      if (percent > failedRate) {
        if (isFailed.get()) {
          // 日志输出
          if (mail) {
            // 发送邮件通知
          }
        } else {
          // 日志输出
          isFailed.set(true);
          if (sms) {
            // 发送短信通知
          }
          if (mail) {
            // 发送邮件通知
          }
        }
      } else { // 服务恢复
        if (isFailed.get()) {
          // 日志输出
          if (sms) {
            // 发送短信通知
          }
          if (mail) {
            // 发送邮件通知
          }
        }
        isFailed.set(false);
      }
      if (log.isInfoEnabled()) {
        // 日志输出
      }
      currentTime.set(newTime);
      failTimes.set(0);
      invokeTimes.set(0);
    }
  }

}

Главная идея:

  1. Объединение на основе статистической информации и объединение, если доля ошибочных запросов превышает пороговое значение.

  2. Период статистики находится на минутном уровне (статистика за 1 минуту достигает порога)

  3. Если общее количество запросов не достигает minTimes в течение нескольких минут, автоматический выключатель не будет выполнен (частота запросов слишком низкая, и статистическая информация не имеет смысла).

  4. Даже если достигнуто состояние автоматического выключателя, 1% (изменяемых) запросов по-прежнему приносится в жертву для проверки.

    isFailed.get()&&System.currentTimeMillis() % 100 == 0

Преимущества и недостатки

Hystrix предоставляет ряд функций защиты услуг, таких как сервисный предохранитель и изоляция потоков. Наши автоматические выключатели с рукописным вводом могут предоставлять только ручные автоматические выключатели на основе вызывающего абонента.

Hystrix предоставляет два метода: пул потоков и семафор. Функция рукописного предохранителя является относительно единственной на основе статистической информации, а степень детализации в минутах относительно грубая.

Методы императивного программирования и регистрации обратных вызовов Hystrix имеют высокую сложность кода. В процессе вторжения в код рукописный предохранитель более ориентирован на процесс и имеет низкую стоимость понимания.

После удаления комментариев и недопустимых пустых строк фактически эффективный код составляет менее 100 строк, и мы использовали менее 100 строк кода для реализации функции прерывателя цепи. Хотя в крупномасштабных сценариях обслуживания есть много дефектов, я надеюсь, что это может дать вам хотя бы представление.

Подписывайтесь на меня

Добро пожаловать, чтобы обратить внимание и связаться со мной в любое время ~