통신을 도와주는 네트워크 주요 기술

NAT (Network Address Translation,NAPT,PAT)

  • IP 주소를 다른 IP주소로 변환해 라우팅을 원할히 해주는 기술
  • 사설 IP주소를 공인 IP로 전환하는 경우에 NAT를 가장 자주 사용함

NAT 필요성

  • IPv4 주소 고갈 문제 : 외부에는 공인 IP로 보이지만 내부에는 사설 IP체계를 사용

  • 보안 강화 : 사내 사설 IP 주소 체계를 숨길 수 있다.

  • IP 주소 체계가 같은 두 개의 네트워크간 통신을 가능하게 해준다. 사내 IP가 겹치는 경우 , IP대역이 같은 네트워크와 통신이 가능함 (Double Nat 기술)

  • 불필요한 설정 변경을 줄일 수 있다.

KISA를 통해 인터넷 독립기관으로 직접 등록하고 소유한 IP주소를 직접 운영하는게 아니라면 통신사업자나 IDC 쪽에서 IP를 할당받아 사용하게 되고 , IP주소가 중간에 변경될 수 있다.
NAT 기술을 사용하면 내부 사설 IP체계를 유지하면서 공인 IP만 독립적으로 변경할 수 있다.

NAT 동작 방식

  1. NAT 내부 사용자는 웹 서버에 접근하기 위해 사용자의 IP주소와 포트번호를 3,4계층 헤더에 넣어 패킷을 보낸다.

  2. NAT 역할을 수행하는 장비에서 사용자가 보낸 패킷을 받아, NAT 정책에 따라 공인 IP주소로 변경한다.
    NAT 장비에서 변경 전후의 IP주소는 NAT 테이블에 저장된다.

  3. 웹 서버는 NAT가 변경한 공인 IP주소로 응답을 보낸다.

  4. NAT 장비는 NAT 테이블을 보고 사용자의 원래 IP주소로 변경하여 사용자에게 패킷을 전달한다.

PAT 동작 방식

  1. NAT 동작 방식과 동일하나 , 다수의 사용자가 동일한 공인 IP 주소로 변환되야 하므로 출발지의 IP주소뿐 아니라 Port 번호도 변경하여 NAT 테이블에 저장한다.
  2. 웹서버가 변경된 공인 IP주소와 변경된 Port번호로 응답한다.
  3. NAT 장비는 NAT 테이블을 보고 사용자의 원래 IP주소와 Port 번호로 패킷을 재작성하여 사용자에게 패킷을 전달한다.
  • PAT는 다수의 IP가 있는 출발지에서 목적지로 갈때 IP주소,포트번호로 사용자를 식별할 수 있는 NAT 테이블이 생성되고, 응답에 대해 NAT 테이블이 원래 사용자를 식별한다.
  • 반대로 PAT IP가 목적지일때에는 해당 IP가 어느 IP에 binding되고 있는지 확인할 수 있는 NAT 테이블이 없으므로 사용할 수 없다.
Read more

4계층 장비 (로드 밸런서 / 방화벽)

4계층 장비의 특징

  • 4계층 장비(세션 장비)는 통신의 방향성이나 순서와 같은 통신 전반에 관한 관리가 필요하는데, 이러한 정보를 세션 테이블이라는 공간에 담아 관리한다.

로드 밸런서

  • 서버의 부하를 분산시키기 위한 장비

  • 로드 밸런서가 서비스에 사용되는 대표 IP주소를 가지고, 그 밑에 실제 서버들의 실제 IP주소로 요청을 전달한다.

  • WAS뿐만아니라 FWLB(FireWall Load Balancing) , VPNLB(VPN Load Balancing) 와 같이 다양한 서비스에 사용될 수 있다.

  • 시스템 Scale Out시 사용

(* Scale up : 메모리,cpu와 같은 하드웨어 리소스의 성능을 높임 / Scale out : 같은 리소스를 가진 시스템을 여러개 병렬로 배치 )

로드밸런서는 동작하는 계층에 따라 4,7계층으로 나뉜다.

  • L4 로드 밸런싱은 일반적인 로드 밸런서가 동작하는 방식으로 , 4계층 헤더 정보(TCP,UDP)를 기반으로 로드 밸런싱을 수행한다.
  • L7 로드 밸런싱 application 계층의 프로토콜 (HTTP,FTP,SMTP)을 기반으로 로드 밸런싱을 수행한다.
  • L7 로드 밸런서를 ADC(application delivery controller) 라고 부르며, proxy 역할을 수행하며, Nginx 의 reverse proxy와 유사한 기능을 수행한다. (https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/)

(* Reverse proxy : 외부에서 내부 서버가 제공하는 서비스 접근시, proxy 서버를 먼저 거치고 내부서버를 들어오는 방식)

  • AWS 에서는 계층별로 L4,L7 로드 밸런싱을 수행해주는 컴포넌트를 제공한다.
  • NLB(Network Load Balaner) == L4 로드 밸런싱 / ALB(Application Load Balancer) == L7 로드 밸런싱

L4 스위치

  • 4계층에서 동작하는 로드 밸런서 기능이 있는 스위치로 여러개의 포트를 가지고 있다.

  • SW 형태의 로드 밸런서도 있지만, 다양한 네트워크 구성이 가능한 스위치형 로드 밸런서가 가장 대중화되어 있다.

  • L4 스위치가 동작하기 위해 필요한 설정은 다음과 같다

  1. 가상 서버 : 사용자가 바라보는 서비스
  2. 가상 IP : 사용자가 접근해야하는 서비스의 IP주소
  3. 리얼 서버 : 실제 서비스를 수행하는 서버
  4. 리얼 IP : 리얼 서버의 IP 주소

L4스위치는 사용자가 가상 IP주소로 접근하였을때 이를 리얼 IP주소에 대한 요청으로 변경해주는 역할을 한다.

Read more

Item61. 박싱된 기본 타입보다는 기본 타입을 사용하라

  • java data type은 primitive type과 reference type 이 있으며, primitive type 에는 각각에 대응되는 reference type이 존재하는데 이를 boxing 된 primitive type이라고 한다.
    ( ex) int - Integer , long - Long )

primitive 타입과 boxing된 primitive type간의 차이점

  1. 기본 타입의 값은 언제나 유효하나 (기본값이 존재) , reference type은 null을 가질 수 있다.
  2. 메모리측면에서 기본타입이 더 가볍다
  3. 기본 타입에 비해 reference type은 값에 더해 식별성(identity)라는 속성을 갖는다. 즉 값이 같아도 서로 다르다고 식별될 수 있다.
Read more

Item60.정확한 답이 필요하다면 float와 double은 피하라

  • float 와 double 타입은 정밀한 근사치로 계산하기 떄문에 정확한 계산이 필요한 경우 (금융) 사용하면 안된다.
1
2
3
4
public static void main(String[] args) {
System.out.println(1.03-0.42); // 0.6100000000000001
System.out.println(1.00 - 9 * 0.10); // 0.09999999999999998
}

double, long 타입을 사용해 정확한 계산이 나오지 않는 또다른 예시는 다음과 같다.

1
2
3
4
5
6
7
8
double funds = 1.00;
int itemsBought = 0;
for (double price = 0.10 ; funds >= price ; price += 0.10){
funds -= price;
itemsBought++;
}
System.out.println(itemsBought + "개 구입"); // 3개 구입
System.out.println("잔돈(달러):"+ funds ); // 잔돈(달러):0.3999999999999999

금융 계산 처럼 정확한 계산이 필요한 경우에는 double,long 대신해서

  • 성능이 중요한 경우라면 int,long타입을 사용하고 , 소수점을 직접 관리한다
  • 성능이 중요하지 않은 경우라면 , BigDecimal 타입을 사용하고, BigDecimal API를 활용한다.
1
2
3
4
5
6
7
8
9
10
// BigDecimal을 활용한 정확한 계산
final BigDecimal TEN_CENTS = new BigDecimal("0.10");
BigDecimal funds = new BigDecimal("1.00");
int itemsBought = 0;
for (BigDecimal price = TEN_CENTS ; funds.compareTo(price) >= 0 ; price = price.add(TEN_CENTS)){
funds = funds.subtract(price);
itemsBought++;
}
System.out.println(itemsBought + "개 구입");
System.out.println("잔돈(달러):"+ funds );
Read more

3계층 장비 (라우터,L3 스위치)

Router 역할

Router는 크게 아래와 같은 3가지 역할을 갖는다.

  1. 경로 지정
  • Router는 최적의 경로를 얻어서 Routing table에 저장하고, 패킷이 Router로 들어오면 도착지 IP주소와 Routing table을 비교해 최적의 경로로 패킷을 내보낸다.
  1. 브로드캐스트 컨트롤
  • 패킷의 목적지 주소가 Rotuing table에 없는 경우 패킷을 버린다.
  • 멀티캐스트 정보를 습득하지 않고 브로드캐스트 패킷을 전달하지 않는다. (브로드캐스트가 다른 네트워크로 전파되는것을 막는다. )
  1. 프로토콜 변환
  • 패킷 포워딩 과정에서 2계층 헤더를 변경할 수 있다.
  • 3계층에서 동작하는 장비이기 떄문에 2계층 헤더를 벗겨내고, 다시 2계층 헤더 정보를 새로 만들어 외부로 내보내는데, 이때 헤더 정보를 변경할 수 있다.
    ex) WAN : PPP protocol / LAN : ethernet protocol

Router 동작방식

  • Hop-by-Hop Routing : 인접한 Router(Next Hop)까지 경로를 지정하면 인접한 Router에서 다시 최적의 경로를 파악해서 Router로 패킷을 포워딩한다.

Next Hop 지정시에는 다음과 같은 3가지 방법이 사용된다.

  1. IP주소 지정
  2. 인터페이스 지정 : IP주소를 모르더라도, MAC주소정보를 아는 경우에 사용
  3. IP,인터페이스 동시에 지정
  • Router table 에 저장되는 데이터는 아래와 같다.
  1. 목적지 주소
  2. next hop IP 주소 , 나가는 로컬 인터페이스
  • Router 에서 패킷의 출발지 주소를 이용해 라우팅하도록 PBR(Policy-Based Routing)기능을 사용할 수는 있으나 특별한 목적으로만 사용된다.
  • TTL (Time To Live) : 3계층 IP헤더에는 TTL이라는 필드가 존재한다. 이 필드는 네트워크에 살아 있을 수 있는 시간(홉)을 제한하여 라우팅 루프 현상을 방지한다.
    (* 라우팅 루프 현상 : 라우터간에 패킷이 무한히 핑퐁되는 현상 )
Read more

Item59. 라이브러리를 익히고 사용하라

표준 라이브러리를 사용함으로 얻을 수 있는 장점은 다음과 같다.

  1. 코드를 작성한 전문가의 지식과 앞서 사용한 프로그래머들의 경험을 활용할 수 있다.
  2. application 기능 개발에 집중할 수 있다.
  3. 표준 라이브러리를 사용하는 client는 노력하지 않아도 성능이 지속해서 개선된다.
  4. 다음 릴리즈에 기능이 추가될 가능성이 있다.
1
2
3
4
5
6
7
8
// java9의 inputStream에 추가된 transferTo Method

public static void main(String[] args) throws IOException {
try(InputStream in = new URL(args[0]).openStream()){
in.transferTo(System.out); //url 내용을 console에 출력
}
}

특정 기능을 개발하려고 할때, 우선은 표준 라이브러리에서 제공하는지 확인하고, 제공하지 않는다면 서드파티 라이브러리를 활용하고 서드파티 라이브러리에서도 제공하지 않으면 그때 직접 구현하자.

Read more

Item58. 전통적인 for문보다는 for-each문을 사용하라

1
2
3
4
5
6
7
8
// collection 순회
for(Iterator<Element> i = c.iterator(); i.haxNext(); ){
Element e = i.next();
}
// 배열 순회
for(int i = 0; i < a.length ; i++){
//a[i];
}

for문을 사용해 collection과 배열을 순환할 경우, 불필요한 반복자와 인덱스가 사용된다. 또한 collection이냐 배열이냐에 따라 코드 형태와 위와 같이 달라진다.

반면 향상된 for문을 사용할때는 반복자와 인덱스 변수를 사용하지 않고, collection과 배열일때 모두 코드 형태가 동일하다.

1
2
3
for ( Element e : elements ){
// e
}
  • for-each문은 collection과 배열은 물론 Iterable interface를 구현한 객체라면 무엇이든 순회할 수 있다.
Read more

Item57. 지역변수의 범위를 최소화하라

  • 지역변수의 유효 범위를 최소로 줄이면 코드 가독성과 유지보수성이 높아지고 오류 가능성은 낮아진다.

지역변수의 유효범위를 최소화하는 방법

  • 지역변수의 범위를 줄이는 방법은 처음쓰일때 선언하는 것이다. 미리 선언해두면 블록 스코프가 끝났을떄도 살아있는 경우가 있다.
  • 지역변수는 선언과 동시에 초기화해야 한다. 초기화에 필요한 정보가 충분하지 않다면 충분할떄까지 선언을 미루어야 한다.
Read more

Item56. 공개된 API요소에는 항상 문서화 주석을 작성하라

문서가 잘 갖춰지지 않은 API는 사용하기 헷갈려서 오류의 원인이 되기 쉽다.

method용 문서화 주석에는 해당 method 와 client 사이의 규약을 명료하게 기술해야 한다.

  • how가 아닌 what 을 기술해야 한다. (어떻게 동작하는지보다 무엇을 하는지를 기술해야 한다.)
  • client가 해당 method를 호출하기 위한 precondition을 모두 나열해야 한다.
  • client가 해당 method를 호출한 뒤에 postcondition을 모두 나열해야 한다.
  • 시스템의 어떠한 변화를 가져오는 경우, 문서에 밝혀야 한다.

@param,@return,@thorws 태그

  • 매개변수에 @param 태그 , 반환타입이 void가 아니라면 @return 태그 , 발생할 가능성이 있는 모든 예외에 @throws 태그를 달아야 한다.
  • 관례상 @param 태그 , @return 태그의 설명은 해당 매개변수가 뜻하는 값이나 반환값을 설명하는 명사구를 쓴다.
  • @throws 태그의 설명은 if로 시작해서 해당 예외를 던지는 조건을 설명하는 절이 뒤따른다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14

/***
* Returns the element at the specified position in this list. --> method가 무엇을 하는지 기술하는 요약 설명
(this:호출된 method가 가르키는 객체)
* <p> This method is <i> not </i> guaranteed to run in constant time.
* In some complementations it may run in time proportional to element position
*
* @param index idnex of element to return; must be non-negative and less than the size of this list
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException if the index is out of range
* ( {@code index< 0 || index >= this.size() })
*/

E get(int index);

javadoc utility는 문서화 주석을 HTML로 변환함으로 문서화 주석안의 HTML 요소들이 최종 HTML 문서에 반영된다.
{@code} 태그는 코드용 폰트로 렌더링, 이스케이프 기능을 제공한다.

  • 문서화 주석의 첫 번쨰 문장은 해당 요소의 요약 설명으로 간주되며, 이 요약 설명은 반드시 대상의 기능을 고유하게 기술해야 한다. 즉 한 class안에서 요약 설명이 똑같은 멤버가 둘 이상이면 안 된다. (다중정의된 method라고 하여도 허용되지 않는다.)
  • 첫번째 문장의 마무리는 마침표여야 한다. 중간에 의도치 않는 마침표가 들어간 경우에는 @literal 태그 (아래에 설명 ) 를 활용하자
1
2
3
4
/**
* A suspect , such as Colonel Mustard or {@listeral Mrs.} Peacok.
*/
public class Suspect{...}
  • java 10부터는 이 요약 설명부분이 @summary 태그로 분리되었으니, 중간에 마침표를 별도로 literal태그로 감쌀필요가 없다
1
2
3
4
/**
* {@summary A suspect , such as Colonel Mustard or Peacok.}
*/
public class Suspect{...}
  • 요약 설명은 method와 생성자의 경우에는 주어가 없는 동사구 여야 한다.
1
- ArrayList (int intialCapacity) : Constructs an empty list with the specified initial capacity
  • 요약 설명은 class,interface,field의 경우에는 대상을 설명하는 명사절이여야 한다.
1
- Instant : An instantaneous point on the time-line.
Read more

Item55. Optional 반환은 신중히 하라

Optional API

jdk 8 이전에는 method가 특정 조건에서 값을 반환할 수 없을 때 취할 수 있는 선택지는 null을 반환하거나 , 예외를 던지는 방법이 있었다.

예외를 던지는 경우에는 진짜 예외적인 상황에만 사용해야 하며, null을 반환하는 경우에는 null처리로직이 client코드에 들어가야한다는 단점이 있었다.

jdk 8 이후부터는 Optional API 가 추가되었다.

Optional<T> 는 null 이 아닌 T 타입의 참조값을 하나 담거나, 혹은 아무것도 담지 않을 수 있다.

보통은 T를 반환해야 하지만 특정 조건에서는 아무것도 반환하지 않아야 할때, T 대신 Optional<T>를 반환하도록 선언하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
// collection에서 최댓값을 구하는 method로 collection이 비어있으면 IllegalArgumentException을 던진다. 
public static <E extends Comparable<E>> E max(Collection<E> c){
if(c.isEmpty()){
throw new IllegalArgumentException("빈 collection");
}
E result = null;
for (E e : c) {
if(result == null || e.compareTo(result)>0){
result = Objects.requireNonNull(e);
}
}
return result;
}

위 method를 Optional을 반환하도록 변경하면 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
public static <E extends Comparable<E>> Optional<E> max(Collection<E> c){
if(c.isEmpty()){
return Optional.empty();
}
E result = null;
for (E e : c) {
if(result == null || e.compareTo(result)>0){
result = Objects.requireNonNull(e);
}
}
return Optional.of(result);
}
  • 빈 optional은 Optional.empty()
  • null이 아닌 값이 들어있는 optional은 Optional.of(value)
  • null일 수 있는 값이 들어있는 optional은 Optional.ofNullable(value)를 사용하면 된다.

주의사항은 optional을 반환하는 method에는 당연히 null을 반환하면 안된다. 이는 API 도입 취지를 완전히 무시하는 행동이다.

추가로 Stream의 max연산을 비롯한 상당수의 종단 연산이 Optional 반환을 지원한다.

1
2
3
public static <E extends Comparable<E>> Optional<E> max(Collection<E> c){
return c.stream().max(Comparator.naturalOrder());
}
Read more