Поговорим о механизме изящных повторных попыток (retry)

задняя часть Spring Шаблоны проектирования Безопасность

Деловая сцена

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

Эволюция решения

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

Решение 1. Простой повтор попытки «поймать-повторить»

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

public void commonRetry(Map<String, Object> dataMap) throws InterruptedException {
		Map<String, Object> paramMap = Maps.newHashMap();
		paramMap.put("tableName", "creativeTable");
		paramMap.put("ds", "20160220");
		paramMap.put("dataMap", dataMap);
		boolean result = false;
		try {
			result = uploadToOdps(paramMap);
			if (!result) {
				Thread.sleep(1000);
				uploadToOdps(paramMap);  //一次重试
			}
		} catch (Exception e) {
			Thread.sleep(1000);
			uploadToOdps(paramMap);//一次重试
		}
	}

Решение 2: режим повтора стратегии стратегии «поймать-повторить-повторить попытку»

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

public void commonRetry(Map<String, Object> dataMap) throws InterruptedException {
		Map<String, Object> paramMap = Maps.newHashMap();
		paramMap.put("tableName", "creativeTable");
		paramMap.put("ds", "20160220");
		paramMap.put("dataMap", dataMap);
		boolean result = false;
		try {
			result = uploadToOdps(paramMap);
			if (!result) {
				reuploadToOdps(paramMap,1000L,10);//延迟多次重试
			}
		} catch (Exception e) {
			reuploadToOdps(paramMap,1000L,10);//延迟多次重试
		}
	}

Есть проблема с первым и вторым решениями: нормальная логика и логика повтора сильно связаны, логика повтора очень зависит от результата выполнения нормальной логики, а ожидаемый результат нормальной логики пассивно запускается Это может привести к непоследовательному пониманию последующих операций и сопровождению того, что должна решать логика повтора. Трудно гарантировать правильность повторных попыток, и они не способствуют эксплуатации и обслуживанию, поскольку схема повторных попыток основана на нормальных логических исключениях или предположениях об первопричине повторных попыток.

Попытка изящной повторной попытки

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

Попытка 1. Разделите нормальную логику и логику повторных попыток, применив шаблон разработки команды.

Конкретное определение шаблона разработки команды не будет подробно описано.Основная идея этой схемы заключается в том, что шаблон команды может завершать логику работы интерфейса через объект выполнения и в то же время инкапсулировать логику повторных попыток обработки внутри, без раскрытие деталей реализации.Для вызывающей стороны выполняется обычная логика для достижения цели разделения, см. подробности реализации функции. (структура диаграммы классов)

IRetry определяет интерфейс загрузки и повторной попытки, а его класс реализации OdpsRetry инкапсулирует логику загрузки ODPS и в то же время инкапсулирует логику загрузки ODPS.механизм повторной попыткии повторите стратегию. В то же время используйте метод восстановления, чтобы выполнить операцию восстановления в конце.

Наш вызывающий LogicClient не должен обращать внимание на повторную попытку и реализует функцию интерфейса контракта через повторную попытку Retryer, в то же время Retryer должен отвечать и обрабатывать логику повторной попытки, а конкретная обработка повторной попытки Retryer передается к реальному классу реализации интерфейса IRtry OdpsRetry Finish. Приняв командный режим, элегантно достигается разделение обычной логики и логики повторных попыток, и в то же время путем создания роли повторной попытки реализуется разделение обычной логики и логики повторных попыток, так что повторная попытка имеет лучшую масштабируемость.

Схема попытки 2: нормальная спецификация spring-retry и логика повторных попыток

spring-retry — это набор инструментов с открытым исходным кодом. В настоящее время доступна версия 1.1.2.RELEASE. Этот инструмент настраивает шаблон операции повторной попытки и может устанавливать стратегию повторной попытки и стратегию отката. В то же время повторите попытку экземпляра выполнения, чтобы обеспечить безопасность потоков.Пример работы конкретного сценария выглядит следующим образом:

public void upload(final Map<String, Object> map) throws Exception {
		// 构建重试模板实例
		RetryTemplate retryTemplate = new RetryTemplate();
		// 设置重试策略,主要设置重试次数
		SimpleRetryPolicy policy = new SimpleRetryPolicy(3, Collections.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class, true));
		// 设置重试回退操作策略,主要设置重试间隔时间
		FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
		fixedBackOffPolicy.setBackOffPeriod(100);
		retryTemplate.setRetryPolicy(policy);
		retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
		// 通过RetryCallback 重试回调实例包装正常逻辑逻辑,第一次执行和重试执行执行的都是这段逻辑
		final RetryCallback<Object, Exception> retryCallback = new RetryCallback<Object, Exception>() {
			//RetryContext 重试操作上下文约定,统一spring-try包装 
			public Object doWithRetry(RetryContext context) throws Exception {
				System.out.println("do some thing");
				Exception e = uploadToOdps(map);
				System.out.println(context.getRetryCount());
				throw e;//这个点特别注意,重试的根源通过Exception返回
			}
		};
		// 通过RecoveryCallback 重试流程正常结束或者达到重试上限后的退出恢复操作实例
		final RecoveryCallback<Object> recoveryCallback = new RecoveryCallback<Object>() {
			public Object recover(RetryContext context) throws Exception {
				System.out.println("do recory operation");
				return null;
			}
		};
		try {
			// 由retryTemplate 执行execute方法开始逻辑执行
			retryTemplate.execute(retryCallback, recoveryCallback);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

Кратко проанализируйте код случая, RetryTemplate берет на себя роль исполнителя повторных попыток, он может устанавливать SimpleRetryPolicy (политика повторных попыток, установка верхнего предела повторных попыток, повторная корневая сущность), FixedBackOffPolicy (фиксированная резервная политика, установка интервала возврата повторной попытки выполнения). RetryTemplate отправляет операцию выполнения через execute и должен подготовить два экземпляра класса, RetryCallback и RecoveryCallback. Первый соответствует экземпляру логики повторного обратного вызова, который обертывает обычную функциональную операцию. RecoveryCallback реализует экземпляр операции восстановления после завершения всей операции выполнения.

Выполнение RetryTemplate является потокобезопасным, а логика реализации использует ThreadLocal для сохранения контекста выполнения RetryContext каждого экземпляра выполнения.

Хотя инструмент spring-retry может элегантно реализовать повторную попытку, есть два недружественных дизайна: во-первых, сущность повторной попытки ограничена подклассом Throwable, что указывает на то, что повторная попытка предназначена для перехватываемых функциональных исключений, но мы надеемся полагаться на данные A. object как сущность повтора, но структура Sping-retry должна привести к подклассу Throwable. Другой заключается в том, что объект утверждения источника повторных попыток использует экземпляр Exception функции doWithRetry, который не соответствует схеме возврата обычных внутренних утверждений.

Spring Retry поддерживает повторную попытку метода в виде аннотаций. Логика повторной попытки выполняется синхронно. «Сбой» повторной попытки предназначен для Throwable. Если вы хотите использовать определенное состояние возвращаемого значения, чтобы определить, нужно ли вам повторить попытку, возможно, вы можете только сами судить о возвращаемом значении и явно создавать исключение.

Абстракция Spring для Retry

«Абстрактность» — неотъемлемое качество каждого программиста. Для моей средней квалификации нет лучшего способа совершенствоваться, чем подражать и понимать отличный исходный код. С этой целью я переписал его основную логику... Давайте взглянем на абстракцию «повторной попытки» в Spring Retry.

логика "повторить"

while(someCondition()) {
    try{
        doSth();
        break;    
    } catch(Throwable th) {
        modifyCondition();
        wait();
    }
}
if(stillFail) {
    doSthWhenStillFail();
}

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

Интерфейс, связанный с повторной попыткой Spring.jpg
  • RetryCallback: инкапсулируйте бизнес-логику, необходимую для повторной попытки (doSth выше).
  • RecoverCallback: инкапсулирует бизнес-логику, которую необходимо выполнить после нескольких неудачных попыток (выше doSthWhenStillFail).
  • RetryContext: контекст в контексте повтора, который можно использовать для передачи параметров или состояния между несколькими повторами или повторами и восстановлением (параметры передаются между несколькими doSth или doSth и doSthWhenStillFail).
  • RetryOperations: определяет базовую структуру (шаблон) «повторной попытки», требует входящего RetryCallback, необязательный входящий RecoveryCallback;
  • RetryListener: типичный «слушатель», который уведомляет «слушателей» на разных этапах повторной попытки (например, doSth, ожидание и т. д.).
  • RetryPolicy : политика или условие повторной попытки, вы можете просто повторить попытку несколько раз или повторить попытку с указанным тайм-аутом (someCondition выше).
  • BackOffPolicy: резервная политика повторных попыток при возникновении исключения при выполнении бизнес-логики. Если нам нужно повторить попытку, нам может потребоваться подождать некоторое время (возможно, сервер слишком занят, и если повторная попытка не повторится, сервер может быть перетащен), конечно, этот период времени может быть равен 0 , фиксированный или случайный (см. стратегию отсрочки TCP в алгоритме управления перегрузкой). Стратегия отката описана выше как wait();
  • RetryTemplate : конкретная реализация RetryOperations, объединяющая RetryListener[], BackOffPolicy и RetryPolicy.

Попытка 3: guava-retryer разделяет нормальную и повторную логику

Инструмент Guava retryer похож на spring-retry тем, что он оборачивает обычные логические повторы, определяя роль повторителя, но Guava retryer имеет лучшее определение политики, которое совместимо с контролем количества повторных попыток и частоты повторных попыток. Определения источника повторных попыток, которые поддерживают несколько исключений или настраиваемых объектов сущностей, обеспечивают большую гибкость в функции повторных попыток. Guava Retryer также является потокобезопасным. Логика вызова входа использует метод вызова Java.util.concurrent.Callable. Пример кода выглядит следующим образом:

public void uploadOdps(final Map<String, Object> map) {
		// RetryerBuilder 构建重试实例 retryer,可以设置重试源且可以支持多个重试源,可以配置重试次数或重试超时时间,以及可以配置等待时间间隔
		Retryer<Boolean> retryer = RetryerBuilder.<Boolean> newBuilder()
				.retryIfException().//设置异常重试源
				retryIfResult(new Predicate<Boolean>() {//设置自定义段元重试源,
			@Override
			public boolean apply(Boolean state) {//特别注意:这个apply返回true说明需要重试,与操作逻辑的语义要区分
				return true;
			}
		})
		.withStopStrategy(StopStrategies.stopAfterAttempt(5))//设置重试5次,同样可以设置重试超时时间
		.withWaitStrategy(WaitStrategies.fixedWait(100L, TimeUnit.MILLISECONDS)).build();//设置每次重试间隔

		try {
			//重试入口采用call方法,用的是java.util.concurrent.Callable<V>的call方法,所以执行是线程安全的
			boolean result = retryer.call(new Callable<Boolean>() { 
				@Override
				public Boolean call() throws Exception {
					try {
						//特别注意:返回false说明无需重试,返回true说明需要继续重试
						return uploadToOdps(map);
					} catch (Exception e) {
						throw new Exception(e);
					}
				}
			});

		} catch (ExecutionException e) {
		} catch (RetryException ex) {
		}
	}

Принцип анализа примера кода:

RetryerBuilder — это создатель фабрик, который может настраивать источник повторных попыток и поддерживать несколько источников повторных попыток, настраивать количество повторных попыток или тайм-аут повторных попыток, а также настраивать интервал времени ожидания для создания экземпляра повторной попытки Retryer.

Источник повторных попыток RetryerBuilder поддерживает объект Exception и настраиваемый объект утверждения, заданный retryIfException и retryIfResult, а также поддерживает несколько и совместимость.

Конфигурация времени ожидания и ограничения повторных попыток RetryerBuilder реализуется различными классами политик и может поддерживать режим без интервала и фиксированный интервал для функции времени ожидания.

Retryer — это экземпляр retryer, который выполняет логику операции через метод вызова и инкапсулирует исходную операцию повтора.

Изящные повторные попытки, общие черты и принципы

  1. Нормальная и повторная попытки элегантно разделены, а экземпляры условия подтверждения повторной попытки или экземпляры логического исключения являются средством связи между ними.
  2. Согласованный интервал повторных попыток, дифференцированная стратегия повторных попыток и установленное время ожидания повторной попытки для дальнейшего обеспечения достоверности повторной попытки и стабильности процесса повторной попытки.
  3. Оба используют шаблон проектирования команды, завершают соответствующую логическую операцию, делегируя объект повтора, и в то же время внутренне инкапсулируют логику повтора.
  4. Средства Spring-tryer и guava-tryer являются потокобезопасными повторными попытками, которые могут поддерживать правильность логики повторных попыток в параллельных бизнес-сценариях.

Применимые сценарии изящной повторной попытки

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

использованная литература

https://blog.csdn.net/paul_wei2008/article/details/53871442