본문 바로가기
IT/API Gateway

Spring cloud Netflix

by 티티알 2021. 5. 13.

https://cloud.spring.io/spring-cloud-static/spring-cloud-netflix/2.1.2.RELEASE/single/spring-cloud-netflix.html

 

Spring Cloud Netflix

Ribbon is a client-side load balancer that gives you a lot of control over the behavior of HTTP and TCP clients. Feign already uses Ribbon, so, if you use @FeignClient, this section also applies. A central concept in Ribbon is that of the named client. Eac

cloud.spring.io

[써킷브레이커]

https://netflixtechblog.com/fault-tolerance-in-a-high-volume-distributed-system-91ab4faae74a

 

Fault Tolerance in a High Volume, Distributed System

How our API and other systems isolate failure, shed load and remain resilient to failures

netflixtechblog.com

 

https://blog.naver.com/pollra32/221589005337

 

Spring Cloud(Netflix OSS) - Hystrix

아래의 포스팅은 다음의 강의를 보고 까먹지 않게 기록한 것입니다. https://youtu.be/iHHuYGdG_Yk ...

blog.naver.com

[Hystrix]

Hystrix

Netflix OSS 에 포함된 도구중 하나로서 AWS 안의 여러 컴포넌트와 자동화 도구를 사용하며 파악한 패턴과 해결법을 블로그, 오픈소스로 공개한 것들 중 하나입니다.

어떨때 사용?

MSA 환경 에서는 A 서버에서 B 서버에 요청을 날렸을때 네트워크 문제 혹은 서버 자체의 에러 등 여러가지 문제때문에 서버에 대한 Request 를 100% 받는다는 보장을 할 수 없게됩니다.

이 경우에 사용할 수 있는 기능이 바로 Hystrix 입니다.

기능 간단 설명

한 서버가 특정 요청을 받았는데 주어진 시간내에 요청을 처리하지 못 할 경우 Hystrix 는 들어온 요청의 갯수, 요청의 성공여부, 요청의 실패 여부기록하여 통계를 냅니다.

내부적으로 Hystrix 은 인터셉터를 만들어서 사용자의 요청을 계산합니다.

10초(기본 설정값)동안 20개(기본 설정값)의 요청이 들어왔을 때 50%(기본 설정값)이상의 호출에서 에러가 날 경우 Circuit Open 을 진행하게됩니다. 이 값은 수정될 수 있으며 아래에서 수정하는 방법이 나와있습니다.

Circuit Open?

Circuit Open 이란, 주어진 시간동안 호출이 제한되며 즉시 에러를 반환하는 기능을 말합니다. 장점은, 시스템에 대한 연동을 조기에 차단(Fail Fast) 시킴으로서 시스템을 보호하게됩니다.

Circuit Open 된 경우 Fallback 메소드를 통해 에러를 반환합니다.

사용방법 jdk 1.8 기준의 코드입니다.

Hystrix 의존성 추가

compile('org.springframework.cloud:spring-cloud-starter-netflix-hystrix')

Hystrix 의 Circuit Breaker 를 사용할 앱의 @SrpingBootApplication 아래에 @EnableCircuitBreaker 애노테이션 추가

@SpringBootApplication @EnableCircuitBreaker public class DisplayApplication { @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(DisplayApplication.class); } }

Hystrix 이 통계를 낼 서비스에게 다음의 애노테이션을 추가.

@HystrixCommand(fallbackMethod = "getProductInfoFallback") public String getProductInfo(String productId) { return restTemplate.getForObject(URL +productId, String.class); } public String getProductInfoFallback(String productId, Throwable throwable){ System.out.println("throwable: "+ throwable); return "[This Product is sold out]"; }

설정 변경

hystrix: #hytrix 설정의 command: #command 의 default: #default (모든설정)을 execution: isolation: thread: # thread 로 분리해서 요청을 보내는데 timeoutInMilliseconds: 3000 # 타임아웃 값을 3000(3)로 지정 circuitBreaker: # 서킷 브레이커의 설정 requestVolumeThreshold: 1 # 서킷 브레이커가 상태를 계산하는 최소 요청 횟수. default : 20 errorThresholdPercentage: 100 # 오류율이 이 이상이 된다면 circuit open. default : 50

예제

먼저 서버 A와 서버 B 가 있어야 합니다.

A 서버에서 받은 데이터를 기준으로 B 서버가 데이터를 정렬해서 화면에 뿌리는 예제입니다.

다음의 문제가 발생할 수 있습니다.

1. A 서버에서 Exception 발생. 그로인해 B 서버에서 요청처리가 원활하지못함.

2. A 서버에서 요청처리시간이 오래걸림. 그로인해 B서버에서 요청을 처리하지 못하고 대기함.

 

[Ribbon]

Netflix OSS 에서 클라이언트 사이드 로드벨런싱을 가능하게 해주는 소프트웨어

데이터를 가져오는 서버에서 다음의 디펜던시와 코드, 설정을 입력해주면 Ribbon 설정이 완료 된다.

사용방법 java 1.8 버전 기준 코드

디펜던시 추가

compile('org.springframework.cloud:spring-cloud-starter-netflix-ribbon')

컨트롤러의 RestTemplate 에 @LoadBalanced 추가

@SpringBootApplication @EnableCircuitBreaker public class DisplayApplication { @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(DisplayApplication.class); } }

RestTemplate 은 다른 서버의 REST API 에 요청을 보내거나 받을 수 있다.

다른 서버에서 데이터를 가져와야 할 때 요청하는 주소를 코드에 직접 기입하지 않고 application.yaml 에 이관함으로서 코드의 품질을 높임

application.yml ( .yaml 과 .yml 은 같은동작을 함 )에 다음의 설정을 추가. 추가된 주소들중 하나로 요청을 날린다.

product: ribbon: listOfServers: localhost:8082

코드 내부에서 사용하던 http://localhost:8082/ 주소를 모두 http://product/ 로 변경

// private final String url = "http://localhost:8082/products/"; private final String url = "http://product/products/";

Ribbon 는 서버 실행 시 인터셉터를 하나 추가하여 @LoadBalanced 애노테이션을 붙인 객체에서 사용하는 http://product 의 product 를 위의 application.yaml 에서 설정한 값인 localhost:8082 로 바꿔서 요청을 시도합니다.

만약 application.yaml 의 listOfServers 에 기입해놓은 서버에서 요청이 수행되지 않았을 경우 Ribbon Retry 를 이용해 지정된 횟수만큼 재시도 할 수 있습니다.

아래의 디펜던시를 추가합니다.

complie('org.springframework.retry:spring-retry:1.2.2.RELEASE')

그리고 위의 application.yaml 의 설정을 추가합니다.

product: ribbon: listOfServers: localhost:8082, localhost:7777 MaxAutoRetries: 0 MaxAutoRetriesNextServer: 1 # 첫번째로 기입한 서버의 요청이 # 네트워크 혹은 서버 자체의 문제로 실패했을 경우 # listOfServers 에 적혀있는 서버 목록을 순차적으로 돌아가며 # 시도하게 되는데 이 때 요청을 바꿔서 실행 할 횟수

MaxAutoRetriesNextServer

첫번째로 기입한 서버의 요청이 네트워크 혹은 서버 자체의 문제로 실패했을 경우 listOfServers 에 적혀있는 서버 목록을 순차적으로 돌아가며 요청을 시도하게 되는데 이 때 요청 URI 을 바꿔서 실행 할 횟수

 

 

[Eureka]

Eureka

Ribbon 에서는 서버 목록을 직접 yml 에 적는 과정이 필요했다.

하지만 서버의 목록을 직접 적게 되면 MSA 에서 IP주소를 관리하기 힘들어지고 동적으로 사용할 수 없기때문에 이를 해결하고싶던 넷플릭스는 Eureka 라는 프로젝트를 만들게된다.

클라우드 네이티브 한 환경을 구성하기위해 사용되며 다른말로 '클라우드의 전화번호부' 이다.

Eureka Server

RibbonEureka Server 에 저장되어있는 Client 서버 정보를 가지고 통신을 진행한다.

Eureka Client

서버 시작 시 Eureka Server (Registry) 에 자동으로 자신의 상태를 등록한다.

eureka.client.register-with-eureka: true (default)

주기적 HeartBeat 으로 Eureka Server 에 자신이 살아있음을 알린다.

eureka.instance.lease-renewal-interval-in-seconds: 30(default)

서버 종료 시 Eureka Server 에 자신의 상태를 변경(DOWN) 혹은 자신의 목록 삭제

Eureka 상에 등록된 이름은 'spring.application.name'


Eureka Server 만들기

Eureka Server

디펜던시 추가

서버로 사용될 Spring 프로젝트에 다음의 디펜던시 추가

compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-server')

어노테이션 추가

메인 메소드가 있는 클래스에 다음 어노테이션 추가

@EnableEurekaServer

두가지만 해주면 Eureka 의 셋팅이 끝납니다(놀라움..)

에플리케이션을 실행시키고 http://localhost:8761로 접속하면 유레카 대시보드가 보이게됩니다.


Eureka Client

Eureka Server 에 주기적으로 신호를 보내며 자신의 IP 를 등록하고자 하는 애플리케이션에 다음의 셋팅을 진행하여 Eureka Client 로 등록합니다.

디펜던시 셋팅

compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')

어노테이션 추가

메인 메소드가 있는 클래스에 다음의 어노테이션 추가

@EnableEurekaClient

설정 추가

application.yml 에 다음의 설정 추가

OS에서 제공하는 hostname 대신 자신의 ip address 를 사용하여 Eureka Server 에 등록

eureka: instance: prefer-ip-address: true

리본 설정 변경

리본의 application.yml 에서 listOfServers 로 명시해놓은 서버의 목록을 제거한다.

제거했을 경우 RibbonEureka 를 통해서 서버의 리스트를 가져온다.

product: ribbon: # listOfServers: localhost:8082, localhost:7777 MaxAutoRetries: 0 MaxAutoRetriesNextServer: 1

Eureka Client 에서 Eureka Server 주소 명시 설정 제거

만약, 서버의 주소를 명시하고싶다면 application.yml 에 다음의 설정을 추가한다.

eureka: client: service-url: defaultZone: http://127.0.0.1:8761/eureka # default address


+

Eureka 서버 에러

위 글을 따라서 Eureka 서버를 셋팅하다보면 다음과 같은 문제가 발생할 수 있습니다.

java.lang.TypeNotPresentException: Type javax.xml.bind.JAXBContext not present org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat

다음의 종속성을 입력해주면 해결할 수 있습니다.

compile('javax.xml.bind:jaxb-api:2.3.0') compile('javax.activation:activation:1.1') compile('org.glassfish.jaxb:jaxb-runtime:2.3.0')


+

같은 서버 포트 다르게 열기 (intelliJ)

포트를 다르게 할 서버를 선택 후 -> 왼쪽 위의 Copy Configureation 선택

이름을 바꾸고 -> Environment 클릭 -> VM options 에 설정 입력 ->

-Dserver.port=8083

Apply 후 OK 를 누르면 창이 닫히며 아래처럼 목록에 자신이 추가한 설정의 애플리케이션이 떠있는것을 볼 수 있습니다.

선택 후 오른쪽의 Run 버튼으로 실행

Eureka Server 에서 결과 확인

정상적으로 localhost:product:8083 이 올라온것을 확인할 수 있습니다.

 

[Feign]

 

관심사의 분리를 위해 사용한다.

서비스의 관심사는 다른 리소스, 외부 서비스 호출과 리턴값이며 관심사가 아닌것은 어떤 URL 을 사용하고 어떻게 파싱할것인가 이다. 테스트가 어렵다는 단점도 있음

Spring Cloud 에서 Open-Feign 기반으로 Wrapping 한것이 Spring Cloud Feign


사용법

디펜던시 추가

compile('org.springframework.cloud:spring-cloud-starter-openfeign')

메인 메소드가 있는 클래스에 어노테이션 추가

@EnableFeignClients

인터페이스 선언

nameRibbon 에서 지정해놓은 이름으로 쓰면 된다.

@FeignClient(name="dp", url="http://localhost:8080/") public interfaceProductResource{ @RequestMapping(value="/query/{itemId}", method=RequestMethod.GET) String getItemDetail(@PathVariable(value="itemId")String itemId); }

사용

@Autowired ProductResource productResource;


Feign Client

Feign Client 는 내부적으로 Ribbon 을 사용하며 Ribbon + Eureka + Hystrix 와 통합하여 사용할 수 있다.

만약 위 코드처럼 셋팅을 했다면 위에서 생성한 인터페이스에서 @FeignClient(name="dp", url="http://localhost:8080/") 이부분에서 URL 을 삭제하면 Ribbon 에 셋팅되어있는 주소를 따라가서 데이터를 가져오며, Eureka 와 함께 사용중일경우 Feign -> Ribbon -> Eureka 의 순서대로 주소데이터를 가져와서 사용하게 되는 식이다.

하지만 URL 을 쓰면 Ribbon, Eureka, Hystrix 을 사용하지 않음을 뜻한다.

+

Feign + Hystrix ( 추천하지않는 셋팅 : 에러의 위치를 알 수 없음. 하지만 방법은 명시해둠.)

application.yml 에 다음의 설정을 추가

feign: hystrix: enabled: true

위의 설정을 추가하면 Feign 의 메소드 하나 하나가 Hystrix Command 로서 호출됨

Fallback 작성

먼저 Fallback 을 처리하기 위한 클래스 작성

@Component public class ProductRemoteFallback implements FeignProductRemoteService { @Override public String getProductInfo(String productId) { return "default value"; } }

Fallback 처리를 위한 인터페이스의 어노테이션에 Fallback 클래스를 명시

@FeignClient(name = "product", fallback = ProductRemoteFallback.class)


+

FallbackFactory 를 사용한 Hystrix 사용

application.yml 에 다음의 설정을 추가

feign: hystrix: enabled: true

위의 설정을 추가하면 Feign 의 메소드 하나 하나가 Hystrix Command 로서 호출됨

FallbackFactory 작성

Fallback 을 처리하기 위한 FallbackFactory 를 생성

@Component public class ProductRemoteFallbackFactory implements FallbackFactory<FeignProductRemoteService> { @Override public FeignProductRemoteService create(Throwable cause) { System.out.println("t = "+cause); return productId -> "[ this product is sold out ]"; } }

FeignProductRemoteService@FeignClient 어노테이션의 fallbackfallbackFactory 로 변경하고 새로 만든 FallbackFactory 를 입력

@FeignClient(name = "product", fallbackFactory = ProductRemoteFallbackFactory.class) public interface FeignProductRemoteService { @GetMapping("/products/{productId}") String getProductInfo(@PathVariable("productId") String productId); }

설정 완료

테스트를 위해 데이터를 가져오는 Controllersleep2000으로 걸고 위의 fallbackFactory 가 명시된 프로젝트의 설정에서 아래와 같이 timeout1000을 걸고 테스트

hystrix: command: FeignProductRemoteService#getProductInfo(String): execution: isolation: thread: timeoutInMilliseconds: 1000 # default 1000ms circuitBreaker: requestVolumeThreadhold: 1 errorThresholdPercentage: 50

FeignProductRemoteService#getProductInfo(String): 이부분 오타나면 안됩니다!

 

API Gateway

클라이언트와 백엔드 서버 사이의 출입문

라우팅 ( 라우팅, 필터링, API 변환, 클라이언트 어댑터 API, 서비스 프록시)

횡단 관심사

* 보안, 인증(authentication), 인가(authorization)

* 일정량 이상의 요청 제한(rate limiting)

* 계측(metering)

Netflix API Gateway 인 Zuul 은 Hystrix, Ribbon, Eureka 가 포함되어있다.

1. Zuul 의 모든 API 요청은 HystrixCommand 로 구성되어 전달된다.

- 각 API 경로(서버군) 별로 Circuit Breaker 생성

- 하나의 서버군이 장애를 일으켜도 다른 서버군의 서비스에는 영향이 없다.

- CircuitBreaker / ThreadPool 의 다양한 속성을 통해 서비스 별 속성에 맞는설정가능

2. API를 전달할 서버의 목록을 갖고 Ribbon 을 통해 LoadBalancing 을 수행한다.

- 주어진 서버 목록들을 Round-Robin 으로 호출

- Coding을 통해 Load Balancing 방식 Customize 가능

3. Eureka Client 를 사용하여 주어진 URL 의 호출을 전달할 '서버 리스트'를 찾는다.

- Zuul 에는 Eureka Client 가 내장

- 각 URL에 Mapping 된 서비스 명을 찾아서 Eureka Server를 통해 목록을 조회한다.

- 조회된 서버 목록을 'Ribbon' 클라이언트에게 전달한다.

4. Eureka + Ribbon 에 의해서 결정된 Server 주소로 HTTP 요청

- Apache Http Client 가 기본 사용

- OKHttp Client 사용 가능

5. 선택된 첫번째 서버로의 호출이 실패할 경우 Ribbon에 의해서 자동으로 Retry 수행

- Retry 수행 조건

* Http Client 에서 Exception 발생 (IOException)

* 설정된 HTTP 응답 코드 반환 (ex 503)


사용법

디펜던시 추가

compile('org.springframework.cloud:spring-cloud-starter-netflix-zuul') compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client') compile('org.springframework.retry:spring-retry:1.2.2.RELEASE')

Main Class 어노테이션 추가

@EnableZuulProxy @EnableDiscoveryClient // EurekaClient 대신 사용

application.yml 설정

spring: application: name: zuul server: port: 8765 zuul: routes: product: path: /products/** serviceId: product stripPrefix: false display: path: /displays/** serviceId: display stripPrefix: false ribbon-isolation-strategy: thread thread-pool: use-separate-thread-pools: true thread-pool-key-prefix: zuul- eureka: instance: non-secure-port: ${server.port} prefer-ip-address: true client: serviceUrl: defaultZone: http://localhost:8761/eureka/

스레드풀 설정 (application.yml)

spring-cloud-zuul 의 기본 Isolation 은 SEMAPHORE 입니다. (Netflix Zuul 은 threadpool)

# 스레드풀 설정하는 방법 hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 1000 product: execution: isolation: thread: timeoutInMilliseconds: 10000 threadpool: zuul-product: coreSize: 30 maximumSize: 100 allowMaximumSizeToDivergeFromCoreSize: true zuul-display: coreSize: 30 maximumSize: 100 allowMaximumSizeToDivergeFromCoreSize: true

리본 타임아웃 지정 설정(application.yml)

# 리본에 타임아웃 지정 product: ribbon: MaxAutoRetriesNextServer: 1 ReadTimeout: 3000 ConnectTimeout: 1000 MaxTotalConnections: 300 MaxConnectionsPerHost: 100 display: ribbon: MaxAutoRetriesNextServer: 1 ReadTimeout: 3000 ConnectTimeout: 1000 MaxTotalConnections: 300 MaxConnectionsPerHost: 100

결과

zuul 서버에서 product 요청

zuul 서버에서 display 요청

[출처] Spring Cloud(Netflix OSS) - Zuul|작성자 푸르잼

'IT > API Gateway' 카테고리의 다른 글

coredns  (0) 2022.01.19
An Example of Load Balancing with Zuul and Eureka  (0) 2021.06.03