5 Week
SOLID
객체지향의 5가지 설계원칙
단일 책임 원칙 (SRP) - 한 클래스는 하나의 책임만 가져야 한다.
모든 클래스는 하나의 책임만 가지며, 클래스는 그 책임을 완전히 캡슐화 해야한다.
한 클래스가 수행할 수 있는 책임 (기능)이 여러가지라면, 클래스 내부 함수끼리의 강한 결합이
발생할 가능성이 높아지고 이는 유지보수에 비효율적이다.
한 클래스를 변경하기 위해 한가지 이상의 이유를 생각할 수 있다면, 그 클래스는 한가지 이상의 책임을 맡고있는 것이다.
개방-폐쇄 원칙 (OCP) - 확장에는 열려있고 변경에는 닫혀 있어야 한다.
수많은 모듈 중 하나를 수정할 때, 해당 모듈을 이용하는 다른 모듈들을 고쳐야 한다면 비효율적이다.
즉, 기능을 추가하거나 변경해도 이미 제대로 동작하고 있던 코드를 변경하지 않아도 기존의 코드에
새로운 코드를 추가함으로써 기능의 추가나 변경이 가능한걸 의미한다.
추상화는 개방-폐쇄 원칙의 핵심요소이다.
자주 변화하는 부분을 추상화 함으로써 유연함을 높인다.
모듈은 고정된 추상화에 의존하기 때문에 수정에 대해 닫혀 있을 수 있고, 추상화의 파생클래스를 만드는것으로 확장이 가능하다.
객체지향 프로그래밍의 가장 큰 장점인 유연성, 재사용성, 유지보수성을 위한 핵심원칙이다.
리스코프 치환원칙 (LSP) - 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위타입의 인스턴스로 바꿀 수 있어야 한다.
하위타입객체는 상위타입객체에서 가능한 행위를 수행할 수 있어야 한다.
즉, 상위타입객체를 하위타입객체로 치환해도 정상적으로 동작해야 한다. == IS-A
객체지향 초기에는 상속을 사용하도록 가이드하는 방법 정도로 간주되었지만 시간이 지나면서 인터페이스와 구현체에도 적용되는 광범위한 원칙으로 바뀌었다.
인터페이스 분리원칙 (ISP) - 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
클라이언트가 자신이 이용하지 않는 메서드에 의존하지 않아야 한다.
큰 덩어리의 인터페이스들을 구체적이고 작은 단위들로 분리시킴으로써 클라이언트들이 꼭 필요한 메서드들만 이용할 수 있게 한다.
이는 클라이언트가 사용하지 않는 인터페이스에 변경이 발생하더라도 영향을 받지 않게 한다.
의존관계 역전원칙 (DIP) - 추상화에 의존해야하고, 구체화에 의존하면 안된다.
의존 관계를 맺을 때,
변하기 쉬운 것 (구체적인 것) 보다는 변하기 어려운 것 (추상적인 것)에 의존해야 한다
즉, 구체화된 클래스 보단 추상클래스나 인터페이스에 의존해야 한다.
상위계층이 하위계층의 구현으로부터 독립적이어야 한다.
- 상위모듈은 하위모듈에 의존해선 안되고, 상위모듈과 하위모듈 모두 추상화에 의존해야한다.
- 추상화는 세부사항에 의존해서는 안된다. 세부사항이 추상화에 의존해야 한다.
스레드
운영체제에 메모리를 할당받아 실행중인 프로그램을 프로세스라고 한다.
스레드란 프로세스 내에서 실제로 작업을 수행하는 주체를 의미한다.
모든 프로세스는 한 개 이상의 스레드가 존재하며 1개는 단일스레드 2개 이상은 멀티스레드 라고 한다.
독립적인 프로세스들은 자원 및 데이터를 공유하지않아 리소스가 많이들어가지만
멀티스레드의 경우는 자원 및 데이터 공유를 공유하기 때문에 리소스가 적게들어간다.
물론 데이터를 공유하기 때문에 동기화가 중요하다 -> thread-safe
모든 자바 어플리케이션은 메인스레드가 main() 메서드를 실행하면서 시작한다.
이러한 Main Thread 흐름 안에서 싱글 스레드가 아닌 멀티 스레드 어플리케이션은 필요에 따라 작업 쓰레드를
만들어 병렬로 코드를 실행할 수 있다. 단일 스레드 같은 경우 메인 스레드가 종료되면 프로세스도 종료되지만
멀티 스레드는 메인 스레드가 종료되더라도 실행 중인 스레드가 하나라도 있다면 프로세스는 종료되지 않는다.
생성
- Runnable 인터페이스 구현
- Thread 클래스 상속
- 람다
class OfThreadClass extends Thread {
@Override
public void run() {}
}
class OfRunnableInterface implements Runnable {
@Override
public void run() {}
}
class OfLambda {
Thread thread = new Thread(() -> {
String threadName = Thread.currentThread().getName();
System.out.println(threadName);
});
thread.setName("Thread #1");
thread.start();
}
자바에서는 다중상속이 안되기 때문에, Thread 클래스를 확장하는 클래스는 다른 클래스를 상속받을 수 없다.
하지만 Runnable 인터페이스를 구현하는 경우에는 다른 인터페이스를 구현할 수도 있고 다른클래스도 상속 받을수 있다는 장점이 있다.
자바는 느리다?
자바는 Python , JavaScript 같은 인터프리터 , 스크립트 언어 보다는 빠르다.
하지만 C/C++ , Pascal , Fortran 같은 네이티브 바이너리 코드를 만드는 언어에 비해선 느리다.
그 이유는 여러가지가 있지만 크게 2가지를 들 수 있다.
- 객체지향 언어의 특성
- JVM이라는 가상머신 사용
자바는 개발자의 편의성을 위해 성능을 희생하고 있는데 시간이 지나면서
자바의 소프트웨어와 하드웨어가 발전해서 초기이슈였던 성능이슈는 잠잠해 졌지만
여전히 부족한 하드웨어 스펙을 쓰는 소규모 장비에선 C언어를 주로 사용한다.
객체지향의 특성
자바는 클래스 단위로 모든 코드를 작성한다. C언어와 같은 함수단위가 아니기때문에
다른 클래스에 있는 메서드나 정보를 사용하기 위해선 해당 클래스 전체를 인스턴스로 만들어야 한다.
그만큼 메모리와 코드를 찾아 로드하는 시간에 많은 리소스가 들게되고 이것을 피하려고 static 사용이나
클래스 단위를 잘게 쪼개서 작성할 경우 객체를 다루는 효율성이 떨어지게 된다.
그렇기 때문에 자바는 C언어와 달리 모든 코드를 미리 메모리에 올려두지 않고 필요할 때 마다 가져다 쓰는
동적 할당 방식을 사용하기 때문에 C언어보다 상대적으로 느릴 수 밖에 없다.
JVM
C언어는 컴파일 시 코드를 모드 기계어로 번역해 메모리에 올려두고 실행한다. 하지만 자바에서는
바이트코드로 먼저 컴파일 한 뒤, 동적할당 된 코드를 JVM의 JIT과 같은 방식으로 실행한다.
이렇게 함으로써 운영체제에 종속적이지 않게 된다는 이점이 있지만 그만큼 성능을 희생하게 된다.
또한 C언어에서는 동적할당한 메모리를 개발자가 직접 바로 해제하지만, 자바는 GC가 메모리를 해제하기 때문에
이 GC또한 프로그램이기 때문에 메모리와 연산작업을 동반하게 된다.
인터프리터 / 스크립트 언어
Python과 같은 인터프리터 방식의 언어는 코드를 한줄씩 읽어 기계어로 번역한다.
그리고 같은 기능을 하는 코드가 다시 나와도 또 다시 해석하여 결과를 출력한다.
이 때문에 일반적으로 컴파일 방식의 언어보다 수행 속도가 느리다.
하지만 수정사항이 발생했을 때, 컴파일러가 소스코드를 읽어 실행 파일을 만드는 자바는 소스코드를
다시 컴파일 해야하지만 인터프리터 언어는 소스코드를 수정해서 실행시키면 끝나기 때문에 수정이
아주 간단하다는 장점이 있다. 즉, 개발편의성이 좋다는 의미이다.
Python은 이 약점을 극복하기 위해 라이브러리들의 내부는 C / C++ 으로 되어있다.
그리고 이 장점을 최대한 살린것이 스크립트 언어이다.
IO / NIO
자바는 직접 메모리를 관리하고 운영체제의 시스템 콜 을 직접 사용하기 힘들다.
자바가 특별히 성능이 좋지 않은 부분이 IO 이고 이걸 개선한 것이 NIO 패키지 이다.
- IO
-
Blocking API
API 를 호출한 스레드가 API의 작업이 끝날 때까지 다른동작을 하지 않는 API 를 말한다.(idle 상태)
자바의 기본 IO는 이Blocking API로 사용되어 왔기 때문에File IO뿐만 아니라
Network IO또한 오래 걸리기 때문에 더욱 자바에게 느리다는 인상을 심어준 녀석이다. -
Stream 기반
스트림 기반의 IO는 스트림으로부터 한번에 여러 바이트를 읽는다. 데이터는 캐싱되어 있지 않고
스트림 속 데이터에서 앞뒤로 이동할 수 없다. 만약 스트림으로 읽은 데이터 내부에서 앞뒤로
이동해야 한다면 버퍼를 만들어 캐싱을 해야한다.
-
- NIO
-
Non Blocking API
API 호출 시 요청한 작업의 완료여부와 상관없이 즉각적으로 현재 상태에 대한 답이 온다.
그렇기 때문에 API 호출 후 스레드 제어권이 있기 때문에 다른작업을 진행할 수 있다. -
Buffer 기반
이미 처리된 버퍼로부터 데이터를 읽는다. 필요하다면 버퍼 내부에서 앞뒤로 이동할 수 있다.
즉, 데이터를 처리하는 동안 좀 더 유연함을 제공해 주지만 데이터를 완벽하게 처리하려면
필요한 데이터가 모두 버퍼 안에 있어야 한다. 또한 버퍼에서 더많은 데이터를 읽을때 버퍼속에서
아직 전처리되지 않은 데이터를 사용하지 않도록 확실히 해야한다.
-
NIO는 불특정 다수의 클라이언트 연결 또는 멀티 파일들을 넌블로킹이나 비동기로 처리할 수 있다.
과도한 스레드 생성을 피하고 스레드를 효과적으로 재사용한다는 점이 큰 장점이다.
운영체제의 버퍼(다이렉트 버퍼)를 이용한 입출력이 가능하기 때문에 입출력 성능 향상
NIO는 연결 클라이언트 수가 많고, 하나의 입출력 처리 작업이 오래 걸리지 않는 경우에 사용하는것이 좋다.
스레드에서 입출력 처리가 오래 걸린다면 대기하는 작업의 수가 늘어나기 때문에 제한된 스레드로 처리하는 것이 불편할 수 있다.
대용량의 데이터 처리의 경우 IO가 좋다.
연결 클라이언트 수가 적고 전송되는 데이터가 대용량이면서 순차적으로 처리될 필요성이 있는 경우
IO로 서버를 구현하는 것이 좋다. NIO는 버퍼 할당 크기도 문제되고, 모든 입출력 작업에 버퍼를
무조건 사용해야 하므로 받은 즉시 처리하는 IO보다 복잡하다.
To Do
I/O
데이터의 입력(Input) / 출력(Output)이다. I/O 는 어플리케이션 성능에 가장 큰 영향을 미치는데
I/O 에서 발생하는 시간은 CPU를 사용하는 시간과 대기 시간이 있고 어플리케이션이 연산을 할 때까지
CPU가 다음 작업 실행을 block 한다. I/O 로 인한 blocking은 CPU를 긴 시간동안 idle 상태에 두는데
이것은 다른작업을 할 수 있음에도 할수가 없어 매우 비효율 적이다.
- 파일
I/O뿐만 아니라 어떤 디바이스를 통해 입력과 출력이 이뤄지는 작업을 모두 말한다.- 다른 서버로부터 데이터를 전송받는것도
I/O에 포함한다. - 콘솔에 출력하는 것도 스트림을 통해 출력하는 것이기 때문에
I/O이다.
- 다른 서버로부터 데이터를 전송받는것도
I/O 는 어플리케이션에서 직접 수행 될 수 없고 무조건 커널에 한번 이상 시스템콜을 보내야 한다.
시스템 콜을 보내면 그순간 커널로 제어권이 넘어가고 (context-switch), 유저 프로세스 / 스레드는 제어권이
다시 돌아오기 전까진 block상태가 되고 그동안 다른 작업을 하지 못하게 된다.
blocking I/O
I/O 작업이 진행되는 동안 프로세스가 자신의 작업을 중단하고 I/O가 끝날때까지 block 되는 방식이다.
I/O 가 호출되면 완료되기 전까지 제어권을 커널이 가져가서 유저 프로세스 / 스레드는 block 상태가 된다.
- 장점
- 호출마다 스레드를 생성하니 요청이 적은 서비스에서 효율이 좋다. (병렬작업의 장점)
- 단점
- 요청마다 스레드를 생성하는 부분의 리소스가 크다.
context-switching이 빈번하게 일어난다.- 각각의 스레드는
I/O작업이 완료되기 전까진 block 상태가 된다.
전통적인 방식에서는 스레드를 늘려 멀티스레드를 지원하는 방식으로 진화해 나갔다.
하지만 스레드 갯수에는 한계가 있고 리소스 측면에서도 좋지 않기 때문에 각 I/O에 대해서 장시간
사용하지 않고 실행되어 있는 스레드를 방치하는 것은 효율성 측면에서 최상의 방법은 아니다.
non blocking
I/O 작업이 완료될 때 까지 커널이 제어권을 가지지 않고 I/O 호출에 대해 즉시 응답을 리턴하는 방식이다.
I/O 호출되면 작업 여부와는 무관하게 즉시 결과를 리턴하고, 커널이 시스템콜을 받자마자 CPU 제어권을 다시 어플리케이션에게
넘겨주게 된다. 그리고 어플리케이션은 다른 작업을 수행하다 중간중간 시스템콜을 보내 I/O가 완료되었는지 커널에게 물어보고 완료되면 I/O 작업을 완료한다.
- 장점
I/O작업이 완료되기 전까진 block 상태가 되지 않는다.
- 단점
- 반복적으로 시스템콜이 일어나게 된다.
싱글스레드 방식으로 block되는 시간을 최소화 할 수 있어 I/O 작업(read/write) 이 많은 경우 유리하지만
CPU의 할일이 많은 경우에는 적합하지 않다.
I/O 이벤트 통지 모델
non blocking 의 문제인 반복적 시스템콜 호출을 해결하기 위해 I/O 이벤트 통지 모델이 도입되었다.
반복적으로 시스템콜을 호출하지 않고 입력 버퍼에서 완료되었다는 알림(이벤트 통지 모델)을 주게 된다.
I/O 결과 반환 방식에 따라 Sync(동기) / Async(비동기) 모델로 분류한다.
- 이벤트 : 수신버퍼 or 출력버퍼에 데이터를 처리하는 동작을 의미한다.
- 수신버퍼 : 입력버퍼에 데이터가 수신되었다는 걸 알림 출력
- 출력버퍼 : 출력버퍼가 비었으니 데이터 전송이 가능한 상황을 알림
Sync (동기)
I/O 작업이 진행되는 동안 유저 프로세스는 결과를 기다렸다가 이벤트(결과)를 직접 처리하는 방식이다.
blocking 방식처럼 완료될 때 까지 기다릴 수도 있고, non blocking 방식처럼 커널에 계속 요청할 수 도 있다.
I/O 가 완료될 때 까지 계속 기다리던가, 다른작업을 하면서 기다리던가 결국에는 기다려야 하지만
다른점은 notify를 유저 프로세스가 주체적으로 진행하고 커널은 유저 프로세스의 요청에 수동적으로 응답한다.
Async (비동기)
I/O가 진행되는 동안 유저 프로세스는 자신의 일을 하다가 이벤트 핸들러에 의해 알림이 오면 처리하는 방식이다.
결국 notify를 커널이 주체적으로 진행하며, 유저 프로세스는 수동적인 입장에서 통지가 오면 그때 I/O 처리를
한다. (이벤트 핸들러, callback)에 의해 운영체제에서 처리 결과 통지받는다.
뭐쓰는데?
Sync : 어떤 작업을 수행한 결과로 다음 작업을 수행해야 하는 즉, 순차적 작업일 때 (인출 후 송금)
Async : I/O 작업이 많을 때
차이?
- 블로킹/논블로킹 : 각 작업의 수행 형태와 우선순위에 따라 각 작업의 수행가능 시기를 어떻게 제어할 것인가.
- 동기/비동기 : 각 작업이 주고받는 데이터의 상태와 흐름을 어떻게 제어할 것인가. 데이터의 일관성 유지
즉, 특정 작업의 행위가 일어나는 시점 관리 라는 관심사가 다르다.
또한notify의 제어권을 가짐으로써 시스템콜 호출에 대한 차이도 있다.
동시성이슈
동시성이슈란?
- 노드js는 프로세스에 참가하는 스레드가 하나 인데 동시성문제에 어떻게 이점을 가져갈까?
- 노드는 컨텍스트 스위칭에 들어가는 리소스가없다?
동시성 임계구역
Web 동작
- Request
- 사용자가 입력한
URL주소 중 도메인 네임 부분을DNS서버에서 검색 후 IP 주소로 변환 - IP 주소로
HTTP프로토콜을 사용해HTTP요청 메시지를 생성 후TCP프로토콜을 사용해 사용자가 입력한URL정보와 함께 전송
- 사용자가 입력한
- Response
HTTP메시지를 받고 요청 URL에 대한 데이터 검색- 검색된 데이터를
HTTP프로토콜을 사용해HTTP응답 메시지를TCP프로토콜을 사용해 전송 - 랜더링 과정을 거쳐 클라이언트 화면에 표시
- Web Server
HTTP프로토콜을 기반으로 하여 웹 브라우저의 요청을 서비스 하는 기능을 담당- 정적 인 컨텐츠를 제공할 때는
WAS를 거치지 않고 바로 제공 - 동적 인 컨텐츠 요청이 들어오면 요청을
WAS로 보내고 처리한 결과는WAS가 전달 Apache Server,Nginx,IIS등
- WAS (Web Application Server)
- DB 조회나 다양한 로직 처리를 요구하는 동적 인 컨텐츠를
HTTP통신을 통해 제공하는 기능을 담당 - 웹 컨테이너, 혹은 서블릿 컨테이너 라고도 불리우며
JSP,Servlet구동 환경을 제공하는 서버 - 분산 트랜잭션, 보안, 메시징, 스레드 처리 등의 기능을 처리하는 분산 환경에서 사용
Tomcat,JBoss,Jeus,Web Sphere등
- DB 조회나 다양한 로직 처리를 요구하는 동적 인 컨텐츠를
- 2개를 따로 쓰는 이유?
- 기능을 분리하여 서버 부하를 방지
- 오류 화면 노출 가능 (
WAS장애 시 오류 화면 노출 불가능 할 수 있음) - 여러 대의
WAS를 연결해 로드 밸런싱 용도로 사용 - 물리적으로 분리하여 보안을 강화
SSL대한 암복호화 처리에Web Server를 사용
- 여러 언어의 웹 어플리케이션 서비스가 가능
- 하나의 서버에서
PHP Application,Java Application를 함께 사용하는 등
- 하나의 서버에서
쿠키 / 세션
HTTP 프로토콜 환경은 connectionless, stateless 하기 때문에 서버는 클라이언트가 누구인지 매번 확인해야 하기 때문에 이 특성을 보완하기 위해서 쿠키와 세션을 사용한다.
예를들어 쇼핑몰에서 옷을 구매하기위해 로그인했지만 페이지를 이동해야할때마다 계속 로그인을 해야한다.
쿠키와 세션은 비슷한 역할을 하며 동작원리도 비슷하지만 정보가 저장되는 위치가 다르다.
- connectionless
- 클라이언트가 요청한 후 응답을 받으면 연결을 끊어버림
- 클라이언트가 request를 보내면 서버는 response를 보내고 접속을 끊는다.
- stateless
- 통신이 끝나면 상태를 유지하지 않는 특성
- 연결이 끝나는 순간 클라와 서버의 통신이 끝나며 상태정보를 유지하지 않는다.
cookie
- 클라이언트 로컬에 저장되어 있는 키와 값으로 구성된 데이터 파일
- 인증의 유효시간을 명시할 수 있고, 유효시간이 정해지면 브라우저가 종료되도 인증이 유지됨
- 쿠키는 클라이언트의 상태정보를 로컬에 저장했다가 참조함
- 따로 요청하지 않아도 Request Header를 넣어서 자동으로 서버에 전송
-
요청속도는 세션보다 빠르다.
- 어디에써?
- 로그인 시 “아이디와 비밀번호를 저장하시겠습니까?”
- 장바구니 기능
- 자동로그인, 팝업 등에서 “오늘 더이상 이창을 보지 않음”
session
- 쿠키를 기반으로 하지만 서버에서 관리함
- 클라이언트를 구분하기 위해 세션 ID를 부여해 브라우저를 종료할 때 까지 인증상태를 유지
- 접속시간에 제한을 둬서 정보가 유지되지 않게 할 수 있음
- 서버에 정보를 둬서 보안에 더 좋지만 메모리를 많이 차지하게 됨
cache
캐시는 웹페이지를 빠르게 렌더링 할 수 있게 도와주기 위해 웹페이지의 요소(css, 이미지, 비디오 등등) 를 저장하기 위한 임시저장소이다.
쿠키/세션은 정보를 저장하기위해 사용하고 사용자의 인증을 도와준다.
JWT
JWT는 Json Web Token의 약자이고 인증에 필요한 정보들을 암호화 시킨 토큰을 말한다.
토큰 자체를 쿠키에 담아서 보내줄 수 있고 HTTP 헤더에 담아서 보내줄 수 도 있다.
하지만 일단 발급을 하면 토큰이 만료되기 전까지 토큰의 유효성을 막을 수 없다.
Servlet
동적인 웹페이지를 만들 때 사용되는 자바기반 기술이다.
Request / Response 흐름을 간단한 메서드 호출로 다룰 수 있게 해준다.
서블릿을 통해 개발자는 비즈니스 로직에 더 집중할 수 있다.
- Request에 동적으로 작동하는 웹 어플리케이션 컴포넌트
- HTML을 사용해 response 한다.
- HTML 변경 시 재 컴파일해야 한다.
- 자바의 스레드를 이용해 동작한다.
- MVC패턴에서 Controller 로 이용된다.
- HTTP 프로토콜을 지원하는 HttpServlet 클래스를 상속받는다.
서블릿 동작 과정
- Servlet Request, Servlet Response 객체를 생성
- 설정 파일을 참고하여 매핑할 Servlet을 확인
- 해당 서블릿 인스턴스 존재의 유무를 확인하여 없으면 생성
- Servlet Container에 스레드를 생성하고 service를 실행
- 응답을 처리하면 Servlet Request, Servlet Response 객체를 소멸
Servlet Container
서블릿을 담고 관리해주는 컨테이너
Request에가 오면 HttpServletRequest, HttpServletResponse 객체를 생성해
GET / POST 여부에 따라 동적 페이지를 생성해 Response를 보낸다.
-
HttpServletRequest
헤더정보, 파라미터, 쿠키, URL 등의 정보를 읽는 메서드와 body의 stream을 읽는 메서드 보유 -
HttpServletResponse
WAS는 HttpServleResponse 객체를 생성해 서블릿에게 전달하고 이 객체로 contentType, 응답코드, 응답메시지 등을 전송한다.
주요기능
-
생명주기 관리
서블릿클래스를 인스턴스화 하고 요청에 따라 적절한 서블릿메서드를 찾아 동작한다.
그리고 라이프사이클이 끝나면 가비지컬렉션을 통해 메모리에서 지운다. -
통신지원
클라이언트와 Request / Response 할 수 있게 웹서버와 소켓을 만들어 통신을 해준다. -
멀티스레드 관리
해당 서블릿의 요청이 들어오면 스레드를 생성해 작업을 수행한다. -
선언적인 보안관리
보안관련 기능을 제공하기 때문에 자바 클래스에 보안관련 메서드를 구현하지 않아도 된다.
댓글남기기