Spring Cloud Hystrix jako realizacja wzorca Circuit Breaker


Spring Cloud Hystrix jako realizacja wzorca Circuit Breaker

Podejście FailFast zakłada dostarczenie informacji, że coś działa niepoprawnie w jak najkrótszym czasie. Strategia ta dobra jest dla początkujących start-upów które chcą szybko zweryfikować czy pomysł idzie w dobrym kierunku. W przypadku mikroserwisów podejście to jest bardzo istotne ponieważ pozwala na wykrycie błędu i podjęcie decyzji co zrobić kiedy coś działa niepoprawnie. Spring Cloud Hystrix to podejście które realizuje strategię FailFast. Kiedy jeden mikroserwis odpytuje inną usługę a usługa ta działa niepoprawnie to decyzja co zrobić w takim przypadku jest kluczowa. Kiedy usługa działa niepoprawnie za pomocą biblioteki Spring Cloud Hystrix (projekt zaprojektowany przez Netflixa, pioniera tematu związanego z mikroserwisami) zostaje włączony bezpiecznik (z ang. Circuit Breaker) – technicznie oznacza to zwrócenie wartości przez zdefiniowaną do tego metodę (z ang. Fallback). Po więcej informacji polecam artykuł Martina Fowlera https://martinfowler.com/bliki/CircuitBreaker.html.

Aby zasymulować opisany przypadek utworzymy dwa mikroserwisy. Pierwszy z nich odpowiada za dostarczanie szczegółowych informacji odnośnie klienta danego sklepu. Drugi z kolei mikroserwis wyświetla informacje o klientach:

Tworzymy pierwszy projekt mikroserwisu spring-cloud-hystrix-user-service – zależności do Mavena:

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-actuator</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-hystrix</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-data-rest</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
		<!-- https://mvnrepository.com/artifact/org.json/json -->
	<dependency>
		<groupId>org.json</groupId>
		<artifactId>json</artifactId>
		<version>20180813</version>
	</dependency>
</dependencies>

<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-dependencies</artifactId>
			<version>Camden.SR6</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>

Rest kontroler – na potrzeby niniejszej aplikacji kontroler tworzy dwóch przykładowych użytkowników (tylko raz – blok statyczny):

@RestController
public class ClientServiceController {

    private static List<Client> clients;

    static {
        clients = new ArrayList();

        Client firstClient = new Client("James", "Spring");
        clients.add(firstClient);

        Client secondClient = new Client("Thomas", "Dummy");
        clients.add(secondClient);

    }

    @RequestMapping(value = "/getClientDetails/{clientId}", method = RequestMethod.GET)
    public Client getClient(@PathVariable int clientId) {
        Client findClient = clients.get(clientId);
        if (findClient == null) {
            findClient = new Client("Not Found", "N/A");
        }
        return findClient;
    }
}

Klasa modelu:

public class Client {

    private String name;
    private String surname;

    public Client(String name, String surname) {
        super();
        this.name = name;
        this.surname = surname;
    }

   // setters & getters
}

Tworzymy drugi projekt mikroserwisu spring-cloud-hystrix-shop-service – zależności do Mavena:

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-actuator</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-hystrix</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-data-rest</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
	<!-- https://mvnrepository.com/artifact/org.json/json -->
	<dependency>
		<groupId>org.json</groupId>
		<artifactId>json</artifactId>
		<version>20180813</version>
	</dependency>
</dependencies>

Rest kontroler:

@RestController
public class ClientServiceController {

    @Autowired
    ClientServiceDelegate clientServiceDelegate;

    @RequestMapping(value = "/getClientDetails/{client}", method = RequestMethod.GET)
    public String getClient(@PathVariable String client) {
        return clientServiceDelegate.callClientServiceAndGetData(client);
    }
}

Serwis który odpytuje o szczegóły klienta i w razie problemów z uzyskaniem informacji wykonuje metodę typu FallBack:

@Service
public class ClientServiceDelegate {

    @Autowired
    RestTemplate restTemplate;

    @HystrixCommand(fallbackMethod = "callClientServiceAndGetData_Fallback")
    public String callClientServiceAndGetData(String client) {

        String response = restTemplate
                .exchange("http://localhost:8098/getClientDetails/{clientId}"
                        , HttpMethod.GET
                        , null
                        , new ParameterizedTypeReference<String>() {
                        }, client).getBody();

        return "NORMAL FLOW !!! - Client Details " + response + " - " + new Date();

    }

    @SuppressWarnings("unused")
    private String callClientServiceAndGetData_Fallback(String clientId) {
        return "CIRCUIT BREAKER ENABLED!!!" + clientId + new Date();
    }

    private void printJsonResponseFromJsonArray(JSONArray jsonArray) {
        for (int i = 0; i < jsonArray.length(); i++) {
            System.out.println(jsonArray.getJSONObject(i).getString("name"));
            System.out.println(jsonArray.getJSONObject(i).getString("surname"));
        }
    }

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

Klasa startowa:

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

Czas na testy!

http://localhost:9099/getClientDetails/0

zwraca:

NORMAL FLOW !!! – Client Details {“name”:”James”,”surname”:”Spring”} – Fri Jun 28 22:48:42 CEST 2019

http://localhost:9099/getClientDetails/1

zwraca:

NORMAL FLOW !!! – Client Details {“name”:”Thomas”,”surname”:”Dummy”} – Fri Jun 28 22:48:02 CEST 2019

Jeśli wyłączony zostanie mikroserwis spring-cloud-hystrix-user-service wynik będzie następujący:

http://localhost:9099/getClientDetails/0

zwraca:

CIRCUIT BREAKER ENABLED!!!0Fri Jun 28 23:11:28 CEST 2019

http://localhost:9099/getClientDetails/1

zwraca:

CIRCUIT BREAKER ENABLED!!!0Fri Jun 28 23:11:28 CEST 2019

Hystrix Dashboard to projekt który graficznie przedstawia wyniki metod adnotowanych za pomocą adnotacji  @HystrixCommand. Dodanie zależności:

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

daje możliwość uzyskania pulpitu Hystrixa pod adresem:

http://localhost:9099/hystrix

gdzie należy uzupełnić pole dla którego chcemy uzyskać monitoring:

http://localhost:9099/hystrix.stream

 


Leave a comment

Your email address will not be published.


*