Cache 가 저장된 데이터를 Flush 시키는 전략은 캐시 서버의 유지에 있어서 중요한 부분이다.


가령 캐시에 저장된 데이터가 실제 데이터와 동기화가 안되어 잘못된 값이 참조된다거나, 이전 데이터가 만료되지 않는 경우,

혹은 저장된 정보인데 Key 값이 Mismatch 나거나 불필요하게 Flush 되어 Cache hit-ratio 가 낮아진다면 이는 전체 서버 구조에 영향을 미치게 된다.


다음은 Cache 서버를 사용할 때 유의해야할 몇가지 Cache 의 Flushing 전략 들을 정리해보았다.


주로 사용되는 캐시인 Redis 위주로 정리가 되어있으며, Cache 자체에 대한 범용적인 정리이지만 예시는 Redis 위주이며 캐시의 종류에 따라 차이가 있을 수 있다.



1. Cache 의 Flushing 은 일반적으로 Active 한 방법과 Passive 한 방법이 있음을 이해하자.


 : Active 한 방법은 별도의 요청에 따라 Cache 를 Flush 시키는 방법이고, Passive 한 방법은 자체에 Expire Time 을 이용해서 외부에서 별도 요청없이 캐시를 비우게끔 만드는 전략이다.

 

 만약 Active Flushing 을 하지 않는다면, 해당 캐시는 수동적으로만 갱신되므로 실시간적인 데이터 동기화 및 캐싱이 불가능하다.

 Passive Flushing 을 하지 않는다면, Active 한 방식으로 Flushing 되지않는 데이터는 영구히 캐시 서버에 남아서 공간을 차지하게 되며 이는 큰 비용이 될 수 있다.


 반대로 지나치게 Active 한 Flushing 이 많아진다면, Cache 는 Read 요청 대비 Write 요청의 비율이 높아져 쓰는 효율이 없어지게 되고, Passive Flushing 도 지나치게 짧은 단위로 Flushing 이 일어난다면 Cache hit ratio 가 줄어들 것이므로 설계에 유의가 필요하다.

 

 일반적으로 API 의 설계에 맞춰 Active Flushing 타이밍을 정확히 맞추고, Passive Flushing 을 위한 Expire Time 의 설정은 선험적 지식 또는 벤치마킹 등에 의해 측정되어 설정되게 된다.

 


2. Cache Refresh / Expire reset 기준을 확인하는 것이 중요하다.


 : 사용하는 캐시 서버 및 설정에 따라 다를 수 있지만, Cache Refresh 의 기준을 확인하는 것은 중요하다. 

 가령 Redis 의 경우 Cache hit Read 기준으로 Expire reset 이 발생하기 때문에, 계속적으로 Cache hit 이 발생하면 자동으로 Expire 는 되지않는다.

 단순 기능이라면 큰 문제가 안되겠지만 캐시를 바라보는 경우가 많을 경우 문제가 생기기 쉬운 상황이므로 잘 체크해주어야 한다.


 즉, 지속적으로 Cache-hit 이 발생하는 한, Active 한 방식의 Flushing 이 아닌 Passive Flushing 을 기대하는 건 잘못된 방식이다.

 개인적으로 QA 프로세스에서 Caching 문제가 발생하고 있었는데, 이 부분을 간과해서 문제가 해결되지 않은 버전이 Live 에 포함될 뻔 한 일이 있었다.



3. 사용하는 캐시 서버의 Expire 로직을 이해하자.


 : Cache 를 사용할 때 캐시 서버의 로직을 파악하고 있는 것이 중요하다. 중요한 것은 사용하는 Cache 서버가 어떻게 내부적으로 동작하냐는 것이다.

 가령 Redis 의 경우 일정한 주기를 갖고 랜덤하게 key 들을 뽑아서 expire 을 검사하고 expiration rate 가 높을 경우 다시 일정 주기로 검사하는 루틴을 지닌다.

 이러한 구조를 이해하는 것은 서버 설정하는 데 있어서 기본이 되며 보다 나은 환경을 구축하는 데 도움을 준다.




캐시에 대한 현업에서 이슈들로 겪으면서 가장 중요했던 기본 내용들만 정리해보았다.


실제 이슈와 그에 대한 트러블 슈팅은 따로 포스팅에 정리하도록 하겠다.





스프링 프레임워크의 주요 철학 중 하나로 가장 큰 테두리 중 하나는 DI(Dependency Injection) 이다.


Dependency Injection 에 대해 먼저 살펴보자면, 말그대로 "의존성 주입" 을 말하며, 스프링 프레임워크는 Framework 레벨에서 DI 를 제공해준다.


Spring 의 Container 들은 Bean 객체들을 관리하는 데 있어서 DI 를 이용하며 이를 통해 Life Cycle 을 용이하게 관리할 수 있으며 이 것이 스프링 프레임워크의 핵심적인 동작이라고 할 수 있다.


즉, 프레임워크 레벨의 관리를 통해 개발자는 객체들간의 의존성에 신경을 덜 쓰고 Coupling 을 줄일 수 있으며 높은 재사용성과 가독성있는 코드를 만들어낼 수 있다.


이를 제어의 역전(Inversion Of Control) 이라 하며, 이것이 스프링 프레임워크의 특징적인 개념인 IOC 이다.

(클래스 관리의 주체가 개발자가 아닌 프레임워크라는 뜻이다.)


그리고 결과적으로 이러한 개발 편리성은 높은 생산성을 이끌어낼 수 있는 스프링의 큰 장점이다.


- Dependency Injection 을 통해 얻을 수 있는 장점 -


 (1) Dependency Reduction : 객체 상호 간 의존성 관계를 줄여준다.


 (2) Reusable Structure : 코드의 재사용과 조합이 용이하다.


 (3) Readability : 코드들이 분리되다보니 가독성이 뛰어나진다.


 (4) Loose Coupling & Easy to change : 구조는 변화에 민감하지 않을 수 있다.


그 외에 테스트가 용이하고 다양한 패턴을 적용하는 데 유연하다는 점도 큰 장점이 될 수 있다.



Spring Framework 에서 개발자가 Dependency Injection 을 하는 데 몇가지 방법들이 있다.


 (1) Field Injection


 가장 흔히 볼 수 있는 Injection 방법으로 사용하기도 간편하고 코드도 읽기 쉽다.



public class Sample {
    
    @Autowired
    private Example example;
    
}


 많이 사용됨에도 불구하고 Field Injection 을 통한 의존성 주입은 권장되지 않는다.

 이유는 너무 추상적인 Injection 기법 때문이다. 의존성 주입이 쉽기 때문에 Dependency 관계가 복잡해질 우려가 있으며 이는 Framework 의 사용에 있어 다음과 같은 안티패턴적 측면을 갖는다.


 - Single Responsibility Principle Violation 

 : 너무나 쉬운 의존성의 주입은 하나의 클래스가 지나치게 많은 기능을 하게됨으로써 초기 설계의 목적성이자 "객체는 그에 맞는 동작만을 한다." 는 원칙에 위배되기 쉽다.

 위배된 경우 리팩토링의 비용은 크다.


 - Dependency Hiding

 : 추상화된 의존관계는 의존성을 검증하기 힘들게 만든다. 


 - DI Container Coupling

 : Field Injection 을 사용하면 해당 클래스를 곧바로 Instant 화시킬 수 없다. 이 부분 때문에 Constructor Injection 이 권장되는 이유이기도 하다.

 가령 Container 밖의 환경에서 해당 클래스의 객체를 참조할 때, Dependency 를 정의해두는 Reflection 을 사용하는 방법 외에는 참조할 수 있는 방법이 없다.

 DI Framework 는 Field Injection 된 클래스의 Instance 화에 대해서 Null Pointer Exception 을 만들어낼 것이다.


 - Immutability

 : Field Injection 된 객체는 final 을 선언할 수 없으므로 가변적(Mutable)이다. 객체는 변경될 수 있으며 이에 대한 대응에는 큰 비용이 든다.



 (2) Setter Injection


 선택적인 의존성을 주입할 경우 유용하며, Spring 3.x 대까지 가장 권장되던 방식이다.



public class Sample {
private Example example;

@Autowired
public void setExample(Example example) {
this.example = example;
}
}


 Field Injection 으로 인한 패턴적 위험성을 상당 부분 해소한다. Optional Injection 의 경우 권장되는 방식이다.

 @Required 어노테이션을 이용하면 의존성이 필요한 Setter 를 만들 수 있다.


 (3) Constructor Injection


 Spring 4.x 이상부터 권장되는 방식이다.



public class Sample {
    private final Example example;

    @Autowired
    public Sample(Example example) {
        this.example = example;
    }
}


 코드를 통해 알 수 있듯, final 선언이 가능하며 Immutability 에 대한 해소가 가능하며 의존성의 순환 참조(Circular Dependency) 에 대한 예방이 가능하다.

 순환 참조 시 위의 방법을 이용한 코드는 BeanCurrentlyCreationExeption 을 발생시킨다.

 역시 위에서 언급된 Container Coupling 문제도 해결이 되는데, 생성자를 통한 Injection 이므로 즉각적인 Instance 화 등에 대한 문제도 해결된다.



 많은 예제 코드들이 Field Injection 방식을 사용하고 있으나 Constructor Injection 의 사용이 권장된다.

추가로 참조한 바에 의하면 Spring Team 에서는 Setter 방식을 좀 더 권장하며 이유는 생성자가 지나치게 복잡해질 수 있기 때문이라고 한다.

패턴적 관점에서 이견이 있는 듯 하다.



Spring 4.3 이상부터는 생성자가 하나인 경우 @Autowired 를 사용하지 않아도 무방하다.



정리에 있어 다음 링크들을 참조하였다.


https://www.vojtechruzicka.com/field-dependency-injection-considered-harmful/


https://dzone.com/articles/dependency-injection-pitfalls


https://blog.outsider.ne.kr/753



Spring 3.1 버전부터 추가된 FlashMap 은 스프링에서 파라미터를 간편하게 전달하기 위한 자료구조이다.


주로 같은 도메인에서 Controller 간, 혹은 웹페이지에서 발생하는 Redirect 시 데이터 처리를 간단하게 하기 위한 목적으로 사용된다.


플래시 맵을 사용하는 용도는, URL 에 데이터를 노출시키지 않으면서 데이터를 전달하고 싶으면서도 Session 데이터에 넣기에 적합하지 않을 경우이다.

(Session 은 사용 후 값을 지워줘야 하므로 실수로 누락되는 경우를 방지해야하며 실제로 유저 정보들이 많이 관리되므로 가벼운 데이터의 경우에도 세션을 이용하기 부담스러운 경우가 많다.)


FlashMap 의 특징은 일반 Map 자료구조처럼 쉽고 간단하게 사용될 수 있으면서 사용되고 난 이후에는 Spring 에서 값을 자동으로 지워준다는 점에 있다.


즉, FlashMap 은 휘발성이며 사용하는 개발자 입장에서는 관리에 부담없이 가볍게 Attribute를 다룰 수 있다.


단, 휘발성이므로 서버가 지속적으로 관리하면서 사용해야 하는 공유 Attribute 에는 적합하지 않다고 한다.



참조 : 


https://docs.spring.io/spring/docs/3.1.x/javadoc-api/org/springframework/web/servlet/FlashMap.html


https://docs.spring.io/spring/docs/3.1.x/spring-framework-reference/html/mvc.html#mvc-flash-attributes




COMET 이란 2006년 알렉스 러셋(Alex Russel)이 정의한 용어로, 브라우저가 HTTP 요청에 대해 데이터를 푸시하는 방법을 고안한 웹 모델이다.


일반적인 웹모델은 널리 알려진 바 대로 서버와 클라이언트 브라우저간 상태를 유지한 통신이 불가능하다. 


COMET 은 이러한 HTTP 의 본질적 한계를 어느정도 극복하고자 만든 Web Application Model 이라고 생각하면 된다.


가령 브라우저가 지속적으로 서버에 데이터를 받아야할 필요가 있을 때 서버의 데이터가 업데이트 되지않았다면 이는 무의미하다.

즉, 클라이언트의 입장에서 갱신이 필요한 절대적인 시점은 무조건 서버의 데이터가 변경되었을 때가 된다.


COMET 은 간단하게는 Client 로 유의미한 메시지를 전달할 때까지 HTTP 응답을 지연시키는 기술이다.


좀 더 정확히는 서버가 클라이언트의 요청에 응답할 때 응답을 "늘어뜨리는 방법" 을 이용해서 긴 시간동안 브라우저가 접속을 끊지않고 서버의 응답을 대기하게 만든다. 


즉, Long polling 과 원리가 같다.

(Long Poll – 서버측에서 단순히 클라이언트 측에 대한 연결을 길게 유지함으로써 지정한 기간 내에 정보가 있으면 응답을 전달한다. 메시지 양이 많으면 Polling 과 차이가 없으며 지정한 시간이 초과되어 끊기면 다시 요청하는 말그대로 긴 – Polling 이다.)


클라이언트는 Long polling 을 해주고, 그 시간 동안 서버는 서버에서 발생한 이벤트 정보 등도 같이 끼워넣어줌으로써 이른바 "HTTP 푸시" 를 흉내내게끔 한다.


COMET 모델에서 서버는 비동기 방식을 취하고, 클라이언트가 다양한 테크닉으로 푸시를 받을 수 있는 구조를 취하는 경우가 일반적이다.


이에 Javascript 레벨에서 사용되는 테크닉으로, Hidden Iframe 영역(Forever frame)을 할당하고 서버측에서 발생하는 이벤트별로 <script> 태그 내에 데이터를 채워넣는 방식 또는 Ajax 의 long polling 방식, 그리고 Active X 를 이용하는 방식을 사용하기도 한다.


COMET 모델에서 서버와 클라이언트가 통신하기 위해 JSONP 라는 방식을 사용하는데, 이는 JSON 규격의 데이터로 통신을 하되 Client 로부터 전달받은 Callback 함수를 호출하게끔 하는 방식이다.


이렇게 되면 브라우저는 Callback 함수로 감싸여진 Json 데이터를 이용해서 <script> 또는 XHR 을 구성하며 해당 리소스를 읽고 수행하게 된다.


이처럼 COMET 모델을 이용하면 서버에서 발생한 이벤트를 Client 의 추가 요청없이 송신할 수 있다. 



현재는 Websocket 및 HTML5에서 표준화된 Server-sent Event 와 연관되어 발전해가고 있다.







백엔드를 개발하다보면, Client 의 요청을 자체 처리하는 경우가 아닌 다른 url 핸들러에게 위임하는 경우가 종종 있다.


이렇게 클라이언트 요청에 대해서 다른 리소스로 연결을 할 때, redirect 와 forward 라는 2가지 테크닉이 있다.


Redirect 와 Forward 모두 클라이언트 요청에 대한 처리를 다른 url 로 보내서 처리를 위임하는 개념이지만, 


두 개념에는 차이가 있다. 각각의 개념을 통해 차이점을 정리해보자.



redirect - 클라이언트의 요청에 대해 서버가 다른 url 로 요청을 하게끔 만듬.




이 때, 원래 클라이언트가 요청한 url 의 핸들링 시, 서버는 다른 url 로 클라이언트가 "요청" 을 던지게끔 한다.


이 후 클라이언트는 서버로부터 전달받은 다른 url, 즉 위의 예제에서 /home 으로 다시 요청을 하게 하고, 해당 url 매퍼에서 요청에 대한 응답이 처리되게 된다.


즉, 연결이 끊기고 재연결이 들어가는 등 요청이 여러번 왕복하기 때문에 Rquest - Response 쌍이 하나 이상 생기게 된다.




forward - 서버가 클라이언트의 요청을 다른 서버로 넘김(forwarding)




서버는 클라이언트로부터 요청을 전달받았을 때, 이 요청을 서버 내부에서 다른 url 핸들러로 요청을 "전달" 한다.


즉, 클라이언트가 다시 서버에 대한 요청을 할 필요 없이 서버가 다른 url 매퍼에서 처리된 응답을 받기만 하면 되는 구조가 된다.


실제 처리는 "다른 url" 에서 처리되었지만 응답은 초기 url 핸들러로부터 내려받으며, 서버와 클라이언트 간 Request - Response 쌍은 하나만 존재하며 연결이 끊기지 않음. (연결이 하나로 유지됨)




실무에서도 많이 쓰이는 개념이고 익숙해지면 차이를 헷갈릴 수 있으니 틈틈히 정리하고 알아두는게 필요하다.



'Server > Basic' 카테고리의 다른 글

Message Oriented Middleware 및 Message Queue 에 대한 설명  (0) 2019.03.13
COMET 이란?  (0) 2019.01.23
Java Servlet 에 대하여  (0) 2018.12.23
HTTP/2 특징들에 대한 정리  (0) 2018.12.17
무중단 배포의 원리와 솔루션 종류  (0) 2018.12.09

 

Servlet 은 WAS에서 동작하는 Java 의 클래스를 말하며, 단순히 HTTP Request 에 대해 HTTP Response 를 응답하는 고차원 추상화를 제공하는 클래스를 말한다.

Java로 웹 어플리케이션 제작 시 동적인 처리를 담당한다.

 

Web Server 의 성능 향상을 위해 사용되는데, 외부 요청에 대해 Thread 로 할당하여 응답하므로 아주 가벼운 서버로 구현되고 Java 의 특성 상 다양한 플랫폼에서도 동작이 가능하다.

 

Servlet 은 일반적으로 HttpServlet  클래스를 상속받으며 웹페이지 개발시 JSP 와 Servlet 을 함께 이용하는 것이 도움이 된다.

(JSP 는 HTML 문서 안에서 Java 코드를 포함하는 반면, Servlet 은 Java 코드 안에서 HTML 코드를 사용하곤 한다.)

 

Servlet 3.0 미만의 버전에서는 web.xml 파일에 Servlet 을 등록하고 사용하도록 되어있지만, 

Servlet 3.0 이상에서는 web.xml 파일을 사용하지 않으며 대신 Annotation 을 이용해 정의한다.

 

3.0 이상에서 어노테이션을 이용해 서블릿을 작성할 때에는

 

@WebServlet("/test")
public class TestServlet extends HttpServlet {

 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 response.setContentType("text/html;charset=utf-8");
 PrintWriter out = response.getWriter();
 out.print("Hello world");
 out.close();
 }
}

위와 같이 작성하게 된다.

 

Java Servlet 은 Servlet Container 위에서 동작가능한데, Tomcat 과 같은 서블릿 컨테이너 들은 다음과 같은 기능들을 지원함으로써 서블릿의 동작을 돕는다.

 

 - Network Services : Remote System 및 네트워크 서비스를 접근하고 이용할 수 있도록 제공한다.

 - Decode and Encode MIME based Message : MIME 타입의 메세지에 대한 인코딩 / 디코딩을 제공한다.

 - Manage Servlet container : 서블릿 들의 라이프사이클을 관리해준다.

 - Resource Management : HTML, JSP 등과 같은 static 및 dynamic 리소스 들을 관리해준다.

 - Security Service : 리소스 접근에 대한 인증과 권한을 담당한다.

 - Session Management : 세션을 관리한다.

 

Tomcat 과 같은 Servlet Container 들은 서블릿들을 구동시켜 WAS 로써 동작할 수 있도록 해준다.

 


HTTP/1.1 은 변해가는 웹서비스 환경에서 발생하는 상당한 규모의 문제들을 처리하는데 훌륭한 방법을 제시해주었고, 


그 결과 성능적으로나 서비스적으로나 변해가는 웹생태계에 걸맞는 진화를 보여주었다.


하지만 그럼에도 불구하고 웹의 사용자는 계속해서 웹서비스에게 많은 리소스를 요구하고, 많은 리소스로 클라이언트를 충족해줘야만 살아남을 수 있게 바뀌어 가고 있다.


실제로 웹 초창기에 비해 평균 다운로드하는 리소스의 용량은 60배 이상 늘었으며, 이 추세는 계속될 것으로 보인다.


이에 HTTP/2 는 HTTP/1.1 에서 다소 불안정하던 부분을 해소하고 웹어플리케이션을 더 빠르고 효율적으로 만들어주는데 초점을 두었다.


HTTP/2 의 근본을 이해하기 위해서는 Google 이 2000년대에 진행했던 프로젝트인 SPDY 를 이해해야 한다.


SPDY(스피디) 란 Google이 개발한 비표준 네트워크 프로토콜로 패킷 압축, Multiplexing 을 기반으로 인터넷에서의 Latency 를 줄이기 위해 고안된 프로토콜로, 초창기 크롬 브라우저에 탑재되어 높은 로딩 속도를 자랑하게 했던 구글의 자체 프로토콜이다.


HTTP/2 는 바로 이 SPDY 에 기반을 둔 HTTP 프로토콜 Layer 하위의 TCP 통신 레이어에 새로운 Binary 계층을 도입하여 HTTP 의 기반이 되는 TCP 연결의 호율성을 추구하였다.


HTTP/2 의 특징들은 다음과 같다.


(1) Binary Framework


 HTTP/2 는 TCP 계층과의 사이에 새로운 Binary Framework 를 통해 네트워크 스택을 구성한다.

기존에 텍스트 기반으로 Header 와 Data 가 연결되고 있던 1.1 이하 버전과 다르게 HTTP/2 는 전송에 필요한 메시지들을 Binary 단위로 구성하며 필요 정보를 더 작은 프레임으로 쪼개서 관리한다. 


여기서 데이터를 Binary Encoding 방식으로 관리하는데, 인코딩된 데이터를 다루기 위해서는 반드시 Decoding 과정이 필요하게 된다.


즉, 이말은 HTTP/1.1 버전의 클라이언트는 HTTP/2 버전의 서버와 통신이 불가능하다는 뜻이다.

그래도 걱정할 필요는 없다. 해당 메커니즘은 이미 도입이 되었고 충분히 지원하는 상황이기 때문에 이슈가 생기지 않는다면 추가로 신경쓸 필요는 없다.



(2) Packet Capsulation


 HTTP/2 의 패킷들은 더 작은 단위로 Capsulation 된다. 여기서 Frame 과 Message, Stream 이라는 개념이 도입된다.


 - Frame : HTTP/2 의 통신 최소단위로 모든 패킷에는 하나의 Frame Header 가 포함된다.

 - Message : Frame 의 시퀀스 데이터를 말한다.

 - Stream : 연결의 흐름을 의미한다. 


HTTP/2 의 모든 연결은 TCP 기반의 Stream 이며 양방향으로 Frame Header 를 지닌 Message 들을 통신한다.

데이터는 위에서 언급한 바 대로 Binary 인코딩된 데이터들이며 Multiplexing 과 성능 최적화 알고리즘들이 적용된다.



(3) Multiplexing 개선


<출처 : https://medium.com/@factoryhr/http-2-the-difference-between-http-1-1-benefits-and-how-to-use-it-38094fa0e95b>


 

 HTTP/1.1 에도 멀티플렉싱을 지원하기 위한 노력은 있었지만, HTTP/1.1 은 이 부분에서 다소 한계점을 갖고 있었다. 

(참고 : http://jins-dev.tistory.com/entry/HTTP11-%EC%9D%98-HTTP-Pipelining-%EA%B3%BC-Persistent-Connection-%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC)


요청과 응답의 동시처리는 이루어지나 결국 응답처리를 지연시키는 블로킹 방식이었기 때문에, 한 개의 Connection 이 하나의 Request / Response 를 처리하는 한계를 극복하기는 어려웠으며 그로 인해 결국 HOL(Head Of Line) 문제 발생과 연결에 있어서 비효율성을 갖는다는 아쉬운 부분이 있었다.


HTTP/2 는 이부분에서 개선된 멀티플렉싱을 지원하며, Connection 하나에서 다수의 입출력이 가능하게끔 지원한다.


이것이 가능한 이유는 HTTP/2는 패킷을 Frame 단위로 세분화하여 순서에 상관없이 받는쪽에서 조립하도록 설계하였기 때문이다. 


그렇기 때문에 HTTP/2 에서는 각 요청과 응답을 병렬로 전달할 수 있으며 하나의 Connection 에서도 여러 응답 / 요청을 처리할 수 있게 되었고 HTTP/1.1 에서 사용하던 임시방편을 사용할 필요가 없어졌다.



(4) Header Compression


 HTTP/2 의 Header 필드는 Huffman code 로 인코딩되며 이에 따라 텍스트적인 압축이 수반된다. 여기서 패킷을 더 최적화해 만들어진 HPACK 알고리즘은 Header 의 크기를 80% 이상 압축해서 전송한다고 알려져있다.



(5) Server Push


 HTTP/2 에서 서버는 단일 클라이언트의 요청에 대해 추가적인 응답을 내려줄 수 있다.

HTTP에서 Push 문제는 쉽지않은 이슈이지만, HTTP/2 에서는 이를 PUSH_PROMISE 라는 Frame 을 이용해 제공한다.

이렇게 제공되는 Push 리소스는 캐싱되거나 재사용 또한 가능해서 유용하게 사용이 가능할 것으로 보인다.



그 외에도 보안적인 측면이나 세세한 부분에서 HTTP/2 는 개선을 위한 노력들이 많이 보이며 확실히 효율적인 프로토콜로 자리매김한 것으로 보인다.


일각에서는 여전히 쿠키 보안에 취약하며 성능에 있어서의 향상 수준이 호환성을 저해할 만큼 대단한 것인가에 대한 의문도 있는 모양이지만, 개인적으로는 상당히 훌륭하고 배울 부분이 많은 프로토콜로 생각된다.



 무중단 배포란 알고있는대로, 서버를 실제로 서비스할 때 서비스적 장애와 배포에 있어서 부담감을 최소화할 수 있게끔 서비스가 중단되지 않고도 코드를 Deploy할 수 있는 기술이다


예전에는 배포 자체가 하나의 거대한 일이었고, 이를 위한 팀과 개발팀이 날을 잡고 새벽에 배포하는 일이 잦았지만, 최근에는 무중단 배포 기능을 탑재한 Deploy 자동화 툴을 이용해서 개발자들이 스스로 배포까지 담당하는 DevOps 의 역할을 하게되면서, DevOps 의 필수 기술 중 하나가 되었다.


 무중단 배포 방식에는 주로 사용되는 것들에 AWS에서 Blue-Green 무중단 배포(Blue는 기존버전, Green은 새로운버전. Router를 통해 Blue로 이동하는 트래픽을 Green으로 변경시켜준다. 원리는 동일하다.), Docker를 이용한 웹서비스 무중단 배포가 있다


IDC에서 직접 L4 스위치를 이용해서 하는 방안도 간단하지만, 이는 비용적으로 효율적이지 않아 많이 없어지는 추세이다


또한 NginX 등을 이용해서 저렴하게 무중단 배포를 하는 방식도 있다


이 방법을 사용하면 클라우드 인프라가 갖춰져있지 않아도 되고 별도의 인스턴스를 갖고 있지 않아도 가능하다

(Spring jar 2개를 여러 포트에 나눠서 배포하고 그 앞에 NginX 로 밸런싱해주면 된다.)


 무중단 배포의 원리




간단한 원리는 위와 같다. 핵심은 Reverse Proxy 가 서로 다른 인스턴스의 각기 다른 포트와 서브 도메인으로 연결하고, 지속적으로 Health Check 하면서 배포시 서브도메인을 메인 도메인으로 Switching 해주고, 배포가 끝나면 다시 메인도메인으로 Reload 해주는 것이다


이 구조에서 주의해야할 점은, 배포가 서비스에 영향을 주지 않도록 해야한다는 것이다. 예를들어 DB의 구조를 바꾸는 JPA와 같은 기술들이 사용되어 있을 경우 검토가 필요하다.


 도커 컨테이너를 이용하면 이는 매우 간단해진다

하나의 이미지에서 여러 컨테이너를 생성해서 호스트의 docker 명령어를 이용해서 손쉽게 서버 이중화 및 Switch, Reload 가 이루어진다

빌드 서버에서 이미지를 만들고 해당 이미지를 distribution 을 통해 다른 서버에서 이를 가져오는 식으로 구성된다.


 도커의 Service Discovery 라는 개념을 이용하면 nGinX 를 통해 배포할 때의 단점인 설정 파일의 수정과 재시작이 수반되어야 한다는점과 Proxy 대상 IP Port 가 고정이어야 한다는 점, Health Check 오버헤드를 피할 수 있다


Service Discovery는 서버들의 정보를 포함한 정보들을 저장해서 가져오고, 값의 변화가 일어날 때 이벤트 형식으로 설정을 수정하고 재시작하는 개념이다.




위의 구조에서는 Key/value 스토어를 이용해서 서버 정보를 저장하였으며 Configuration Manager 가 이를 watch하면서 이벤트 방식으로 설정 파일을 만들고 기존 파일에 덮어 쓰는 작업을 하고 있다


docker에서 대표적인 Service discovery tool docker-gen 이 있다


자세한 내용 참조

(https://subicura.com/2016/06/07/zero-downtime-docker-deployment.html)


실습가능한 참조 링크

http://jojoldu.tistory.com/267




nginx 의 설정은 nginx 를 구성하는 핵심으로 nginx 는 설정파일만 갖고 동작을 결정한다고 해도 과언이 아니다.


보통 nginx 의 환경설정을 구성하는 파일은 /usr/local/nginx/conf 폴더 하위에 위치하며 nginx.conf 파일을 수정함으로써 환경설정을 정의할 수 있다.


환경설정은 document 를 보는 것보다 예제로 파악하는게 좋아보이므로 예제를 중심으로 기술한다.



http {
### Load balancing ###
upstream {
### Target Proxy Host ###
server server01;
server server02;
}
### Main server configuration ###
server {
listen 80;
server_name nginx.sample.com;
client_header_timeout 10;
client_body_timeout 10;
charset UTF-8;
root /home/my-doc;
######### log #######
access_log logs/access.log main;
error_log logs/error.log;
#####################
############## internal location ############################
location /monitor {
allow 127.0.0.1;
deny all;

proxy_pass http://monitor.proxy:8080;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
############## main location ############################
location / {
proxy_pass http://localhost:8080;
}
}
}


nginx 설정은 계층적 구조를 이루며 하위 블록에서의 선언은 상위 블록에서의 선언을 덮어쓴다.


http 블록은 nginx 설정의 루트 블록이라고 할 수 있다. 기본적으로 여러개 정의할 수는 있으나 관리상의 이유로 보통 하나의 블록 안에서 해결한다.


server 블록은 Host 의 개념으로 웹서버의 메인 설정이라 할 수 있다.


upstream 블록은 로드밸런싱을 하는데 사용되며 nginx 를 로드밸런서로 사용할 경우 기술된 서버로 해당 요청을 중계해준다.


옵션값으로 default(선언하지 않을 경우 라운드로빈), least_conn (연결이 가장 적은 서버로 중계), ip_hash(ip값을 이용한 해시주소로 요청 분배) 등 분산 알고리즘의 설정이 가능하다.


log 항목은 nginx 의 중요 기능 중 하나로 nginx 를 거치는 모든 로그 및 에러 로그를 기록하는 데 사용된다.


location 블록은 서버 내에서 요청을 다르게 라우팅하고 싶을 경우 사용한다. nginx 로 프록시 서버를 구성할 때 해당 경로로 proxy pass 를 지정함으로써 라우팅을 처리할 수 있다.


nginx 의 설정 반영은 서버를 재시작해야 적용되므로 설정 후에는 OS 에 맞게 서비스를 재시작 해주도록 하자.





Cache 는 서버의 동작을 이해하는 데 있어서 빼놓을 수 없는 부분이며, 서버의 부하를 줄여주고 서버가 가진 능력을 최대한으로 활용할 수 있게 해줄 뿐만 아니라, 클라이언트에도 중요한 역할을 한다.


흔히 말하는 Cache 의 종류로는 Redis 나 Memcached 를 사용하며 대부분 인메모리 형태로 서버의 값을 저장하고, 필요할 때에 해당 값을 반환함으로써 서버의 작업 공수를 줄여준다.


Spring Framework 는 프레임워크 레벨에서 캐시의 추상화를 지원해준다.


Cache의 추상화란, 흔히 캐시를 사용할 때 작업이 필요한 부분에 대한 인터페이스를 제공해준다는 뜻이다.


가령, 웹서버에 캐싱 기능을 적용하기 위해서는 다음과 같은 캐싱의 기본 로직이 탑제되어야 한다.


(1) Memory 혹은 원격 캐시에 연결된 객체를 생성한다. (이를 Cache Manager라 한다.)


(2) 캐시의 값을 불러온다.


(3) 캐시에 값이 존재하지 않는다면 캐싱할 값을 일정한 기준을 갖고 등록한다.


(4) 등록된 캐시값에 대해 조회가 가능하다.


(5) 필요할 때에 캐시의 값을 불러오고 적당한 때에 캐시를 업데이트 한다.


위의 단계들은 기본적으로 캐시가 가져야할 역할이며, 위의 역할 정도는 수행할 수 있어야 서버측에서 "캐시" 로써 동작한다고 할 수 있다.


Spring 에서 위와 같은 단계는 다음 Annotation 들로 대체될 수 있다.


Cache 의 생성 : Spring 의 Cache Configuration 참조


Cache Key Value 의 등록 : @Cacheable



@Cacheable(value="user")
public List<User> getUserListFromDB() {
return selectUserListFromDB();
}

@Cacheable(value="user", key="#uid", condition="#result!=null")
public User getUserFromDB(String uid) {
return selectUserFromDB(uid);
}


@Cacheable 어노테이션과 함께 저장할 캐시의 이름을 value에 명시하고, key 값을 지정하면 해당 결과값을 설정된 Cache에 캐싱할 수 있다.


값은 캐싱될 뿐 아니라, 다시 해당 함수로 접근 시 캐싱된 값이 있다면 내부 함수를 수행하지 않는다.


condition 은 해당 캐시에 적용 시 어떤 항목들에 대해 캐싱하거나 캐싱하지 않을 지를 결정할 수 있다.



Cache Key Value 의 삭제 : @CacheEvict



@CacheEvict(value="user", key="#user.uid", beforeInvocation=false)
public void putUserToDB(User user) {
insertUserToDB(user);
}


@CacheEvict 어노테이션을 이용하면 해당 캐시 이름과 Key 에 저장되어 있는 Cache Value 를 제거할 수 있다. 

이 때 종종 사용되는 옵션으로 beforeInvocation 옵션이 있는데, 이 옵션을 true 로 지정하면 함수가 시작되기 전에 캐시를 비우는 작업을 수행한다.


Cache 의 갱신 : @CachePut


@CachePut 어노테이션은 값이 변경되었을 경우에만 해당 캐시를 비운다.


여러 개의 Caching 동작에 대해 : @Caching



@Caching(evict = {
@CacheEvict(value="user", key="#user.uid")),
@CacheEvict(value="userGroup", key="#user.groupNo")
})
public void addNewUserToDB(User user) {
insertUserToDB(user);
insertUserGroupToDB(user.getUserGroup());
}


Cache 에 대한 여러 동작을 수행하고자 할 때에는 @Caching 어노테이션을 사용한다.



Spring 의 Cache 어노테이션은 내부적으로 SPEL(Spring Expression Language) 라는 문법을 사용한다.

위의 간단한 예시만으로도 사용하는 데 큰 지장은 없을 것이다.



+ Recent posts