1장. 인프라 아키텍쳐를 살펴보자

  • Scale-up: 서버의 자체 성능을 증가시키는 것 = "수직 스케일"
  • Scale-out: 기존의 서버와 같은 사양 또는 비슷한 사양의 서버 대수를 증가시키는 것 = "수평 스케일"

집약형 아키텍쳐 <=> 분할형 아키텍쳐(분산 시스템)

  • 수직 분할: 서버별로 다른 역할 담당하는 경우
    • 클라이언트 - 서버형: ex) 클라이언트측(PC, 스마트폰, 태블릿 등)에 전용 SW 설치
      • 클라이언트: 화면 표시나 단순 계산
      • 서버: 데이터 제공
    • 3계층형 아키텍쳐
      • 프레젠테이션 계층: 사용자 input을 받고 웹 브라우저 화면 표시
      • 애플리케이션 계층: request에 따라 데이터 처리
      • 데이터 계층: 애플리케이션 계층에 따라 데이터 입출력
  • 수평 분할: 용도가 같은 서버를 늘려나가는 방식
    • 단순 수평 분할형 아키텍쳐: 샤딩/파티셔닝 ex) 서울 지사 시스템과 부산 지사 시스템을 나눈다
    • 공유형 아키텍쳐: 단순 분할형과 달리 일부 계층(데이터 계층)에서 데이터 교환(동기화 등)이 이루어진다.

 

위 수직/수평 분할을 적절히 조합하여 아래 아키텍쳐를 만들 수 있다.

  • 지리 분할형 아키텍쳐: 업무 연속성 및 시스템 가용성을 높이기 위해 지리적으로 분할
    • 스탠바이형 아키텍쳐: 물리 서버를 최소 두 대를 준비하여 한 대가 고장나면 가동중인 소프트웨어를 다른 한 대로 옮겨서 운영하는 방식
    • 재해 대책형 아키텍쳐: 위와 마찬가지로 동일한 환경의 서버를 별도 사이트에 배치하고 재해가 발생하면 다른 사이트에 있는 정보 사용한다. 애플리케이션 최신화와 데이터 최신화를 주의해야 한다.

클라우드형 아키텍쳐: 가상화: 집약형+분할형 양쪽의 장점을 취하는 방법

  • 물리 서버를 가상화 기능으로 여러 대의 가상 서버로 분할

 

 

2장. 서버를 열어보자

서버는 rack에 장착되고, 랙에는 서버 외에도 HDD, 네트워크 스위치 등이 붙어있다. 

  • CPU(=코어): 서버 중심에 위치하며 연산 처리(1초에 10억회 정도)를 한다. 코어는 각자가 독립된 처리를 할 수 있다.
    • 프로세스/사용자IO가 OS에 명령 -> OS가 CPU에 명령을 내림
    • CPU 자체에도 메모리가 있다. (레지스터, L1/L2/L3 캐시 등)
    • 레지스터: CPU 구조의 일부로 CPU에서 연산에 사용하는 데이터를 기억하는 소규모 기억장치
  • 메모리: CPU 옆에 위치하며 데이터를 저장하거나, 처리 결과를 받는다. 속도가 빠르지만 서버를 재시작하면 정보가 없어짐.
    • L1/L2/L3캐시 등으로 메인 메모리로부터 데이터 처리 시간을 줄일 수 있다. L1/L2는 각 코어 전용, L3는 CPU 전체가 공유
    • 컴퓨터는 캐시로부터 데이터를 읽어들여 레지스터에 저장한 후, 레지스터 사이로 데이터를 전달하면서 연산을 수행한다.
    • 메모리 인터리빙(memory interleaving): 메모리를 여러 개의 모듈로 나누어 메모리에 접근하더라도 block 되지 않고 단위시간에 여러 메모리로 동시에 접근이 가능하도록 하는 기법 => 다음에 필요한 데이터를 미리 가져다놓는다
      • 상위 인터리빙: 메모리 주소의 상위 비트에 의해 모듈 선택, 하위 비트에 의해 모듈 내 주소 선택
        • 장점: 모듈간의 독립성 -> 한 모듈에 에러가 나더라도 해당 모듈만 영향
        • 단점: 동시 접근을 통한 성능 향상이 어렵다
      • 하위 인터리빙: 메모리 주소의 하위 비트에 의해 모듈 선택, 상위 비트에 의해 모듈 내 주소 선택
        • 장점: 다수의 모듈이 동시 동작 가능
        • 단점: 새로운 메모리 뱅크 추가 시, 전체에 영향을 주게 된다
      • 혼합 인터리빙: 메모리 뱅크를 몇개의 모듈로 나누어 구성하는데, 메모리 뱅크 선택은 상위 인터리빙 방식을 따르고, 뱅크 선택 후, 뱅크 내 모듈 간에는 하위 인터리빙 방식을 따른다.
    • 메모리 ~ CPU 간 데이터 교환은 채널을 통해 이루어진다.
  • IO 장치
    • HDD(하드디스크): 장기 저장 목적의 데이터  저장 장소
      • 자기 원반이 여러 개 들어 있음, 고속으로 회전해서 read/write 처리 => 회전 구조 때문에 물리 법칙에 좌우되며, 메모리처럼 순식간에 access 할 수 없다.
      • 서버와 IO 시에는 캐시를 통해 데이터를 교환한다.
        • Read => 캐시에 없는 경우 디스크에서 읽은 후 캐시에 올리고, 캐시를 서버에 반환
        • Write Through => 캐시와 저장소에 기록하고 IO 종료 여부를 서버에 알림
          • 장점: 캐시와 메모리에 업데이트를 같이 해버리는 방식이기때문에 데이터 일관성 유지, 안정적
          • 단점: 속도가 느린 저장소에 데이터를 기록할 때, cpu block 시간이 필요
        • Write Back => 캐시에 기록하고 IO 종료 여부를 서버에 알림(캐시 내에 일시적으로 데이터 저장할 때 사용)
          • 장점: 빠르다
          • 단점: 데이터 일관성이 깨진다
      • 대형 저장소와 연결할 때에는 일반적으로 FC(Fibre Channel) 케이블을 사용해서 SAN(Storage Area Network) 네트워크를 경유한다. 서버 사이에 FC 포트가 없는 경우, 서버 사이의 통신을 위해 PCI 슬롯에 HBA 카드를 삽입하기도 한다.
      • c.f. 최근에는 SSD(반도체 디스크): 물리적인 회전 요소를 사용하지 않는 디스크
    • 네트워크 인터페이스: 서버와 외부 장비를 연결하기 위한 것(외부 접속용)
    • IOH/ICH: IO를 제어한다
      • IOH(IO 핸들러): 이전에는 메모리 IO가 주 역할이었지만, CPU에 그 역할이 옮겨간 후, 고속 처리가 필요한 PCI Express나 네트워크 IO를 제어한다. 또는 CPU간 데이터 전송 제어를 하기도 한다.
      • ICH(IO 컨트롤러): 속도가 느려도 괜찮은 DVD/USB 등의 IO를 제어하거나, IOH간의 데이터 전송 제어를 한다.
    • 아래는 다양한 I/O 예로, HDD의 I/O와 DVD의 I/O 경로가 물리적으로 다르다는 것을 알 수 있다.

 

  • 버스: 서버 내부 컴포넌트들을 연결시키는 회선으로, 전송능력(대역: 전송폭+전송횟수 = throughput)이 중요하다.

 

 

3장. 3계층형 시스템을 알아보자

3계층형 시스템의 전체 구성

  • 프로세스 및 스레드: 프로그램이 서버 내부의 디스크 상에 설치된 후, 실행 요청이 들어오면 커널이 프로세스를 실행 시키고 프로세스 시작 시에 요청 분량만큼 메모리를 할당한다. 즉, 프로세스 및 스레드는 프로그램 실행 후 OS 상에서 실행 되면서 어느 정도 독립성을 가지며 동작하는 것이다. 
    • 프로세스: 전용 메모리 공간을 이용해서 동작한다. (웹서버) => 독자 메모리 공간을 가지고 있기 때문에 생성 시 CPU 부하가 스레드와 비교할 때 높아진다. 따라서 멀티 프로세스 애플리케이션에서는 프로세스 생성 부담을 낮추기 위해 미리 프로세스를 시작시켜 둔다.(풀링 pooling)
    • 스레드: 다른 스레드와 메모리 공간을 공유하고 있다. (AP 서버) => 메모리 공간을 공유하기 때문에 의도하지 않는 데이터 읽기/쓰기가 발생할 수 있다.
    • 프로세스가 메모리 공간을 공유할 수 없는 것은 아니다. (오라클 DB) 여러 프로세스가 공유 메모리 공간을 상호 이용할 수 있는 방법도 있다. 이와 별도로 프로세스 별로 독자 메모리 영역도 있어서 용도별로 나누어 사용할 수 있다. 캐시 데이터를 일반적으로 프로세스 간에 공유하기 때문에 공유 메모리 상에 둔다.

 

  • OS 커널: OS 처리는 원칙적으로 커널을 통해 이루어진다. 커널은 다른 Layer(?)에서 이루어지는 처리를 은폐하고 편리한 인터페이스를 제공한다. 커널의 역할은 아래 6가지로 정리할 수 있다.
    • 시스템 콜 인터페이스: 프로세스나 스레드로부터(애플리케이션) OS 를 통해 어떤 처리를 하고 싶으면 시스템 콜로 커널에 명령을 하고, 이 명령이 인터페이스를 통해 전달된다. (키보드나 마우스 입력은 인터럽트로 처리)
    • 프로세스 관리: 가동되고 있는 프로세스 관리와 CPU 이용 우선순위 등을 스케쥴한다. => CPU 코어 고려
    • 메모리 관리: 서버 상의 메모리를 단위 크기의 블록으로 분할하여 프로세스에 할당하거나, 메모리 독립성 등을 관리 => 물리 메모리 공간 고려
    • 네트워크 스택: 네트워크를 관리 => 6장
    • 파일 시스템 관리: 디렉터리 구조 제공, 액세스 관리, 고속화, 안정성 향상 등의 기능을 제공함으로써 애플리케이션이 `파일` 단위로 데이터를 작성하거나 삭제할 수 있음
    • 장치 드라이버: 디스크, NIC, HBA 등의 물리 장치용 인터페이스 제공 => 이 장치 드라이버가 해당 OS 의 표준 장치로서 커널을 경유해 이용할 수 있도록 한다.
    • 커널 설계 및 구현 방식
      • monolithic 커널: OS의 주요 구성 요소를 모두 하나의 메모리 공간을 통해 제공한다. ex) UNIX 계열의 OS, 리눅스
      • micro 커널: 최소한의 기능만 커널이 제공하고, 그 외 기능은 커널 밖에서 제공한다. ex) Mac OS X

 

  • 웹 데이터 흐름
  •  

'작성중...' 카테고리의 다른 글

Java Reflection이란?  (0) 2021.01.23
Java HashMap vs LinkedHashMap vs TreeMap  (0) 2020.12.19
Web Development w/ Google’s Go (golang) Programming Language:  (0) 2020.12.17
Go: 기본 문법  (0) 2020.12.17

https://www.geeksforgeeks.org/map-interface-java-examples/

 

HashMap

  • HashMap은 Map 인터페이스를 implements 하고 있다. 
  • null key, null value 허용
  • 동기화되지 않고 null을 허용한다는 점을 제외하면 Hashtable과 거의 동일하다.
  • 순서가 보장되지 않는다.
  • 해시맵의 성능을 결정짓는 두 가지: initial capacity and load factor

 

 

https://codingdog.tistory.com/entry/%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-%EC%A7%81%EC%A0%91-%EA%B5%AC%ED%98%84%ED%95%B4-%EB%B4%85%EC%8B%9C%EB%8B%A4?category=1055061

https://codingdog.tistory.com/entry/%EC%99%9C-java%EC%97%90%EC%84%9C%EB%8A%94-equals-%EB%A9%94%EC%84%9C%EB%93%9C%EB%A5%BC-%EC%98%A4%EB%B2%84%EB%9D%BC%EC%9D%B4%EB%93%9C-%ED%95%98%EB%A9%B4-hashCode-%EB%8F%84-%EA%B0%99%EC%9D%B4-%ED%95%B4%EC%95%BC-%ED%95%A0%EA%B9%8C%EC%9A%94

 

 

 

www.udemy.com/course/go-programming-language/

 

Web Development w/ Google’s Go (golang) Programming Language

Learn Web Programming from a University Professor in Computer Science with over 15 years of teaching experience.

www.udemy.com

 

'작성중...' 카테고리의 다른 글

그림으로 배우는 IT 인프라 구조 후기  (0) 2021.03.01
Java Reflection이란?  (0) 2021.01.23
Java HashMap vs LinkedHashMap vs TreeMap  (0) 2020.12.19
Go: 기본 문법  (0) 2020.12.17

변수 및 상수, 열거형

변수

  • 하나씩 선언
    • var {변수명} {변수 타입}
    • 초기화 하지 않으면 기본값으로 초기화 된다.
package main

import "fmt"

var d string //package level scope

func main() {
	var a bool
	var b int
	var c float32 //block level scope

	fmt.Println(a) //false
	fmt.Println(b) //0
	fmt.Println(c) //0
	fmt.Println(d) //
}
  • 여러개 한번에 선언
package main

import "fmt"

func main() {
	var (
		name string = "name"
		age  int32
	)

	age = 25
	fmt.Println(name) //name
	fmt.Println(age) //25
}
  • 짧은 선언
    • 반드시 제한된 범위의 함수 내에서 사용(전역으로는 사용 불가) => 코드 가독성을 높일 수 있다.
    • 선언 후 재할당 하면 예외 발생한다. 즉, 짧은 선언은 1회성으로 메소드 안에서만 사용하는 경우 명시적으로 알려주는 기능
package main

import "fmt"

func main() {
	age := 3 // 내부적으로 알아서 int 할당

	// age := 10 -> 에러 발생: "no new variables on left side of :="

	fmt.Println(age)
    
    //언제 사용하는지? 예시
    if i := 10; i < 11{ // i는 if문 밖에서는 사용 불가, if문이 끝나면 소멸된다.
    	fmt.Println("test")
    }
}

 

상수

  • const로 사용하며 선언과 동시에 초기화, 한번 선언 후에는 값 변경 금지
  • 함수 리턴값을 받을 수 없다.
// 위와 같이 한줄씩, 여러개 한꺼번에 선언하는 방식 모두 가능하다  
  const a, b int = 1, 2
  const c, d = "Hi", false
  const (
    x, y int = 16, 21
  )

 

열거형

연속된 성질의 상수를 나열할 때 사용한다. (자바의 enum class와 비슷...?), 일정한 규칙에 따라 나열된 수

iota를 적절히 활용해서 초기화 할 수 있다.

package main

import "fmt"

func main() {
	const (
		x = iota
		y
		z
	)

	fmt.Println(x, y, z)

	const (
		_ = iota
		a // 1
		_ // 생략하고 싶은 부분은 _로 스킵 가능하다.
		b // 3
		c // 4
	)

	fmt.Println(a, b, c)
}

 

제어문과 반복문

if문

  • 반드시 boolean만 검사 (자동 형 변환 불가)
  • 엄격한 form을 요구 - 줄맞춤이 중요하다, 괄호 생략하면 안됨
package main

import "fmt"

func main() {
    const a int = 4
    if a < 11{ // 여는 괄호가 아랫줄로 가면 오류남
    	fmt.Println("1")
    } else if a < 5 { // else if 또는 else는 닫는 괄호 바로 뒤에 위치해야 한다
    	fmt.Println("2")
    } else {
    	fmt.Println("3")
    }
}

 

switch문

  • if문에서 할 수 있는 모든 조건을 switch문에서 할 수 있다.
  • switch/case 뒤 expression 생략 가능
  • 기본적으로는 자동 break => fallthrough 넣으면 다음 case문을 실행한다. (마지막 케이스에는 fallthrough넣으면 오류)
  • type 분기 => 값이 아닌 타입으로 분기 가능
package main

import "fmt"

func main() {
	a := 7

	switch {
	case a > 0: // case 인덴트는 switch 위치와 동일해야
		fmt.Println("양수 ", a)
	case a == 0: // case문에 && || 연산자 모두 사용 가능
		fmt.Println("0 ", a)
	case a < 0:
		fmt.Println("음수 ", a)
	}
	

	switch b := 7; { // 범위가 정확히 switch문으로 제한된다 (scoped)
	case b > 0: // 위 case문과 동일하다. go에서는 이렇게 더 많이 씀
		fmt.Println("양수 ", b)
	case b == 0:
		fmt.Println("0 ", b)
	case b < 0:
		fmt.Println("음수 ", b)
	}
    
	switch i, j := 7, 8; { // 여러개도 가능
	case i < j:
		fmt.Println("i < j")
	case i == j:
		fmt.Println("i == j")
	case i > j:
		fmt.Println("i > j")
	}
    
	switch c := "GO"; c {
	case "GO": // c == "GO" 해줄 필요 없음
		fmt.Println("go")
	case "JAVA":
		fmt.Println("java")
	default:
		fmt.Println("불일치")
	}
    
	switch c := "GO"; c + "LANG" { // 이 영역에서 연산도 가능하다
	case "GOLANG", "GO": // 다양한 값 매칭 가능 
		fmt.Println("go")
	case "JAVA":
		fmt.Println("java")
	default:
		fmt.Println("불일치")
	}
}

 

for문

  • go에서는 while 없음, 반복문은 for만 제공된다
  • if와 마찬가지로 괄호 위치 중요하다
  • 자바처럼 루프에 label을 주고 continue, break에 사용할 수 있다
  • continue, break 다른 언어와 동일하게 사용할 수 있다
package main

import "fmt"

func main() {

	for i := 0; i < 5; i++ { // 괄호 여부 및 위치 중요, i++ 대신 i = i+1 이런식으로 해도 된다
		fmt.Println(i) // 0~4 출력
	}
    
	j := 0
	for j < 5; { // 위와 동일
		fmt.Println(j) // 0~4 출력
		j++ // 주의 후치연산은 go에서 리턴값이 없음 -> k := j++ -> 컴파일 에러
	}

	//무한루프 패턴
	sum, k := 0, 0
	for {
		if i > 100 {
			break
		}
		sum += i
		i++
		// ++i -> c.f. 전치 연산자는 go에서 정의하지 않음, 컴파일 에러난다.
	}
    
	//range용법
	location := []string{"Seoul", "Incheon", "Busan"}
	for index, name := range loc { //첫번째로 가져오는 것은 무조건 인덱스
		fmt.Println(index, name)
	}

	location := []string{"Seoul", "Incheon", "Busan"}
	for _, name := range loc { //인덱스 안가져오고 싶은 경우엔 이렇게 하면 된다
		fmt.Println(index, name)
	}
}

 

배열, 슬라이스, 맵

 

 

 

포인터는 허용하는데, 포인터 연산은 비허용

 

+ Recent posts