본문 바로가기
프로그래밍/Spring Cloud

[MSA] Spring Cloud Ribbon - 개념과 실습편

by 사바라다 2020. 1. 12.

저번 시간까지는 Spring Cloud의 Hystrix에 대해서 알아보았습니다. 오늘은 Ribbon에 대해서 알아보도록 하겠습니다.

Ribbon은 Client에 탑재하는 Load Balancer입니다.

Load Balancer

우리가 일반적으로 사용하는 LoadBalancer는 서버사이드 로드밸런싱을 처리하는 L4 Switch와 같은 하드웨어 장비였습니다. 하지만 MSA에서는 이런 장비보다는 소프트웨어적으로 구현된 클라이언트사이드 로드밸런싱을 주로 이용합니다. 이들의 차이를 한번 알아보도록 하겠습니다.

server-side load balancing

위는 일반적인 L4 switch 기반의 로드 밸런서입니다. Client는 L4의 주소만 알고 있으면되며 모든 로드 밸런싱은 L4에서 처리해 주고 있습니다.

이런 하드웨어 기반의 서버 사이드 로드 밸런서의 단점은 기본적으로 별도의 스위치 장비가 필요하기 때문에 상대적으로 비용이 많이 소모되게 되며 유연성도 떨어지게 됩니다. 또한 서버 목록의 추가는 수동으로 보통 이루어집니다. 이러한 단점 때문에 클라이이언트 사이드 로드밸런서가 MSA에서는 주로 사용됩니다. 클라이언트 사이드 로드밸런서를 사용하게 되면 아래와 같이 아키텍처가 형성됩니다.

client-side load balancing

Ribbon

Spring Cloud에서는 위와 같은 클라이언트 사이드 로드밸런서로 Ribbon을 이용하고 있습니다. Ribbon에 대해서 알아보도록 하겠습니다.

Ribbon의 구성요소로는 Rule, Ping, ServerList가 있습니다. 각각은 아래와 같습니다.

  • ServerList - 로드 밸런싱 대상 서버 목록
    • configuration을 통해 static 하게 설정 가능
    • eureka 등을 기반으로 dynamic하게 설정 가능
  • Rule - 요청을 보낼 서버를 선택하는 논리
    • Round Robbin - 한 서버씩 돌아가며 전달
    • Available Filtering - 에러가 많은 서버 제외
    • weighted Response Time - 서버별 응답 시간에 따라 확률 조절
  • Ping - 서버list가 살아있는지 체크하는논리
    • static, dynamic 모두 가능

그리고 개인적으로 Ribbon의 가장 중요하다고 생각하는 기능중 하나는 Retry입니다. Retry는 말 그대로 만약 server에게 응답을 받지 못하였을 경우 동일한 서버로 재시도 하거나 다른 서버로 재시도하는 기능입니다.

ribbon retry

위의 경우 A_1에게 먼저 요청을 한 후 응답이 존재하지 않아 A_2로 재시도를 하여 성공응답을 받아낸 상황입니다.

그렇다면 이런 Ribbon에 대해서 실습을 진행해보도록 하겠습니다.

실습

의존성

Ribbon을 사용하기 위해서는 먼저 의존성을 부여해야합니다. 아래의 의존성을 부여하면 우리는 Ribbon을 사용할 수 있습니다.

implementation 'org.springframework.cloud:spring-cloud-starter-netflix-ribbon:2.1.4.RELEASE'

Configuration

# 첫 시도 실패시 같은 서버로 재시도 하는 수(첫번째 전송은 제외)
sample-client.ribbon.MaxAutoRetries=1

# 첫 시도 실패시 다음 서버로 재시도 하는 수(첫번째 전송은 제외)
sample-client.ribbon.MaxAutoRetriesNextServer=1

# Whether all operations can be retried for this client
sample-client.ribbon.OkToRetryOnAllOperations=true

# source로 부터 서버 리스트를 ribbon으로 다시 가져오는 interval time
sample-client.ribbon.ServerListRefreshInterval=2000

# HttpClient의 Connection timeout(연결과정의 Timeout 시간)
sample-client.ribbon.ConnectTimeout=3000

# HttpClient의 Read Timeout(데이터를 읽어오는 과정의 Timeout 시간)
sample-client.ribbon.ReadTimeout=3000

# 최초의 서버 리스트, 다이나믹하게 변경될 수 있다.
sample-client.ribbon.listOfServers=www.microsoft.com:80,www.yahoo.com:80,www.google.com:80

ribbon에 사용되는 설정파일입니다. 위는 properties 파일로 되어있지만 yaml 파일로도 변경하여 사용할 수 있습니다. 각각의 설명은 주석으로 달아두었으니 적절히 수정하시어 사용하시면 좋을 것 같습니다. sample-client를 생략하게 되면 전체적으로 설정이 걸리게 되며, sample-client에 서비스 이름을 기입하면 해당 서비스에만 적용되어지게 됩니다.

ribbon의 서버리스트를 환경파일에서 가져올지 eureka에서 동적으로 가져올지 설정할 수도 있습니다.

ribbon.eureka.enabled=true

enabled가 true라면 eureka를 통해 서버의 정보를 얻어오겠다는 의미입니다. 이렇게 하게되면 sample-client.ribbon.listOfServers에서 수동으로 기입했던 정보는 의미가 없어집니다.

ribbon-Client 생성

ribbon을 사용하기 위해서는 ribbon-client를 생성해야합니다. ribbon-client란 로드밸런서 그룹이라고 생각하시면 됩니다. 일반적으로 서비스 단위로 생성하면 됩니다. 예제를 보고 설명드리겠습니다.

@Configuration
@RibbonClients({
        @RibbonClient(name = "service-group-1", configuration = LoadBalancerConfiguration.class)
})
public class RibbonClientConfig {
}
service-group-1.ribbon.MaxAutoRetries=1
service-group-1.ribbon.listOfServers=localhost:8081,localhost:8081

RibbonClient는 @Configuration이 붙어있는 bean(@Controller, @Service 어디든 상관 없으나 명확하게 하기위하여 @Configuration을 추천합니다)으로 만들어지는 class에 붙여서 사용할 수 있습니다. 여기서 설정된 name은 ribbon client의 서버 리스트의 묶음의 단위로 설정되게 됩니다. 이렇게 설정된 이름은 위의 sample-client에 들어가 설정할 수 있게 되며 로드밸런싱의 단위이기도 합니다.

ribbon client가 하나 생성 되었기 때문에 우리는 해당 client에 대한 properties를 설정해 줄 수 있습니다. 위와같이 설정하면 실패시 1번의 retry, Round Robbin 방식으로 8081과 8082번 포트로 전송할 것임을 알 수 있습니다.

RestTemplate에 Ribbon 적용

클라이언트 사이드 로드밸런싱은 우리가 Http통신에 익히 사용하던 RestTemplate에 적용할 수 있습니다. 아래와 같이 @LoadBalanced를 적용해주시면 쉽게 적용되어 Ribbon의 retry, loadbalancing 등의 특징을 사용할 수 있게 됩니다.

    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }

실제 사용

이렇게 Ribbon을 사용하게 된 RestTemplate은 아래처럼 사용할 수 있습니다.

묵시적 사용

@Service
public class LogisticsServiceImpl {

    RestTemplate restTemplate;

    public LogisticsServiceImpl(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public String getLogisticsInfo() {
        return restTemplate.getForObject("http://server-group-1",String.class);
    }
}

restTemplate의 url 정보를 http://server-group-1로 썼습니다. 이것은 실제 실행이 된다면 http://localhost:8081 또는 http://localhost:8082로 치환되어 실행 될것입니다.

명시적 사용

만약 위와같이 치환되는게 마음에 안드신다고 하시면 명시적으로 ribbon에서 list를 얻어올 수 있습니다.

@Service
public class LogisticsServiceImpl {

    @Autowired
    LoadBalancerClient loadBalancerClient;

    RestTemplate restTemplate;

    public LogisticsServiceImpl(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public String getLogisticsInfo() {
        final ServiceInstance choose = loadBalancerClient.choose("server-group-1");
        return restTemplate.getForObject(String.format("http://%s:%s", choose.getHost(), choose.getPort()),String.class);
    }
}

Ribbon customizing

Ribbon의 구성요소는 Ping, ServerList, Rule의 3가지라고 했습니다. 이 3가지 요소는 커스터마이징 할 수 있습니다. 아래는 Ribbon의 구성요소를 커스터마이징하는 소스입니다.

public class LoadBalancerConfiguration {

    @Autowired
    IClientConfig ribbonClientConfig;

    @Bean
    public IPing ribbonPing(IClientConfig config) {
        return new PingUrl(false, "/ping");
    }

    @Bean
    public IRule ribbonRule(IClientConfig config) {
        return new AvailabilityFilteringRule();
    }
}

IPing은 ping관련으로 ribbon-client에 물려있는 서버가 살아있는지 판단할 때 사용합니다. ping이 정상적으로 수행되지 않으면 해당 서버로 실제 요청을 보내지 않습니다. IRule은 로드밸런싱 룰을 설정하는 부분입니다.

이렇게 설정한 것들은 @Configuration으로 주입하면 안되며 @RibbonClient(name = "service-group-1", configuration = LoadBalancerConfiguration.class) 으로 configuration에 들어가도록 적용해주어야 합니다.

마무리

오늘은 이렇게 Ribbon의 개념과 실습을 진행해보았습니다.

감사합니다.

참조

https://github.com/Netflix/ribbon/wiki/Getting-Started

https://cloud.spring.io/spring-cloud-netflix/reference/html/#spring-cloud-ribbon

댓글