Ribbon이란?

  • 클라이언트 사이드 load balancer
  • 로드밸런싱 룰을 커스터마이징 가능

@LoadBalanced

RestTemplate, WebClient가 Bean으로 선언된 것에 한해서 @LoadBalanced 어노테이션을 붙이면 Intercept를 통해서 LoadBalancer를 끼어넣게된다. 따라서 알아서 로드 밸런싱을 하게된다.

이 때, RestTemplate, WebClient에 주는 주소는 IP, Port를 지우고 호출할 서버 군의 이름을 적으면 군에 해당하는 목록을 알아서 LoadBalancer에서 호출한다.

 

서버쪽 설정

서버에 들어올 경우 어디로 들어왔는지 콘솔에 로그를 남기도록 간단하게 구현했다.

pom.xml

    <dependencies>
        <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>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
package com.example.circuitbreakerbookstore;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.List;
import java.util.Random;

@RestController
@SpringBootApplication
public class CircuitBreakerBookstoreApplication {
    private static Logger logger = LoggerFactory.getLogger(CircuitBreakerBookstoreApplication.class);

    @RequestMapping(value = "/recommended")
    public String readingList(){
        logger.info("Access /recommended");
        Random random = new Random();
        List<String> bookList = Arrays.asList("Spring in ACtion (Manning)", "Cloud Native Java", "Learning Spring Boot");
        int randomNum = random.nextInt(bookList.size());
        return bookList.get(randomNum);
    }

    @RequestMapping(value = "/")
    public String home(){
        logger.info("Access home");
        return "Welcome home";
    }
    public static void main(String[] args){
        SpringApplication.run(CircuitBreakerBookstoreApplication.class, args);
    }
}

 

클라이언트 쪽 설정

pom.xml

 <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</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>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR3</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
@EnableCircuitBreaker
@RestController
@SpringBootApplication
//다른 자바 설정파일 처럼 @Configuration을 사용하면 안된다. @ComponentScan으로 해당 설정이 로딩될 경우 에러갈 발생한다.
@RibbonClient(name="say-book", configuration = SayBookConfiguration.class) //RibbonClient Annotaion은 Controller에 넣어야한다.
public class CircuitBreakerReadingApplication {

    @Autowired
    private BookService bookService;

    @Bean
    @LoadBalanced
    public WebClient.Builder WebClient(){ //공홈에서는 RestTemplate를 썻지만 그냥 Webflux를 써봤다.
        return WebClient.builder();
    }

    @RequestMapping("/to-read")
    public String toRead(@RequestParam(value="name", defaultValue = "Aroon") String name)
    {

        return String.format("%s, %s",name,bookService.readingList());
    }

    public static void main(String[] args) {
        SpringApplication.run(CircuitBreakerReadingApplication.class, args);
    }
}
public class SayBookConfiguration {
    @Autowired
    IClientConfig ribbonClientConfig;

    @Bean // 첫번째 Parameter가 True일 경우 https false일 경우 http. AppendString은 Ping 보낼 URL
    public IPing ribbonPing(IClientConfig config){
        return new PingUrl(false, "/");
    }

    @Bean //LoadBalancing Rule을 설정하는 것
    public IRule ribbonRule(IClientConfig config){
        return new AvailabilityFilteringRule();
    }
}
@Service
public class BookService {
    @Autowired
    WebClient.Builder webClientBuilder;
    /*
        Hystrix Command 설정
        commandKey 별로 Circuit Breaker 생성
        fallbackMethod : 실패시 해당 메소드 실행
     */
    @HystrixCommand(commandKey = "EX", fallbackMethod = "reliable")
    public String readingList(){
        return webClientBuilder.baseUrl("http://say-book").build().get() //get 방식
                .uri("recommended") //endpoint
                .retrieve() // body를 가지고 온다는 뜻
                .bodyToMono(String.class)
                .block()
                ;
    }

    public String reliable(){
        return "Success Fallback";
    }
}

Eureka와 연동하지 않을 것이므로 false를 하고 Server 군의 목록을 지정해준다.

ServerListRefreshInterval마다 IPing이 실행되며 서버 상태를 확인한다.

server.port= 8888
say-book.ribbon.eureka.enabled=false #유레카를 사용하지 않겠다. 
say-book.ribbon.listOfServers=localhost:8090,localhost:9092 #서버군 목록
say-book.ribbon.ServerListRefreshInterval= 15000 #서버리스트 refresh 간격 ms

 

실행방법 :

 

1. 서버를 아래 mvn 명령어도 두개를 실행한다.

SERVER_PORT=9092 mvn spring-boot:run

SERVER_PORT=8090 mvn spring-boot:run

 

*만약 Maven이 설치되어 있지 않은 경우 homebrew insall maven 명령어로 설치해준다.

 

2. Client를 실행 후 접속해서 확인해본다.

 

 

 

참고 

https://gunju-ko.github.io/spring-cloud/netflixoss/2018/12/14/Ribbon.html

 

Ribbon

Ribbon Netflix가 만든 Software Load Balancer를 내장한 REST Library이다. Ribbon은 Client 사이드 LoadBalancer로 Ribbon을 사용하면 L4등과 같이 하드웨어에서 이루어지던 Load Balance를 애플리케이션 영역에서 할 수 있다. Ribbon을 사용하면 애플리케이션 영역에서 서버목록을 가지고 번갈아가면서 호출하게 된다. Spring Cloud에서 Ribbon 클라이언트를 직접 사용하는 경우는

gunju-ko.github.io

https://spring.io/guides/gs/client-side-load-balancing/

 

Spring

Level up your Java code and explore what Spring can do for you.

spring.io

https://piotrminkowski.com/2018/05/04/reactive-microservices-with-spring-webflux-and-spring-cloud/

 

Reactive Microservices with Spring WebFlux and Spring Cloud

I have already described Spring reactive support about one year ago in the article Reactive microservices with Spring 5. At that time project Spring WebFlux has been under active development, and n…

piotrminkowski.com

 

'Spring' 카테고리의 다른 글

SpringBoot Hystrix 적용하기  (0) 2020.03.01
Hystrix 란  (0) 2020.03.01

간단한 Hystrix Circuit Breaker를 실습하기 위해선 두개의 프로젝트를 생성해야한다.

서버 설정

1. pom.xml

    <dependencies>
        <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>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

2. application.properties에서 server port 설정 (서버, 클라이언트를 한 컴퓨터에서 실행하기 때문에 port를 설정해줘야함. )

server.port=8090

3. Controller 생성

package com.example.circuitbreakerbookstore;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@SpringBootApplication
public class CircuitBreakerBookstoreApplication {
    @RequestMapping(value = "/recommended")
    public String readingList(){
        return "Spring in ACtion (Manning), Cloud Native Java, Learning Spring Boot";
    }
    public static void main(String[] args){
        SpringApplication.run(CircuitBreakerBookstoreApplication.class, args);
    }
}

 

클라이언트 설정

1. pom.xml

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.M3</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

2. application.properties

server.port=8080

3.Controller

package com.example.circuitbreakerreading;


import com.example.circuitbreakerreading.service.BookService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.web.client.RestTemplate;


@EnableCircuitBreaker
@RestController
@SpringBootApplication
public class CircuitBreakerReadingApplication {

    @Autowired
    private BookService bookService;

    @Bean
    public RestTemplate rest(RestTemplateBuilder builder) {
        return builder.build();
    }

    @RequestMapping("/to-read")
    public String toRead() {
        return bookService.readingList();
    }

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

4. Service

package com.example.circuitbreakerreading.service;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;

@Service
public class BookService {
    private final WebClient webClient;

    public BookService(){
        this.webClient = WebClient.builder().baseUrl("http://localhost:8090").build();
    }

    /*
        Hystrix Command 설정
        commandKey 별로 Circuit Breaker 생성
        fallbackMethod : 실패시 해당 메소드 실행
     */
    @HystrixCommand(commandKey = "EX", fallbackMethod = "reliable")
    public String readingList(){
        return webClient.get() //get 방식
                .uri("recommended") //endpoint
                .retrieve() // body를 가지고 온다는 뜻
                .bodyToMono(String.class)
                .block()
                ;
    }

    public String reliable(){
        return "Success Fallback";
    }
}

 

참고 :

https://spring.io/guides/gs/circuit-breaker/

'Spring' 카테고리의 다른 글

Springboot ribbon 적용하기  (1) 2020.03.03
Hystrix 란  (0) 2020.03.01

Hystrix

  • Hystirx는 Netflix에서 만든 마이크로 서비스 아키텍처에서 장애 전파방지 & Resilience 를 위한 라이브러리
  • Hystirx의 주요 4가지 기능
    • Circuit Breaker
    • Fallback
    • Thread Isolation
    • Timeout

Hystrix Command를 호출할 때 벌어지는 일

  1. 실행 메소드를 Intercept 하여 대신 실행한다.
    • Thread Isolation
      • 제가 제 메소드를 호출했는데도 디버그를 찍어보면 쓰레드가 변경되는 걸 볼 수 있다 -> 누군가 가로채서 실행한다
  2. 메소드의 실행 결과 혹은 실패 발생 여부를 기록(인스턴스 단위)하고 통계를 낸다. 통계에 따라 Circuit Open 여부를 결졍한다.
    • Circuit Breaker
  3. 실패한 경우 사용자가 제공한 메소드를 대신 실행한다.
    • Fallback
  4. 특정시간 동안 메소드가 종료되지 않은 경우 Exception을 발생시킨다.
    • Timeout(서비스 레이어에 걸기 적당하다)

Circuit Open(호출차단)

  • 메소드를 호출하지만 디버그로 찍어보면 메소드 바디안에 들어오지 않는다.
  • 즉, 누군가 가로채서 바로 Exception으로 보내버리는 것
  • A가  B, C, D를 호출하는데 B가 장애가 발생할 경우 B를 차단해 버리므로 장애가 전파되지 않는 차단기 역할

Circuit Close(호출허용)

  • 설정된 시간 이후 단 한개의 요청에 대해 호출을 허용하며 이 호출이 성공하면 Circuit Close

Circuit Breaker의 단위

  • Circuit Breaker 인스턴스는 함꼐 통계를 내고 함께 차단한다. 즉, Circuit Breaker 는 CommandKey 단위로 생성된다.
  • 만약, Command Key를 주지 않으면 Annotation이 박혀있는 메소드 별로 Circuit Breaker가 생성되어 모든 메소드가 Circuit Breaker 를 가지게 된다.

* 너무 작은 단위로 Circuit Breaker를 생성할 경우 메소드가 n초에 m번 이상 호출이 안될 가능성이 높다. 따라서 50, 60 퍼 확률로 장애가 발생하더라도 동작을 안할 수 있으니 주의.

 

Hystrix - Fallback(@HystrixCommand( commandKey = "Ex1", fallbackMethod="exampleFallBack")

Fallback으로 지정된 메소드는 다음의 경우에 원본 대신 실행된다. (정상동작하지 않을 경우 실행)

  • Circuit Open
  • Any Exception(HystrixBadRequestException 제외)
  • Semaphore / ThreadPool Rejection
  • Timeout

* HystrixBadRequestException

해당 에러 발생시 FallBack을 실행하지 않으며 Circuit Open을 위한 통계에도 집계되지 않는다.

로직을 호출한 Caller가 잘못한 것일 때 이 Exception으로 Mapping 해야한다.(ex. Parameter 잘못 넘김, 잘못된 시점에 호출 - 자바에서는 illegalArgument, illegalstateexception)

 

Hystrix - Timeout

Circuit Breaker 단위로 Timeout을 설정할 수 있다.

 

Hystrix - Isolation

특정 시스템에 지연이 일어나 전체가 망가지는 것을 막기 위해서 뒷단 시스템에 적정 용량을 제한함으로써 과도한 Request가 들어가려고 할 때 자동으로 Rejection을 발생시킨다.

  • Semaphore
    • Circuit Breaker마다 Semaphore가 붙어있다.
      • 연동 시스템이 3개가 있을 때 Circuit Breaker를 3개 지정하고 Semaphore Isolation이라고 지정했다면, 뒤 연동 시스템에 최대 동시 호출 갯수를 지정할 수 있는 것.
    • Caller Thread와 실제 메소드가 실행되는 스레드가 같다. (Command를 호출한 Caller Thread에서 메소드 실행)
  • Thread(default)
    • Thread Pool이기 때문에 Semaphore와 동일하게 최대 요청 개수가 제한되어있다.
    • Semaphore와는 다르게 Caller 스레드와 실제 메소드를 실행할 Thread가 분리 되어서 특정 시간이 지나게 되면 Caller 스레드(사용자에게 응답을 줘야하는 스레드)는 바로 Return 된다.

*Semaphore isolation의 경우 Timeout이 제 시간에 발생하지 못할 수 있다.

이유 : 다른 스레드를 임의로 중단시킬 방법은 없다. 유일한 방법은 Thread.Interrupt를 호출하는 것인데 그것도 실제 실행하는 로직에  Thread.Interrupt가 인식하도록 구현되어 있어야 한다.

따라서, Hystrix에서도 Timeout이 지났다고 복잡한 loop를 돌고 있는데 중단하라고 명령을 내려서 중단시킬 방법은 없다.

 

 

 

 

'Spring' 카테고리의 다른 글

Springboot ribbon 적용하기  (1) 2020.03.03
SpringBoot Hystrix 적용하기  (0) 2020.03.01

+ Recent posts