Spring Cloud — Netflix Hystrix ile Circuit Breaker

Ahmet Cokgungordu
6 min readSep 9, 2019

Bu yazımda hata tolerans kütüphanesi olan Spring Cloud Netflix Hystrix’e bakacağız. Bu kütüphaneyi kullanarak, Circuit Breaker paterni ile bir sistemde belirli bir zaman dilimi içerisinde bir şeyler ters gittiğinde ve failure threshold’a ulaşıldığında bir strateji tanımlayacağız.

Hystrix, ilgili servislerdeki başarısız istekler için dinleme yapmaktadır. Böyle bir sorun varsa, devreyi “circuit open” yaparak, yani devreyi açarak çağrıyı “fallback method” olarak yazdığımız bir geri dönüş metoduna iletecektir.

Hystrix kütüphanesi başarısızlıkları bir eşik değerine kadar tolere eder ve devreyi açık bırakır. Bunun anlamı, gelecekteki hata alacak istekleri önlemek için sonraki tüm çağrıları geri dönüş metoduna yönlendirir. Bu sayede, ilgili servisin başarısız dönme durumundan çıkması için bir zaman tamponu oluşturur.

REST Producer

Circuit Breaker patternini gösteren bir senaryo oluşturmak için öncelikle bir servise ihtiyacımız var. Adını “REST Producer” olarak adlandıracağız. Çünkü, Hystrix-enabled bir “REST Consumer” için veri sağlayacağız.

Öncelikle spring-boot-starter-web kütüphanesini kullanarak yeni bir Maven projesi yaratalım.

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>1.4.0.RELEASE</version>
</dependency>

Projemizi özellikle basit tutuyoruz. Sadece String döndüren GET yöntemine sahip bir Controller’dan oluşturuyoruz.

Öncelikle Interface’imizi oluşturalım.

public interface GreetingController {   @GetMapping("/greeting/{username}")
String greeting(@PathVariable("username") String username);
}

Uygulamamız da şu şekilde olacak.

@RestController
public class GreetingControllerImpl implements GreetingController {

@Override
public String greeting(@PathVariable("username")String username){
return String.format("Hello %s!\n", username);
}
}

Son olarak, ana uygulama sınıfını yazıyoruz.

@SpringBootApplication
public class RestProducerApplication {
public static void main(String[] args) {
SpringApplication.run(RestProducerApplication.class, args);
}
}

Uygulamayı tamamlamak için dinleyeceğimiz bir uygulama portu yapılandıracağız. Varsayılan 8080 numaralı bağlantı noktasını kullanmayacağız. Çünkü bu bağlantı noktasını bir sonraki uygulama için kullanacağız. Ayrıca, “REST Producer” daha sonra tanımlayacağımız müşteri servisinden arama yapabilmek için bir uygulama adı tanımlıyoruz. Aşağıdaki içeriğe sahip bir application.properties oluşturalım.

server.port=9090
spring.application.name=rest-producer

Artık “REST Producer” test edilebilecek durumda. Curl ile testimizi yapıyoruz ve gelen cevabı görüyoruz.

$> curl http://localhost:9090/greeting/Egabyte
Hello Egabyte!

İlk servisimizi yaratıp çalışır halde bırakıyoruz ve ikinci servisimizi yazmaya geçiyoruz.

Hystrix ile REST Consumer

RestTemplate ve Hystrix kullanarak REST servisini yazıyoruz. Basit olması için, “REST Consumer” olarak adlandıracağız.

Öncelikle spring-cloud-starter-hystrix, spring-boot-starter-web ve spring-boot-starter-thymeleaf kütüphaneleri ile yeni bir Maven projesi yaratıyoruz.

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>1.4.0.RELEASE</version>
</dependency>

Circuit Breaker’ın çalışması için, Hystrix @Component veya @Service anotasyonlu classları tarayarak @HystixCommand anotasyonlu metotları bulur. Önce bir Service sınıfı oluşturup Controller sınıfından çağıracağız. Bu, bir fallback metot ile @HystrixCommand içeren @Service sınıfımız olacaktır. Bu fallback metodu, orijinal metot ile aynı geri dönüşe sahip olmalıdır.

@Service
public class GreetingService {
@HystrixCommand(fallbackMethod = "defaultGreeting")
public String getGreeting(String username) {
return new RestTemplate()
.getForObject("http://localhost:9090/greeting/{username}",
String.class, username);
}

private String defaultGreeting(String username) {
return "Hello User!";
}
}

Aşağıdaki RestConsumerApplication ana uygulama sınıfımız olacaktır. @EnableCircuitBreaker anotasyonu, Circuit Breaker için classpathi tarar.

@SpringBootApplication
@EnableCircuitBreaker
public class RestConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(RestConsumerApplication.class, args);
}
}

GreetingService sınıfımızı kullanarak Controller sınıfımızı ayarlayacağız.

@Controller
public class GreetingController {

@Autowired
private GreetingService greetingService;

@GetMapping("/get-greeting/{username}")
public String getGreeting(Model model, @PathVariable("username") String username) {
return greetingService.getGreeting(username);
}
}

Uygulamanın portunu 8080 olarak ayarlıyoruz.

server.port=8080

Bir Hystrix Circuit Breaker aksiyonunu görmek için, “REST Consumer” servisimizi başlatıyoruz ve tarayıcımızdan http://localhost:8080/get-greeting/Ahmet olarak çağırıyoruz. Normal şartlar altında aşağıdaki gibi bir cevap dönecektir.

Hello Ahmet!

“REST Producer” servisimizi failure durumuna geçirmek için durduracağız. Tarayıcıyı yeniledikten sonra, @Service class’ındaki tanımladığımız fallback metodunu döndürülen bir mesaj görmüş olacağız. Bu bize failure durumunda “REST Consumer” servisinin default tanımlanmış değeri Hystrix Circuit Breaker aracılığıyla döndürdüğünü gösterecektir.

Hello User!

Hystrix ve Feign ile REST Consumer

Şimdi Spring RestTemplate yerine, Spring Netflix Feign kullanarak projeyi değiştireceğiz. Bunun avantajı ise, daha sonra servisi bulmak için Spring Netflix Eureka’yı kullanıp Feign Client Interface’imizi kolayca kullanabileceğiz.

Yeni projeye başlamak için “REST Consumer” projesinin bir kopyasını alacağız. Daha sonra varolan “REST Producer” servisimizi ve spring-cloud-starter-feign bağımlılıklarını ekliyoruz.

<dependency>
<groupId>com.egabyte.cloud</groupId>
<artifactId>rest-producer</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.1.5.RELEASE</version>
</dependency>

Bir Feign Client’ı extend etmek için GreetingController sınıfımızı kullanabiliriz. Hystrix fallback sınıfımızı, @Component ile import edilmiş statik bir iç sınıf olarak kullanacağız.

Alternatif olarak, bu fallback sınıfının bir örneğini döndüren @Bean anotasyonlu bir metot tanımlayabiliriz.

@FeignClient’in “name” özelliği zorunludur. Bu özellik, Eureka Client veya URL üzerinden Service Discovery yoluyla bulmak için kullanılır.

Spring Netflix Eureka’yı, Service Discovery ile kullanmak hakkında daha fazla bilgi için bu makaleye göz atabilirsiniz.

@FeignClient(
name = "rest-producer"
url = "http://localhost:9090",
fallback = GreetingClient.GreetingClientFallback.class
)
public interface GreetingClient extends GreetingController {

@Component
public static class GreetingClientFallback implements GreetingController {

@Override
public String greeting(@PathVariable("username")String username){
return "Hello User!";
}
}
}

RestConsumerFeignApplication servisi içinde, Feign entegrasyonunu etkinleştirmek için ana uygulama sınıfı içinde @EnableFeignClients anotasyonunu kullanıyoruz.

@SpringBootApplication
@EnableCircuitBreaker
@EnableFeignClients
public class RestConsumerFeignApplication {

public static void main(String[] args) {
SpringApplication.run(RestConsumerFeignApplication.class, args);
}
}

GreetingController sınıfımızda, daha önce GreetingService clasını @Autowired ile bağlamıştık. Şimdi bunu Feign Client ile aşağıdaki gibi güncelliyoruz.

@Controller
public class GreetingController {

@Autowired
private GreetingClient greetingClient;

@GetMapping(“/get-greeting/{username}”)
public String getGreeting(Model model, @PathVariable(“username”) String username) {
return greetingClient.getGreeting(username);
}
}

Bu örneğimizi bir öncekinden ayırmak için, servisimizin portunu değiştiriyoruz.

server.port=8082

Son olarak, Feign-enabled “REST Consumer” servisimizi önceki gibi test edeceğiz. Beklediğimiz sonuç önceki testimizdeki sonuçla aynı olacaktır.

Hystrix ile Cache Fallback

Şimdi, Spring Cloud projemize Hstrix’i ekleyeceğiz. Bu cloud projesinde, veri tabanı ile bağlantısı olan ve kitap derecelendirmelerini hesaplayan bir derecelendirme servisimiz olacak.

Veri tabanımızdan talep edilen bir veri olduğunu düşünelim. Bu verinin yanıt süresinin gecikmesini veya zaman zaman kullanılamayacağını da varsayalım. Bu senaryoyu, bir hata olduğunda Hystrix Circuit-Breaker ile cache üzerinden geri dönmesiyle sağlayacağız.

Kurulum ve Yapılandırma

spring-cloud-starter-hystrix bağımlılığını kitap derecelendirme modülümüze ekleyelim.

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>

Derecelendirmeler veri tabanına eklendiğinde, güncellendiğinde veya silindiğinde, bu işlemin bir örneğini Redis Cache üzerine kopyalayacağız.

@HystrixCommand ile Hystrix komutunda veri tabanı işlemlerini, Redis’ten bir fallback metodu ile yapılandırabilmek için RatingService’i güncelleyelim:

@HystrixCommand(
commandKey = “ratingsByIdFromDB”,
fallbackMethod = “findCachedRatingById”,
ignoreExceptions = { RatingNotFoundException.class })
public Rating findRatingById(Long ratingId) {
return Optional.ofNullable( ratingRepository.findOne(ratingId)).orElseThrow(() ->
new RatingNotFoundException(“Rating not found. ID:“ + ratingId));
}

public Rating findCachedRatingById(Long ratingId) {
return cacheRepository.findCachedRatingById(ratingId);
}

Fallback metodunun kullanılabilmesi için aynı sınıfta bulunması gerektiğini unutmayın. Bu şekilde kullanırsak, findRatingById metodu başarısız olduğunda veya cevap süresi belirli bir eşikten fazla ise, Hystrix ile findCachedRatingById metodunu geri dönecektir.

Hystrix yetenekleri AOP Advice gibi eklenmektedir. Bu yüzden Spring, Transactional Advice gibi başka bir Advice’a sahipse, Advice’ların sırasını ayarlamak zorundayız. Aşağıda Spring’in Transactional AOP Advice’ını, Hystrix AOP Advice’dan daha düşük önceliğe sahip olacak şekilde ayarlayacağız.

@EnableHystrix
@EnableTransactionManagement(
order=Ordered.LOWEST_PRECEDENCE,
mode=AdviceMode.ASPECTJ)
public class RatingServiceApplication {
@Bean
@Primary
@Order(value=Ordered.HIGHEST_PRECEDENCE)
public HystrixCommandAspect hystrixAspect() {
return new HystrixCommandAspect();
}
}

Burada, Spring’in Transactional AOP Advice’ını, Hystrix AOP Advice’ından daha düşük önceliğe ayarladık.

Hystrix Fallback Testi

Şimdi Hystrix için devreyi yapılandırdığımıza göre, H2 veri tabanını indirerek test edebiliriz. Ancak bunu yapmak için, H2 veri tabanımızı gömülü çalıştırmak yerine dışarıdan çalıştırmamız gerekmektedir.

H2 kütüphanesini(h2–1.4.193.jar) belirli bir dizine kopyalayalım ve aşağıdaki gibi sunucuyu başlatalım;

>java -cp h2–1.4.193.jar org.h2.tools.Server -tcp
TCP server running at tcp://192.168.99.1:9092 (sadece local bağlantılar)

Modülümüzün veri tabanı kaynağının URL’ini H2 sunucusuna bağlamak için rating-service.properties içerisini aşağıdaki gibi güncelliyoruz;

spring.datasource.url = jdbc:h2:tcp://localhost/~/ratings

Servislerimizin hepsini tekrar başlatabilir ve dışarıdan erişilebilir H2 sunucumuzu kullanarak her kitabın puanlarını test edebiliriz.

H2 veri tabanına erişilemediğinde, Hystrix’in her kitabın derecelendirmesini okumak için otomatik olarak Redis’e döndüğünü görebiliriz.

Scope Kullanma

Normalde @HystrixCommand anotasyon metodu, bir thread pool context içerisinde çalıştırılır. Ancak bazen bir local scope içerisinde de çalıştırılması gerekebilir. Örneğin @SessionScope veya @RequestScope gibi. Bu anotasyon içinde ek parametreler verilerek yapılabilir.

@HystrixCommand(fallbackMethod = “getSomeDefault”, commandProperties = {
@HystrixProperty(name = “execution.isolation.strategy”, value = “SEMAPHORE”)
})

Hystrix Dashboard

Hystrix’in güzel bir özelliği ise, isteğe bağlı olarak gösterge paneli ile sistem durumunu izleme yeteneğidir.

Bunu etkinleştirmek için, “Rest Consumer” projesinde pom.xml içerisine spring-cloud-starter-hystrix-dashboard ve spring-boot-starter-actuator kütüphanelerini ekliyoruz.

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
<version>1.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>1.4.0.RELEASE</version>
</dependency>

Daha sonra @Configuration anotasyonunun kullanıldığı sınıfa @EnableHystrixDashboard ekleyerek aktifleştiriyoruz. spring-boot-starter-actuator kütüphanesi ile de uygulamamızdaki gerekli metrikleri otomatik aktifleştirmiş oluyoruz.

Sonuç olarak arayüzde aşağıdaki gibi bir dashboard ekranı göreceğiz.

Bir “hystrix.stream” izlemek iyi bir şeydir. Ancak birden fazla uygulamanız varsa ve Hystrix Dashboard ile izlemeniz gerekirse, bu durum uygun olmayacaktır. Bunun için Spring Cloud, birden fazla uygulamadan gelen Hystrix verilerini bir Hystrix Dashboard’da sunmak üzere bu verileri toplamaya yarayan Turbine Stream adlı bir araç sunmaktadır. Turbine Stream konusuna daha sonraki yazılarımda yer vereceğim.

Yukarıda Spring RestTemplate veya Spring Netflix Feign ile birlikte Spring Netflix Hystrix kullanarak Circuit Breaker modelinin nasıl uygulanacağını gördük. Bu sayede “statik” veya “default” veriler ile geri dönüş hizmeti sağlayan uygulamalar geliştirebileceğiz ve bu verilerin kullanımını izleyebileceğiz.

--

--