알고리즘 강의를 통해 해당 문제에서 짚고 넘어가야 할 포인트를 정리하는 위주로 써내려 가겠다. 

그 포인트는 나만의 기준으로, 평소에 잘 몰랐거나 효율적으로 잘 쓰지 못했던 부분이 될 것이다. 

 


String 

문제 1 ) 영어 알파벳과 특수문자로 구성된 문자열이 주어지면 영어 알파벳만 뒤집고,

특수문자는 자기 자리에 그대로 있는 문자열을 만들어 출력하는 프로그램을 작성하세요.

a#b!GE*T@S   ->    S#T!EG*b@a

 

String -> .toCharArray()를 이용해 char 배열로 변경해주고, 

for문이 아닌 index를 이용해 문자들을 교체해준다. (left++ / right --)

-> 나는 처음에 for문으로 해결하려 했다 

 

Character.isAlphabetic(xxx) 을 이용하면 알파벳인지 아닌지 확인이 가능하다.

left와 right은 인덱스 위치를 표시하는 건데 왼쪽에서 읽어들이는 인덱스가 오른쪽에서 줄어드는 인덱스를 초과하지 않을 때 까지 체크를 하는 방식을 취한다. 

=> while (left < rifht) { } 


Stack & Queue


개념정리

  • Stack : Last In First Out (LIFO)
    • 메소드
      • public Element push(Element item); // 스택에 데이터 추가
      • public Element pop(); // 최상단 데이터 삭제
      • public Element peek(); // 최상단 데이터 조회
      • public boolean empty(); // 스택에 데이터 있는지 확인
      • public int seach(Object o); // 인자값으로 전달받은 데이터 위치 확인
    • 구현방법
      • LInkedList 이용
public class Node {
    private int data;
    private Node nextNode;

    public Node (int data) {
        this.data = data;
        this.nextNode = null;
    }

    protected void linkNode(Node node) {
        this.nextNode = node;
    }

    protected int getData() {
        return this.data;
    }

    protected Node getPreviousNode() {
        return this.nextNode;
    }
}

/////////////////////////////////////////////////////////////////////////////////////////

public class LinkedListStack {

    Node top;

    public LinkedListStack() {
        this.top = null;
    }

    private void push(int data) {
        Node node = new Node(data);
        node.linkNode(top);
        top = node;
    }

    public int peek() {
        return top.getData();
    }

    private void pop() {
        if (top == null) {
            throw new ArrayIndexOutOfBoundsException("Stack is empty");
        } else {
            top = top.getPreviousNode();
        }
    }

    private int search (int item) {
        Node searchNode = top;
        int index = 1;
        while(true) {
            if (searchNode.getData() == item) {
                return index;
            } else {
                searchNode = searchNode.getPreviousNode();
                index ++;
                if (searchNode == null) {
                    break;
                }
            }
        }
        return -1;
    }
}
      • Array vs LinkedList 비교
        • Array는 데이터 접근 속도가 빠르지만 최대 개수를 항상 정해놔야 하는 단점이 있음
        • 최대 개수 정해놓지 않아도 되지만 데이터의 조회가 힘듬 (노드를 따라 가야되기 때문)
  • Queue : First In First Out (FIFO)
    • 메소드 
      • boolean add(T data) // 큐에 추가 
      • Object remove() // 객체 꺼내서 반환
      • Object element() // 삭제 없이 데이터 읽기
      • boolean offer(Object o) // Queue에 객체를 저장 
      • Object poll() // Queue에서 객체를 꺼내서 반환
      • Object peek() // 삭제없이 요소를 읽어옴 

문제 1 ) 괄호가 입력되면 올바른 괄호이면 'YES', 아니면 'NO'를 출력합니다.

(( )(( )))(( ) -> NO

(())() -> YES

 

스택을 이용하는 문제 

'('가 들어오면 push, ')'가 나오면 pop을 한다. 

전부 다 스택에 넣는 것이 아니라 ( 만 스택에 쌓는다. 

 

Stack<Character> stack = new Stack<>();
for (char x : str.toCharArray()) { 
	if (x == 'C') { 
    	stack.push(x);
    } else { 
    	if (stack.isEmpty()) {
        	System.out.println("NO"); 
        } 
        stack.pop();
    }
}

if (!stack.isEmpty()) {
	System.out.println("NO");
} 

System.out.println("YES");

 


문제 2 ) 인형뽑기 

NXN 배열에 있는 인형들을 바구니에 위로 쌓는다. 이때 같은 인형이 바구니에 쌓이면 인형이 터뜨려지게 된다. M사이즈의 배열에는 어떤 NXN 배열에서 어떤 '열'로 이동하는지 정보가 있다고 가정했을 때 이동이 끝나서 터뜨려지는 인형의 개수를 구하라.

 

5 (N)
0 0 0 0 0
0 0 1 0 3
0 2 5 0 1
4 2 4 4 2
3 5 1 3 1  -> NxN 배열이고 0은 인형이 없고 비어있는 개수라고 생각하면 된다.
8 (M)
1 5 3 5 1 2 1 4 (열을 어떤 순서로 이동할지 정보가 담겨있는 M사이즈의 배열)

 

이것도 마찬가지로 스택을 이용하는 문제이고, .peek()메소드를 이용해 맨 상단의 수를 체크해서 같지 않은 경우엔 .push() 같으면 .pop()을 시킨다. 다차원 배열도 같이 잘 다뤄줘야 하는데 int[N][N] box = new int[][]; 라고 가정하면 box.length는 행의 개수, box[3].length 이렇게 하면 열의 개수를 알 수 있다. 

 


DFS

개념정리

  • 전위순회 : 부모 - 왼 - 오 (0 -> 1 -> 3 -> 4 -> 2 -> 5 -> 6)
  • 중위순회 : 왼 - 부모 - 오 (3 -> 1 -> 4 -> 0 -> 5 -> 2 -> 6)
  • 후위순회 : 왼 - 오 - 부모 (3 -> 4 -> 1 -> 5 -> 6 -> 2 -> 0)

 

class Node {
    int data;
    Node lt;
    Node rt;
    
    public Node (int data) {
    	this.data = data;
        lt=rt=null;
    }
}
public class Main {
    Node root;
    public void DFS(Node root) {
    	if (root == null) return;
        else {
            // System.out.println(root.data + " "); // 전위순회
            DFS(root.lt); // 왼쪽으로
            // System.out.println(root.data + " "); // 중위순회
            DFS(root.rt); // 오른쪽으로
            // System.out.println(root.data + " "); // 후위순회
        }
    }
    
    public static void main(String args[]) {
    Main tree = new Main();
    tree.root = new Node(1);
    tree.root.lt = new Node(2);
    tree.root.rt = new Node(3);
    tree.root.lt.lt = new Node(4);
    tree.root.lt.rt = new Node(5);
    tree.root.rt.lt = new Node(6);
    tree.root.rt.rt = new Node(7);
    tree.DFS(tree.root);
}

문제 1 ) 자연수 N이 주어지면 1부터 N까지의 원소를 갖는 집합의 부분집합을 모두 출력하는 프로그램을 작성 (공집합 제외)

3 -> 1,2,3 / 1,2 / 1,3 / 1 / 2,3 / 2 / 

 

1,2,3 에서 각 자연수는 집합에 들어갈 수 있다/없다 (LT / RT) 두가지 경우의수를 가진다. 

 

class Main {
    static int n;
    static int[] ch;
    
    public void DFS(int L) {
    	if (L == n+1) {
        	// 1로 표시된 인덱스를 표시해준다.
            String tmp = "";
            for (int i = 1; i <= n ; i++) {
            	if (ch[i] == 1) tmp += (i + " ");
            }
            if (tmp.length() > 0) {
            	System.out.println(tmp);
            }
        } else { 
            ch[L] = 1;
            DFS(L+1) // 사용한다 (왼쪽)
            ch[L] = 0; 
            DFS(L+1) // 사용하지 않는다 (오른쪽)
        }
    }

    public static void main(String[] args) {
    	Main T = new Main();
        n = 3;
        ch = new int[n+1];
        T.DFS(1);
    }
}

Spring Initializer로 Spring Boot 프로젝트를 쉽게 생성하는데, 어딘지 모르게 죄책감이 든다.

나는 과연 Spring과 Spring Boot의 차이를 알고 매일 이렇게 쉽게 이용하는 것인가.

누군가 물어보면 쉽게 대답할 수 있는가? 했을 때 자신이 없어 이참에 알아보기로 하였다. 

 

Spring

  • 자바를 더 쉽게 사용할 수 있게 해주는 오픈소스 프레임워크 
    • 중복코드의 사용률을 줄여주고 비즈니스 로직을 더 간단하게 한다. 
      • 프레임워크? 자주 쓰일만한 기능들을 한데 모아 놓은 클래스들의 집합으로 기본적인 설계나 필요한 라이브러리를 제공한다. 
  • 특징
    • 의존성 주입(Dependency Injection)
      • 구성요소 간의 의존 관계가 소스코드 내부가 아닌 외부의 설정 파일을 통해 정의되는 방식
      • 어노테이션, 설정파일을 통해 
    • 제어의 역전(IOC, Inversion Of Control)
      • 객체 생명주기 관리를 개발자가 하지 않고 컨테이너가 대신 해주는 것
        • 필요한 부분을 적절한 상황에 따라 자유롭게 교체할 수 있음
          • MyBatic를 쓰던 JPA를 쓰던 Oracle Database를 쓰던...
    • 관점 지향 프로그래밍 (Aspect Object Programming)
      • 로깅, 트랜잭션, 보안 등 여러 모듈에서 공통적으로 사용하는 기능을 분리하여 관리
    • POJO (Plain Old Java Object)
      • 객체 지향 원리에 충실하되, 특정 환경에 종속되지 않고 순수 자바로 설계된 객체

Spring Boot

  • 스프링 프레임워크를 기반으로 바로 실행가능한 애플리케이션을 쉽게 만들도록 도와줍니다. 스프링 관련된 복잡한 설정을 자동으로 처리해서 개발자는 최소한의 설정만 진행하도록 할 수 있게 하는 스프링 프레임워크의 서브 프로젝트
    •  (단독 실행 가능 스프링애플리케이션 생성 + 최소한의 초기 스프링 구성)
  • 생성 배경
    • 스프링으로 애플리케이션을 개발하려면 사전에 많은 작업을 해야 했음 (비즈니스 로직에만 더 집중하고 싶음)
    • 웹 애플리케이션 개발에 필요한 라이브러리와 그 라이브러리에 종속된 라이브러리들을 개발자가 일일이 추가해야만 했음
      • 버전이 다르거나 충돌이 나는 경우가 있어 개발 환경 구성에 어려움이 존재하였음
    • 비즈니스 로직보다 개발환경 구성 및 스프링의 기능 구성에 시간을 쏟게 됨
      • Transaction Manager
      • Hibernate - spring-boot-starter-data-jpa
      • Entity Manager
      • Spring MVC
  • 장점
    • 프로젝트에 따라 자주 사용되는 라이브러리들이 미리 조합됨
      • Spring MVC, Jackson Databind, Hibernate 코어 및 Log4j
    • 복잡한 설정을 자동으로 처리 
    • 내장 서버를 포함해서 톰캣과 같은 서버를 추가로 설치하지 않아도 바로 개발이 가능
    • JAR파일로 웹 애플리케이션 개발이 가능 
      • 스프링의 jar 파일이 클래스 패스에 있는 경우 Spring Boot는 Dipatcher Servlet으로 자동 구성

 

 

출처 

책 - 스프링부트 시작하기 

https://goddaehee.tistory.com/238

https://jerryjerryjerry.tistory.com/62

 

 

드디어 도커를 실제로 사용해보는 날이다. 

이노무 자식, 너를 낱낱히 파헤쳐보겠다.

 

출처는 아래와 같고, 이 포스팅은 아래 출처에서 나온 내용을 정리하기 위함이다. 

https://subicura.com/2017/01/19/docker-guide-for-beginners-2.html

 

다운로드는 Docker for Mac .dmg파일을 다운로드 받아 설치하면 된다.

도커는 리눅스 컨테이너이므로 네이티브 처럼 설치되었지만 실제로는 가상머신에 설치된 것.

 

도커를 설치하고 아래의 명령어를 보면 설치가 잘 완료된 것을 볼 수 있다. 

$ docker version

Client와 Server정보가 정상적으로 출력되면 된다. 

 

여기서 버전 정보가 Client와 Server로 나뉘어져 있는 이유는 도커는 하나의 실행파일이지만 실제로 클라이언트와 서버 역할을 할 수 있기 때문이다. 도커 커맨드를 입력하면 클라이언트에서 도커 서버로 명령을 전송하고 결과를 받아 터미널에 출력한다. 기본값이 도커 서버의 소켓을 바라보고 있기 때문에 터미널에서 명령어를 입력했을 때 바로 명령을 내리는 것 같은 느낌을 받는다. 이게 가상 서버에 설치된 도커가 동작하는 이유!

 

 

아래는 docker 공식 Documentaion에서 발췌한 내용이다.

https://docs.docker.com/get-started/overview/

https://docs.docker.com/get-started/overview/

Docker 클라이언트 는 Docker 컨테이너를 빌드, 실행 및 배포하는 무거운 작업을 수행 하는 Docker 데몬 과 통신합니다. Docker 클라이언트와 데몬 은 동일한 시스템에서 실행되거나 Docker 클라이언트를 원격 Docker 데몬에 연결할 수 있습니다. Docker 클라이언트와 데몬은 UNIX 소켓 또는 네트워크 인터페이스를 통해 REST API를 사용하여 통신합니다. 

 

도커 데몬 

Docker 데몬( dockerd)은 Docker API 요청을 수신하고 이미지, 컨테이너, 네트워크 및 볼륨과 같은 Docker 객체를 관리합니다. 데몬은 Docker 서비스를 관리하기 위해 다른 데몬과 통신할 수도 있습니다.

도커 클라이언트 

Docker 클라이언트( docker)는 많은 Docker 사용자가 Docker와 상호 작용하는 기본 방법입니다. 와 같은 명령을 사용할 때 docker run클라이언트는 이러한 명령을 로 보냅니다 dockerd. 이 docker명령은 Docker API를 사용합니다. Docker 클라이언트는 둘 이상의 데몬과 통신할 수 있습니다.

도커 레지스트리 

Docker 레지스트리 는 Docker 이미지를 저장합니다. Docker Hub는 누구나 사용할 수 있는 공개 레지스트리이며 Docker는 기본적으로 Docker Hub에서 이미지를 찾도록 구성되어 있습니다. 자신의 개인 레지스트리를 실행할 수도 있습니다. docker pull또는 docker run명령 을 사용하면 구성된 레지스트리에서 필요한 이미지를 가져옵니다. docker push명령 을 사용하면 이미지가 구성된 레지스트리로 푸시됩니다.

이미지

이미지 도커 컨테이너를 만들기위한 읽기 전용 템플릿입니다. 종종 이미지는 몇 가지 추가 사용자 정의와 함께 다른 이미지를 기반으로 합니다. 예를 들어, 이미지를 기반으로 하는 이미지를 빌드할 수 ubuntu 있지만 Apache 웹 서버와 애플리케이션은 물론 애플리케이션을 실행하는 데 필요한 구성 세부 정보도 설치합니다.

자신만의 이미지를 만들거나 다른 사람이 만들고 레지스트리에 게시한 이미지만 사용할 수 있습니다. 고유한 이미지를 빌드하려면 이미지를 만들고 실행하는 데 필요한 단계를 정의하는 간단한 구문 으로 Dockerfile 을 만듭니다. Dockerfile의 각 명령은 이미지에 계층을 생성합니다. Dockerfile을 변경하고 이미지를 다시 빌드하면 변경된 레이어만 다시 빌드됩니다. 이것은 다른 가상화 기술과 비교할 때 이미지를 가볍고 작고 빠르게 만드는 부분입니다.

컨테이너

컨테이너는 이미지의 실행 가능한 인스턴스입니다. Docker API 또는 CLI를 사용하여 컨테이너를 생성, 시작, 중지, 이동 또는 삭제할 수 있습니다. 컨테이너를 하나 이상의 네트워크에 연결하거나, 스토리지를 연결하거나, 현재 상태를 기반으로 새 이미지를 생성할 수도 있습니다.

기본적으로 컨테이너는 다른 컨테이너 및 해당 호스트 시스템과 비교적 잘 격리되어 있습니다. 컨테이너의 네트워크, 저장소 또는 기타 기본 하위 시스템이 다른 컨테이너나 호스트 시스템과 얼마나 격리되었는지 제어할 수 있습니다.

컨테이너는 이미지와 컨테이너를 만들거나 시작할 때 제공하는 구성 옵션으로 정의됩니다. 컨테이너가 제거되면 영구 저장소에 저장되지 않은 상태 변경 사항이 사라집니다.

 

 

도커를 실행하는 명령어는 다음과 같습니다.

docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...]

https://subicura.com/2017/01/19/docker-guide-for-beginners-2.html

Ubuntu 컨테이너 생성 명령어

컨테이너는 ubuntu 16.04로 예제와 동일하게 실행했고, local에 존재하지 않아서 이미지를 pull하였다. 

$ docker run ubuntu:16.04

컨테이너는 생성되자마자 종료되었다.

컨테이너는 프로세스이기 때문에 실행중인 프로세스가 없으면 컨테이너는 종료된다. 

 

/bin/bash 명령어를 사용하여 컨테이너를 실행

$ docker run --rm -it ubuntu:16.04 /bin/bash

바로 이전에 이미지를 다운 받았기 때문에 이미지를 다운로드 하는 화면 없이 바로 실행되었다. 

exit 명령어를 통해 bash 쉘을 종료하면 컨테이너도 같이 종료됩니다. 

 

redis 컨테이너 생성

메모리 기반의 다양한 기능을 가진 스토리지 redis,

redis 컨테이너를 실행해보자!

 

-d : 백그라운드 모드 

-p : 컨테이너의 포트를 호스트의 포트로 연결

 

docker run -d -p 1234:6379 redis
$ telnet localhost 1234 // telnet이 없어서 brew로 설치해주었다.
Trying ::1...
Connected to localhost.
Escape character is '^]'.

//여기서부터 명령어를 입력해주면 된다.
set mykey hello
+OK
get my key
$5
hello
quit

백그라운드 모드에서 동작하고 있던 컨테이너. -p 로 동작시킨 컨테이너이기 때문에 호스트의 1234 포트를 컨테이너의 6379 포트로 연결하였다. 따라서 localhost의 1234 포트로 접속하게 되면 redis를 바로 사용할 수 있게 된다.

 

MySQL 컨테이너 생성

 

-e : 환경변수 설정

--name : 컨테이너에 읽기 어려운 ID 대신 쉬운 이름을 부여한다. 

$ docker run -d -p 3306:3306 -e MYSQL_ALLOW_EMPTY_PASSWORD=true --name mysql mysql:5.7

$ mysql -h127.0.0.1 -uroot // mysql 도 brew로 설치해주었다.

mysql> show databases;
mysql> quit

MYSQL_ALLOW_EMPTY_PASSWORD 환경변수를 설정하여 패스워드 없이 root 계정을 만들었다. 

 

그 외

$ mysql -h127.0.0.1 -uroot
create database wp CHARACTER SET utf8;
grant all privileges on wp.* to wp@'%' identified by 'wp';
flush privileges;
quit

# run wordpress container docker run -d -p 8080:80 --link mysql:mysql \
  -e WORDPRESS_DB_HOST=mysql \
  -e WORDPRESS_DB_NAME=wp \
  -e WORDPRESS_DB_USER=wp \
  -e WORDPRESS_DB_PASSWORD=wp \
  wordpress

워드프레스용 데이터베이스를 생성하고 워드프레스 컨테이너를 실행합니다. 호스트의 8080포트를 컨테이너의 80포트로 연결하고 MySQL 컨테이너와 연결한 후 각종 데이터베이스 설정 정보를 환경변수로 입력합니다.

중요한건 MySQL 컨테이너와 연결한다는 것 같다. 

 

<tensorflow>

$ docker run -d -p 8888:8888 -p 6006:6006 teamlab/pydata-tensorflow:0.1

 

이렇게 여러개의 컨테이너를 실행해보았고, 컴퓨터가 컨테이너 기반의 도커를 이용해 짱짱하게 실행하는 것을 볼 수 있었다. 


도커 기본 명령어

 

 

컨테이너 목록 확인하기 

docker ps [OPTIONS]

OPTIONS : -a , --all

 

컨테이너 중지 및 삭제 

docker stop [OPTIONS] CONTAINER [CONTAINER...] //중지
docker rm [OPTIONS] ${CONTAINER_ID} //삭제

이미지 목록 확인하기

docker images [OPTIONS] [REPOSITORY[:TAG]]

이미지 다운로드하기

docker pull [OPTIONS] NAME[:TAG|@DIGEST]
docker pull ubuntu:14.04

이미지 삭제하기

docker rmi [OPTIONS] IMAGE [IMAGE...]

 

 


 

 

컨테이너 둘러보기

컨테이너 로그 보기 

컨테이너가 정상 동작하는지 확인 

docker logs [OPTIONS] CONTAINER

options : -f, --tail

 

아무 옵션이 없는 경우 전체 로그 출력, --tail 옵션은 마지막 10줄만, -f 옵션은 실시간 로그 생성 확인을 위함

 

컨테이너 명령어 실행하기 

실행중인 컨테이너에 들어가서 파일을 실행하고 싶을 때가 있음 -> 그럴 때 exec 명령어로 실행함

docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
docker exec -it mysql /bin/bash //실행중인 MySQL 컨테이너에 접속

 

컨테이너 업데이트

먼저 새 버전의 이미지를 다운받고, 기존 컨테이너를 중지 혹은 삭제한 다음 새 이미지를 기반으로 새 컨테이너를 실행 

그런데 컨테이너를 삭제할 때 컨테이너에서 생성된 모든 파일이 사라지게 됨으로 중요한 데이터의 경우에는 외부 스토리지에 저장해야 하는데 AWS S3 혹은 Data Volumes를 컨테이너에 추가해서 사용. 

 

데이터 볼륨은 호스트의 디렉토리를 마운트해서 사용하는 방법이 있다. 

MySQL은 /var/lib/mysql디렉토리에 모든 DB정보가 담기므로 호스트의 특정 디렉토리를 연결해주도록 한다.

 

# before
$ docker run -d -p 3306:3306 \
  -e MYSQL_ALLOW_EMPTY_PASSWORD=true \
  --name mysql \
  mysql:5.7

# after
$ docker run -d -p 3306:3306 \
  -e MYSQL_ALLOW_EMPTY_PASSWORD=true \
  --name mysql \
  -v /my/own/datadir:/var/lib/mysql \ # <- volume mount
  mysql:5.7

https://subicura.com/2017/01/19/docker-guide-for-beginners-2.html

/my/own/daradir 디렉토리를 컨테이너의 /var/lib/mysql 디렉토리로 마운트한다. 

이전 버전의 MySQL 이미지를 삭제하고 최신 버전의 MySQL 이미지를 다운받고 컨테이너를 실행할 때 동일한 디렉토리를 마운트한다면 데이터를 그대로 사용할 수 있게 되는 것이다.

 

 


 

Docker Compose

커맨드라인에서 명령어로 작업하지 않고, YAML방식의 설정파일을 이용해서 복잡한 명령어를 쉽게 수행할 수 있게 한다.

$ curl -L "https://github.com/docker/compose/releases/download/1.9.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
$ chmod +x /usr/local/bin/docker-compose
$ docker-compose version

 

아래는 docker-compose.yml 파일의 내용이다.

version: '2'

services:
   db:
     image: mysql:5.7
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: wordpress
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress

   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     volumes:
       - wp_data:/var/www/html
     ports:
       - "8000:80"
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_PASSWORD: wordpress
volumes:
    db_data:
    wp_data:
$ mkdir wp
$ cd wp
$ wp vi docker-compose.yml
$ wp docker-compose up

 

실행하게 되면 손쉽게 워드프레스가 만들어진 것을 확인할 수 있다. 

 

 

이 포스팅의 대부분의 글은 아래의 사이트를 참고하였습니다.

https://subicura.com/2017/01/19/docker-guide-for-beginners-2.html

도커,, 정말 지겹게도 많이 들어본 단어인데 아직도 정확히 뭔지 모른다.

궁금증이 극에 달할 때 마다 구글링을 해서 찾아봤었고, 아 이런거구나 싶었다가도 금방 다시 원상태로 복귀.

정확하게 무엇인지 이해를 못했으니.. 

오늘 또 궁금증이 극에 달하여 구글링을 시작했고, 이왕 이렇게 된거 포스팅을 정리하며 간단하게 정리해보고자 한다.

부디 이번에는 단기기억에서 장기기억으로 넘어갈 수 있도록 해보겠다.

장황하게 쓰지 않고 짧고 간단하게.

가능하면 도커 이해를 위한 미니프로젝트도 해보고 싶다.

왜냐면 나란 사람은 실제 코딩을 해봐야 와닿는 사람이기 때문에.. (부딪혀보고 보는 편)

 

 

 

일단, 아래 글을 정독하며 이해해보기로 한다.

https://subicura.com/2017/01/19/docker-guide-for-beginners-1.html

 

 

글쓴이는 실제 아래와 같은 상황에서 어려움을 겪어다고 한다.

  • Redhat Enterprise Linux 4에 Oracle 10g을 설치해야 하는데 설치가 잘 되지 않았다.
  • 회사에서 사용하는 리눅스와 오라클 버전은 딱 정해져있다. 
  • 버전을 업데이트 하는 건 정말 어렵고 Risky함
  • 서버 세팅은 정말 어렵
  • 하나의 서버에 여러개의 프로그램을 설치하는 것은 어렵다.
    • 라이브러리의 버전이 다르거나 동일한 포트를 사용하는 문제가 있기 때문에 
  • 서버 환경은 계속 바뀌고 있음 AWS -> Azure 혹은 CentOS -> Ubuntu 
  • 마이크로서비스 아키텍쳐로 프로그램이 많아져 서버 관리가 어렵다. (서버가 수백, 수천대)

 

이런 상황에 도커가 등장! 

서버 관리 방식이 완전히 바뀌게 된다.

 

 

 

도커는 그래서 무엇인가 ?

  • 컨테이너 기반의 오픈소스 가상화 플랫폼 
    • ^^ ㅋㅋㅋㅋㅋ 이게 뭔말인가 
    • 실생활의 컨테이너 
      • 네모난 화물 수송용 박스로 옷, 신발 등의 다양한 화물을 넣어서 배로 쉽게 옮기는 것
    • 서버에서의 컨테이너
      • 다양한 프로그램, 실행환경을 컨테이너로 추상화하고 동일한 인터페이스를 제공하여 프로그램의 배포 및 관리를 단순하게 해준다. 
      • PC, AWS, Azure, Google cloud등 어디에서든 실행가능 
  • 컨테이너 
    • 격리된 공간에서 프로세스가 동작하는 기술로 가상화 기술의 하나 (프로세스 격리 방법 or 격리된 공간)
      • 기존의 가상화 기술과 다른 점은?
        • 전체 OS를 가상화 한 것이 아님 (리눅스에서 윈도우를 돌림)
          • 호스트 OS 위에 게스트 OS 전체를 가상화하는 방식  (구방식) -> 무겁&느림
        • 게스트 OS 정도만 필요라 하는 거기 때문에 성능이 기존 가상화보다 성능이 향상됨
    • 하나의 서버에 여러개의 컨테이너를 실행하면 서로 영향을 미치지 않고 독립적으로 실행 (가벼운 VM느낌)
    • 컨테이너를 띄워서 할 수 있는 것은 ?
      • 실행중인 컨테이너에 접속해서 apt-get or yum으로 패키지 설치 가능
      • 여러개의 프로세스를 백그라운드로 실행할 수 있음
      • CPU나 메모리 사용량 제한 가능 
      • 호스트와 특정 포트와 연결하거나 호스트의 특정 디렉토리를 내부 디렉토리인 것처럼 사용 가능 
    • 새로운 컨테이너를 하나 만드는 데는 1-2초로 가상 머신과 비교할 수가 없음 

  • 이미지
    • 컨테이너 실행에 필요한 파일과 설정값등을 포함하고 있는 것 (Immutable - 불변)
    • 컨테이너 = 이미지를 실행한 상태 
      • 추가되고 변하는 값은 컨테이너에 저장
    • 같은 이미지에서 여러개의 컨테이너를 생성할 수 있음 
      • 컨테이너의 상태가 변하거나 삭제되도 이미지는 변하지 않고 그대로 남아 있음 
    • 이미지는 컨테이너를 실행하기 위해 필요한 모~~든 정보를 가지고 있기 때문에 의존성 파일을 컴파일하고 이것저것 설치하지 않아도 된다. 
      • 그냥 이미지를 다운받고 컨테이너를 생성하면 된다. 
    • 이미지 저장 방식 
      • 컨테이너를 실행하기 위한 모든 정보를 가지고 있기 때문에 도커 이미지는 용량이 굉장히 크다. 
        • 기존 이미지에 파일 하나를 추가 함으로써 전체를 다시 다운받는다면 매우 비효율적  
      • 그래서 레이어라는 개념을 사용
        • 유니온 파일 시스템을 이용하여 어러개의 레이어를 하나의 파일시스템으로 사용함
        • 읽기 전용 레이어로 구성되고 파일이 추가 되거나 수정되면 새로운 레이어가 생성됨 
            • ubuntu 이미지가 A + B + C의 집합이라면, ubuntu 이미지를 베이스로 만든 nginx 이미지는 A + B + C + nginx가 됩니다.
  • Dockerfile
    • 도커는 이미지를 만들기 위해 Dockerfile 이라는 파일에 DSL 언어를 사용하여 이미지 생성 과정을 적음 
    • 서버에 어떤 프로그램을 설치하려고 이것저것 의존성 패키지를 설치하고 설정 파일을 만들었던 것을 이제는 Dockerfile로 관리할 수 있다. 
 vertx/vertx3 debian version
FROM subicura/vertx3:3.3.1
MAINTAINER chungsub.kim@purpleworks.co.kr

ADD build/distributions/app-3.3.1.tar /
ADD config.template.json /app-3.3.1/bin/config.json
ADD docker/script/start.sh /usr/local/bin/
RUN ln -s /usr/local/bin/start.sh /start.sh

EXPOSE 8080
EXPOSE 7000

CMD ["start.sh"]

 

'TIL (Today I Learned)' 카테고리의 다른 글

Redis  (0) 2021.12.08
Docker mac에 설치해서 컨테이너 실행해보기 / Docker 문법  (0) 2021.08.04
BigQuery - Cloud SQL , CSV 파일  (0) 2019.10.18
Google Kubernetes Engine(GKE)  (1) 2019.10.15
NGINX  (0) 2019.10.15

사수님은 Map을 함부로 쓰지 말라는 충고를 자주 해주셨다.

Map의 무분별한 활용으로 Memory Leak이 운영중인 서비스에 발생할 수 있다 하셨다.

그래서 오늘 스터디 내용의 주제는 Map이다. 몸은 가까웠지만 마음은 멀었던 요놈, 파헤쳐보기로..

 

아래 내용은 기술 세미나(?)를 위해 내가 준비한 자료들이다.

 

인터페이스 ListSet을 구현한 컬렉션 클래스들은 많은 공통부분이 있어서,

공통된 부분을 다시 뽑아 Collection 인터페이스를 정의할 수 있었지만

Map인터페이스는 이들과는 다른 형태로 컬렉션을 다루기 때문에 같은 상속 계층도에 포함되지 못한다.

 

HashMap Entry 타입을 구현한 Node라는 내부 클래스를 정의하며

키와 값은 별개의 값이 아니라 서로 관련된 값이기 때문에 하나의 클래스로 정의하여 하나의 배열로 다룬다.

키와 벨류는 Object 타입이므로 어떠한 객체도 저장할 수 있다.

 

Node 클래스 자체는 사실상 Java 7Entry 클래스와 내용이 같지만,

Java 8에서는 일정 개수 이상이 되면 트리구조를 이용하는 것으로 발전했다.

링크드 리스트 대신 트리를 사용할 수 있도록 하위 클래스인 TreeNode가 있다는 것이 Java 7 HashMap과 다릅니다.

 

해싱에서 사용하는 자료구조는 배열과 링크드 리스트의 조합으로 되어있다.

저장할 데이터의 키를 해시함수에 넣으면 배열의 한 요소를 얻게 되고,

다시 그곳에 연결되어 있는 링크드 리스트에 저장한다.

 

예를 들면 주민등록번호의 맨 앞자리인 생년을 기준으로 데이터를 분류해서 10개의 서랍에 나눠담는다.

71,72년생과 같은 70년대 환자들의 데이터는 같은 서랍에 저장하는 식으로

이렇게 분류하여 저장하면 환자의 주민번호로 태어난 년대를 계산해서 어느 서랍에서 찾아야 할지를 쉽게 알 수 있다.

 

링크드리스트는 크기가 커질수록 검색속도가 떨어지기 때문에

하나의 서랍에 데이터의 수가 많을수록 검색에 시간이 더 걸린다.

배열은 배열의 크기가 커져도,

원하는 요소가 몇 번째에 있는지만 알면 아래의 공식에 의해서 빠르게 원하는 값을 찾을 수 있다.

 

링크드리스트는 불연속적으로 존재하는 데이터를 서로 연결한 형태로 구성되어 있는데,

리스트의 각 요소(node)들은 자신과 연결된 다음 요소에 대한 참조(주소값)와 데이터로 구성되어 있다.

연속적으로 메모리상에 존재하는게 아니기 때문에

링크드리스트는 불연속적으로 위치한 각 요소들이 서로 연결된 것이라

처음부터 n번째 데이터까지 차례대로 따라가야만 원하는 값을 얻을 수 있다.

그래서 데이터의 개수가 많아질수록 데이터를 읽어 오는 시간이 길어진다는 단점이 존재 

 

키를 있는 그대로 저장하는 경우 다양한 키의 길이 만큼의 크기를 마련해두어야 하기 때문에

일정한 길이의 해시로 변경해야 한다고 했다. 

 

키의 전체 개수와 동일한 크기의 버킷을 가진 해시테이블을 Direct-address table라고한다.
Direct-address table의 장점은 키의 개수와 테이블의 크기가 같기 때문에

해시 충돌 문제가 발생하지 않는다다. 


Q. 실제 사용하는 키는 몇개 되지 않을 경우에는?

- 전체키 100개중에 실제로는 10개의 키만 사용하는데 100개 크기의 테이블을 유지하고 있는 것은 메모리 낭비이다.

따라서 보통의 경우 실제 사용하는 키 개수보다 적은 해시테이블을 운용한다고 한다.

그렇기에 해시 충돌이 발생할 수 밖에 없고, 해시 충돌을 해결하기 위한 다양한 방법들이 고안되었다.

 

 

둘 이상의 키에 동일한 인덱스 충돌이 발생할 경우 해결방법에 따라 크게 두가지 형태로 나눈다.

 

첫번째로는 Separate chaning으로 충돌 발생시 링크드 리스트로 연결하는 방식으로 간단한 해시 함수를 사용하기 때문에 가장 널리 쓰이는 방식. Java HashMap에서 이용하는 방식으로 동일한 버킷의 데이터에 대해 리스트 혹은 트리 자료구조를 이용해서 추가 메모리를 사용하여 다음 데이터의 주소를 저장한다. HashMap은 리스트의 개수가 8개 이상이 되면 트리 자료구조를 사용하게 된다.

 

두번째는 OpenAddressing으로 충돌 발생시빈 공간을 찾아 나서는 탐사 방식으로 탐사 방식에 따라 Linear probing, Qudratic probing, double hasing 등이 사용 되는데 이 중에서 가장 단순한 Linear probing의 경우 충돌이 발생할 때 마다 한 칸씩 아래로 빈 공간을 찾아 탐색에 나선다. 그림 처럼 빈 공간이 많다면 금방 자리를 찾지만 아닌 경우 계속 탐사를 하게 되므로 효율성이 급격하게 떨어진다는 단점이 있다.

 

HashMap 클래스를 보면 기본 용량은 16, 로드팩터는 0.75 기본으로 사용하고 있다. 기본 용량은 버킷의 수와 같고 로드팩터는 (데이터의 개수)/(기본용량)을 의미하는데 로드팩터의 값에 도달하게 되면 버킷의 수를 동적으로 2배 확장하게 된다.

위의 예시로 들면 기본 용량은 16이기 때문에 데이터의 개수가 12개가 차면 버킷의용량은 16 -> 32로 늘리는 과정이 일어남.. 여기서 바로 Map조심히 사용해야 되는 이유가 나타나게 되는데 이때 원래 버킷에 있던 것을 새로운 버킷에다 옮기는 과정이 일어나고 이 과정이 성능에 악영향을 끼치게 된다.

 

 

 

 

출처. 

https://velog.io/@adam2/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0%ED%95%B4%EC%8B%9C-%ED%85%8C%EC%9D%B4%EB%B8%94

https://sabarada.tistory.com/57

+ Recent posts