Ribbon

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

Spring Cloud에서 Ribbon 클라이언트를 직접 사용하는 경우는 많지 않다. 대부분은 옵션이나 설정을 통해 Ribbon 클라이언트를 사용하게 된다. 왜냐하면 Spring Cloud의 HTTP 통신이 필요한 요소에 이미 내장되어 있기 때문이다. 예를 들어 Zuul, RestTemplate, Spring Cloud Feign에는 Ribbon이 내장되어 있어서 아주 간단한 옵션이나 설정을 통해 Ribbon이 사용되도록 할 수 있다.

RestTemplate는 HTTP 요청에 대해 pre, post 인터셉터를 사용할 수 있다. @Bean 메소드에 @LoadBalanced 애노테이션을 추가하면, 스프링 클라우드는 RestTemplate에 Ribbon을 인식할 수 있는 로드밸런싱 인터셉터를 적용한다.

Ribbon의 기능

LoadBalancing - IRule

서비스의 인스턴스가 여러대 실행중인 경우 Ribbon을 통해 로드밸런싱을 할 수 있다. Ribbon은 rule 기반의 로드밸런싱 기능을 제공한다. Ribbon은 라운드 로빈 방식, 응답 가중치 방식 등 기본적인 IRule 인터페이스 구현체를 제공한다. 만약 rule를 커스텀마이징하고 싶다면 IRule 인터페이스를 구현해서 사용하면 된다.

public interface IRule{
    public Server choose(Object key);
    
    public void setLoadBalancer(ILoadBalancer lb);
    
    public ILoadBalancer getLoadBalancer();    
}

Cloud enabled

Ribbon은 zone에 기반해서 로드밸런싱할 수 있다. 예를 들어, 같은 zone에 위치한 서버에 로드밸런싱을 하도록 설정할 수 있다. 또한 zone 전체의 상태를 체크하고 상태가 좋지 않은 zone으로 로드밸런싱 되지 않도록 할 수 있다.

Integration with service discovery - ServerList

Service discovery로부터 서버 목록을 동적으로 가져올 수 있다. 유레카 서버로부터 서버 목록을 가져오는 DiscoveryEnabledNIWSServerList 구현체가 제공된다.

public interface ServerList<T extends Server> {

    public List<T> getInitialListOfServers();
    
    /**
     * Return updated list of servers. This is called say every 30 secs
     * (configurable) by the Loadbalancer's Ping cycle
     * 
     */
    public List<T> getUpdatedListOfServers();   

}

Built-in failure resiliency

Ribbon은 IPing을 통해 각 서버가 살아있는지 검사한다. 그리고 더이상 살아있지 않는 서버는 로드밸런싱 목록에서 제거한다. circuit-breaker 패턴을 기반으로 추가로 서버를 필터링할 수도 있다.

public interface IPing {
    public boolean isAlive(Server server);
}

Clients integrated with load balancers

Ribbon은 클라이언트와 로드밸런스를 통합하는 인터페이스 및 추상 클래스를 제공한다.

Configuration based client factory

Ribbon은 Archaius를 사용해서 로드밸런서와 클라이언트를 설정 기반으로 생성하는 기능을 제공한다. 로드밸런서와 클라이언트는 ClientFactory를 통해 생성된다.

Getting Started

시작하는 가장 쉬운 방법은 속성 기반의 Factory를 사용하여 로드 밸런서가 있는 클라이언트 객체를 생성하는 것이다. sample application은 ribbon의 가장 기본적인 사용방법을 보여준다.

설정 파일

# Max number of retries on the same server (excluding the first try)
sample-client.ribbon.MaxAutoRetries=1

# Max number of next servers to retry (excluding the first server)
sample-client.ribbon.MaxAutoRetriesNextServer=1

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

# Interval to refresh the server list from the source
sample-client.ribbon.ServerListRefreshInterval=2000

# Connect timeout used by Apache HttpClient
sample-client.ribbon.ConnectTimeout=3000

# Read timeout used by Apache HttpClient
sample-client.ribbon.ReadTimeout=3000

# Initial list of servers, can be changed via Archaius dynamic property at runtime
sample-client.ribbon.listOfServers=www.microsoft.com:80,www.yahoo.com:80,www.google.com:80

각 엔트리 포맷은 아래와 같다.

<clientName>.<nameSpace>.<propertyName>=<value>

clientName은 Factory에서 클라이언트를 생성하기위해 사용된다. nameSpace는 설정 가능하며 기본값은 “ribbon”이다. 공통 속성 이름은 CommonClientConfigKey에서 확인할 수 있다. 속성은 시스템 프로퍼티로 정의할 수 있다.

스프링 클라우드를 사용하는 경우는 application.yml에 설정하면 된다.

Programmers Guide

Client Configuration Options

클라이언트와 로드밸런서를 설정하는 가장 쉬운 방법은 아래와 같은 형식의 프로퍼티를 추가하는 것이다.

<clientName>.<nameSpace>.<propertyName>=<value>

프로퍼티는 클래스패스에 위치한 파일에 작성해도 되며, 시스템 프로퍼티로 추가해도 된다. 특정 파일에 프로퍼티를 추가한 경우 ConfigurationManager.loadPropertiesFromResources() 메소드를 통해 파일에 있는 프로퍼티을 읽어오도록 해야한다.

namespace의 디폴트는 ribbon이다.

만약 clientName에 특정 프로퍼티가 없으면 ClientFactory는 디폴트값을 사용한다. 디폴트값이 궁금하다면 DefaultClientConfigImpl를 참고하길 바란다.

프로퍼티에 clientName이 없으면, 모든 클라이언트에 적용되는 프로퍼티가 된다. 예를 들어 아래와 같은 프로퍼티를 추가하면 ReadTimeout의 디폴트값은 1000이 된다.

ribbon:
  ReadTimeout: 1000

Integration with Eureka

Eureka는 Ribbon과 결합되어 동작한다. Eureka와 연동해서 Ribbon을 사용하면 서버 리스트를 동적으로 변경할 수 있다. 다음과 같은 방법으로 Eureka와 Ribbon을 함께 사용할 수 있다.

  • ribbon-eureka 의존성을 추가한다.
  • 로드밸런서로 DynamicServerListLoadBalancer 클래스를 사용하도록 설정한다.
  • ServerList를 DiscoveryEnabledNIWSServerList로 한다.
  • 서버 업데이트 주기를 설정한다. (기본값은 30초이다)
  • DeploymentContextBasedVipAddresse를 설정한다. DeploymentContextBasedVipAddresses는 Target 서버의 serverId를 넣으면 된다.
myclient:
  ribbon:
    NIWSServerListClassName: com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList
    ServerListRefreshInterval: 6000
    DeploymentContextBasedVipAddresses: movieservice

Load Balancer

Ribbon은 소프트웨어 로드밸런서를 제공하여 서버 클러스터와 통신한다. 로드밸런서는 다음과 같은 기본 기능을 제공한다.

  • 클라이언트에게 개별 서버의 IP 주소 또는 공용 DNS 이름을 제공한다.
  • 특정 로직에 따라 로드 밸런싱한다.

아래와 같은 고급 기능을 사용하기도 한다. 단 아래와 같은 고급 기능을 사용하기 위해선 Ribbon에서 제공하고 있는 클라이언트를 사용해야한다.

  • 네트워크 지연을 줄이기 위해 같은 zone에 위치한 서버를 우선적으로 호출한다.
  • 각 서버의 통계를 내서, 지연이 심하거나 자주 실패하는 서버는 피한다.
  • zone에 대한 통계를 내서 장애가 발생한 zone에 대한 호출은 피한다.

Ribbon의 대부분의 동작이 Programmabble하다. 따라서 L4와 같은 하드웨어 로드밸런서보다 많은 장점을 가지게 된다.

Components of load balancer

  • Rule : 주어진 서버 목록에서 어떤 서버를 선택할 것인가?
  • Ping : 각 서버가 살아있는지 검사
  • ServerList : 대상 서버 목록을 제공
    • 서버 리스트는 동적일수도 있으며 정적일수도 있다. 만약 동적이라면 백그라운드 스레드는 일정한 간격으로 서버 목록을 업데이트한다.

이러한 구성 요소들은 코드를 통해 설정이 하거나 또는 프로퍼티를 통해 설정할 수 있다. 아래는 관련 속성 이름이다. prefix로 <clientName>.<namespace>을 붙여야한다.

NFLoadBalancerClassName
NFLoadBalancerRuleClassName
NFLoadBalancerPingClassName
NIWSServerListClassName
NIWSServerListFilterClassName

Common rules

  • RoundRobinRule : 라운드로빈을 통해 서버를 선택한다. 이 Rule은 대부분의 경우 디폴트로 사용된다.
  • AvailabilityFilteringRule : 이 Rule은 circuit breaker 패턴처럼 circuit이 오픈된 서버는 건너뛴다. 기본적으로 RestClient가 3번 연속 커넥션을 생성하지 못하면 circuit이 오픈된다. circuit이 오픈되면 30초후에 닫힌다. 하지만 또 커넥션을 생성하지 못하면, 다시 circuit이 오픈되고, circuit 오픈이 유지되는 시간은 기하급수적으로 늘어난다. 아래와 같이 설정하면 된다.
# successive connection failures threshold to put the server in circuit tripped state, default 3
niws.loadbalancer.<clientName>.connectionFailureCountThreshold
# Maximal period that an instance can remain in "unusable" state regardless of the exponential increase, default 30
niws.loadbalancer.<clientName>.circuitTripMaxTimeoutSeconds
# threshold of concurrent connections count to skip the server, default is Integer.MAX_INT
<clientName>.<clientConfigNameSpace>.ActiveConnectionsLimit
  • WeightedResponseTimeRule : 이 규칙의 경우, 응답시간에 따라 가중치를 둔다. 응답시간이 길어질수록 가중치는 낮아진다. Rule은 가중치에 따라 서버를 선택한다. 높은 가중치를 가질수록 선택될 확률이 더 높다. 이 Rule를 사용하려면 아래와 같이 설정하면 된다.
<clientName>.<clientConfigNameSpace>.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.WeightedResponseTimeRule

ServerList

  • Adhoc static server list : 동적인 서버 리스트를 코딩을 통해 설정할 수 있다. BaseLoadBalancer.setServersList() 메소드를 통해 서버 리스트를 전달해주며 된다.
  • ConfigurationBasedList : 로드밸런서를 위한 디폴트 ServerList 구현체이다. 서버 목록을 프로퍼티를 통해 설정할 수 있다. 아래는 간단한 예이다. 만약 프로퍼티가 동적으로 변경된다면, 로드밸런서에서 서버 목록도 업데이트된다.
sample-client:
  ribbon:
    listOfServers: www.microsoft.com:80,www.yahoo.com:80,www.google.com:80
  • DiscoveryEnabledNIWServerList : 이 구현은 유레카 클라이언트로부터 서버 목록을 가져온다.
myClient:
  ribbon:
    NIWSServerListClassName: com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList 

ServerListFilter

ServerListFilter는 DynamicServerListLoadBalancer에 의해 사용되며, 대상 서버들 중에서 호출할 대상을 필터링하기 위해 사용된다. Ribbon에서는 아래와 같은 구현체를 제공한다.

  • ZoneAffinityServerListFilter : 클라이언트와 같은 zone에 위치하고 있지 않은 서버들은 호출 대상에서 제외시킨다. (단 클라이언트와 같은 zone에 위치한 서버가 존재하지 않는 경우는 제외시키지 않는다) 이 필터를 사용하려면 아래와 같은 프로퍼티를 추가하면 된다.
myclient:
  ribbon:
    EnableZoneAffiniy: true
  • ServerListSubsetFilter : 이 필터를 사용하면, ServerList가 반환한 전체 서버중 고정된 개수의 서버로만 호출한다. 예를 들어 아래와 같이 설정하는 경우 전체 서버 목록 중에서 5개의 서버만 사용하게 된다.
myClient:
  ribbon:
    NIWSServerListClassName: com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList
    DeploymentContextBasedVipAddresses: myservice
    NIWSServerListFilterClassName: com.netflix.loadbalancer.ServerListSubsetFilter
    ServerListSubsetFilter:
      size: 5

ServerListUpdater

DynamicServerListLoadBalancer가 서버 리스트를 동적으로 업데이트할 때 사용하는 전략 클래스이다.

  • PollingServerListUpdater : ServerListRefreshInterval를 주기로 서버 리스트를 업데이트한다.
  • EurekaNotificationServerListUpdater : Eureka가 제공하는 이벤트 리스너를 사용해서 서버 리스트를 업데이트한다. 유레카 클라이언트 스레드로부터 CacheRefreshedEvent를 받으면 ServerList를 업데이트한다. ServerList를 업데이트하는 작업은 별도의 스레드풀에서 진행된다.

주의할 점은 Spring Cloud에서는 유레카와 Ribbon을 함께 사용할 때 PollingServerListUpdater를 사용한다는 점이다.

Spring Cloud Netflix Ribbon

Ribbon은 클라이언트 사이드 로드밸런서이다. Ribbon의 기본 컨셉은 이름이 있는 클라이언트이다. 각각의 Ribbon은 이름이 있는데, 여기서 이름은 하나의 서버군을 뜻한다. 만약에 특정 서버군으로 요청을 보내고 싶다면, 해당 Ribbon 클라이언트를 통해 요청을 보내면 된다. 그러면 Ribbon 클라이언트는 서버 목록에서 특정 규칙으로 하나의 서버를 선택하고, 해당 서버로 요청을 보낸다.

Customizing the Ribbon Client

Ribbon 클라이언트는 프로퍼티를 통해 설정할 수 있다. 프로퍼티는 <clientName>.ribbon.* 과 같은 형식이다. 프로퍼티는 application.yml 혹은 application.properties 파일에 추가하면 된다. Ribbon의 다양한 설정이 궁금하다면 CommonClientConfigKey 클래스를 참고하길 바란다.

혹은 @RibbonClient 애노테이션을 사용해서 특정 Ribbon 클라이언트를 커스터마이징할 수도 있다. 자세한 내용은 공식문서를 참고하길 바란다. (개인적으로는 application.yml을 통해 커스터마이징하는게 더 간단해보인다)

Spring Cloud Netflix가 디폴트로 등록하는 빈들의 타입이다.

  • IClientConfig : DefaultClientConfigImpl
  • IRule : ZoneAvoidanceRule
  • IPing : DummyPing
  • ServerList<Server> : ConfigurationBasedServerList
  • ServerListFileter<Server> : ZonePreferenceServerListFilter
  • ILoadBalancer : ZoneAwareLoadBalancer
  • ServerListUpdater : PollingServerListUpdater

Customizing the Default for All Ribbon Clients

@RibbonClients 애노테이션을 통해 모든 Ribbon 클라이언트에 적용되는 기본 설정을 제공할 수 있다. 아래는 간단한 예제이다.

@RibbonClients(defaultConfiguration = DefaultRibbonConfig.class)
public class RibbonClientDefaultConfigurationTestsConfig {

	public static class BazServiceList extends ConfigurationBasedServerList {
		public BazServiceList(IClientConfig config) {
			super.initWithNiwsConfig(config);
		}
	}
}

@Configuration
class DefaultRibbonConfig {

	@Bean
	public IRule ribbonRule() {
		return new BestAvailableRule();
	}

	@Bean
	public IPing ribbonPing() {
		return new PingUrl();
	}

	@Bean
	public ServerList<Server> ribbonServerList(IClientConfig config) {
		return new RibbonClientDefaultConfigurationTestsConfig.BazServiceList(config);
	}

	@Bean
	public ServerListSubsetFilter serverListFilter() {
		ServerListSubsetFilter filter = new ServerListSubsetFilter();
		return filter;
	}

}

Customizing the Ribbon Client by Setting Properties

Spring Cloud Netflix는 Ribbon 클라이언트를 프로퍼티를 통해 커스터마이징 할 수 있도록 해준다.

  • <clientName>.ribbon.NFLoadBalancerClassNam : ILoadBalancer 구현체
  • <clientName>.ribbon.NFLoadBalancerRuleClassName : IRule 구현체
  • <clientName>.ribbon.NFLoadBalancerPingClassName : IPing 구현체
  • <clientName>.ribbon.NIWSServerListClassName : ServerList 구현체
  • <clientName>.ribbon.NIWSServerListFilterClassName : ServerListFilter 구현체

프로퍼티를 통해 설정된 클래스는 @RibbonClient (configuration = MyRibbonConfig.class)를 사용해서 정의된 Bean과 스프링 클라우드가 기본적으로 제공하는 빈들보다 더 높은 우선순위를 가진다. 또한 프로퍼티를 통해 설정된 클래슨는 autoscan 되지 않도록 주의를 해야한다.

Using Ribbon with Eureka

만약에 Eureka에 대한 의존성이 있는 경우 Spring Cloud는 다음의 Ribbon Bean을 대체한다.

  • ServerList<Server>
    • 기본 : ConfigurableBasedServerList
    • 변경 : DomainExtractingServerList (내부적으로는 DiscoveryEnabledNIWSServerList를 사용해서 유레카로부터 서버 목록을 가져온다)
  • IPing
    • 기본 : DummyPing
    • 변경 : NIWSDiscoveryPing (유레카에 여전히 등록되어 있는지 주기적으로 확인해본다)

DomainExtractingServerList는 로드밸런서에게 메타데이터를 사용할 수 있도록 해준다. 유레카로부터 가져온 서버 정보에는 zone 정보가 포함되어 있다. (eureka.instance.metadataMap.zone 프로퍼티가 인스턴스의 zone 정보이다) 만약 서버 정보에 zone 정보가 포함되어 있지 않고, approximateZoneFromHostname 프로퍼티가 true라면, 서버 호스트 이름에서 도메인 이름을 zone으로 사용한다.

zone 정보를 사용할 수 있다면, zone 정보는 ServerListFilter에서 사용될 수 있다. ServerListFilter의 기본 구현체는 ZonePreferenceServerListFilter이므로 로드밸런서는 클라이언트와 같은 zone에 위치한 서버를 우선적으로 사용한다.

참고로 클라이언트의 zone도 eureka.instance.metadataMap.zone 프로퍼티로 결정된다.

만약에 유레카로부터 서버 목록을 가져오고 싶지 않다면 아래와 같은 설정을 추가하면 된다.

ribbon:
  eureka:
    enabled: false

Using the Ribbon API Directly

LoadBalancerClient를 직접 사용할수도 있다. 아래는 간단한 예제이다.

public class MyClass {
    @Autowired
    private LoadBalancerClient loadBalancer;

    public void doStuff() {
        ServiceInstance instance = loadBalancer.choose("stores");
        URI storesUri = URI.create(String.format("http://%s:%s", instance.getHost(), instance.getPort()));
        // ... do something with the URI
    }
}

Caching of Ribbon Configuration

애플리케이션 컨텍스트는 Ribbon 클라이언트을 lazy하게 로드한다. (해당 클라이언트로 첫 요청이 들어왔을때) lazy 로딩 대신에 eagerly 로드를 사용할 수도 있다. eagerly 로드를 할 Ribbon 클라이언트의 이름을 아래와 같이 명시하면 된다.

ribbon:
  eager-load:
    enabled: true
    clients: client1, client2, client3

출처

  • Cloud Native Java

  • Ribbon
  • [spring-cloud-netflix](