
| 구분 | Push 캐싱 | Pull 캐싱 |
|---|---|---|
| 데이터 전달 주체 | 서버가 캐시에 데이터를 미리 넣음 | 클라이언트나 캐시 서버가 필요할 때 데이터를 가져옴 |
| 동작 방식 | 원본 서버가 변경된 데이터를 캐시 서버로 밀어 넣음 | 요청이 들어오면 캐시에 있는지 확인하고, 없으면 원본 서버에서 가져옴 |
| 캐시 생성 시점 | 사용자가 요청하기 전에 미리 생성 | 사용자가 요청한 후 필요할 때 생성 |
| 장점 | 사용자가 요청할 때 빠르게 응답 가능 | 불필요한 데이터를 미리 저장하지 않음 |
| 단점 | 사용하지 않을 데이터까지 캐싱될 수 있음 | 첫 요청은 원본 서버에 접근해야 해서 느릴 수 있음 |
| 적합한 경우 | 자주 사용될 데이터가 미리 정해져 있을 때 | 어떤 데이터가 요청될지 예측하기 어려울 때 |
| 예시 | 인기 영상, 뉴스 속보, CDN 사전 배포 | 웹 페이지 이미지, API 응답, 일반 CDN 캐시 |
| 방식 | 한 줄 요약 |
|---|---|
| Push 캐싱 | 서버가 필요할 것 같은 데이터를 미리 캐시에 넣는 방식 |
| Pull 캐싱 | 요청이 들어왔을 때 캐시에 없으면 그때 원본 서버에서 가져오는 방식 |
간단히 말하면, Push는 미리 넣어두는 캐싱, Pull은 요청이 오면 가져오는 캐싱입니다.
| 이유 | 설명 |
|---|---|
| 콘텐츠 수요 예측이 가능함 | 넷플릭스는 어떤 지역에서 어떤 콘텐츠가 많이 볼지 데이터를 기반으로 예측할 수 있다. 그래서 미리 가까운 캐시 서버에 넣어두기 좋다. |
| 첫 요청 지연을 줄일 수 있음 | Pull 캐싱은 첫 사용자가 요청할 때 원본 서버에서 가져와야 하므로 느릴 수 있다. 넷플릭스는 영상 재생 시작 지연이 중요하므로 미리 배치하는 Push 방식이 유리하다. |
| 영상 파일 크기가 큼 | 영화나 드라마는 용량이 크다. 요청이 들어올 때마다 원본에서 가져오면 네트워크 부담이 크다. |
| 피크 시간 트래픽을 줄일 수 있음 | 저녁 시간처럼 시청자가 몰리는 시간 전에 콘텐츠를 미리 캐시에 넣어두면, 피크 시간에 원본 서버와 백본망 부담을 줄일 수 있다. |
| 지역별 최적화가 가능함 | 한국 사용자에게 인기 있는 콘텐츠와 미국 사용자에게 인기 있는 콘텐츠가 다를 수 있다. 지역별 캐시 서버에 적절한 콘텐츠를 미리 배치할 수 있다. |
| ISP 내부에서 제공 가능 | 넷플릭스 Open Connect Appliance를 ISP 망 안에 배치하면 사용자는 더 가까운 서버에서 영상을 받을 수 있다. |
| 안정적인 스트리밍 품질 확보 | 캐시 미스가 줄어들어 버퍼링, 지연, 품질 저하 가능성을 낮출 수 있다. |
| 방식 | 넷플릭스에 대한 적합성 |
|---|---|
| Pull 캐싱 | 요청이 와야 캐싱되므로 첫 요청이 느리고, 인기 콘텐츠 공개 직후 원본 서버에 부하가 몰릴 수 있음 |
| Push 캐싱 | 콘텐츠를 미리 지역 캐시 서버에 넣어두므로 빠르고 안정적인 스트리밍 제공 가능 |
넷플릭스는 무엇이 많이 재생될지 예측할 수 있고, 영상 파일이 크며, 재생 지연에 민감한 서비스이기 때문에 Pull 캐싱보다 Push 캐싱이 더 적합하다.
UDP와 TCP는 모두 클라이언트와 서버가 소켓을 통해 데이터를 주고받는 방식이다.
하지만 UDP는 연결 없이 데이터를 바로 보내고, TCP는 먼저 연결을 만든 뒤 데이터를 주고받는다.
| 구분 | UDP | TCP |
|---|---|---|
| 소켓 타입 | SOCK_DGRAM | SOCK_STREAM |
| 연결 방식 | 연결 없이 전송 | connect()로 연결 후 전송 |
| 서버 준비 | bind() 후 바로 수신 대기 | bind() → listen() → accept() |
| 데이터 전송 | sendto(data, address) | send(data) |
| 데이터 수신 | recvfrom() | recv() |
| 주소 처리 | 보낼 때마다 목적지 주소 필요 | 연결 후에는 주소 생략 |
| 서버 소켓 | 하나의 소켓으로 송수신 | 환영 소켓과 연결 소켓 사용 |
| 특징 | 빠르지만 신뢰성 낮음 | 안정적이지만 연결 과정 필요 |
sendto()로 서버에 문장을 보낸다.recvfrom()으로 문장과 클라이언트 주소를 받는다.sendto()로 클라이언트에게 결과를 보낸다.recvfrom()으로 결과를 받고 출력한다.listen()으로 연결 요청을 기다린다.connect()로 서버에 연결한다.accept()로 연결을 수락하고 연결 소켓을 만든다.send()로 문장을 보낸다.recv()로 문장을 받고 대문자로 바꾼다.send()로 결과를 보낸다.recv()로 결과를 받고 출력한다.UDP는 연결을 만들지 않기 때문에 코드가 단순하고 빠르다.
대신 데이터가 반드시 도착한다는 보장은 없다.
TCP는 데이터를 보내기 전에 연결을 먼저 만든다.
코드는 UDP보다 조금 복잡하지만, 데이터의 도착과 순서를 보장한다.
인터넷 호스트의 식별자 중 하나는 www.facebook.com, www.google.com 등의 호스트 이름(hostname)이다.
그러나 호스트의 이름은 인터넷에서의 호스트 위치에 대한 정보를 거의 제공하지 않는다.
또한, 가변 길이의 알파뉴메릭 문자로 구성되므로 라우터가 처리하는 데 어려움이 있다.
이러한 이유로 호스트는 흔히 말하는 IP 주소(IP address)로 식별된다.
IP 주소는 4 바이트로 구성되고, 계층구조를 갖는다.
121.7.106.83과 같은 형태로 0 ~ 255의 십진수로 표현하는 각 바이트는 점으로 구분한다.사람은 호스트 네임을 선호하지만, 라우터는 고정 길이의 계층구조를 가진 IP 주소를 선호한다.
이 차이를 절충하기 위해 호스트 이름을 IP 주소로 변환해주는 디렉터리(directory) 서비스가 필요하다.
이 서비스가 인터넷 DNS(Domain name system)의 주요 임무다. (hostname translations, address resolutions)
DNS 서버들의 계층구조로 구현된 분산 데이터 베이스이다.
implemented in hierarchy of many name servers
호스트가 분산 데이터 베이스로 질의하도록 허락하는 애플리케이션 계층 프로토콜이다.
DNS 서버는 주로 BIND(Berkeley Internet Name Domain) 소프트웨어를 수행하는 유닉스(UNIX) 컴퓨터다.
DNS 프로토콜은 UDP 상에서 수행되고 포트 번호 53을 이용한다.
TCP의 경우 데이터 전송 시작 전에 3-way-handshaking 과정이 있는 반면, UDP는 연결 설정에 드는 비용이 없다.
DNS는 신뢰성보다 속도가 더 중요한 서비스이기 때문에 TCP보다 UDP가 더 적합하다.
또한, UDP는 512 bytes를 넘어가지 않는 패킷만 전송이 가능하고 오버헤드가 없어서 속도가 빠른데,
DNS가 전송하는 데이터 패킷 사이즈가 매우 작으므로 UDP가 유리하다.
이때 단순히 패킷의 사이즈가 작다고 DNS가 UDP를 채택한 것은 아니고,
전달하는 패킷의 크기가 작기 때문에 신뢰성이 보장되지 않아도 되기 때문이다. (못 받으면 다시 전달하면 된다.)
TCP는 호스트 간의 연결 상태를 유지한다.
이때 TCP의 패킷 안에는 여러 정보가 담겨 있지만, UDP는 어떤 정보도 기록하지 않고 유지할 필요도 없다.
DNS 서버는 TCP보다 많은 클라이언트를 수용할 수 있으므로 연결 상태를 유지하지 않고 정보 기록을 최소화할 수 있는 UDP를 채택하였다.
DNS는 위 예시에서 알 수 있듯이 추가 지연을 주지만, 가까운 DNS 서버에 캐싱되어 있어서 평균 DNS 지연 뿐만 아니라 DNS 네크워크 트래픽 감소에 도움을 준다.
복잡한 호스트 이름을 가진 호스트는 하나 이상의 별명을 가질 수 있다.
relay1.west-coast.enterprise.com 같은 호스트 이름은 enterprise.com 같은 별칭을 가질 수 있다.
이 경우에 relay1.west-coast.enterprise.com를 정식 호스트 이름(canonical hostname)이라고 한다.
DNS는 호스트의 IP 주소 뿐만 아니라 제시한 별칭 호스트 이름에 대한 정식 호스트 이름을 얻기 위해 이용될 수 있다.
전자 메일 주소는 간단하지만 그 서버의 호스트 네임은 일반적으로 더 복잡하다.
load balancing among the servers
인기 있는 사이트는 여러 서버에 중복되어 있어서, 각 서버가 다른 종단 시스템에서 수행되고 다른 IP 주소를 갖는다.
이때 여러 IP 주소가 하나의 정식 호스트 이름과 연관되어 있다. DNS 데이터베이스는 이 IP 주소 집합을 갖고 있다.
클라이언트가 주소 집합으로 매핑하는 호스트 이름에 대한 DNS 질의를 하면, 서버는 IP 주소 집합 전체를 가지고 응답한다. 각 응답에서의 주소는 순환식으로 보낸다.
클라이언트는 대체로 주소 집합 내부의 첫 번째 IP 주소로 HTTP 요청 메시지를 보내므로, DNS의 순환 방식은 트래픽을 분산하는 효과를 낸다.
[참고] What is DNS Load Balancing?
사용자의 호스트에서 실행되는 어떤 애플리케이션이 호스트 이름을 IP 주소로 변환하려 한다고 가정하자.
과정은 다음과 같다.
포트 53의 UDP 데이터그램으로 보내진다.DNS는 간단해보이지만 매우 복잡한데, 이는 전 세계에 분산된 많은 DNS 서버 뿐만 아니라
DNS 서버와 질의를 하는 호스트 사이에서 어떻게 통신하는지를 명시하는 애플리케이션 계층 프로토콜로 구성되어 있다.
why not centralize DNS?
만약 DNS가 간단한 설계로 모든 매핑을 포함하는 하나의 인터넷 네임 서버를 갖고 있다면, 수많은 호스트를 가진 오늘날 다음과 같은 문제를 일으킬 수 있다.
single point of failure)요약하면 중앙 집중 데이터베이스는 확장성(scalability)이 전혀 없고, 결과적으로 DNS는 분산되도록 설계되어있다.
DNS는 많은 서버를 이용하고 이들을 계층 형태로 구성하며 전세계에 분산시킨다.
인터넷 할당 번호 관리기관 ICANN(Internet Corporation for Assigned Names and Numbers)에 의해 조정된다.com, org, net 같은 상위 레벨 도메인과 kr, uk 같은 모든 국가의 상위 레벨 도메인에 대한 TLD 서버가 있다.
위 그림에서 cse.nyu.edu가 gaia.cs.umass.edu의 IP 주소를 원한다고 가정해보자.
edu를 인식하고, edu에 대한 책임을 가진 TLD 서버의 IP 주소 목록을 로컬 DNS 서버에 보낸다.umass.edu를 인식하고, dns.umass.edu로 이름 지어진 책임 DNS 서버의 IP 주소로 응답한다.gaia.cs.umass.edu의 IP 주소를 응답한다.여기서는 총 8번의 DNS 메시지가 보내졌다.
일반적으로 TLD 서버는 위의 예시와 같이 책임 DNS 서버를 알지 않고, 책임 DNS 서버를 아는 중간 DNS 서버를 알고 있다.
즉, 해당 질의 과정 까지 포함되면 전체 10번의 메시지를 보내게 된다.
위 예는 재귀적 질의와 반복적 질의를 사용한다.
cse.nyu.edu로부터 dns.nyu.edu로 보내는 질의는 자신을 필요한 매핑을 대신하여 얻도록 dns.nyu.edu에 요구하므로 재귀적 질의이고, 나머지는 반복적 질의다.
아래 그림은 모든 질의가 재귀적인 DNS 질의 사슬을 보여준다.
일반 질의는 전형적으로 반복적 질의를 따른다.
실제로는 DNS 지연 성능 향상과 네트워크의 DNS 메시지 수를 줄이기 위해 캐싱(caching)을 사용한다.
질의 사슬에서 DNS 서버는 DNS 응답을 받았을 때 로컬 메모리에 응답에 대한 정보를 저장할 수 있다.
만약 호스트의 이름과 IP 주소 쌍이 DNS 서버에 저장되고 다른 호스트 이름으로부터 같은 질의가 DNS 서버로 도착한다면,
DNS 서버는 호스트 이름에 대한 책임이 없을 때조차 원하는 주소를 제공할 수 있다.
호스트 DNS와 IP 사이의 매핑과 호스트는 영구적이지 않기 때문에 어떤 기간(TTL, Time to Live) 이후에 저장된 정보를 제거한다.
로컬 DNS 서버는 구체적인 IP 주소 이외에도 TLD 서버의 IP를 저장하여 루트 DNS 서버를 우회할 수 있게 한다.
각 DNS는 하나 이상의 자원 레코드를 가진 메시지로 응답한다.
DNS 서버들은 호스트 이름을 IP 주소로 매핑하기 위한 자원 레코드(Resource Records)를 저장한다.
자원 레코드는 다음과 같은 필드를 포함하는 4개의 Tuple로 되어 있다.
(Name, Value, Type, TTL)
TTL(Time to Live)은 자원 레코드의 생존 기간이다.
Name과 Value는 Type을 따른다. 즉, Type에 따라서 Name과 Value에 대한 semantic(해석법)이 달라진다.
AAddress
Type A 레코드는 표준 호스트 이름의 IP 주소 매핑을 제공한다.
NSName Server
CNAMECanonical NAME
MXMail eXchange
메일 서버의 정식 이름을 얻기 위해서는 MX 레코드에 대한 질의를 해야 하고, 다른 서버의 정식 이름을 얻기 위해선 CNAME 레코드에 대한 질의를 한다.
DNS 서버가 특별한 호스트 이름에 대한 책임 서버이면, 그 DNS 서버는 호스트 이름에 대한 Type A 레코드를 포함한다.
서버가 호스트 이름에 대한 책임 서버가 아니라면, 그 서버는 호스트 이름을 포함하는 DNS 서버의 IP 주소를 제공하는 Type A 레코드도 포함할 것이다.
DNS의 요청과 응답 메시지는 모두 아래 그림과 같은 포맷을 갖고 있다.
처음 12 byte의 헤더 영역 : 여러 필드를 갖고 있다.
첫 필드는 질의를 식별하는 16 bit의 숫자이다.
이 식별자는 질의에 대한 응답 메시지에 복사되어, 클라이언트가 보낸 질의와 수신된 응답 간의 일치를 식별하게 한다.
플래그(flag) 필드에는 여러 개의 플래그가 있다.
질의/응답 플래그는 메시지가 질의인지 응답인지 구분하게 한다.책임 플래그는 DNS 서버가 질의 이름에 대해 책임 서버일 때 응답 메시지에 설정된다.재귀 요구 플래그는 DNS 서버가 레코드를 갖지 않을 때 재귀적 질의를 수행하기를 클라이언트가 원할 때 설정된다.재귀 가능 필드는 DNS 서버가 재귀 질의를 지원하면 응답에 설정된다.나머지 4개의 '개수' 필드는 헤더 다음에 오는 데이터 영역의 네 가지 타입의 발생 횟수를 나타낸다.
질문 영역답변 영역책임 영역추가 영역도메인 네임 networkutopia.com을 등록 기관(DNS registrar)에 등록한다고 가정해보자.
등록 기관(DNS registrar)은 도메인 네임의 유일성을 확인하고,
그 도메인 네임을 DNS 데이터베이스에 넣고, 그 서비스에 대한 요금을 받는 상업 기관이다.
이전에는 작은 등록기관이 독점했었지만, 이제는 많은 기관이 경쟁하고 ICANN이 이러한 여러 등록기관을 승인해준다.
도메인 네임을 어떤 등록기관에 등록할 때 등록 기관에 주책임 서버와 부책임 서버의 이름과 IP 주소를 등록기관에 제공해야 한다.
dns1.networkutopia.com / 주책임 서버 IP : 212.2.212.1dns2.networkutopia.com / 부책임 서버 IP : 212.2.212.2위와 같다고 가정하자.
이 두 책임 DNS 서버 각각에 대해 등록 기관은 Type NS와 Type A 레코드가 TLD com 서버에 등록되도록 확인한다.
특히 주책임 서버의 경우 다음 두 개의 자원 레코드를 DNS 서버에 삽입한다.
(networkutopia.com, dns1.networkutopia.com, NS)
(dns1.networkutopia.com, 212.212.212.1, A)
또한, Type A 레코드와 메일 서버에 대한 Type MX 자원 레코드가 책임 DNS 서버에 등록되는 것을 확인한다.
이러한 모든 단계가 끝나면 여러 사람들이 웹 사이트를 방문할 수 있고, 전자메일을 보낼 수 있게 된다.
공격자는 DNS 루트 서버로 다량의 패킷을 보내려는 시도를 하여 다른 DNS 질의들이 응답을 받지 못하게 하려 한다.
실제로 이 일이 일어났지만, 많은 DNS 루트 서버들은 루트 서버로 향하는 공격자가 사용한 ICMP 핑 메시지를 블록하도록 형상화한 패킷 필터로 보호되었고,
대부분의 로컬 DNS 서버가 최상위 도메인 서버들의 IP 주소들을 캐싱하고 있어서 피해가 거의 없었다.
즉, 더 효과적인 공격은 최상위 도메인 서버를 공격하는 것이고, 실제로 최상위 도메인 서비스 제공자 Dyn에 이러한 일이 발생했다.
이는 유명 애플리케이션들이 무차별 교란되는 결과를 야기했다.
공격자는 DNS 서버로 가짜 응답을 보내어 그 서버가 자신의 캐시에 가짜 레코드를 받아들이도록 속임수를 쓴다.
이러한 공격은 웹 사용자들을 공격자의 웹사이트로 유도하는 데 이용될 수 있다.
이러한 공격을 막기 위해 DNS 보안 확장 프로토콜이 개발되어 사용되고 있다.
비디오는 이미지의 연속으로서 일반적으로 초당 24개에서 30개의 이미지로 일정한 속도로 표시된다.
압축되지 않은 디지털 인코딩된 이미지는 픽셀 단위로 구성되며, 각 픽셀은 휘도와 색상을 나타내는 여러 비트들로 인코딩된다.
비디오의 중요한 특징은 압축될 수 있다는 것인데, 비디오 품질과 비트 전송률은 서로 반비례한다.
오늘날의 상용 압축 알고리즘은 근본적으로 원하는 모든 비트 전송률로 비디오를 압축할 수 있다. (비트 전송률이 높을수록 이미지 품질이 좋다.)
네트워킹 측면에서 비디오의 가장 두드러진 특성은 높은 비트 전송률이다.
인터넷 비디오는 일반적으로 고화질 동영상을 스트리밍 하기 위해 100 kbps에서 4 Mbps 이상으로 구성된다.
4K 스트리밍은 10 Mbps 이상의 비트 전송률로 예상된다. 이는 하이엔드 동영상의 경우 트래픽과 스토리지 용량이 엄청나게 필요함을 의미한다.
연속 재생을 제공하기 위해, 네트워크는 압축된 비디오의 전송률 이상의 스트리밍 애플리케이션에 대한 평균 처리량을 제공해야 한다.
→ 압축을 사용하여 동일한 비디오를 여러 버전의 품질로 만들 수 있다.
HTTP 스트리밍에서 비디오는 HTTP 서버 내의 특정 URL을 갖는 일반적인 파일로 저장된다.
특히, 버퍼에서 주기적으로 비디오 프레임을 가져와 프레임을 압축 해제한 다음 사용자의 화면에 표시한다.
가용 대역폭이 달라도 똑같이 인코딩된 비디오를 전송 받는다는 문제가 있다.
이 문제로 인한 HTTP 기반 스트리밍인 DASH(Dynamic Adaptive Streaming over HTTP)가 개발되었다.
비디오는 여러가지 버전으로 인코딩 되며, 각 버전은 비트율과 품질 수준이 서로 다르다.
클라이언트는 동적으로 서로 다른 버전의 비디오를 몇 초 분량의 길이를 갖는 비디오 조각 단위로 요청한다.
가용 대역폭이 충분할 때는 높은 비트율의 비디오 버전을 요청하며, 가용 대역폭이 적을 때는 낮은 비트율의 비디오 버전을 요청한다.
즉, 클라이언트는 자신의 상황에 알맞은 비디오 버전을 요청한다.
각 비디오 버전은 HTTP 서버에 서로 다른 URL을 가지고 저장된다.
HTTP 서버는 비트율에 따른 각 버전의 URL을 제공하는 매니페스트(manifest) 파일을 갖고 있다.
클라이언트는 이 매니페스트 파일을 제공받고, 이에 따라 매번 원하는 버전의 비디오 조각 단위를 선택하여 HTTP GET 요청 메시지에 URL과 byte-range를 지정하여 요청한다.
궁극적으로 DASH는 클라이언트가 서로 다른 품질 수준을 자유롭게 변화시킬 수 있도록 허용한다.
단일 거대 데이터 센터를 구축하고 모든 비디오 자료를 데이터 센터에 저장한 뒤, 전 세계의 사용자에게 비디오 스트림을 데이터 센터로부터 직접 전송한다.
이러한 문제를 해결하기 위해 대부분의 비디오 스트리밍 회사들은 콘텐츠 분배 네트워크(CDN)를 이용한다.
CDN은 다수의 지점에 분산된 서버들을 운영하며, 비디오 및 다른 형태의 웹 콘텐츠 데이터의 복사본을 이러한 분산 서버에 저장한다.
사용자는 최적의 사용자 경험을 제공받을 수 있는 지점의 CDN 서버로 연결된다.
CDN은 구글처럼 사설 CDN일 수도 있으며, 제 3자가 운영하는 CDN을 통해 서비스될 수도 있다.
두 개의 철학 중 하나를 채용한다.
push CDN servers deep into many access networks
Akamai에 의해 주창된 것으로서 서버 클러스터를 세계 곳곳의 접속 네트워크에 구축함으로써 ISP의 접속 네트워크로 깊숙이 들어가는 것이다.
즉, 최대한 서버를 사용자 근처에 위치시켜 링크 및 라우터를 거치는 횟수를 줄여 지연시간 및 처리율을 개선하는 것이다.
smaller number (10’s) of larger clusters in POPs near (but not within) access networks
Limelight와 다른 회사들에 의해 적용된 것으로, 좀 더 적은 수의 핵심 지점에 큰 규모의 서버 클러스터를 구축하여 ISP를 Home으로 가져오는 개념이다.
접속 ISP에 연결하는 대신, 일반적으로 CDN들은 그들의 클러스터를 인터넷 교환 지점(IXP)에 배치한다.
이에 따라 Enter Deep보다 처리율(throughput)은 더 낮고 delay가 더 걸릴 수 있지만, 회사의 입장에서는 유지 보수하기에 편하며, 비용이 적게 든다.
CDN은 콘텐츠의 복사본을 이들 클러스터에 저장하는데 모든 복사본을 유지하지는 않는다.
어떤 비디오는 인기가 거의 없거나 특정 국가에서만 인기가 있을 수 있기 때문이다.
실제로 CDN은 클러스터에 대해 사용자의 요청이 오면 중앙 서버나 다른 클러스터로부터 전송받아 사용자에게 서비스하는 동시에 복사본을 만들어 저장하는 pull 방식을 이용한다.
저장 공간이 가득 차게 되면 자주 사용되지 않는 비디오 데이터는 삭제된다.
요청을 가로챌 때 CDN은 DNS를 활용한다. 이를 DNS redirection이라고 한다.
위 CDN이 DNS를 통해 가로채는 과정에서 CDN은 클라이언트의 로컬 DNS 서버의 IP 주소를 알게된다. 이 IP 주소에 기초해 최선의 클러스터를 선택한다.
지리적으로 가장 가까운 클러스터를 할당한다.
사용자 지리정보 데이터베이스를 이용하면 얻은 IP 주소를 지리적으로 매핑할 수 있고, 가장 가까운 클러스터를 선택하는 것이다.
대부분 잘 동작하나, 지리적으로 가까운 클러스터가 네트워크 경로의 길이 홉의 수에 따라 가장 가까운 클러스터가 아닐 수 있고, 까운 로컬 DNS 서버를 이용하고 있지 않을 수 있기 때문에 잘 동작하지 않을 수 있다.
실시간 측정
클러스터와 클라이언트 간의 지연 및 손실 성능에 대한 주기적인 실시간 측정을 통해 현재 네트워크 상황을 반영하여 최선의 클러스터를 선택하는 것이다.
문제는 많은 로컬 DNS 서버가 이러한 측정에 응답을 하지 않도록 설정되어 있다.
자체 CDN을 구축하기 위해 넷플릭스는 IXP 및 거주용 ISP 자체에서 서버 랙(rack)을 설치했다.
현재 IXP 위치에 200대 이상의 서버 렉과 서버 랙을 수용하는 수백 개의 ISP 장소도 보유하고 있다.
각각의 랙 서버에는 10 Gbps 이더넷 포트와 100 테라바이트 이상의 스토리지가 있다.
넷플릭스는 푸시 캐싱(push caching)을 사용하여 IXP 및 ISP CDN 서버를 채운다.
전체 라이브러리를 보유할 수 없는 위치의 경우, 매일매일 가장 많이 결정되는 비디오만 푸시한다.
넷플릭스는 자체 CDN을 사용하고 있기 때문에 DNS redirection을 사용할 필요가 없다.
대신 아마존 클라우드에서 실행되는 것처럼 넷플릭스 소프트웨어는 클라이언트에게 특정 CDN 서버를 사용하도록 알려준다.
또한 넷플릭스 CDN은 풀 캐싱보다 푸시 캐싱을 사용한다.
콘텐츠는 캐시 미스 중에 동적으로 사용되는 것이 아니라 사용량이 적은 시간 중 예약된 시간에 서버에 푸시한다.
넷플릭스와 마찬가지로 자체 CDN을 사용한다.
구글 역시 사용자를 특정 서버 클러스터와 연결하는 데 DNS를 사용한다.
대부분의 경우 클러스터 선택 정책은 클라이언트와 클러스터간의 RTT가 가장 적은 곳을 선택하는 것이다.
때로는 작업 부하를 위해 더 멀리 있는 CDN을 선택하기도 한다.
유튜브는 HTTP 스트리밍을 채용하여 사용자가 직접 버전을 선택하게 했다.
재생 위치 조정과 조기 종료로 인한 대역폭과 서버 자원의 낭비를 줄이기 위해,
유튜브는 HTTP byte-range 헤더를 이용해 목표한 분량의 선인출 데이터 이후에 추가로 전송되는 데이터 흐름을 제한한다.
네트워크 애플리케이션을 생성할 때는 두 프로그램, 클라이언트와 서버 프로그램을 작성해야 한다.
두 프로그램을 실행하면 프로세스가 생성되고, 두 프로세스가 소켓으로부터 읽고 쓰기를 통해 서로 통신한다.
클라이언트 - 서버 애플리케이션에는 두 가지 형태가 있다.
1️⃣ HTTP 등의 RFC에 정의된 표준 프로토콜을 구현하는 클라이언트-서버 애플리케이션
이 애플리케이션을 구현할 때 그 프로토콜과 연관된 port를 사용하여야 한다.
2️⃣ 개인의 독점적인 네트워크 애플리케이션으로 RFC 또는 다른 곳에 공식적으로 출판되지 않은 애플리케이션 계층 프로토콜을 채택하여 구현하는 애플리케이션
다른 독립 개발자는 이 애플리케이션과 상호작용하는 코드를 개발할 수 없다. 이 애플리케이션을 구현할 때는 잘 알려진 포트번호를 사용하지 않도록 유의해야 한다.
개발자는 TCP, UDP 프로토콜 중 어떤 프로토콜을 사용해야 하는지 각 프로토콜의 특징을 고려하여 선택해야 한다.
UDP를 사용할 때에는 송신 프로세스가 데이터의 패킷을 소켓 문 밖으로 밀어내기 전에, 먼저 패킷에 목적지 주소를 붙여넣어야 한다.
이 패킷이 송신자의 소켓을 통과한 후 인터넷은 이 목적지 주소를 이용하여 그 패킷을 인터넷을 통해 수신 프로세스에 있는 소켓으로 라우트(route)할 것이다.
패킷이 수신 소켓에 도착하면 수신 프로세스는 소켓을 통해 그 패킷을 추출하고, 다음에 패킷의 콘텐츠를 조사하여 적절한 동작을 취한다.
패킷에 목적지 주소를 포함함으로써 인터넷의 라우터는 목적지 호스트로 인터넷을 통해 패킷을 라우트할 수 있다.
호스트는 각자 IP 주소를 식별자로 갖는다.
그러나 호스트는 하나 혹은 그 이상의 소켓을 갖는 많은 네트워크 애플리케이션 프로세스를 수행하고 있을 수 있기 때문에,
목적지 호스트 내의 특정한 소켓을 식별할 필요가 있다.
소켓이 생성될 때 포트 번호라고 하는 프로세스 식별자가 소켓에 할당된다.
즉, 패킷에는 IP 주소와 포트번호로 구성된 목적지 주소가 붙게 되며, 송신자의 출발지 주소(IP와 port)도 붙게 된다.
일반적으로 이렇게 주소를 붙이는 것은 하부 운영체제가 자동으로 실행한다.
위 그림은 UDP 서비스 상에서 통신하는 클라이언트와 서버의 주요 소켓 관련 활동을 나타낸다.
위 순서로 작동하는 간단한 클라이언트-서버 애플리케이션을 만들 예정이다.
소켓을 생성할 때는 따로 소켓의 포트 번호를 명시하지 않아도 되며, 운영체제가 이 작업을 대신 수행한다.
# socket module이다. 이 module을 통해 소켓을 생성할 수 있다.
from socket import *
#서버의 IP 혹은 서버의 호스트 이름을 할당한다.
serverName = ’hostname’
# 목적지 port 번호를 나타낸다.
serverPort = 12000
# 클라이언트 소켓을 생성한다. AF_INET은 IPv4를 사용하고 있음을 나타내고, SOCK_DGRAM은 UDP 소켓임을 의미한다.
clientSocket = socket(AF_INET, SOCK_DGRAM)
# 보낼 메시지를 입력 받는다.
message = Input(’Input lowercase sentence:’)
# 소켓으로 바이트 형태를 보내기 위해 먼저 encode()를 통해 바이트 타입으로 변환한다.
# sendTo() 메서드는 목적지 주소를 메시지에 붙이고 그 패킷을 프로세스 소켓인 clientSocket으로 보낸다.
# 클라이언트 주소도 같이 보내지는데 이는 자동으로 수행된다.
clientSocket.sendto(message.encode(),(serverName, serverPort))
# 패킷 데이터는 modifiedMessage에 저장되고, 패킷의 출발지 주소(IP, port)는 serverAddress에 할당된다.
# recvfrom() 메서드는 2048의 버퍼 크기로 받아들인다.
modifiedMessage, serverAddress = clientSocket.recvfrom(2048)
# 출력
print(modifiedMessage.decode())
# 소켓 닫기
clientSocket.close()
while 문을 통하여 한 번의 통신 이후에도, 계속 다음 UDP 패킷이 도착하기를 기다린다.
from socket import *
# 포트 번호
serverPort = 12000
# UDP 소켓 생성
serverSocket = socket(AF_INET, SOCK_DGRAM)
# 12000 포트 번호를 소켓에 할당한다. 이를 통해 서버 IP 주소의 12000 포트로 패킷을 보내면 해당 소켓으로 패킷이 전달된다.
serverSocket.bind((’’, serverPort))
print(”The server is ready to receive”)
while True:
# 패킷이 서버에 도착하면 데이터는 메세지에 할당되고 패킷의 출발지 주소는 clientAddress에 저장된다.
# 해당 주소로 서버는 응답을 어디에 보내야할지 알 수 있다.
message, clientAddress = serverSocket.recvfrom(2048)
# 바이트 데이터를 decode()하고 대문자로 변환한다.
modifiedMessage = message.decode().upper()
# 클라이언트 주소를 대문자로 변환된 메시지에 붙이고, 그 결과로 만들어진 패킷을 서버에 보낸다.
# 서버의 주소도 같이 보내지는데 이는 자동으로 수행된다.
serverSocket.sendto(modifiedMessage.encode(), clientAddress)
TCP는 연결 지향 프로토콜로, 서로 데이터를 보내기 전에 먼저 TCP 연결을 설정할 필요가 있다.
TCP 연결을 생성할 때 클라이언트 소켓 주소와 서버 소켓 주소를 연결과 연관시킨다.
연결이 설정된 후 소켓을 통해 데이터를 TCP 연결로 보내면 된다.
서버 프로세스가 실행되면 클라이언트 프로세스는 서버로의 TCP 연결을 시도하는데, 이는 클라이언트 프로그램에서 TCP 소켓을 생성함으로써 가능하다.
TCP 소켓을 생성할 때, 서버에 있는 환영(welcome) 소켓의 주소(IP, port #)를 명시한다.
소켓을 생성한 후 클라이언트는 3-way handshake를 하고 서버와 TCP 연결을 설정한다. (핸드셰이킹은 프로그램에서 전혀 인지 못한다.)
핸드셰이킹 동안 서버는 해당 클라이언트에게 지정되는 새로운 소켓을 생성한다. 이를 연결 소켓이라고 한다.
애플리케이션 관점에서 볼 때 클라이언트의 소켓과 서버의 연결 소켓은 파이프에 의해 직접 연결된다.
파이프를 통해 클라이언트는 자신의 소켓으로 임의의 바이트를 보낼 수 있으며, 서버 프로세스가 그것을 수신하는 것을 TCP가 보장한다. 이는 서버 입장에서도 마찬가지이다.
위 그림은 전형적인 클라이언트-서버 TCP 연결 구조이다.
UDP 프로그램과 똑같은 기능을 하는 프로그램을 작성해보자.
주석은 UDP와 다른 부분을 위주로 작성하였다.
from socket import *
serverName = ’servername’
serverPort = 12000
# 클라이언트 소켓을 의미한다. SOCK_STREAM으로 TCP 소켓임을 명시했다.
# UDP 때와 마찬가지로 따로 출발지 주소를 명시하지 않는다. (운영체제가 대신 해준다.)
clientSocket = socket(AF_INET, SOCK_STREAM)
# 클라이언트가 TCP 소켓을 이용하여 서버로 데이터를 보내기 전에 TCP 연결이 먼저 클라이언트와 서버 사이에 설정되어야 한다.
# 해당 라인으로 TCP 연결을 시작하고, connect() 메서드의 파라미터는 연결의 서버 쪽 주소이다.
# 이 라인이 수행된 후에 3-way handshake가 수행되고 클라이언트와 서버 간에 TCP 연결이 설정된다.
clientSocket.connect((serverName, serverPort))
sentence = raw_input(’Input lowercase sentence:’)
# 클라이언트 소켓을 통해 TCP 연결로 보낸다. UDP 소켓처럼 패킷을 명시적으로 생성하지 않으며 패킷에 목적지 주소를 붙이지 않는다.
# 대신 클라이언트 프로그램은 단순히 문자열에 있는 바이트를 TCP 연결에 제공한다.
clientSocket.send(sentence.encode())
# 서버로부터 바이트를 수신하기를 기다린다.
modifiedSentence = clientSocket.recv(1024)
print(’From Server: ’, modifiedSentence.decode())
# 연결을 닫는다. 이는 클라이언트 TCP가 서버의 TCP에게 TCP 메시지를 보내게 한다.
clientSocket.close()
from socket import *
serverPort = 12000
# TCP 소켓 생성
serverSocket = socket(AF_INET, SOCK_STREAM)
# 서버의 포트 번호를 소켓과 연관시킨다.
serverSocket.bind((’’, serverPort))
# 연관시킨 소켓은 대기하며 클라이언트가 문을 두드리기를 기다린다.
# 큐잉되는 연결의 최대 수를 나타낸다.
serverSocket.listen(1)
print(’The server is ready to receive’)
while True:
# 클라이언트가 TCP 연결 요청을 하면 accept() 메소드를 시작해서 클라이언트를 위한 연결 소켓을 서버에 생성한다.
# 그 뒤 클라이언트와 서버는 핸드셰이킹을 완료해서 클라이언트의 소켓과 연결 소켓 사이의 TCP 연결을 생성한다.
connectionSocket, addr = serverSocket.accept()
sentence = connectionSocket.recv(1024).decode()
capitalizedSentence = sentence.upper()
connectionSocket.send(capitalizedSentence.encode())
# 응답을 보내고 연결 소켓을 닫는다. 그러나 환영소켓인 serverSocket이 열려있어 다른 클라이언트가 서버에 연결을 요청할 수 있다.
connectionSocket.close()