Print
카테고리: [ Cloud Computing & MSA ]
조회수: 3751

1. 상위문서


2. Ribbon이란?

2.1. 개념

Client 측의 Load balancer이다.

일반적으로 MSA 플랫폼 외부의 호출은 API Gateway를 이용하게 되는데, 그렇다면 MSA 내부의 API Server간 호출은 어떻게 처리할 것이냐? 라는 문제가 생긴다. 이 때 선택 가능하는 것이,

2.2. 장단점

장단점이 있다. Gateway 경유 시에는 API 호출 관리를 단일화할 수 있고 각 API 서버들이 다른 API 서버의 주소를 몰라도 된다. 하지만 네트워크 부하가 가중되고 Gateway가 단일 장애 지점이 될 수 있다.

P2P 호출 시에는 Hop 감소로 네트워크 부하가 줄어들고 API 서버 간 호출 증가 시에 유리한 점이 있다. 반대로 API 호출에 대한 전체적인 통제가 어렵고 모든 API 서버가 다른 API 서버의 주소를 알고 있어야 한다. 다만 API 서버가 다른 API 서버의 주소를 알아야 한다는 점은 Eureka로 극복할 수 있다. 추가로 Ribbon과 조합을 통해 P2P 호출을 구현할 수 있게 된다.

2.3. API Gateway

Zuul + Hystrix + Ribbon + Eureka Client 조합으로 API Gateway 구성이 가능한데, Ribbon 단에서 각 서비스(예: 전시/상품/결제 등..)로 배분이 가능해진다. 


3. 구성 요소

3.1. Rule

리스트 중 리턴할 서버를 정하는 로직

3.2. Ping

백그라운드로 동작하면서 서버들이 살았는지 죽었는지 확인하는 기능

3.3. ServerList

Static 혹은 Dynamic 형태가 가능하다. DynamicServerListLoadBalancer가 사용하는 Dynamic의 경우 백그라운드 스레드가 정해진 주기에 리스트를 갱신하고 필터한다.


4. Client Load Balance의 특징

 


5. 장애 시나리오

특정 API 서버 한대에 장애가 났다고 할 때...

  1. 가장 먼저 Ribbon Client가 대응한다. Ribbon 모듈 종류나 버전에 따라 다르지만 자체 Retry 기능으로 처리하거나 아래 기술할 Spring Retry 기능을 사용한다.
  2. 일정 시간이 지나면 Eureka 서버에서 다운된 서버 목록이 사라지게 된다. 특정 서버로부터 일정 횟수 이상의 Heartbeat 유실 시 다운된 것으로 간주하기 떄문이다.
  3. 4대 중 1대가 다운되었다면 에러 비율은 약 25%일 것이다. 만약 Hystrix 설정이 에러 비율 50%라면 Circuit은 동작하지 않는다. 다만 Timeout, Isolation, Fallback 등이 동작하여 적절히 조치한다.

6. Load Balance Rule


7. 사용

Ribbon 사용을 위하여 RestTemplate이 자동으로 설정된다. 

@Configuration
public class MyConfiguration {
 
    @LoadBalanced
    @Bean
    RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
 
public class MyClass {
    @Autowired
    private RestTemplate restTemplate;
 
    public String doOtherStuff() {
        String results = restTemplate.getForObject("http://stores/stores", String.class);
        return results;
    }
}

출처 : https://cloud.spring.io/spring-cloud-static/spring-cloud.html#_spring_resttemplate_as_a_load_balancer_client


8. 테스트 (Eureka는 사용하지 않음)


9. 테스트 코드

우선 RestTemplate을 이용한 Rest Server, Rest Client를 생성하는 예제가 선행된 것을 전제로 한다.

9.1. RestServer

RestServer는 수정할 필요가 없다.

9.2. RestClient

추가된 로직과 특히 Annotation을 잘 확인해야 한다.

package io.sarc.springcloud.MyRestClient;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
 
@SpringBootApplication
@RestController
@RibbonClient(name = "my-rest-server")
public class MyRestClientApplication {
  @LoadBalanced
  @Bean
  RestTemplate restTemplate() {
    return new RestTemplate();
  }
 
  @Autowired
  RestTemplate restTemplate;
 
  public static void main(String[] args) {
    SpringApplication.run(MyRestClientApplication.class, args);
  }
 
  @RequestMapping("/hello")
  public String hello(@RequestParam(value = "name", defaultValue = "sarc.io") String name) {
    String greeting = this.restTemplate.getForObject("http://my-rest-server/greeting", String.class);
    return String.format("Hello from '%s %s'!", greeting, name);
  }
}

application.yml

spring:
  application:
    name: my-rest-client

server:
  port: 8888

my-rest-server:
  ribbon:
    listOfServers: localhost:8180, localhost:8280

RestServer의 존재를 알려주는 my-rest-server가 추가된 것을 확인할 수 있다.

이  my-rest-server는 코드 내에서 @RibbonClient와 연결되며, API 호출 시 URL 주소와도 연결된다. (http://my-rest-server/...)

9.3. 테스트

9.3.1. RestServer 기동

RestServer의 application.yml에는 server.port가 8180 포트로 설정되어 있다.

2대의 RestServer를 각기 다른 포트로 기동하기 위해 아래와 같이 실행한다.

  1. mvn -Dserver.port=8180 spring-boot:run (혹은 mvnw 사용)
  2. mvn -Dserver.port=8280 spring-boot:run (혹은 mvnw 사용)

9.3.2. RestClient 기동

RestClient는 1대만 기동하므로 기존과 동일하게 mvn spring-boot:run으로 기동하면 된다.

9.3.3. Request 생성

curl http://localhost:8888/hello를 반복적으로 호출하면 Response를 받을 수 있고, 2대의 RestServer(8180, 8280)에서 Access /greeting라는 로그가 번갈아가면서 생성되는 것을 확인할 수 있다.


10. with Spring Retry

앞서 8번 테스트에서는 8180이나 8280 중 하나의 RestServer 인스턴스를 중지하더라도 여전히 해당 인스턴스로 요청이 전달되고 RestClient 측에서는 Exception이 발생하게 된다.

2019-09-01 04:56:39.405 ERROR 82853 --- [nio-8888-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://my-rest-server/greeting": Connection refused (Connection refused); nested exception is java.net.ConnectException: Connection refused (Connection refused)] with root cause

java.net.ConnectException: Connection refused (Connection refused)
    at java.net.PlainSocketImpl.socketConnect(Native Method) ~[na:1.8.0_191]
    at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350) ~[na:1.8.0_191]

중지된 인스턴스로 요청을 보내지 않기 위해 Spring Retry를 함께 사용할 수 있다.

        <dependency>
            <groupId<org.springframework.retry</groupId>
            <artifactId<spring-retry</artifactId>
        </dependency>

동작을 좀 더 자세히 모니터링하기 위해서는 application.yml 파일에 logging.level.org.springframework.retry=DEBUG를 추가하여 디버그한다.

2019-09-01 05:07:38.560 DEBUG 82995 --- [nio-8888-exec-1] o.s.retry.support.RetryTemplate          : Retry: count=0
2019-09-01 05:07:38.571 DEBUG 82995 --- [nio-8888-exec-1] o.s.retry.support.RetryTemplate          : Checking for rethrow: count=1
2019-09-01 05:07:38.571 DEBUG 82995 --- [nio-8888-exec-1] o.s.retry.support.RetryTemplate          : Retry: count=1
2019-09-01 05:07:39.512  INFO 82995 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty  : Flipping property: my-rest-server.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647