https://www.baeldung.com/ant-maven-gradle

'기타' 카테고리의 다른 글

nginx configuration  (0) 2020.01.07
DB 순위 비교 사이트  (0) 2019.07.31
Thread 분석  (0) 2018.11.19
Oracle JDK 유료화의 논쟁, OpenJDK와의 차이  (0) 2018.11.03
DDD(Domain Driven Design)  (0) 2018.10.14


Java Heap


- Java Heap의 메모리 해제는 오로지 Garbage Collection에 의해 수행됨을 JVM 스펙에 제시되어 있다.

- 제시된 스펙을 각 Vendor(Oracle-Hotspot, IBM ..) 들은 최대한 따르고 있다.

- 하지만 이에 대한 Gabage Collection, Heap 영역의 구현은 각 Vendor 별로 다르다.



Oracle Hotspot JVM의 Heap


- 그 중 가장 대중적으로 알려진 Oracle HopSpot JVM의 구조는 다음과 같다.





1) Young Generation : Eden 영역과 Survivor영역으로 구성

 

- Eden 영역은 Object(객체)가 최초로 Heap에 할당되는 장소이다. 만일 Eden 영역이 가득 찼다면, Object의 참조 여부를 파악하고 Live Object는 Suvrvior 영역으로 넘긴다. 그리고 참조가 사라진 Garbage Object이면 남겨 놓는다. 그리고 모든 Live Object가 Survivor 영역으로 넘어간다면 Eden 영역을 모두 청소한다. 


- Survivor 영역은 Survivor0과 Survivor1로 구성되며 Eden 영역에 살아 남은 Object들이 잠시 머무르는 곳이며 Live Object들은 하나의 Survivor 영역만 사용하게 되며 이러한 전반적인 과정을 Minor GC라고 한다.




2) Old Generation


- Young Generation은 새로 Heap에 할당된 Object가 들어오는 것이 아닌, Survivor 영역에서 살아남아 오랫동안 참조 되었고 앞으로도 사용될 확률이 높은 Object들을 저장하는 영역이다. 이러한 Promotion 과정 중 Old Generation의 메모리가 충분하지 않으면 해당 영역에서 GC가 발생하는데 이를 Major GC라고 한다.(Tenured 영역에서 발행한 GC)



3) Perm 


- Perm 영역은 보통 Class Meta 정보나 Method의 메타 정보, static 변수와 상수 정보들이 저장되는 공간으로 흔히 메타데이터 저장 영역이라고 한다. 이 영역은 JAVA8 부터 Native Memory 영역으로 이동하였다.( 기존의 Perm영역에 존재하는 static object는 Heap 영역으로 옮겨졌다.)






JAVA7 까지의 Heap 영역과 Java8의 Heap 영역

이미지 :  http://equj65.net/tech/java8hotspot/






- 가장 큰 차이점은 Perm 영역이 Heap 영역에서 사라졌다는 점이다. Native Memory 영역은 일반적으로 OS Level에서 관리되며 Permanent 영역에 저장 되었던 Class나 Method의 메타 정보들이 Metaspace 영역으로 변경됨에 따라 Heap 영역의 확보의 Max 값을 크게 의식하지 않아도 된다. 


- Perm 영역 메모리 크기 옵션  -XX:PermSize / -XX:PermMaxSize, Metaspace 영역 메모리 크기 옵션 -XX:Metaspace / -XX:MaxMetaspaceSize


- Perm영역과 Metaspace 영역의 기본 값은 시스템 별로 크게 다를 수 있으므로 튜닝 시 초기치와 최대 치를 확인해야 하며, Mac OS 기준 확인 방법은 다음과 같다.



Java7

$ j./java -XX:+PrintFlagsFinal -version -Server | grep "PermSize"






Java8

$ java -XX:+PrintFlagsFinal -version -Server | grep "MetaspaceSize"





MaxMetaspaceSize는 18446.....byte (약 16Exabyte)의 큰 값을 가지고 프로세서가 취급할 수 있는 메모리의 상한치이다. Metaspace는 Native 메모리로 다루기 때문에 프로세스가 이용할 수 있는 메모리 자원을 최대한 활용 할 수 있다. 그러나 이 영역 또한 별도의 옵션을 통해 제한이 가능한데 독자적인 ClassLoader를 구현한 채 메모리 누수를 의심할 경우 

" -XX:MaxMetaspaceSize" 옵션을 활용하여 제한이 가능하다.


하지만 Metaspace는 필요에 따라 자동 증가하며 일반적으로 크게 주의를 갖고 설정할 필요는 없어 보인다.


'JVM Optimization &Tuning' 카테고리의 다른 글

Minor GC , Major GC, Full GC  (0) 2019.12.26
GC 튜닝이 불필요한 상황  (0) 2019.12.26
JVM(Java Virtual Machine)에 대한 이해  (2) 2018.08.12

Hystrix


- Hystrix는 Netflix에서 공개한 대부분의 OSS에서 범용적으로 사용되는 오픈소스 라이브러리이다.



Hystrix는 다음과 같은 특징을 따른다.


- 분산환경을 위한 Latencey and Fault Tolerance 시스템

- 복잡한 분산 시스템에서 cascading failure를 멈춤

- 실패에 대해 빠르고 급격한 회복

-  gracefully degrade와 fallback

- 거의 실시간으로 모니터링, 경고, 작동 제어가 가능하다.



Cascade Failure - 서비스간 장애 전파


대부분의 마이크로 서비스 아키텍쳐로 설계된 시스템에서 서비스 컴포넌트는 무수히 많이 존재하며, 서비스와 서비스 혹은 서비스와 게이트웨이 등 컴포넌트 별 호출은 일반적으로 REST-end-point를 가지기 때문에 Http 통신을 통해 서비스를 호출한다. 


그런데 만일 어떤 서비스 컴포넌트나 Database, 아니면 시스템이 다운 됐을 경우 의존적으로 연결된 각 서비스는 Cascade Failure(계단식 오류, 연속적 오류)를 맞이하게 된다.







출처: https://subscription.packtpub.com/book/application_development/9781788624398/8/ch08lvl1sec60/when-the-services-fail-hello-hystrix




그림을 보면 쉽게 이해 할 수 있는데 장애가 발생한 Y서비스에, A서비스와 연결된 end-point를 comsumer가 호출할 경우 ex) /Service-Y/Service-A, A서비스와 M서비스 또한 잠재적으로 장애가 연속적으로 발생할 가능성이 높다.


이와 같은 문제를 Circuit Breaker Pattern을 통해 도움을 받을 수 있다.




Circuit Breaker Pattern


Hystrix는  Circuit breaker 패턴을 자바 기반으로 오픈소스화한 라이브러리이다. 



출처: https://amandeepbatra.wordpress.com/2015/01/05/what-is-circuit-breaker-design-pattern/



- 만일 서비스가 정상적인 상황이라면, Client Request는 Remote Service를 호출하도록 by-pass한다.

- Database의 서비스가 장애가 발생했다면, 복구 될 때까지 Fallback 메세지와 함께 5xx response를 반환할 수 있다.






Hystrix Flow Chart



flow 차트는 위와 같은데, 아래의 코드를 통해 비교해보자

spring-cloud-starter-netflix-hystrix는 annotation 기반으로 간단하게 구성할 수 있다. Getting_Started

아래 코드는 spring-cloud-starter-netflix-hystrix가 아닌 webflux와 hystrix-javanica library를 추가해 각 단계별에 대해 분석하였다.


dependencies {
compile('com.netflix.hystrix:hystrix-javanica:1.5.10')

compile('org.springframework.boot:spring-boot-starter-webflux')

}



1. HystrixCommand나 HystrixObservableCommand Object를 생성한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 static class MessageCacheGetCommand extends HystrixObservableCommand<Object> {
 
        @Autowired
        private final StatefulRedisConnection<StringString> redisConnection;
        private String id;
 
        private static Setter setter = Setter
                .withGroupKey(HystrixCommandGroupKey.Factory.asKey(MessageCacheGetCommand.class.getSimpleName()))
                .andCommandKey(HystrixCommandKey.Factory.asKey(MessageCacheGetCommand.class.getSimpleName()))
                .andCommandPropertiesDefaults(
                        HystrixCommandProperties.Setter()
                                .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)  // semaphore strategy
                                .withExecutionIsolationSemaphoreMaxConcurrentRequests(5// failure 5 requsts, circuit on.
                                .withExecutionTimeoutEnabled(true)
                                .withExecutionTimeoutInMilliseconds(100)
  );


 
cs





2. execute(sync), queue(async), observe(hot observable), toObservable(cold observable) 중 선택하여 실행


1
2
3
4
 public Observable<Object> get(String id) {
        return new MessageCacheGetCommand(redisConnection, id).toObservable();
   }
 
cs




3. 캐쉬 응답이 있다면, Response를 수행한다.



4. Circuit이 Open되어 있으면 8) 단계로 넘어 간다.

1
2
3
4
5
if (HystrixCircuitBreaker.Factory.getInstance(HystrixCommandKey.Factory.asKey(MessageCacheGetCommand.class.getSimpleName())).allowRequest()) {
            redisConnection.sync().setex(id, expireTimeSeconds, value);
        } else {
            log.info("Cache update canceled, Circuit breaker opened!");
        }
cs


5. Thread Pool/Queue/Semaphore가 Full이면 8)로 넘어간다.

6. HystrixObservableCommand.construct() 또는 HystrixCommand.run()을 수행한다. 수행 중 실패하거나 Timeout이 초과되면 8) 단계로 아니면 9) 종료 단계로 간다.


1
2
3
4
5
6
7
8
9
10
11
 
        @Override
        protected Observable<Object> construct() {
            return redisConnection.reactive()
                    .get(id)
                    .defaultIfEmpty(null)
                    .map(value -> {
                        return value;
                    });
        }
 
cs



7. Circuit Health를 계산한다. Hystrix는 성공, 실패, Rejection 또는 Timeout 등의 정보를 Circuit Breaker에게 제공한다. Circuit Breaker는 이를 기반으로 Circuit을 열고 닫는다.

8. Fallback은 각 단계에서 실패, Timeout, Rejection 등이 발생할 때, 명시적인 핸들링을 할 수 있게하고 적절한 Response를 리턴할 수 있게한다.

1
2
3
4
   @Override
        protected Observable<Object> resumeWithFallback() {
            return Observable.empty();
        }
cs


9. 성공적으로 Response를 반환한다.







출처


https://github.com/Netflix/Hystrix/wiki/How-it-Works

https://medium.com/@goinhacker/hystrix-500452f4fae2

https://bcho.tistory.com/1247

https://supawer0728.github.io/2018/03/11/Spring-Cloud-Hystrix/

'Framework > Spring ' 카테고리의 다른 글

Spring AOP (1)  (0) 2019.08.02
Spring Cloud Netflix (3) - zuul  (0) 2019.06.06
Spring Cloud Netflix (1) - OverView  (0) 2019.02.10
Spring Websocket (Handler, STOMP, Reactive Websocket)  (6) 2019.02.10

12요소 애플리케이션 방법론

- Heroku가 제시한 12 요소 애플리케이션 방법론은 클라우드 환경에서 운영 가능한 가장 현대적인 애플리케이션에서 기대할 수 있는 특징을 기술한 방법론이다.

1. 단일 코드 베이스

- 애플리케이션은 하나의 코드 베이스만 가져야 한다. 개발/테스트/운영 등 여러 개의 인스턴스를 동일한 코드 베이스 기반으로 구성해야 하며, git/svn 과 같은 형상관리를 이용한다. 이 코드 베이스는 다른 마이크로 서비스와 공유되지 않는다.


2. 의존성 꾸러미(Bundling dependencies)

- 애플리케이션에 필요한 모든 의존성은 애플리케이션과 함께 관리되야한다. maven/gradle 을 통해 pom.xml/.gradle 을 통해 명시적으로 의존성을 관리할 수 있으며 최종 실행 파일은 .jar 혹은 .war 파일에 모두 패키징되어야 한다.


3. 환경설정 외부화(externalizing configuration)

- 모든 환경설정 파라미터를 코드와 분리해서 외부화하라고 권고한다.


4. 후방지원 서비스(backend service)

- URL을 통해 접근가능해야 하며, 모든 서비스는 살아있는 동안 외부의 자원과 의사소통할 수 있어야 한다. 마이크로 서비스 세상에서의 API end-point는 일반적으로 REST를 사용하는 HTTP 기반의 end-point이다. 


5. 빌드, 배포, 운영의 격리

- 빌드/배포/운영 단계를 뚜렷하게 격리하는 것이 좋다. 빌드 -> 배포 -> 운영의 방향은 일방향이며 파이프라인을 모두 통과하여야 한다.


6. 무상태, 비공유 프로세스

- 모든 마이크로 서비스는 무상태 기반으로 설계되어야 하며, 상태를 저장해야 할 경우 DB 혹은 인메모리 캐쉬 같은 후방 지원 서비스에서 처리 해야한다.


7. 서비스를 포트에 바인딩해서 노출

- 12요소 애플리케이션은 standalone 이어야 하기 때문에 포트바인딩을 통해 자율적이고 자기 완비적인 특성을 유지해야 한다.


8. 확장을 위한 동시성

- replication(복제)를 통해 프로세스가 확장될 수 있게 설계 되어야 한다.


9. 폐기 영향 최소화

- 구동 및 종료에 필요한 시간을 최소화 하고, 서버가 종료될 때는 종료에 필요한 작업이 모두 수행되는 우아한 방식으로 종료해야 한다. 특히 자동화된 배포의 시간이 오래 걸리면 자동화에 부정적인 요소가 미칠 우려가 있다. 때문에, lazy loading(지연 로딩) 혹은 크기를 가능한 한 작게 유지하는 것이 중요하다.

10. 개발/운영 환경의 동일

- 개발/ 운영 환경은 동일해야 한다. 그렇지 않으면, 개발에서 일어나지 않았던 비정상적인 상황이 운영에서 발생할 수 있다.


11. 로그 외부화

- 로그 파일은 절대 애플리케이션 영역에 담지 않고, 로그 스트림을 통해 중앙 집중화하는 것이 중요하다.


12. 관리자 프로세스 패키징

- 관리에 필요한 코드 및 태스크 또한 애플리케이션과 함께 패키징되고 포함되어야 한다.



'Architecture & Protocol' 카테고리의 다른 글

HTTP2 Protocol 분석  (0) 2019.04.16
Websocket Protocol 분석  (0) 2019.01.28
서비스 지향 아키텍쳐(SOA)  (0) 2018.12.30
마이크로서비스 아키텍처(MSA)  (0) 2018.12.02


Defualt Method

- 디폴트 메소드는 자바8 에서 더 유연하게 인터페이스를 만들 수 있도록 추가한 방법이다.

- 자바 8 이전에는 인터페이스의 메소드는 반드시 모두 상속해야만 했다. 

- 자바8 에서는 default라는 키워드를 통해 디폴트 메소드를 지정할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

interface Calcuator {
 
 default void add(int a, int b) {
        System.out.println((a+b));
    }
 
    default void minus(int a, int b) {
        System.out.println((a-b));
    }
}
 
 
 
 
public class Test implements  Calcuator{
      public void cal(){
           add(3,5);
       } 
}
    
 
cs

위와 같은 방법으로 상속받은 Calculator 인터페이스는 add, minus 라는 디폴트 메소드를 호출 할 수 있다.

또한 그동안 자바에서는 다중상속이 불가능한 것으로 알려져 있던 것을, 디폴트 메소드를 통해 어느정도 구현할 수 있다. 하지만 다이아몬드 상속 문제를 반드시 피해야 하는데 만일 인지하지 못할 경우 다음과 같은 컴파일 에러가 날 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
 

interface
 Calcuator {
 
    default void add(int a, int b) {
        System.out.println((a+b));
    }
 
    default void minus(int a, int b) {
        System.out.println((a-b));
    }
 
}
 
interface  DoubleCalculator {
    default void add(int a, int b) {
        System.out.println((a+b)*2);
    }
 
    default void minus(int a, int b) {
        System.out.println((a-b)*2);
    }
 
 
}
 
/**
 * 디폴트 메소드로 인해 어느 정도 다중 상속 개념이 될 수 있다. 다이아모몬드 상속 문제를 피해야한다.
 */
public class DefaultMethod implements  Calcuator, DoubleCalculator{
 
   
    public void cal() {
        add(3,5);
        minus(3,6);
    }
}
 
cs


위와 같은 경우, 컴파일 시 에러가 나는데   Interface의 상속을 통해 Override를 통해 문제를 해결할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
 
 
interface Calcuator {
 
    default void add(int a, int b) {
        System.out.println((a+b));
    }
 
    default void minus(int a, int b) {
        System.out.println((a-b));
    }
 
}
 
interface  DoubleCalculator {
    default void add(int a, int b) {
        System.out.println((a+b)*2);
    }
 
    default void minus(int a, int b) {
        System.out.println((a-b)*2);
    }
 
 
}
 
interface CalAndDoubleCal extends Calcuator,DoubleCalculator{
 
    @Override
    default void add(int a, int b){
        Calcuator.super.add(a,b);
        DoubleCalculator.super.add(a,b);
    };
    @Override
    default void minus(int a, int b) {
        Calcuator.super.minus(a,b);
        DoubleCalculator.super.minus(a,b);
    }
 
}
 
 
public class DefaultMethod implements CalAndDoubleCal{
 
    public void cal(){
        add(3,5);
    }
}
cs




함수형 인터페이스(Functinal Interface)

- 오직 하나의 추상 메소드를 지정한다.

- @Functional Interface 를 붙이는 것을 권장한다.

- 람다식 사용 가능


Predicate<T>

Java.util API 내부를 살펴보면 test라는 하나의 추상 메소드만 지정된다.  test는 제네릭 형식의 T 객체를 인수로 받아 boolean을 반환한다.

또한 Predicate 인터페이스는 복작한 predicate를 만들 수 있도록 negate, and, or 메소드를 제공하는데 이는 default 메소드이다.


1
2
3
4
5
6
7
8
        //predicate
       Predicate<String> nonEmptyString = s -> !s.isEmpty();
       Predicate<String> emptyString = s -> s.isEmpty();
       System.err.println(nonEmptyString.test("")); //false
       System.err.println(nonEmptyString.and(emptyString).test("")); //두개의 조건을 모두 만족해야하므로 false
       System.err.println(nonEmptyString.or(emptyString).test("")); //둘중 하나만 만족해도 되므로 true
       System.err.println(nonEmptyString.negate().test("")); // nonEmpytyString predicate의 반대상황을 테스트 true
 
cs



Consumer<T>

Java.util API 내부를 살펴보면 accept라는 하나의 추상 메소드만 지정된다.  accept는 제네릭 형식의 T 객체를 인수로 받아 void을 반환한다.


1
2
3
4
5
6
7
        //consumer
        Consumer<String> outConsumer = s -> System.out.println(s);
        Consumer<String> errConsumer = s -> System.err.println(s);
 
        outConsumer.accept("k");  //k
        outConsumer.andThen(errConsumer).accept("g"); // g, g
cs


Function<T,R>

Java.util API 내부를 살펴보면 aaply라는 하나의 추상 메소드만 지정된다.  appy는 제네릭 형식의 T 객체를 인수로 받아 제네릭 형태의 R객체를 반환한다.



1
2
3
4
5
6
7
    //function
        Function<String,String> lowerFunction = s -> s.toLowerCase();
        Function<String,String> upperFunction = s -> s.toUpperCase();
 
        System.out.println(lowerFunction.apply("S"));       //s
        System.out.println(lowerFunction.andThen(upperFunction).apply("S"));    // S
 
cs


'programming > Java' 카테고리의 다른 글

primitive type과 boxed primitives  (0) 2019.11.24
Reflection  (0) 2019.09.20
[JAVA 8] - 함수형 프로그래밍(Functional Programming)  (0) 2019.02.17
# Enum (열거형)  (0) 2018.05.20
# NIO - Selector  (0) 2018.05.19

함수형 프로그래밍 (Functional-style programming)

부수 효과를 없애고 순수 함수를 만들어 모듈화 수준을 높이는 프로그래밍 패러다임

: 부수효과란  주어진 값 이외의 외부 변수 및 프로그램 실행에 영향을 끼치지 않아야 된다는 의미이며, 부수효과를 만족하는 함수를 순수함수라고 한다.


- 순수함수 : 모든 입력이 입력으로만, 모든 출력이 출력으로만 사용
- 비순수함수: 숨겨진 입력이나 출력이 존재


즉 함수형 프로그래밍은 순수함수를 지향, 최대한 숨겨진 입력과 출력을 제거하여 가능한  코드를 입력과 출력의 관계로 사용해야 한다.


객체지향 프로그래밍과 함수형 프로그래밍

1) 객체 지향은 명령형 프로그래밍이고, 함수형 프로그래밍은 선언형 프로그래밍이다.


: 우선 명령형 프로그래밍과 함수형프로그래밍의 차이의 핵심은 문제해결의 관점이다. 기존의 우리가 객체지향 프로그래밍을 할 때는 데이터를 어떻게 처리할 지에 대해 명령을 통해 풀어 나아갔다면, 함수형 프로그래밍은 선언적 함수를 통해 무엇을 풀어나아갈지 결정하는 것이다.


아직 Stream API에 대해 다뤄보진 않았지만, 간단히 샘플 코드를 통해 비교해본다면 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 
        List<String> myList =
                Arrays.asList("c1""a2""b3""4""5");
 
        // 기존방식
        for(int i=0; i<myList.size(); i++){
            String s = myList.get(i);
            if(s.startsWith("c")){
                System.out.println(s.toUpperCase());
            }
        }
 
        // stream API를 이용한 방식
        myList.stream()
              .filter(s -> s.startsWith("c"))
              .map(String::toUpperCase)
              .forEach(System.out::println);
 
 
cs


처음보면 크게 와 닿지 않을 수 있지만, 기존 방식과는 많이 다르다. 중요한 점은 프로그래밍 패러다임의 변화이다. 단순하게 함수를 선언함으로써 데이터들을 내가 원하는 방향으로 처리해 나아가고 있다는 점이다. 한눈에 보더라도 함수형프로그래밍을 통해 구현한 아래코드가 무엇을 하고있는지에 대해 명확하다.


2) 함수형 프로그래밍에서 함수는 1급객체 여야 한다.

위키피디아의 정의된 1급객체의 의미를 인용하면..

특정 언어의 일급 객체 (first-class citizens, 일급 값, 일급 엔티티, 혹은 일급 시민)이라 함은 컴퓨터 프로그래밍 언어 디자인에서 일반적으로 다른 객체들에 적용 가능한 연산을 모두 지원하는 객체를 가리킨다.


1급 객체(First class object)란 다음과 같은 조건을 만족하는 객체이다.

  • 변수나 데이터 구조안에 담을 수 있다.
  • 파라미터로 전달 할 수 있다.
  • 반환값(return value)으로 사용할 수 있다.
  • 할당에 사용된 이름과 관계없이 고유한 구별이 가능하다.
  • 동적으로 프로퍼티 할당이 가능하다.
  • 기존 데이터의 불변성(Immutable)


자바에서 그동안 함수는 메소드를 값으로 표현할 수 없어 1급 시민이 아닌 2급 시민으로써 역할 해왔다. 하지만 자바8에서 함수는 1급시민이 되어 함수형 프로그래밍의 핵심이 되었다.


사실 위의 2가지 특징이 함수형 프로그래밍의 모든 것을 나타낸다고 생각하는데, 위의 특징이 단점이 될 수도 있다. 예를 들면, 덕분에 코드의 간결성이 증가되었지만 그만큼 학습의 필요로 한다. 함수형 프로그래밍을 학습하는 것은 결코 쉽지 않기 때문에 오랜 학습이 필요하다.



Java 8만의 3가지 기술

- 인터페이스의 디폴트 메소드

- 메소드 레퍼런스, 람다

- Stream API




참조

- https://velog.io/@kyusung/%ED%95%A8%EC%88%98%ED%98%95-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%9A%94%EC%95%BD#%EB%AA%85%EB%A0%B9%ED%98%95-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EA%B3%BC%EC%9D%98-%EB%B9%84%EA%B5%90

- Java8 in action


Spring-Cloud-Netflix


Spring Cloud Netflix자동 환경 설정과 Spring Environment 및 다른 Spring 프로그래밍 모델 관념의 바인딩을 바탕으로 Spring Boot 어플리케이션을 위한 Netflix OSS(Open Source Software) 통합을 제공합니다. 몇 가지 간단한 어노테이션을 사용하여 어플리케이션 내부의 공통 패턴을 신속하게 사용하고 설정할 수 있습니다. 그리고 battle-tested를 거친 Netflix component를 통해 대규모 분산 시스템을 구축할 수 있습니다.


제공되는 패턴은 다음과 같다.


1. Service Discovery (Eureka) : 자바 환경 구성으로  embeded Eureka 서버를 설정할 수 있으며,  eureka 인스턴스를 등록한 서비스를 클라이언트는 스프링이 관리하는 bean을 

사용하여 발견할 수 있다.

2. Circuit Breaker (Hystrix) : Hystrix 대쉬보드 및 어노테이션을 통한 hystrix 클라이언트 구현 

3. Declarative REST Client (Feign) : Feign은 JAX-RX 혹은 MVC Annotation으로(선언적) 인터페이스를 동적으로 구현한다.

4.  Client Side Load Balancer(Ribbon) : zuul에 내장된 로드 밸런서 

5. External Configuration : Spring 환경에서 Archaius로 연결 (Spring Boot를 사용하여 Netflix 구성 요소를 설정 가능)

6. Router and Filter - Proxy(Zuul) : Zuul 필터의 자동 등록 및 Reverse Proxy 생성 설정 접근에 대한 간단한 규칙





Spring-Cloud-Netflix EchoSystem


spring-cloud-netflix 생태계는 간단하게 다음과 같다.


1. 모든 요청은 Zuul(API-Gateway)에서 처리되며, URI에 맞는 서비스를 라우팅한다. 만약 동일한 서비스가 여러 개의 인스턴스에 있는 경우에는 로드밸런싱을 통해 트래픽을 나눈다. 이는 Zuul에 내장된 Ribbon이 하는 기능이며, Default는 라운드로빈 방식에 의해 분산이 된다.


2. 모든 서비스는 유레카 서버에 등록된다.

3. 인스턴스간의 모든 호출(서비스와 서비스, gateway와 서비스 ... 등)  사이에는  Hystrix Circuit breaker 패턴이 적용된다.



https://www.optisolbusiness.com/insight/micro-services-architecture-spring-boot-and-netflix-infrastructure

https://spring.io/projects/spring-cloud-netflix






'Framework > Spring ' 카테고리의 다른 글

Spring AOP (1)  (0) 2019.08.02
Spring Cloud Netflix (3) - zuul  (0) 2019.06.06
Spring Cloud Netflix (2) - hystrix  (0) 2019.02.25
Spring Websocket (Handler, STOMP, Reactive Websocket)  (6) 2019.02.10

WebSocket 

레퍼런스에 따르면, websocket은 웹 브라우저(클라이언트)와 서버간의 full-duplex(양방향), bi-directional(전이중적), persistent connection(지속적인 연결)의 특징을 갖는 프로토콜이라고 규정한다. 

여기서 핵심은 클라이언트와 서버간의 지속적인 연결을 한다는 점이다.  Websocket이 탄생하기 이전에는 HTTP을 통해 양방향 통신을 하기 위해  polling, long polling, http streaming과 같은 방식을 사용했다.



Http Polling : 클라이언트가 지속적으로 서버로  request를 하여 이벤트를 수신하는 방식이다. 가장 간단한 방법이지만, 지속적으로 서버에 요청을 던지기 때문에 서버의 오버헤드를 고려할 수 밖에 없는 상황이다.

Http Long Polling: polling에 비해 클라이언트는 이벤트를 받기 전까지 다음 요청을 날리지 않는다. 하지만, 원하는 이벤트를 얻기 위해 지속적으로 요청해야한다는 점에서 서버의 부담은 여전히 증가된다.

Http Streaming: 서버는 클라이언트로부터 request를 받으면, response을 주고 연결을 끊지 않고. 이벤트가 발생함에 따라 클라이언트로 전송하는 방식인데 역시나 근본적인 원인인 해결하지 못한다.


물론 위의 방식으로 원하는 데이터를  클라이언트와 서버간의 주고 받는데 문제는 없다. 하지만 HTTP 프로토콜 특성의  request-response의 지속적인 수행 그리고 그에 따른 중복적인 패킷전달(http-header) 문제로 인해 속도 저하 및 오버헤드는 근본적으로 문제가 있게 된다.


쉽게 말하면, 내가 원하는 데이터에 비해  동반되는 데이터들이 너무 많고, 지속적으로 이 데이터를 포함해야 하며 맺고 끊는 연결을 계속하는 등 리소스의 낭비가 크다는 점이다.


그래서 웹 브라우저 환경에서 tcp 통신처럼 연결 지향 프로토콜이 필요했는데 이를 해결하기 위해, 2011년 Websocket 프로토콜이 탄생하게 되었다. 

그렇다면, Websocket과 TCP는 어떤 차이가 있는 걸까?


1. 웹소켓은 연결 요청에 대해 http를 통해 switching 및 Handshaking이 이루어진다. (웹소켓 프로토콜 분석하기)

2. TCP는 Binary 데이터만 주고 받을 수 있지만, Websocket은 Binary 데이터 뿐만 아니라 Text 데이터를 주고 받을 수 있다


탄생 배경과 정의 그리고 특성으로 미루어 보아, WebSocket은 HTTP와 TCP의 특성을 섞어 놓은 프로토콜이며 

결국 핵심은 웹 브라우저 환경에서 연결지향 통신하기 위한 기술이라는 점이다.




Spring WebSocket 

Spring Websocket은 Spring 4.0 부터 지원하며, Maven 3.2+, gradle 4+, jdk8 이상 필요하다.

Spring에서는 2가지 방식으로 Websocket 을 구현 할 수 있다.


- WebSocket 데이터를 직접 처리

- Stomp 프로토콜을 사용하여  메세징 처리


1) Websocket Data 직접 처리



Config를 통해 Websocket 옵션을 설정할 수 있다. 그리고  웹소켓 핸들러를 상속받은  클래스는 low level 수준에서 원시적으로 데이터를 처리할 수 있으며,  다음과 같은 4가지 이벤트를 처리 할 수 있다.

1
2
3
4
5
6
7
8
9
10
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
//        WebSocket을 사용할 수없는 경우 대체 전송을 사용할 수 있도록 SockJS 폴백 옵션을 활성화합니다.
//        SockJS 클라이언트는 "/ws"에 연결하여 사용 가능한 최상의 전송 (websocket, xhr-streaming, xhr-polling 등)을 시도.
        registry.addHandler(new WsTranportHandler(), "/ws").setAllowedOrigins("*").withSockJS();
    }
}
cs


눈여겨 봐야 할 설정은 registerStompEndpoints 메소드이다. 

Websocket은 안타깝게도 모든 브라우저를 지원하지 않는데, 만일 브라우저 버전이 Websocket을 호환하지 않는 경우 설정을 통해 폴백 옵션을 활성화 할 수 있다.

또한 CORS(Cross Origin Resource Sharing) 문제 또한 옵션을 통해 해결할 수 있다.




@Component
public class WsTranportHandler extends TextWebSocketHandler {
 
    // connection이 맺어진 후 실행된다
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        System.err.println("session connected +=" + session);
    }
    // 메세지 수신
    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
        System.err.println("handle message +=" + session) + ", message=" + message);
 
        //echo Message
        session.sendMessage(message);
 
    }
    // transport 중 error
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        System.err.println("transport error =" + session +", exception =" + exception);
    }
    // connection close
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
 
        System.err.println("session close -=" + session);
 
    }
}

 

그리고 WebSocketHandler를 상속받은 클래스는 직접 세션레벨에서 데이터를 Handle 해야한다.




2) Stomp 프로토콜을 사용하여  메세징 처리


우선 STOMP 프로토콜은 (simple text oriented messaging protocol)의 약자이며, 텍스트 기반의 프로토콜이다.




스프링 문서에 Websocket과 함께 STOMP 프로토콜을 사용하는 방법은 위와 같은데, Spring 내부의 In Memory Broker를 통해 메세지를 처리한다.

도식화 된 그림의 키포인트는 3가지이다.


1)  Receive Client

2) Send Client

3) Brkoer


Receive Client

- 메세지를 받기 위해 특정 토픽이 사전에 서버에 subscribe 되어야 한다.


Send Client

- 서버와 연결된 클라이언트는 특정 path로 ex) /app/message  전달한다.


Broker

- 메세지 브로커는 Kafka, RabbitMQ, ActiveMQ 등의 오픈소스들 처럼  MQ 이며, pub/sub 모델을 따른다. 토픽에 따라 메세지를 전달해야 하는 사용자를 구분한다.

- 연결된 클라이언트의 세션을 관리한다.

- 특정 토픽과 메세지를 Mapping 하여, 토픽을 구독하는 세션에 존재하는 클라이언트에게 메세지를 전달한다.


Configuration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketStompConfig implements WebSocketMessageBrokerConfigurer {
 
    //messageBroker config
    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
 
        //in-memory message-broker, topic에 대한 prefix 설정
         config.enableSimpleBroker("/topic");
 
 
        //메세지를 수신하는 handler의 메세지 prefix 설정
        config.setApplicationDestinationPrefixes("/api");
 
    };
 
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
       registry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS();
    }
}
 

cs

 


 Controller


1
2
3
4
5
6
7
8
9
10
11
 
@RestController
public class MessageHandleController {
 
    @MessageMapping("/echo")
    @SendTo("/topic/messages")
    public EchoMessage echo(String message) throws Exception {
        System.err.println(message);
        return  new EchoMessage(message,LocalDateTime.now());
    }
}
cs


그림의 SimpleAnnotationMethod로 된 부분을 @MessageMapping으로 처리할 수 있다.  설정의 setApplicationDestinationPrefix의 /api로 설정했기 때문에, 최종적으로 메세지를 보낼려고 할 경우 /api/echo 로 메세지를 보낸다면, MessageHandler는 메세지를 수신한다. 

@MessageMapping을 통해 메세지를 수신했다면, @SendTo를 통해  특정 토픽을 구독하는 클라이언트에게 메세지를 보낼 수 있다.





Reactive & Spring Webflux 

Spirng5에서는 비동기 측면의 통신에 대한 지원을 확대했으며, Webflux의 Reactor를 통해 reactive-stream에 대한 사양을 정의 했다.





기존의 수 많은 Http 요청의 처리 성능(throughput)을 증가 시키기 위해, 톰캣 쓰레드를 더 많이 할당하는 방식으로 구현되었으나 Spring5 에서는 적은 수의 쓰레드로  Reactive-stream에서 비동기 처리를 지향한다.



Reactive Websocket 

Spring5 에서는 websocket 채널에 Reactive 능력을 추가함에 따라 조금 더 유연하게 사용할 수 있도록 지원하였다.


spring5 에서 web flow는 다음과 같다.



https://blog.monkey.codes/how-to-build-a-chat-app-using-webflux-websockets-react/



1. Spring4에서 웹소켓을 직접 처리하는 방식과 같이 기본적으로 WebsocketHandler를 구현하여 처리해야한다.

2. handler는 연결이 설정될 때마다, 웹소켓 세션이 제공되며 세션에는 receive 와 send 를 포함하는 flux stream을 갖게 된다.

3. GMS(UnicastProcessor)를 통해 모든 웹소켓 세션은 연결된다.

4. GMS를 통해 메세지를 보내야 하며, publisher는 websocket session을 통해 receive한  flux 메세지를 수신한다.

5.  GMS는 모든 웹소켓 세션을 연결하고 있지 않고 가장 최근 생성된 25개의 subscrber만 갖고 있으며, 메세지를 전달한다.



reactive websocket echo server 구현하기



websocket을 spring5에서 구현하면, 기본적으로 톰캣이 아닌, netty conatiner 에서 동작한다.





https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-websocket

https://www.baeldung.com/spring-5-reactive-websockets

https://blog.monkey.codes/how-to-build-a-chat-app-using-webflux-websockets-react/

https://docs.spring.io/spring/docs/5.0.0.BUILD-SNAPSHOT/spring-framework-reference/html/websocket.html

http://www.egovframe.go.kr/wiki/doku.php?id=egovframework:rte3.5:ptl:sockjs

https://adrenal.tistory.com/20




'Framework > Spring ' 카테고리의 다른 글

Spring AOP (1)  (0) 2019.08.02
Spring Cloud Netflix (3) - zuul  (0) 2019.06.06
Spring Cloud Netflix (2) - hystrix  (0) 2019.02.25
Spring Cloud Netflix (1) - OverView  (0) 2019.02.10

+ Recent posts