Java9 에서 새로 추가된 JShell 을 이용하면 마치 Java를 파이썬을 사용하듯이 손쉽게 프로토타이핑할 수 있다.


JShell 이란 Command Line Interface 로 제공되는 자바 코드 작성을 위한 도구이다.


당연히 복잡한 종류의 모듈을 JShell 을 이용해 제작하는 건 무리지만, 간단히 자바 코드를 실행하고, 모듈을 작성해보거나, 


Java Library 의 기능을 테스트해보아야 한다면 실무에서도 유용한 도구이다.



JShell 을 이용하는 방법은 다음과 같다.


1. Java9 를 설치한다.



2. 환경변수 Path 에 설치된 경로를 지정한다.




3. 커맨드 라인에서 Jshell 을 실행한다.




Jshell 을 이용하면, 커맨드라인에서 즉시 자바 명령어를 실행할 수 있으며, 다음과 같이 모듈의 Import 및 사용 역시 가능하다.


이렇게 실행하는 Java Statements, Definition, Expression 등을 스니펫(Snippet) 이라고 하며, 스니펫을 실행하면 JShell 에서 즉각적인 피드백(Feedback)을 제공해준다.






또한 JShell 은 위의 그림에 마지막에 작성된 스니펫인 /edit 을 이용하면, 별도의 Edit pad 를 제공하는데, 


이를 이용하면 JShell 만을 이용해서도 마치 IDE 를 사용하는 듯 하게 에디터를 이용해 작업을 해볼 수 있다.





실행시킨 Jshell 은 /exit 명령어를 이용해서 종료할 수도 있다.


실무에서도 JShell 은 간단한 코드를 프로젝트 수정없이 테스트할 경우 굉장히 유용하며, 본인은 때때로 알고리즘을 테스트할 때 JShell 을 이용하기도 한다.


따라서... 이제는 더 이상 간단한 자바 코드의 Prototyping 을 위해 이클립스를 켜지 않아도 된다!


이처럼 Java 는 버전을 올려감에 따라서 함수형 언어의 특징을 받아들이면서 좀 더 Modern 한 언어의 특징을 위해 업그레이드되어 가고 있다.


좀 더 자세한 사용은 아래 블로그에 잘 정리되어있으니 참고해보는 것도 좋을 듯 하다.


http://taewan.kim/post/trans_jshell/




컴퓨터는 CPU에서 tick 을 발생시키며 1970년 1월 1일부터 발생된 tick 의 수를 계산해서 시간을 측정한다.


이 시작점을 UNIX 계열에서는 POSIX time 혹은 Epoch time 이라 하며, 1970년 1월 1일 00:00:00 UTC 이고, 


이때부터 경과 시간을 초로 환산해서 사용한다.


이런 방식에는 몇가지 문제가 있는데, 먼저 윤초(예를 들어 1998년 12월 31일 23:59:60)는 표현할 수 없이 무시된다.


현재 32비트 운영체제에서 초 시간을 지정하는 time_t 자료형은 32비트 Integer 이기 때문에, 


2038년이 되면 overflow 문제가 발생한다.


이를 2038 Years Problem 이라 하며 2038년 1월 19일 03:14:07 UTC 가 되면 오버플로우가 발생해서 


32비트 유닉스 시스템의 시간은 음수가 되어버린다.


그렇기 때문에 현재 int32 를 int64 로 바꾸는 노력을 계속하고 있고, 지속적으로 수정이 진행되고 있다.






백엔드를 개발하다보면, 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 는 개선을 위한 노력들이 많이 보이며 확실히 효율적인 프로토콜로 자리매김한 것으로 보인다.


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





행렬의 곱셈을 수행할 때에는 특별한 법칙이 있다.


곱하고자 하는 앞 행렬의 Column 수와 뒷 행렬의 Row 수가 일치해야 하며, 계산 결과로 앞 행렬의 Row X 뒷 행렬의 Column 크기를 갖는 행렬 결과가 나타난다.


그렇기 때문에 여러 개의 행렬을 곱할 때, 행렬의 곱셈 순서에 따라 곱셈 연산의 순서가 달라진다. 


앞 행렬의 Column 과 뒷 행렬의 Row 가 넓은 범위에서 공통될 수록 연산의 비용이 커지게 된다.


행렬의 곱셈은 CS에서도 많이 사용되는 연산이기 때문에 이 문제는 알고리즘적으로도 유명한 행렬 곱셈(Matrix Multiplication) 문제이다.


이 문제의 일반적인 솔루션은 DP를 이용한 방식으로 O(n^3) 만큼의 시간복잡도와 O(n^2)의 공간복잡도를 갖는다.


간단하고 직관적이므로 DP를 입문할 때 대표적인 예제로 많이 언급된다.


각각 r[i]개의 행(row)과 c[i]개의 열(column) 을 가진 행렬 n개를 순차적으로 곱셈한다고 가정해보자.


여기서 순차적이라 함은 가령 (AB)C 와 A(BC) 처럼 A, B, C 행렬 3개의 곱의 순서가 보장되어야 한다는 것이다.


이 때 알고리즘은 다음과 같다.


(정의)

dp[i][j] => 행렬i부터 행렬j 까지를 곱할 때 필요한 곱셈의 최소횟수


dp[i][j] = dp[i][k] + dp[k+1][j] + 곱셈 비용 의 최솟값.


간단히 설명하면 구간 i ~ j까지 행렬곱셈을 수행시, i~k 까지의 행렬곱셈 X k+1~j 까지의 행렬곱셈 을 순차적으로 수행하는데 추가로 k~k+1 두 곱셈 결과 행렬의 연산 비용을 추가해주는 것이다.


다음 예제와 해답 코드로 공부해보도록 하자.


예제 : https://www.acmicpc.net/problem/11049 



#include <iostream>
using namespace std;

int n = 0;
int r[501] = { 0, };
int c[501] = { 0, };
int dp[501][501] = { 0, };
int min(int a, int b)
{
    return (a > b) ? b : a;
}

int main()
{
    cin >> n;
    for (int i = 0; i < 501; i++)
    {
        for (int j = 0; j < 501; j++)
        {
            dp[i][j] = 0xfffffff;
        }
    }
    for (int i = 0; i < n; i++){
        cin >> r[i] >> c[i];
        dp[i][i] = 0;
    }
    int cnt = 0;
    for (int i = 1; i < n; i++)
    {
        for (int j = 0; j+i < n; j++)
        {
            for (int k = 1; j + k < n; k++)
            {
                int left = dp[j][j + k - 1];
                int down = dp[j + k][j + i];
//곱셈 결과
                int val = (c[j + k - 1] == r[j + k]) ? r[j] * c[j + k - 1] * c[j + i] : 0xfffffff;
                dp[j][j + i] = min(dp[j][j + i], left + down + val);
            }
        }
    }
    cout << dp[0][n - 1] << endl;
    return 0;
}



Matrix Multiplication 알고리즘은 Strassen Algorithm 을 통해 O(n^2)에 가깝게 튜닝이 가능하다. 

다소 수학적인 이해가 필요하므로 다음 링크를 참조한다.

(https://en.wikipedia.org/wiki/Matrix_multiplication)





Hashing 이란 임의의 데이터를 고정된 길이의 구분되는 데이터로 매핑하는 과정을 말한다.


해시 알고리즘은 Computer Science 전반에 걸쳐 사용되는데 암호학적 Hash 또는 자료구조로써 해시테이블에서 구별된 인덱스를 나타내기 위한 도구로도 사용된다.


해시 알고리즘의 특징은 다음과 같다.


(1) 단방향(One-way) : 해시 알고리즘은 데이터를 단방향으로 변경하며, 그 역연산은 계산이 사실상 불가능하다. 

이는 수학적으로 "One-way Function" 을 사용하기 때문이며, 이 함수는 충분히 큰 가능한 경우의 수에 대해 다항 시간 안에 입력을 구하기 어렵게 한다.


* 물론 현재 수학적으로 정의된 One-way Function 이 실제 일방향인지는 증명되지 않았다. 그렇기 때문에 해시 알고리즘은 뚫린 알고리즘이 있을 수 있다.

만약 수학적으로 One-way Function 이 실제로 역방향 연산이 불가능한지가 증명된다면 가장 큰 수학적 난제가 풀리는 상황이며, 이는 P != NP 문제를 증명하는 셈이 된다.



(2) 임의성(Randomness) : 해시 알고리즘은 일정한 포맷을 기준으로 데이터의 변경에 따라 유추할 수 없게 임의로 인코딩된다. 

값 하나만 바뀌어도 전체 해시 값은 이전과 아예 다른 방향으로 패턴이 만들어진다.



(3) 공개된 함수(Hash function) : 해시 알고리즘은 아무나 알수없는 키값을 가지는 것이 아닌 모두에게 공유된 해시 함수(Hash function)을 사용한다. 이 말은 공격하는 측도 함수를 알고 있다는 뜻이다. 

대신 암호화와 다른 부분이므로 매우 빠른 속도로 함수가 수행된다.



Hash Algorithm 은 단방향인만큼 해커가 공격 시에 역추적을 시도하는 일은 거의 없다. 


대신 주로 공격할 때에는 입력값을 다변화해서 Brute-Force 를 적용하거나 특정 키를 바탕으로 범위를 축소하는 방향으로 공격이 이루어진다.


이런 점을 보완하기 위해 대부분의 Hash Algorithm 은 알고리즘을 여러번 수행하거나 해시와 암호화 알고리즘을 같이 병행해서 수행하거나 임의의 키값(Salt) 를 추가하기도 한다.



반면, 암호화 알고리즘은 양방향성을 지닌다.


암호화 알고리즘을 위해선 암호화(Encrypt)에 쓰이는 Key 가 필요하며, Key 와 사용자 데이터를 알고리즘을 통해 묶어서 암호문을 생성해낸다.

역으로 암호문은 Key 를 통해 복호화(Decrypt)되며 사용자 데이터를 꺼낼 수 있게 주어진다.


암호화는 해시 알고리즘과 달리 데이터의 인덱싱보다는 전달에 의미를 두기 때문에 좀 더 복잡하며 보안을 위한 다양한 방법이 수반된다.


실제 보안에서는 암호학적 해시 알고리즘은 암호화에 주로 같이 사용된다.


유명한 암호화 해시 알고리즘으로는 SHA-1, SHA-256, SHA-512, MD5 등이 있으며, 해시 값을 만들어내는 Hash function 자체로는 Murmur hash 가 있다. 



 무중단 배포란 알고있는대로, 서버를 실제로 서비스할 때 서비스적 장애와 배포에 있어서 부담감을 최소화할 수 있게끔 서비스가 중단되지 않고도 코드를 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


알고리즘을 구현하다보면 2차원 배열에서 분할 탐색을 적용하고 싶을 때가 있다.


특히 알고리즘 문제를 풀 때 이런 경우를 종종 마주치게 되는데, 구현에 익숙하지 않다면 상당히 당황스럽다.


2차원 배열에서 Binary Search 를 적용하기 위해선 몇가지 방법이 있는데, 가장 간단한 방법은 2차원을 1차원으로 축소시켜서 Binary Search 를 적용하는 방법이다.


m개의 행과 n개의 열을 가진 2차원 Matrix 가 있다고 가정해보자.


이를 1차원으로 이해한다면 0~m*n 까지의 Element 가 포함되어 있다고 이해할 수 있다.


그리고 행과 열의 정보는 n 으로 나눈값과 n으로 나눈 나머지를 통해 알 수 있다.


코드를 통해 이해해보자.



public boolean searchMatrix(int[][] matrix, int target) {
if(matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return false;
}
int m = matrix.length;
int n = matrix[0].length;

int start = 0;
int end = m*n-1;

while(start<=end) {
int mid = (start+end)/2;
int midX = mid/n;
int midY = mid%n;
if(matrix[midX][midY] == target) {
return true;
}
if(matrix[midX][midY] < target) {
start = mid + 1;
} else {
end = mid - 1;
}
}
return false;
}


일반 Binary Search 와 동일한 로직이면서 좌표값만 변경하면서 똑같이 구현한 내용이다.


이진탐색 및 분할정복이 익숙하지 않다면 익혀두고 사용하는 것도 괜찮은 방법이다.




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 에 맞게 서비스를 재시작 해주도록 하자.



+ Recent posts