28 Aralık 2020 Pazartesi

Fault Tolerance ve Resiliency İçin Retry Örüntüsü

Giriş
Bu yazı aslında Fault Tolerance ve Resiliency İçin Bazı Yazılım Çözümleri serisinin bir parçası. 

Retry-After Parametresi
REST çağrılarında sunucu Http cevabına Retry-After parametresini atayabilirHttp Cevap Parametreleri yazısına bakabilirsiniz.

Retry İşlemini Kim Yapmalı
1. Her servis kendisi Retry işlemini gerçekleştirebilir
2. Ortak bir Retry servisi olabilir. Açıklaması şöyle
... it removes the retry complexity from all microservices, and places it in a single retry microservice. The retry microservice’s job is to track and action all retries. This microservice receives an event, writing it to its own topics with both the event to retry and the timestamp to retry that event. It then pushes out these retry events once their timestamp has been reached.

Retry Verisi Nerede Saklanmalı
1. Kafka, AMQP gibi bir kuyrukta saklanabilir
2. Veri tabanında saklanabilir

Retry Detayları
Sistem içinde hata olsa bile işlem belli bir süre sonra tekrar denenir. Retry yönteminde dikkat edilecek noktalar şöyle
1. Retry sayısı
2. Retry aralığı (back off süresi)
3. Tüm denemeler başarısız olursa ne yapılacağı
4. Retry işleminin stateless veya stateful olacağı
5. Retry 'ın çağırdığı şilemin Idempotent Receiver olması. Idempotency Nedir yazısına bakabilirsiniz.

Bazen Retry alt yapısını bizim kodlamamız gerekir, eğer şanslıysak kullandığımız alt yapı bu yeteneği bütünüyle veya kısmen de sağlıyor olabilir.

Not : Bu konuda okunması gereken bir yaz burada

Retry Storms
Elimizde bir servis çağrısı dizisi olsun
A -> B -> C -> D
ve D çalışmıyor olsun. Bu durum Retry Storm sebebidir. Açıklaması şöyle
Now imagine that each service has a retry policy installed which performs up to 3 retries on failed calls (a total of 3+1 requests). Think about what happens if D goes down, and this failure is propagated all the way back up the call chain:
C calls D 4x more
B calls C 4x more
A calls B 4x more

The traffic D receives might be as much as 64 times (!!) that of normal levels. And this is happening when D is already unhealthy — likely impairing it further.

Sounds bad, but the real problem is that these traffic increases grow exponentially. If K is the number of attempts made per call at each node, then the magnitude of the increase in call volume at depth N in the call graph is K^N. This behavior is known as a retry storm, and it can compound minor outages into major cascading failures.

1. Retry Sayısı
Mantıklı bir üst sınıf vermekte fayda var. Yoksa işlem/mesaj sonsuz döngü şeklinde sürekli tekrar tekrar kaynak tüketir.

2. Retry Aralığı
Açıklaması şöyle. Sabit, rastgele veya artan aralıklar kullanılabilir.
You retry few times hoping to get a response, with a fixed, random or exponential wait period in between each attempt and eventually give up to try in the next poll cycle.
Örnek
Java kullanıyorsak elimizde şöyle bir kod olsun .
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
Şöyle yaparız.
service.execute(task);
...
service.schedule(task, 20, TimeUnit.SECONDS);//retry
Örnek
Java kullanıyorsak elimizde şöyle bir kod olsun . java.util.Timer kullanıyoruz
Timer timer = new Timer();
Şöyle yaparız.
timer.schedule(task, 20);
3. Tüm denemeler başarısız olursa ne yapılacağı
Aslında iki tane seçenek var
1. Dead Letter Queue veya benzeri bir yere bir mesaj gönderilebilir.
2. İşlem/mesaj tamamen çöpe atılır

4. Retry işleminin stateless veya stateful olacağı
- Stateless retry işlemlerinden retry mekanizması aynı thread içinde N defa işlemi dener. Thread bu N deneme süresince başka bir işle uğraşmaz.

- Stateful retry işlemlerinden retry mekanizması aynı thread içinde işlemi dener. Eğer başarısız ise, thread başka bir işe geri döner. Yani bloke olmaz. Retry zamanı gelince tekrar dener.

Örnek - Resilience4j
Şöyle yaparız
public List<CompanyDto> searchCompanyByName(String name) {
  RetryConfig retryConfig =
    RetryConfig.custom().maxAttempts(4).waitDuration(Duration.of(2, SECONDS)).build();

  RetryRegistry retryRegistry = RetryRegistry.of(retryConfig);

  Retry retryConfiguration = retryRegistry.retry("companySearchService", retryConfig);

  Supplier<List> companiesSupplier = () -> companyRepository.findAllByName(name);

  Supplier<List> retryingCompaniesSearch =
    Retry.decorateSupplier(retryConfiguration, companiesSupplier);

  List<CompanyDto> companyDtos = new ArrayList<>();
  List companies = retryingCompaniesSearch.get();

  for(Company company : companies) {
    CompanyDto companyDto = new CompanyDto(company.getName(), company.getType(),
      company.getCity(), company.getState(), company.getDescription());
      companyDtos.add(companyDto);
  }

  return companyDtos;
}
Açıklaması şöyle
While using resilience4j-retry library, you can register a custom global RetryConfig with a RetryRegistry builder. Use this registry to build a Retry.

In the above method, we first create RetryConfig. We create a RetryRegistry and add RetryConfig in this registry. Then when we create our call to fetch a list of companies. We decorate this call with retryConfiguration.
RetryConfig açıklaması şöyle
Customizations with Resilience4j-Retry
RetryConfig offers different customization:
1. maxAttempts — 3 is the default number of attempts for retries.
2. waitDuration — a fixed wait duration between each retry attempt.
3. intervalFunction — a function to modify the waiting interval after a failure.
4. retryOnResultPredicate — configures a predicate that evaluates if a result should be retried.
5. retryExceptions — Configures a list of throwable classes that are used for retrying
6. ignoreExceptions — Configures a list of throwable classes that are ignored
7. failAfterMaxRetries — A boolean to enable or disable throwing of MaxRetriesExceededException when the Retry has reached the configured maxAttempts

Retry Design Pattern vs Circuit Breaker Pattern
Açıklaması şöyle. Circuit Breaker eğer bir sistemin çalışmadığını anlarsa Retry işlemine girmeden direkt bir cevap döner. Bu yüzden Retry örüntüsüne göre biraz farklıdır.
I would like to mention, a subtle difference with "Circuit Breaker" pattern, which is actually one level up. It has an implicit Retry but also prevents further communication until the remote service is available and responds with a test call. This strategy is recommended when you expect services to be unavailable for a longer duration whereas "Retry" is recommended for transient failures (short duration or temporary failures).
Doğası Gereği Retry Olması Gereken İşlemler
Bazı sistemlerin doğasında Retry vardır. Karşı sistemden bir cevap dönmüyorsa, kullanılan alt yapı, iletişim ağı güvenilir değilse ister istemez Retry benzeri bir çözüm sistemde kullanılmaya başlanıyor.

Örnek
Bir sistemde UDP kullanıldığı için mesajlar kaybolabiliyordu. "Transmission Queue" bileşeninde her mesaj aynı birincil anahtar ile N defa tekrar edilecek şekilde bir çözüm geliştirildi. Böylece Retry örüntüsü aslında bir nevi gerçekleştirilmiş oldu.

Hiç yorum yok:

Yorum Gönder