Dev Book Review/Java8 in Action

[자바8인액션] Chap.2 동작 파라미터화 코드 전달하기

소스코드 https://github.com/mjung1798/Jyami-Java-Lab/tree/master/java8-in-action mjung1798/Jyami-Java-Lab 💻 Jyami의 Spring boot 및 Java 실험소 💻. Contribute to mjung1798/Jyami-Java-Lab development by creating an account on GitHub. github.com 동작 파라미터화 : 아직은 어떻게 실행할 것인지 결정하지 않은 코드 블록, 코드 블록의 실행은 나중으로 미뤄진다. 변화하는 요구사항에 유연하게 대응할 수 있게 코드를 구현하는 방법 리스트의 모든 요소에 '어떤 동작'을 수행할 수 있음 리스트 관련 작업을 끝낸 다음에 '어떤 다른 동작'을 수행할 ..

[자바8인액션] Chap.2 동작 파라미터화 코드 전달하기

728x90

소스코드

https://github.com/mjung1798/Jyami-Java-Lab/tree/master/java8-in-action

 

mjung1798/Jyami-Java-Lab

💻 Jyami의 Spring boot 및 Java 실험소 💻. Contribute to mjung1798/Jyami-Java-Lab development by creating an account on GitHub.

github.com

 

동작 파라미터화 : 아직은 어떻게 실행할 것인지 결정하지 않은 코드 블록, 코드 블록의 실행은 나중으로 미뤄진다.

변화하는 요구사항에 유연하게 대응할 수 있게 코드를 구현하는 방법

  • 리스트의 모든 요소에 '어떤 동작'을 수행할 수 있음
  • 리스트 관련 작업을 끝낸 다음에 '어떤 다른 동작'을 수행할 수 있음
  • 에러가 발생하면 '정해진 어떤 다른 동작'을 수행할 수 있음

 

1. 변화하는 요구사항에 대응하기

DRY(don't repeat yourself) : 같은 것을 반복하지 말것.

문자열, 정수, 불린 등의 값으로 메서드를 파라미터화 할 때 => 한줄이 아니라 메서드 전체 구현을 바꾸어야 한다.

녹색 사과 필터링 filterGreenApples(List<Apple> inventory)
-> 색에 따른 필터링 filterApplyByColor(List<Apple> inventory, String color)
-> 속성 필터링 filterApples(List<Apple> inventory, String color, int wieght, boolean flag)

별로다

 

2. 동작 파라미터화

프레디케이트 Predicate : 선택 조건을 결정하는 인터페이스 => 어떤 속성에 기초해서 불린값을 반환

전략 디자인 패턴(strategy design pattern)

  • 각 알고리즘(전략이라 불리는)을 캡슐화하는 알고리즘 패밀리를 정의해둔 다음에 런타임에 알고리즘을 선택하는 기법
  • 객체가 할 수 있는 행위를 전략으로 만들고 동적으로 행위의 수정이 필요한 경우 전략으로 바꾸는 것만으로도 행위의 수정이 가능하도록 만든 패턴
public interface ApplePredicate{
  boolean test(Apple apple); 
}

public class AppleGreenColorPredicate implements ApplePredicate {
    @Override
    public boolean test(Apple apple) {
        return "green".equals(apple.getColor());
    }
}

public class AppleHeavyWeightPredicate implements ApplePredicate{
    @Override
    public boolean test(Apple apple) {
        return apple.getWeight() > 150;
    }
}

동작 파라미터화 : 메서드가 다양한 동작(전략)을 받아서 내부적으로 다양한 동작을 수행한다.

public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p){
  List<Apple> result = new ArrayList<>();
  for(Apple apple: inventory){
    if(p.test(apple))
      result.add(apple);
  }
  return result;
}

List<Apple> heavyApples = FilteringApple.filterApples(inventory, new AppleHeavyWeightPredicate());
List<Apple> greenApples = FilteringApple.filterApples(inventory, new AppleGreenColorPredicate());

내가 전달한 ApplePredicate 객체에 의해 filterApples 메서드의 동작이 결정된다.

한 메서드가 다른 동작을 수행하도록 재활용이 가능하다 : 한개의 파라미터 다양한 동작

 

3. 간소화 하기

a. 익명 클래스

익명클래스(anonymous class) 기법 : 클래스의 선언과 인스턴스화를 동시에 수행할 수 있는 기법

클래스 선언과 인스턴스화를 동시에 할 수 있다 == 즉석에서 필요한 구현을 만들어서 사용할 수 있다.

Predicate 인터페이스를 구현하는 클래스 만들어서 인스턴스 화 해야한다 (귀찮음)

List<Apple> redApples = FilteringApple.filterApples(inventory, new ApplePredicate() {
  @Override
  public boolean test(Apple apple) {
    return "red".equals(apple.getColor());
  }
});
assertThat(redApples.size()).isEqualTo(2);

그러나 장황하다 : 나쁘다!

람다 표현식으로 코드를 더 간결하게 정리할 수 있다.

b. 람다 표현식

List<Apple> redApples = FilteringApple.filterApples(inventory, apple -> "red".equals(apple.getColor()));

유연함과 간결함 두가지를 모두 가져간다!

c. 형식 파라미터를 이용한 추상화

public static <T> List<T> filter(List<T> list, Predicate<T> p){
  List<T> result = new ArrayList<>();
  for(T e: list){
    if(p.test(e))
      result.add(e);
  }
  return result;
}

d. 종합: 동작 파라미터화의 3가지 방법

  • 클래스
  • 익명 클래스
  • 람다

 

5. 실전 예제

a. Comparator 정렬

sort 동작을 파라미터화 할 수 있다.

// 익명클래스 이용
inventory.sort(new Comparator<Apple>() {
  @Override
  public int compare(Apple o1, Apple o2) {
    return o1.getWeight().compareTo(o2.getWeight());
  }
});
// 람다식 이용
inventory.sort((a1, a2)->a1.getWeight().compareTo(a2.getWeight()));

b. Runnable 코드 블록 실행

// 익명클래스 이용
Thread t1 = new Thread(new Runnable() {
  @Override
  public void run() {
    System.out.println("helloWorld");
  }
});
t1.run();
// 람다식 이용
Thread t2 = new Thread(() -> System.out.println("helloWorld"));
t2.run();

댓글

Comments

Dev Book Review/Java8 in Action

[자바8인액션] Chap.1 자바 8을 눈여겨 봐야하는 이유

소스코드 https://github.com/mjung1798/Jyami-Java-Lab/tree/master/java8-in-action mjung1798/Jyami-Java-Lab 💻 Jyami의 Spring boot 및 Java 실험소 💻. Contribute to mjung1798/Jyami-Java-Lab development by creating an account on GitHub. github.com 1. 자바의 변화 a. 자바의 시작 출발자체가 많은 유용한 라이브러리를 포함하는 잘 설계된 객체지향 언어 소소한 동시성도 지원 JVM 바이트코드로 컴파일하는 특징 -> 인터넷 애플릿 프로그램 주 언어 캡슐화 덕분에 C에 비해 소프트웨어 엔지니어링적 문제가 훨씬 적다. b. 언어 생태계의 변화 빅..

[자바8인액션] Chap.1 자바 8을 눈여겨 봐야하는 이유

728x90

소스코드

https://github.com/mjung1798/Jyami-Java-Lab/tree/master/java8-in-action

 

mjung1798/Jyami-Java-Lab

💻 Jyami의 Spring boot 및 Java 실험소 💻. Contribute to mjung1798/Jyami-Java-Lab development by creating an account on GitHub.

github.com

1. 자바의 변화

a. 자바의 시작

  • 출발자체가 많은 유용한 라이브러리를 포함하는 잘 설계된 객체지향 언어
  • 소소한 동시성도 지원
  • JVM 바이트코드로 컴파일하는 특징 -> 인터넷 애플릿 프로그램 주 언어
  • 캡슐화 덕분에 C에 비해 소프트웨어 엔지니어링적 문제가 훨씬 적다.

b. 언어 생태계의 변화

빅데이터에 직면하면서 멀티코어 컴퓨터, 컴퓨팅 클러스터를 이용하는 움직임이 생김 : 병렬 프로세싱 활용

대용량 데이터와 멀티코어 CPU를 효과적으로 활용해야 했다 => 자바 8의 변화

c. 자바 8의 3가지 프로그래밍 개념

1. 스트림 처리(Stream processing) : 여러행의 스트림을 입력으로 받아 여러 행의 스트림을 출력으로 만든다.

cat file1 file2 | tr "[A-Z]" "[a-z]" | sort | tail -3

Stream<T> : 하려는 작업을 추상화하여 일련의 스트림으로 만들어 처리 가능, 입력 부분을 여러 CPU 코어에 할당 가능

2. 동작 파라미터화(behavior prameterization)로 메서드에 코드 전달

코드의 일부를 API로 전달하는 기능 : 함수형 프로그래밍

3. 병렬성과 공유 가변 데이터

스트림 메서드로 전달하는 코드는 다른 코드와 동시에 실행되도 안전하게 실행이 된다.

공유 가변데이터에 접근하지 않기 때문이다 => 순수(pure) 함수, 부작용없는(side-effect-free) 함수, 상태없는(stateless) 함수 : 함수형 프로그래밍

자바 역시 하드웨어나 프로그래머의 기대의 변화에 부응할 수 있도록 변화하였다.

 

2. 자바 함수

자바의 함수 : 부작용을 일으키지 않는 함수

자바8로 인해 이급 시민 => 일급 시민 변화가 일어났다.

  • 일급(first-class) 시민 : 조작이 가능한 것, 바꿀 수 있는 값 : 기본값(int, double), 객체(String, Integer, HashMap 등)
  • 이급 시민 : 전달 할 수 없는 구조체, 바꿀 수 없는 값 : 메서드, 클래스

a. 메서드 레퍼런스(method reference)

이 메서드를 값으로 사용한다 : 메서드가 이급값이 아닌 일급값이 되었다.

객체 레퍼런스(new Object())로 객체를 주고받았던 것 처럼 메서드 레퍼런스를 만들어 전달 할 수 있게되었다.

b. 람다(lambda):익명함수(anonymous function)

함수도 값으로 취급할 수 있다.

직접 메서드를 정의할 수도 있지만, 이용할 수 있는 있는 편리한 클래스나 메서드가 없을 때 새로운 람다 문법을 이용하면 코드를 더 간결하게 구현할 수 있다.

람다 문법 형식으로 구현된 프로그램 == 함수형 프로그래밍 == 함수를 일급값으로 넘겨주는 프로그램을 구현한다.

 

3. 스트림(stream)

컬렉션 스트림
반복과정을 직접 처리한다 (for-each) 라이브러리 내부에서 모든 데이터가 처리된다
외부반복(external iteration) 내부 반복(internal iteration)
  여러 CPU 코어에 작업을 각각 할당해서 처리시간을 줄일 수 있다.
  • 주어진 조건에 따라 데이터를 필터링(filtering)
  • 데이터를 추출(extracting)
  • 데이터 그룹화(gropuing)
  • 동작을 쉽게 병렬화 가능
    포킹(forking) => 포킹된 리스트 처리 => 처리된 결과를 합침

추가 : 함수형 프로그래밍

  • 함수를 일급값으로 사용한다
  • 프로그램이 실행되는 동안 컴포넌트간에 상호작용이 일어나지 않는다(가변이 아니다)

 

4. 디폴트 메서드

기존 인터페이스의 변경에 대한 어려움 해결 : 인터페이스를 업데이트하면 인터페이스 구현클래스도 업데이트 해야했다.

구현 클래스에서 구현하지 않아도 되는 메서드를 인터페이스가 포함 할 수 있는 기능

 

5. 그 외

  • Optional<T> 클래스 : 값을 갖거나 갖지 않을 수 있는 컨테이너 객체
  • 구조적(structural) 패턴 매칭 기법 : 데이터 형식 분류와 분석을 한번에 수행

댓글

Comments

Daily/About Jyami

네이버 백엔드 인턴십 후기 | Naver Backend Internship Review

올해 3월부터 6월까지 약 3개월간 했던 네이버 인턴십 후기를 작성하려 한다.계속 써야지 써야지 하면서 미루다가 인턴십이 끝난 지 거의 2달가량이 돼서 사실 시간이 꽤 많이 지났지만, 아무래도 첫 면접, 첫 실무의 기억이기 때문에 아직도 생생해서 기록으로 남긴다 :) 인턴십 준비 과정1학년 때는 대학 개발 동아리를 위주로 2~3학년 때는 각종 회사 연계 대외활동을 위주로 내 개발 실력을 한 단계씩 높이려는데 목적이 있었다. 하지만 더 이상 대학생들 사이가 아닌 더 많은 현직자분들을 만나고 나도 현직자로 나아가고 싶다는 생각을 가지게 되었다.그래서 3학년 2학기 말에는 네이버 핵 데이, 우아한 프리코스부터 시작해서 대학생 프로젝트에서 인턴십 및 취업으로 나아갈 수 있는 활동에 몰두했었다. 하지만 두 ..

네이버 백엔드 인턴십 후기 | Naver Backend Internship Review

728x90

올해 3월부터 6월까지 약 3개월간 했던 네이버 인턴십 후기를 작성하려 한다.

계속 써야지 써야지 하면서 미루다가 인턴십이 끝난 지 거의 2달가량이 돼서 사실 시간이 꽤 많이 지났지만, 아무래도 첫 면접, 첫 실무의 기억이기 때문에 아직도 생생해서 기록으로 남긴다 :)

 

재택이 끝나는 날 받았던 웰컴 키트

 

 

인턴십 준비 과정

1학년 때는 대학 개발 동아리를 위주로 2~3학년 때는 각종 회사 연계 대외활동을 위주로 내 개발 실력을 한 단계씩 높이려는데 목적이 있었다. 하지만 더 이상 대학생들 사이가 아닌 더 많은 현직자분들을 만나고 나도 현직자로 나아가고 싶다는 생각을 가지게 되었다.

그래서 3학년 2학기 말에는  네이버 핵 데이, 우아한 프리코스부터 시작해서 대학생 프로젝트에서 인턴십 및 취업으로 나아갈 수 있는 활동에 몰두했었다. 하지만 두 프로그램 모두 인턴십, 테크 코스까지는 이어지지 못했고, 안 좋은 결과에 기가 죽기보다는 오히려 시선을 돌려서 인턴십에 직접 지원을 하자고 마음을 먹게 되었다.

아무래도 네이버 핵데이를 2번이나 참가하다 보니 네이버라는 회사는 나에게 시작점이 되고 싶은 회사였다. 직접 작성한 레주메를 바탕으로 마침 기회가 생겨서 의도치 않게 네이버 인턴십에 여러 프로세스로 지원을 하게 되었는데

1. 클로바에 12월 경 교수님 추천으로 인턴십 지원을 하게 되었고, 서류 -> 코딩 테스트 후에 결과를 기다리고 있었다.

2. 클로바 코딩 테스트 결과가 약 한 달 동안 안 나와서 떨어졌다 생각했고, 네이버 웹툰 체험형 인턴십을 지원했다. 그 결과 서류 통과 후 면접에 오라는 메일을 받았다.

3. 네이버 웹툰에 지원서를 낸 상태였는데 우연히 아폴로 CIC 쪽에 지인 추천으로 서류를 낼 수 있는 기회가 생겨, 서류 통과 후 면접을 보았다 (당시 매우 프로세스가 빨랐다)

4. 아폴로 CIC 쪽에서 가장 빠르게 합격 결과를 알려주어서 아폴로 CIC 백엔드 인턴십을 진행하게 되었다. (의도치 않게 3개 조직에 민폐를 끼친 건 아닌가 싶어서 당시 매우 조마조마하면서 웹툰 면접을 거절했다) (클로바에서도 면접에 오라는 연락을 받았지만 아폴로 입사가 확정이라 거절했다)

 

조직마다 달랐던 결과 메일 템플릿

 

1차 면접

아폴로 CIC에서 본 면접이 내 인생 첫 인턴십 면접이었다. 그래서 당시 매우 떨리는 마음으로 면접을 봤던 기억이 난다. 당시에 무슨 깡이었는지ㅋㅋ 면접에서 물어볼 법한 컴퓨터 공학 지식에 대한 공부를 하나도 안 하고 갔다. (패기 봐라) 당시 외주 프로젝트 기한 때문에 면접 준비를 많이 못했었고, 같이 스터디를 했었던 자바봄(http://javabom.tistory.com/) 멤버들이 한번 봐줬던 모의 면접이 끝이었는데🧐그래서 잠을 못 자서 밤을 꼴닥 새우고 면접을 보러 갔었다.

나는 주로 기존에 알고 있던 Java와 관련된 지식, Cleancode, 자주 사용하던 test 관련 개념, 사용했던 디자인 패턴 위주 복습하고, 이전에 했던 프로젝트에서 사용했던 기술의 간단한 특징 정도를 리마인드하고 갔다.(queryDSL을 사용했을 때 장점 같은 것들!)

면접을 준비할 때는 기술에 대한 키워드만 적어두고 그 키워드에 대한 내용을 보기만 하기보다는, 실제로 다른 사람에게 전달한다 생각하고 말을 하면서 준비를 했었다. 이렇게 하다 보니 이 키워드에 대한 질문이 나왔을 때 어떤 흐름으로 대답을 해야겠다는 노하우가 생겨서 수월했다.👍

면접 질문은 크게 2가지 종류였다. 

1. 종이에 문제가 2가지 적혀있었고, 30분 동안 각 문제에 대한 해결법을 말로 풀어내는 것이었다. 문제 중 기억에 남는 문제가 있었는데 당시 복기해서 적어놨었다.

25마리의 말이 있습니다. 이중에 가장 순서대로 빠른 말 3마리를 찾아내고 싶습니다. 말의 절대 속도는 알 수가 없으며, 한 경주에 최대 5마리의 말이 참여할 수 있고, 그들의 상대적인 순위만을 알 수 있습니다 최소한의 횟수로 찾아내려면 어떤 방법을 써야 할까요?

이 문제를 풀 때 종이에 적어가며 설명을 드렸었는데, 처음에 낸 정답에 대해 "여기서 더 최적화할 수 있을 것 같아요!", "이런 경우엔 어떻게 해야 할까요?" 등의 질문을 면접관님이 넌지시 던져주셔서 정답에 도달할 수 있었다. 내 생각을 기반으로 면접관 님이랑 소통하면서 같이 정답에 도달한 느낌이라 아직도 기억에 남을 정도로 재미있었다👍

2. 두 번째로는 약 1시간 반 동안 자바에 대한 기술 질문이 이어졌다. JVM의 구조, GC의 동작 방식과 같은 기본적인 질문을 하시기도 했고, 내  깃허브에 들어가서 했던 프로젝트에서 dto와 domain 폴더링의 이유를 여쭤보셨던 기억도 난다. 외에도 테스트 코드를 짜면 좋은 점이나 테스트 코드를 짤 때 어떤 부분을 생각하며 짜는지를 여쭤보셨다.(자바 봄 스터디원들이 해준 모의 면접에 나왔던 질문이 많이 나와서 정말 좋았다)

그 결과 합격!!! 3월로 입사일이 정해지게 되었다.

당시 원래는 휴학을 하려 했었는데, 채용형 인턴십이었기 때문에 빠른 졸업이 필요해 4학년 1학기 학점을 인턴십으로 대체할 수 있도록 교수님과 조율하면서 정신없이 입사를 기다렸었다.

 

 

 

코로나로 인한 원격 입사

그렇지만 3월에 코로나 확진자 수가 급격하게 늘어나면서 입사일이 미뤄지는 사태가 발생했다. 많이 기다리던 입사일이었는데 코로나로 인해 걱정을 하면서 회사 측의 연락을 기다렸었다.

 

 

그 결과 입사일이 일주일이 미뤄졌었고, 미뤄진 입사일 조차도 네이버에서 풀 재택근무 체계로 들어가다 보니 원격 입사를 하게 되었다. OT 진행도 화상으로 했고, 각종 계약서도 모두 메일로 받았다.

그래서 입사일에 각종 회사 장비가 집으로 배송이 되었고, 당시 생활멘토, 기술멘토 멘토님이 두 분이 계셨는데 웍스로만 연락을 드리게 되었다.

그래서 입사일부터 약 한 달 반 정도 풀 재택근무를 했었다. 그리고 남은 한 달 반은 주 2회 출근을 했었어서 그때 팀원분들이나 멘토님들을 볼 수 있었다.

 

인턴 과제

입사 2일 후에 멘토님과 앞으로 인턴십 기간 동안 어떤 과제를 진행할지에 대한 부분을 이야기했었다. 나는 모먼트 팀 소속이었는데, 모먼트는 4월에 런칭된 서비스라서 한 달 동안 다른 분들께 내 부서를 설명할 때 둘러 대며 말했던 기억이 난다.

인턴 기간 동안 내가 했던 job은 3가지였다.

1. 이펙티브 자바와 클린 코드를 읽고 정리하기

2. 현재 모먼트 서버와 비슷한 환경의 토이 프로젝트를 진행하기
    2-1. 네이버 서비스를 연동하는 서버 만들기
    2-2. 만든 서버를 k8s를 이용하여 배포하기
    2-3. jenkins pipeline을 작성하여 해당 서버의 CI/CD 구축하기 
    2-4. sonarlint를 사용하여 코드 리팩터링 하기

3. 모먼트 서비스에 대한 분석 및 모먼트 팀 소속으로 회의 참석

 

1. 이펙티브 자바와 클린 코드를 읽고 정리하기

정리한 내용은 내 블로그에 조금씩 업로드를 하고 있다
https://jyami.tistory.com/category/Dev%20Book%20Review/Effective%20Java
다만 티스토리 에디터에 맞춰서 글을 조금 편집해야 하는 게 번거로워서 아직도 모든 문서를 올리지 못했다.. 크흐

책 읽는 과제를 하면서 내가 집중했던 건 모르는 구절이 나와도 넘어가지 않는 것이었다. 자바 봄 스터디에서 이 방식으로 책을 읽고 있어서 이 과제를 할 때도 영향을 줬었다.

다른 사람 블로그를 찾아보기도 하고, oracle docs를 찾아보기도 하고, 책에 있는 내용을 코드로 작성해보기도 했었다. 당시 prallel stream 부분을 읽으면서 forkjoinpool에 대한 이야기가 나왔었는데, JDK 코드를 보면서 까지 책을 읽었던 기억이 난다.

ForkJoinPool JDK 코드를 보고 참조관계에 대한 정리를 해놓은 필기

 

두 책이 인턴 과제였던 점은 아직까지도 영향을 미치고 있다. 사실 나는 개발 서적 읽는걸 별로 안 좋아했다. 대학원 생각이 없는 이유가 문서를 많이 읽어야 하는 게 싫어서였을 정도로 그런데, 당시 매주 정해진 분량에 대한 계획을 세우고 책을 읽었었는데 이 습관이 지금까지 이어지고 있다 (요즘은 하루에 한 챕터씩 자바 8 인 액션을 읽고 있다.)

생각보다 내가 잘 알고 있다고 생각했던 자바에는 공부를 계속해도 궁금한 점이 나왔었고, 실제로 내가 모르던 부분에 대한 키워드를 책에서 얻어서 몇 번 포스팅한 적이 있다.

https://jyami.tistory.com/112

 

volatile을 사용한 쓰레드간 통신 동기화

이펙티브 자바를 읽으면서 짜릿한 단일검사(racy single-check)에 대해 찾아보던 중, 알게된 내용이다. 동기화의 기능 자바의 쓰레드 프로그래밍을 해보았다면 synchronized 키워드를 몇번 접해볼 수 있�

jyami.tistory.com

https://jyami.tistory.com/99

 

자바의 제네릭 타입 소거, 리스트에 관하여 (Java Generics Type Erasure, List)

1. 자바의 제네릭과 로타입 (Java Generics and Raw Type) public class Example{ private T member; } 위와 같이 클래스 및 인터페이스 선언에 타입 매개변수 T 가 사용되면 제네릭 클래스, 제네릭 인터페이스라..

jyami.tistory.com

그래서 이 과제 이후로 부터 책을 읽는다는 것에 거부감이 덜 생기게 되었고, 앞으로 읽고 싶은 책이 정말 많다. (실제로 많이 사뒀다..ㅎㅎ)

 

2. 현재 모먼트 서버와 비슷한 환경의 토이 프로젝트를 진행하기

주로 삽질속에서 성장을 하는 과정이었다ㅋㅋ 아무래도 당시 모먼트의 런칭이 얼마 안 되었을 때였고, 나의 집념도 한몫해서 멘토님께는 정말 해결이 안 될 때만 여쭤봤었다. 해결이 안 될 때에는 "어떤 부분을 하고 있는데, 이런 부분이 해결이 안돼서 이렇게 에러를 해결해 보려고 몇 가지 방법을 찾아보았습니다 제가 생각할 때는 이 부분이 문제인 것 같아 이 부분을 다시 찾아보려고 하는데 올바른 방향이 맞나요?" 이런 식의 현재의 상황을 브리핑하면서 조언을 얻는 방향으로 여쭤봤었다.

아무래도 구글링을 해서 바로 나오는 내용이면 좀 민망하기도 하고, 한심해 보이지 않을까 싶어서 그랬는데, 후에 멘토님 피드백으로 뭔가 혼자 알아서 해결하고 혼자 잘 정리해오는 스타일이었다고...ㅋㅋㅋㅋ 

2-1. 연동 서버 구현

먼저 네이버 api를 연동하여 여러 서비스의 response 값을 조회하고, 이 내용을 디비, 캐시에 CRUD 하는 서버를 만들었다. 사실 구현은 어렵지 않았지만 이 부분에서는 멘토님과의 커뮤니케이션을 많이 할 수 있었다. 요구사항에 대해 구체화하고, 나에게는 접근권한이 없는 docs 페이지의 경우엔 멘토님께 도움을 요청해야 구현까지 할 수 있었기 때문이다.

코드를 구현하면서 가장 내가 신경 썼던 것은 테스트 코드 작성이었다. 한 가지 기능을 구현할 때 그 기능과 대응하는 TC를 작성하려고 의식적으로 생각했었다. 그래서 구현 중간에 test line coverage를 측정해봤을 때 구현을 마치고 측정했을 때 모두 80프로 대가 나왔었다.

혼자 하는 과제이지만 테스트 코드는 내가 작성한 프로젝트의 요구사항을 알 수 있는 커뮤니케이션 수단이라고 생각했기 때문에, @DisplayName까지 꼼꼼하게 작성했었다 (미래의 내가 봤을 때 못 알아보는 경우를 그나마 방지하기 위해)

2-2. 컨테이너 환경에서의 배포

이렇게 완성한 서버를 k8s를 이용해서 배포해야 했었는데, 당시 나는 docker, k8s에 대한 지식이 전무했다. (대충 환경을 저장하는 게 도커구나 만 알고 있었음) 과제를 하면서 어떻게든 공부를 해야 하다 보니 익히게 되었는데 요즘 개발할 때 정말 유용하게 써먹고 있다ㅋㅋㅋ (얼마 전에 나간 엔젤핵의 배포 시스템이 dockerfile 기반이라 수월했다) 

당시 helm chart를 이용해서 pod 오브젝트 스펙들을 관리했었고, 사내 컨테이너 배포 시스템을 이용해 배포 파이프라인까지 모두 잘 작성하였다. 당시 과제 기한이 3주였는데 여기까지 다 했을 때 1주가 남아서, 추가적으로 HPA 설정, ELK 설정까지 도전해 볼 수 있었다.

이 부분에 대한 인턴 발표를 진행했을 때, 사내 컨테이너 배포 시스템의 불편한 점에 대해 언급했었다. 그때 한 개발자 님이 본인 팀에도 도입하려 했었는데 이런 불편함을 참고해서 개발해야겠다고 발표 내용이 인상적이라고 피드백 주셨었는데 진짜 뿌듯했었다.

다시 해보자. (단호한 온점)

배포를 하면서 여러 Devops적 지식을 알게 된 점이 있었다. 그래서 매일매일 위 사진처럼 트러블 슈팅에 대한 문서를 작성했었다. 근데 대부분의 말이 "다시 해보자." "왜 안되지" "으아아아ㅏㄱ!!" 이런 거였음ㅋ

2-3. jenkins pipeline을 이용한 CI/CD 

jenkins도 인턴 과제 덕분에 처음 사용해봤었다 (그동안은 팀원들이 해주던 CI/CD를 사용했었으니..)

역시나 삽질은 많이 했었는데, 삽질속에 깨달음이 많았음.. jenkins는 앞으로 사용할 일이 많으니 좋은 경험이었다 생각 중이다ㅋㅋ

아무래도 처음 jenkinsFile을 작성하다 보니 문법 오류도 나에겐 퍼포먼스 저하의 하나의 원인이었다. 그때 사내 github에 있는 소스코드를 가져와서 해당 소스 폴더에 있는 jenkinsFile을 읽어 jenkins pipeline을 실행하도록 했었는데 문제가 많았다.

1. jenkinsFile 문법이 맞는지 확인하려고 한 줄을 수정해도 그때그때 github에 push 했었다.

2. 후에 commit 내역이 더러워지고 있음을 깨닫고 force push를 막 했었다
그래도 github에 push를 계속해보면서 jenkinsFile을 테스트한다는 사실이 올바른 방법 같진 않았다.

3. 똑똑한 방법으로 발전! : jenkins안에서 jenkinsFile 동작을 확인할 수 있게 script 테스트
jenkins의 build 내역에 들어가서 replay 버튼을 이용해 작성한 스크립트의 재 빌드를 할 수 있었다. 이렇게 하니 그때그때 빌드 파이프라인이 잘 작동하는지 여부를 확인할 수 있었다. (앞으로 종종 써먹을 듯하다.)

2-4. sonarlint를 사용하여 코드 리팩터링 하기

마지막 1주에는 리팩터링 및 코드 품질을 향상하는 작업을 했었는데, 이때 테스트의 중요성을 다시 한번 느꼈다. 소나린트에 따라 소스를 리팩터링 할 때, 패키지 명을 바꾸는 간단한 리팩터링이더라도, 사람의 실수로 혹은 테스트 스코프가 벗어나서 등 다양한 이유로 소스가 안 돌아갈 수 있었다.

코드 악취가 146에서 13으로 줄어드는 걸 보면서도, 리팩터링 과정에서 에러가 없음을 보장받을 수 있는 수단이 TestCase 였다. 테스트의 중요성은 아무리 말해도 부족한 듯.

 

3. 모먼트 팀 소속 인턴

당시 남들보다 먼저 사용해 볼 수 있었던 모먼트!!

모먼트 팀의 소속으로 각종 회의에 참석하고 팀원 분들과 개발에 대한 이야기부터 앞으로 계획에 대한 이야기도 하면서 실무에 대한 이해를 높일 수 있었다.

인턴 시작 3일 때 모먼트 프로세스 문서를 보고 궁금한 점을 정리했었는데, 실무가 아니면 몰랐을 사실들이 있었다. (예를 들면 DB설계 문서에서 Auto Increasement가 빠져있었는데, 분산 DB를 사용하기 때문이라는 사실이 너무 놀라웠다ㅋㅋㅋ)

팀원들과 일과를 보낼 때 나와 같은 인턴도 똑같은 개발자라는 마음으로 대해주셔서 좋았다.
일부 이슈 해결에 대한 일정이 있었음에도, 페어 프로그래밍으로 팀원들과 같이 도메인 지식의 수준을 동등하게 맞춰나가는 시니어 개발자님의 모습이 좋았다. 인턴임에도 옆에서 같이 보고 의견을 낼 수 있었고 환상으로만 있었던 이런 개발자 회사의 문화에 감동했었다.

그리고 코로나 시기 인턴으로서 팀원들 그리고 멘토님께 감사했던 점은 활발한 비대면 커뮤니케이션이었다! 사실 인턴십을 하면서 모르는 점을 물어볼 때 메신저로 갑자기 연락을 드리기가 죄송했었다(이건 내 성격이다. 다들 친절하셨음). 그래서 초반에는 차라리 직접 만나서 대면 인턴십을 진행했다면 좀 달랐을까 싶었는데, 뒤로 갈수록 비대면으로도 너무 의사소통이 잘돼서 재택을 사랑하게 되었다..ㅎㅎ

팀 내 시니어 개발자님이 나를 만나고 얼마 안 되었을 때, 갑자기 웍스로 "민정님 이 코드의 문제점이 뭘까요?" 하고 먼저 말을 걸어주셨는데, 코드에 대한 이야기를(Optional과 Null을 같이 사용할 때 문제점에 대한 이야기를 했었다.) 30분 내내 웍스로 집중해서 하다 보니까, 얼굴을 뵌 적이 없는 분임에도 내적 친밀감이 마구 상승했었다ㅋㅋㅋ

외에도, 과제를 하다가 의존성 때문에 문제가 생긴 적이 있었는데, 이 의존성 문제를 해결하려고 역시 웍스로 1시간 내내 커뮤니케이션하면서 해결했었다. 

또 팀원 분들과 많은 커피타임을 가지면서 운동의 중요성에 대한 부분을 정말 많이 들었었는데ㅋㅋ (사실 지금도 엄청 듣고 있다) 개발 외적으로도 자기 계발에 힘쓰는 분들과 일을 할 수 있어서 감사하다는 마음이 들었다.

 

인턴십 끝

배운 것들 부터해서 좋은 팀원 분들까지 얻은 게 너무 많은 인턴십이었지만, 아쉽게도 채용까지 이어지지는 못했다.

인턴 평가는 좋았으나, 전환면접 (2차 면접)에서 (다시 말하지만 나는 이 인턴십이 첫 기술면접이었다.) 너무 긴장한 나머지 나의 장점을 말하기보다는 내가 못하는 부분, 나의 단점을 더 드러내는 면접을 했었다.

 

항상 내 사진은 왜 화장실 찍...ㅋㅋㅋㅋ

 

어느 때보다 치열하게 살았던 3개월이었지만, 인턴십 자체만으로도 좋은 경험임을 알고 있기에 결과보다는 과정 위주로 기억이 남았다. (좋은 얘기만 있어서 포장이라 할 수 있는데, 포장아니다 ㄹㅇ 재밌었음)
아까운 결과이지만, 2달이 지났음에도 좋은 기억으로 남은 걸 보니 후기를 안 쓰면 후회할 것 같아서 기록으로 남긴다!

끝! 

I'd like to write about my Naver internship experience, which lasted about 3 months from March to June of this year.

I kept telling myself I'd write it up but kept putting it off, and now it's been almost 2 months since the internship ended, so quite a bit of time has passed. But since it was my first interview and first real work experience, the memories are still vivid, so I'm putting them down in writing :)

 

The welcome kit I received on the day remote work ended

 

 

Preparing for the Internship

During my freshman year, I focused on university dev clubs, and in my sophomore and junior years, I focused on various company-affiliated extracurricular activities — all with the goal of leveling up my development skills step by step. But I started wanting to meet more industry professionals rather than just staying among college students, and I wanted to step into the professional world myself.

So toward the end of the second semester of my junior year, I dove into activities that could lead from student projects to internships and employment — starting with Naver Hack Day and Woowa Pre-Course. Unfortunately, neither program led to an internship or tech course, but rather than feeling discouraged by the bad results, I shifted my focus and decided to apply directly for internships.

Having participated in Naver Hack Day twice, Naver was a company I wanted as my starting point. Based on a resume I'd written myself, an opportunity came up and I unexpectedly ended up applying to Naver internships through multiple channels:

1. Around December, I applied for an internship at Clova through a professor's recommendation. I was waiting for results after the document screening -> coding test.

2. The Clova coding test results didn't come for about a month, so I assumed I'd been rejected and applied for the Naver Webtoon experiential internship. As a result, I passed the document screening and received an email inviting me for an interview.

3. While my Naver Webtoon application was still pending, I happened to get a chance to submit my resume to Apollo CIC through someone I knew. After passing the document screening, I had the interview (the process was super fast at the time).

4. Apollo CIC was the fastest to let me know I'd been accepted, so I went ahead with the Apollo CIC Backend Internship. (I felt terrible that I might have inconvenienced all three teams, and I was super nervous when I had to decline the Webtoon interview) (I also got an interview invitation from Clova, but I declined since my Apollo onboarding was already confirmed)

 

Each team had different result email templates

 

First Interview

The interview at Apollo CIC was the very first internship interview of my life. So I remember being incredibly nervous. Looking back, I don't know where I got the nerve lol — I went in without studying any of the computer science knowledge they might ask about. (The audacity!) I couldn't prepare much for the interview because of a freelance project deadline, and the only prep I had was a mock interview that my Java Bom (http://javabom.tistory.com/) study group members put me through 🧐 So I ended up pulling an all-nighter and went to the interview without any sleep.

I mainly reviewed Java-related knowledge I already knew, Clean Code concepts, testing concepts I frequently used, and design patterns I'd worked with. I also reminded myself of the key characteristics of technologies I'd used in previous projects (like the advantages of using queryDSL!).

When preparing for the interview, rather than just jotting down keywords and reading about them, I practiced actually speaking as if I were explaining to someone else. Doing this gave me a sense of how to structure my answers when questions about those keywords came up, which was really helpful. 👍

The interview questions fell into two main categories. 

1. There were 2 problems written on paper, and I had 30 minutes to verbally explain my solutions. One problem that stuck with me — I actually wrote it down from memory at the time:

You have 25 horses. You want to find the top 3 fastest horses in order. You can't measure their absolute speed, and each race can have a maximum of 5 horses, where you can only see their relative rankings. What's the minimum number of races needed to find the top 3?

When solving this problem, I explained while writing on paper. When I gave my initial answer, the interviewer gently nudged me with questions like "I think you could optimize this further!" and "What would you do in this case?" — which helped me arrive at the correct answer. It felt like we reached the answer together through communication based on my ideas, and it was so fun that I still remember it vividly. 👍

2. The second part consisted of about an hour and a half of technical questions about Java. They asked basic questions like JVM structure and how GC works, and I also remember them going to my GitHub and asking about why I structured my DTO and domain folders the way I did. They also asked about the benefits of writing test code and what I think about when writing tests. (A lot of the questions from the mock interview my Java Bom study group gave me actually came up, which was really great!)

The result: I passed!!! My start date was set for March.

At the time, I was originally planning to take a leave of absence, but since it was a conversion-type internship, I needed to graduate quickly. So I coordinated with my professor to have my senior year first semester credits replaced by the internship, and I waited anxiously for my start date.

 

 

 

Remote Onboarding Due to COVID

However, as COVID cases surged in March, my start date got pushed back. I'd been looking forward to it so much, but I waited for the company's update while worrying about COVID.

 

 

As a result, the start date was pushed back by a week, and even on the delayed start date, Naver had transitioned to a full remote work system, so I ended up onboarding remotely. The orientation was conducted via video call, and all contracts were received by email.

So on my start date, all the company equipment was delivered to my home, and I had two mentors at the time — a life mentor and a tech mentor — and I could only contact them through Works (the company messenger).

So from my start date, I worked fully remote for about a month and a half. For the remaining month and a half, I went to the office twice a week, which is when I got to meet my team members and mentors in person.

 

Intern Tasks

Two days after joining, I discussed with my mentor what tasks I'd be working on during the internship. I was part of the Moment team, and since Moment was a service that launched in April, I remember having to talk around it when explaining my department to others for the first month.

During the internship, I had 3 main jobs:

1. Read and summarize Effective Java and Clean Code

2. Build a toy project in an environment similar to the current Moment server
    2-1. Build a server that integrates with Naver services
    2-2. Deploy the server using k8s
    2-3. Set up CI/CD for the server by writing a jenkins pipeline
    2-4. Refactor the code using sonarlint

3. Analyze the Moment service and attend meetings as a Moment team member

 

1. Reading and Summarizing Effective Java and Clean Code

I've been gradually uploading the summaries to my blog
https://jyami.tistory.com/category/Dev%20Book%20Review/Effective%20Java
Though it's a hassle to format the posts for the Tistory editor, so I still haven't uploaded everything.. ugh

What I focused on while doing this reading task was not skipping over any passages I didn't understand. I'd been reading books this way in my Java Bom study group, and that approach carried over to this task.

I'd look things up on other people's blogs, check the Oracle docs, and try coding out examples from the book. I remember reading the section on parallel streams, which led to a discussion about ForkJoinPool — I even ended up reading through the JDK source code while studying that part.

Notes I took on the reference relationships after looking at the ForkJoinPool JDK code

 

Having those two books as intern tasks still has a lasting impact on me. Honestly, I wasn't a big fan of reading dev books. I disliked reading documents so much that it was one of the reasons I didn't want to go to grad school. But back then, I'd set a plan each week for how much to read, and that habit has stuck with me to this day. (These days I'm reading a chapter a day of Java 8 in Action.)

I was surprised to find that Java, which I thought I knew well, kept raising new questions no matter how much I studied. I actually got keywords for things I didn't know from the books and wrote a few blog posts about them.

https://jyami.tistory.com/112

 

volatile을 사용한 쓰레드간 통신 동기화

이펙티브 자바를 읽으면서 짜릿한 단일검사(racy single-check)에 대해 찾아보던 중, 알게된 내용이다. 동기화의 기능 자바의 쓰레드 프로그래밍을 해보았다면 synchronized 키워드를 몇번 접해볼 수 있�

jyami.tistory.com

https://jyami.tistory.com/99

 

자바의 제네릭 타입 소거, 리스트에 관하여 (Java Generics Type Erasure, List)

1. 자바의 제네릭과 로타입 (Java Generics and Raw Type) public class Example{ private T member; } 위와 같이 클래스 및 인터페이스 선언에 타입 매개변수 T 가 사용되면 제네릭 클래스, 제네릭 인터페이스라..

jyami.tistory.com

So after this task, I became much less resistant to reading books, and there are so many books I want to read now. (I've actually bought a bunch already..haha)

 

2. Building a Toy Project in an Environment Similar to the Moment Server

This was mostly a process of growing through trial and error lol. Since Moment had just recently launched at the time, and partly due to my own stubbornness, I only asked my mentor when I truly couldn't solve something. When I was stuck, I'd ask in this format: "I'm working on this part, and I'm stuck on this issue. I've looked into a few approaches to resolve it, and I think the problem is in this area, so I'm going to look into it further — am I heading in the right direction?" It was all about briefing the current situation and getting guidance.

I was a bit embarrassed to ask questions that could be easily Googled, and I didn't want to look clueless. Later, in my mentor's feedback, they said I was the type to figure things out on my own and come back with everything neatly organized... lol

2-1. Building the Integration Server

First, I built a server that integrated with Naver APIs to query response data from various services and perform CRUD operations with a database and cache. The implementation itself wasn't that difficult, but this part allowed me to communicate a lot with my mentor. I needed to flesh out the requirements, and for docs pages I didn't have access to, I had to ask my mentor for help before I could implement anything.

What I focused on most while coding was writing test code. When implementing a feature, I consciously made an effort to write corresponding test cases. So when I measured the test line coverage mid-implementation and after finishing, it was in the 80% range both times.

Even though it was a solo project, I considered test code as a communication tool that conveys the project's requirements, so I was thorough about writing @DisplayName annotations too. (To at least prevent my future self from not understanding what's going on)

2-2. Deploying in a Container Environment

I had to deploy the finished server using k8s, but at the time, I had zero knowledge of docker or k8s. (I vaguely knew that Docker was something that saves environments) Since I had to learn it for the task, I picked it up along the way, and I'm actually using it all the time these days lol (The deployment system for Angel Hack, which I recently participated in, was Dockerfile-based, so it went smoothly!)

At the time, I managed pod object specs using helm charts and successfully set up the entire deployment pipeline using the company's internal container deployment system. The task deadline was 3 weeks, and when I finished all of this with 1 week to spare, I was able to additionally try setting up HPA and ELK configurations.

When I gave my intern presentation on this part, I mentioned some pain points about the internal container deployment system. A developer there gave me feedback saying they'd been planning to adopt it for their team too and would keep these issues in mind — they said the presentation content was impressive. That made me really proud.

Let's try again. (Firm period.)

I learned a lot of DevOps knowledge while doing deployments. So every day I wrote troubleshooting documentation like the screenshot above. But most of it was like "Let's try again." "Why isn't this working?" "AAAARGH!!" lol

2-3. CI/CD Using Jenkins Pipeline

Jenkins was also something I used for the first time thanks to this intern task. (I'd always just used the CI/CD that teammates set up..)

There was plenty of trial and error as expected, but I learned so much through the struggle.. Jenkins is something I'll use a lot going forward, so I consider it a great experience lol

Since I was writing a JenkinsFile for the first time, even syntax errors were a source of performance slowdowns. I was pulling source code from the company's internal GitHub and running the Jenkins pipeline from the JenkinsFile in that source folder, but there were many issues.

1. To check if the JenkinsFile syntax was correct, I'd push to GitHub every time I changed even a single line.

2. Later I realized the commit history was getting messy and started force pushing carelessly.
Still, testing the JenkinsFile by continuously pushing to GitHub didn't feel like the right approach.

3. Evolved to a smarter approach: Testing scripts directly within Jenkins
I found that I could go into Jenkins build history and use the replay button to rebuild with a modified script. This way, I could verify whether the build pipeline was working correctly on the spot. (I'll definitely use this trick going forward.)

2-4. Refactoring Code Using SonarLint

In the last week, I worked on refactoring and improving code quality, and this is when I once again felt the importance of testing. When refactoring based on SonarLint suggestions, even simple refactoring like changing a package name could break things — due to human error, tests going out of scope, and various other reasons.

Watching the code smells drop from 146 to 13, the thing that guaranteed there were no errors during refactoring was TestCase. You can never say enough about the importance of testing.

 

3. Being an Intern on the Moment Team

Moment, which I got to try before everyone else!!

As a member of the Moment team, I attended various meetings and talked with team members about everything from development to future plans, which helped deepen my understanding of real-world work.

On my third day as an intern, I reviewed the Moment process documentation and wrote down my questions. There were things I never would have known without actual work experience. (For example, I was surprised to find that Auto Increment was missing from the DB design docs — turns out it was because they were using a distributed DB lol)

I appreciated that when spending time with team members, they treated me as a fellow developer, even though I was just an intern.
Even when there were deadlines for resolving certain issues, it was great to see a senior developer doing pair programming with team members to bring everyone's domain knowledge up to the same level. Even as an intern, I could sit alongside them, watch, and share my opinions. I was moved by this kind of developer culture that I'd only dreamed about.

And as a COVID-era intern, one thing I was really grateful to my team and mentors for was their active remote communication! Honestly, during the internship, I felt bad about suddenly messaging people on the company messenger whenever I had questions (that's just my personality — everyone was actually very kind). So early on, I wished the internship had been in person instead. But as time went on, communication worked so well even remotely that I fell in love with working from home..haha

Shortly after I first met a senior developer on the team, they suddenly messaged me on Works saying, "Minjeong, what do you think the problem with this code is?" We ended up having an intense 30-minute conversation about code on Works (we discussed the issues with using Optional and Null together), and even though I'd never seen this person's face, my sense of closeness shot through the roof lol

On another occasion, I ran into a dependency issue while working on a task, and we spent an entire hour communicating through Works to resolve it.

I also had lots of coffee chats with team members, and I kept hearing about the importance of exercise lol (I'm honestly still hearing about it constantly). It made me grateful to work with people who invest in self-improvement beyond just development.

 

End of the Internship

From everything I learned to the great team members I gained, it was an internship that gave me so much. Unfortunately, it didn't lead to a full-time offer.

My intern evaluation was good, but in the conversion interview (2nd interview) — (again, this internship was my very first technical interview experience) — I was so nervous that instead of highlighting my strengths, I ended up revealing the things I couldn't do and my weaknesses.

 

Why are my photos always taken in the bathroom... lol

 

It was 3 months of living more intensely than ever, but since I know the internship itself was a great experience, the memories are more about the journey than the outcome. (It might sound like I'm sugarcoating things since it's all positive, but I'm not — it was genuinely fun)
It's a bittersweet result, but the fact that it remains a good memory even 2 months later makes me think I'd regret not writing this up, so I'm leaving it as a record!

The End! 

댓글

Comments

Develop/DevOps

GCP MySQL(MariaDB) DB 외부연결 | GCP MySQL (MariaDB) DB External Connection

GCP compute engine에 mysql을 연결하기 위해 삽질하면서 정리한 내용.1. compute engine에 mysql 설치sudo apt-get updatesudo apt-get install mysql-serversudo mysql_secure_installation // mysql 설정mysql_secure_installation에서 설정한 패스워드로 입력해서 접속한다.접속 방법 : root 유저로 접속하고, 패스워드를 입력하겠음sudo mysql -u root -p 2. 외부 ip 접근에 대한 포트 방화벽 열기VPC network > filewallrules 메뉴에 들어가기traffic : ingress protocols and port : 33063306 포트가 방화벽에 연결된다.소스..

GCP MySQL(MariaDB) DB 외부연결 | GCP MySQL (MariaDB) DB External Connection

728x90

GCP compute engine에 mysql을 연결하기 위해 삽질하면서 정리한 내용.

1. compute engine에 mysql 설치

sudo apt-get update
sudo apt-get install mysql-server
sudo mysql_secure_installation // mysql 설정

mysql_secure_installation에서 설정한 패스워드로 입력해서 접속한다.

접속 방법 : root 유저로 접속하고, 패스워드를 입력하겠음

sudo mysql -u root -p

 

2. 외부 ip 접근에 대한 포트 방화벽 열기

VPC network > filewallrules 메뉴에 들어가기

traffic : ingress

protocols and port : 3306

3306 포트가 방화벽에 연결된다.

소스 필터나 대상은 커스텀하게 사용하자. 난 범용적 사용을 위해 모든 인스턴스로 지정해둠

 

3. demon addresss 변경하기

ip 통해서 mysql에 접근 가능하도록 할 때 mysql 데몬이 0.0.0.0으로 떠 있어야 한다 (기본값 127.0.0.1)
이를 위해서 compute engine의 shell에서 conf 파일 수정이 필요하다.

cd /ect/mysql/mariadb.conf.d
sudo vi 50-server.cnf

해당 경로에 설정파일이 존재한다. 이곳으로 들어가서 bind-address를 변경해준다.

# bind-address = 127.0.0.1
bind-address = 0.0.0.0

이렇게 해준 후에 데몬을 재시작해주어야 한다.

service restart를 해주어도 되지만, vm에 안 깔려 있길래 다른 방법을 사용하였다.

sudo /etc/init.d/mysql restart

 

4. 접속 유저 권한 부여

위에서 sudo 권한을 이용해 db에 접속했었다. 따라서 외부 접속 전용 사용자를 추가하고, 해당 사용자에 권한을 부여 해주자

User 목록 확인

SELECT user, host FROM mysql.user;

 

사용자 계정 추가

CREATE USER 'jyami'@'%' IDENTIFIED BY 'password';
  • 사용자 명 : jyami
  • 접근 : % - 외부에서의 접근을 허용함
  • 비밀번호 : password

사용자 접근 권한

localhost local에서만의 접근을 허용함
% 모든 IP에서의 접근을 허용함
xxx.xxx.xxx.xxx 지정한 IP에서만 접근을 허용함
xxx.xxx.% 특정 IP 대역에서의 접근을 허용함

DB 권한 부여

GRANT ALL PRIVILEGES ON *.* TO 'jyami'@'%' WITH GRANT OPTION;

모든곳에서 접속하는 사용자 jyami에게 모든 권한을 부여한다.

*.* 모든 것을 할 수 잇는 권한
[database_name].* 특정 DB를 관리할 수 있는 권한
[database_name].[tablename] 특정 DB의 특정 Table을 관리할 수 잇는 권한

부여된 권한 보기

SHOW GRANTS FOR jyami@%

 

5. springboot 접속

GCE로 접근했을 때 분명 위 명령어대로 mysql을 깔았는데 직접 Mysql에 접속해보니 MariaDB [DB]> 로 떴었다. 이때 mysql connection은 안됐는데, mariaDB connection은 됐다.

[DB가 MariaDB 인 경우]

# build.gradle
runtimeOnly 'org.mariadb.jdbc:mariadb-java-client:2.1.2'
# application.yml

spring:
  datasource:
    driver-class-name: org.mariadb.jdbc.Driver
    url: jdbc:mariadb://[GCE public IP]:3306/[DB명]?useSSL=false&serverTimezone=UTC
    username: [유저이름]
    password: [비밀번호]

  jpa:
    show-sql: true
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL5InnoDBDialect

  logging:
    level:
      org:
        hibernate:
          SQL: DEBUG
          type:
            trace

 

[DB가 MySQL 인 경우]

# build.gradle
runtimeOnly 'mysql:mysql-connector-java'
# application.yml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://[GCE public IP]:3306/[DB명]?useSSL=false&serverTimezone=UTC
    username: [유저이름]
    password: [비밀번호]

  jpa:
    show-sql: true
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL5InnoDBDialect

  logging:
    level:
      org:
        hibernate:
          SQL: DEBUG
          type:
            trace

 

6. 추가 DB 관련 명령어

database 목록 확인

SHOW DATABASES;

사용할 database 지정

USE [database 이름];

database 생성

CREATE DATABASE [데이터베이스 이름];

 

이외 외부 접속 관련 참고 사이트

https://m.blog.naver.com/PostView.nhn?blogId=varkiry05&logNo=198610727&proxyReferer=https:%2F%2Fwww.google.com%2F

Notes from my struggles trying to connect MySQL to a GCP compute engine.

1. Installing MySQL on Compute Engine

sudo apt-get update
sudo apt-get install mysql-server
sudo mysql_secure_installation // mysql 설정

Log in using the password you set during mysql_secure_installation.

How to connect: Log in as the root user with a password

sudo mysql -u root -p

 

2. Opening Firewall Ports for External IP Access

Go to VPC network > firewall rules menu

traffic : ingress

protocols and port : 3306

Port 3306 will be opened in the firewall.

Customize the source filters and targets as you need. I set it to all instances for general-purpose use.

 

3. Changing the Daemon Address

To allow MySQL access via IP, the MySQL daemon needs to be listening on 0.0.0.0 (the default is 127.0.0.1)
To do this, you need to modify the conf file in the compute engine's shell.

cd /ect/mysql/mariadb.conf.d
sudo vi 50-server.cnf

The config file is located at this path. Go in and change the bind-address.

# bind-address = 127.0.0.1
bind-address = 0.0.0.0

After making this change, you need to restart the daemon.

You could use service restart, but since it wasn't installed on the VM, I used a different method.

sudo /etc/init.d/mysql restart

 

4. Granting User Permissions

Earlier, we connected to the DB using sudo privileges. So let's add a dedicated user for external access and grant permissions to that user.

Check User List

SELECT user, host FROM mysql.user;

 

Add a User Account

CREATE USER 'jyami'@'%' IDENTIFIED BY 'password';
  • Username: jyami
  • Access: % - allows access from external sources
  • Password: password

User Access Scope

localhost Allows access only from local
% Allows access from all IPs
xxx.xxx.xxx.xxx Allows access only from a specific IP
xxx.xxx.% Allows access from a specific IP range

Grant DB Privileges

GRANT ALL PRIVILEGES ON *.* TO 'jyami'@'%' WITH GRANT OPTION;

This grants all privileges to the user jyami connecting from anywhere.

*.* Privileges to do everything
[database_name].* Privileges to manage a specific DB
[database_name].[tablename] Privileges to manage a specific table in a specific DB

View Granted Privileges

SHOW GRANTS FOR jyami@%

 

5. Connecting from Spring Boot

When I accessed the GCE, I definitely installed MySQL following the commands above, but when I actually connected to MySQL, it showed MariaDB [DB]>. In this case, the MySQL connection didn't work, but the MariaDB connection did.

[If the DB is MariaDB]

# build.gradle
runtimeOnly 'org.mariadb.jdbc:mariadb-java-client:2.1.2'
# application.yml

spring:
  datasource:
    driver-class-name: org.mariadb.jdbc.Driver
    url: jdbc:mariadb://[GCE public IP]:3306/[DB명]?useSSL=false&serverTimezone=UTC
    username: [유저이름]
    password: [비밀번호]

  jpa:
    show-sql: true
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL5InnoDBDialect

  logging:
    level:
      org:
        hibernate:
          SQL: DEBUG
          type:
            trace

 

[If the DB is MySQL]

# build.gradle
runtimeOnly 'mysql:mysql-connector-java'
# application.yml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://[GCE public IP]:3306/[DB명]?useSSL=false&serverTimezone=UTC
    username: [유저이름]
    password: [비밀번호]

  jpa:
    show-sql: true
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL5InnoDBDialect

  logging:
    level:
      org:
        hibernate:
          SQL: DEBUG
          type:
            trace

 

6. Additional DB-Related Commands

Check Database List

SHOW DATABASES;

Select a Database to Use

USE [database 이름];

Create a Database

CREATE DATABASE [데이터베이스 이름];

 

Additional reference site for external access

https://m.blog.naver.com/PostView.nhn?blogId=varkiry05&logNo=198610727&proxyReferer=https:%2F%2Fwww.google.com%2F

댓글

Comments

Develop/DevOps

알아두면 언젠간 깨달을 도커지식 2 - 도커 네트워크 | Docker Knowledge You'll Eventually Appreciate 2 - Docker Network

이글은 야매로 작성된 글이며 필자가 깨달아갈 수록 추가되는 글입니다필자가 도커를 공부하며 깨달은 지극히 주관적인 관점일 수 있으니 이상한 점은 친절한 댓글 부탁드립니다!그리고 항상 도커의 길로 인도해주는 영우찡 감사여! 가상 네트워크아이피의 갯수는 제한적이다 우리가 사용하는 공유기는 주로 192.168.x.x 아이피 대역으로 내 컴퓨터에 아이피를 할당해준다. 그치만 이 아이피는 내 공유기 밖에서는 접근 할 수 없다.그럼 이 때 외부에서 공유기 안의 특정 서버에 접근하기 위해서는 포트 포워딩을 사용한다. 포트포워딩이 필요한 이유는 공유기는 하나인데 비해(ip는 하나인데 비해) 내부 서버는 여러대일 수 있으니 어느 서버의 포트로 연결을 해주어야 하는지 몰라서 포트 포워딩을 사용하는 것이다. 외부에서 8080..

알아두면 언젠간 깨달을 도커지식 2 - 도커 네트워크 | Docker Knowledge You'll Eventually Appreciate 2 - Docker Network

728x90

이글은 야매로 작성된 글이며 필자가 깨달아갈 수록 추가되는 글입니다
필자가 도커를 공부하며 깨달은 지극히 주관적인 관점일 수 있으니 이상한 점은 친절한 댓글 부탁드립니다!
그리고 항상 도커의 길로 인도해주는 영우찡 감사여!

 

가상 네트워크

아이피의 갯수는 제한적이다 우리가 사용하는 공유기는 주로 192.168.x.x 아이피 대역으로 내 컴퓨터에 아이피를 할당해준다. 그치만 이 아이피는 내 공유기 밖에서는 접근 할 수 없다.

그럼 이 때 외부에서 공유기 안의 특정 서버에 접근하기 위해서는 포트 포워딩을 사용한다. 포트포워딩이 필요한 이유는 공유기는 하나인데 비해(ip는 하나인데 비해) 내부 서버는 여러대일 수 있으니 어느 서버의 포트로 연결을 해주어야 하는지 몰라서 포트 포워딩을 사용하는 것이다. 
외부에서 8080포트를 요청할 때 공유기 하위에 있는 서버들이 모두 8080을 쓰고 있을 때 어느 서버의 8080인지를 몰라서 서버를 지정하는게 목적이다. 추가로 서버를 지정하면서 포트도 바꿀수 있게 되었고!

주로 사용하는 http와 tcp 요청은 ip와 포트로 타겟을 지정하는데, 외부에서 요청할 때

ip는 우리집 공유기가 가진 공인 아이피를 지정할 테고,
port는 공유기 아래 있는 여러대의 서버중에 어떤 곳에 이 요청을 전달해야 할 지 모르기 때문에 포트포워딩으로 미리 정해두고 전달하는 것이다.

 

도커의 포트포워딩

도커도 마찬가지로 -p 플래그를 사용해서 포트포워딩을 지정할 수 있다. 도커도 하나의 서버에 여러개의 컨테이너가 있어서, 서버의 포트에 컨테이너를 지정할지가 애매해서, 위에서 공유기에서 서버를 지정할 때 애매했던 것과 동일한 이유로 포트포워딩이 필요하다.

도커환경에서는 OS가 설치된 host machine이 공유기의 역할을 하고, 도커가 가상 망을 이루고 있는 것이다.

ip는 도커가 설치된 머신을 의미하고
port는 도커 망안에서 어떤 컨테이너로 연결을 할지에 대한 의미로 생각하자 (-p)

 

도커 안에서는 모든 컨테이너가 전부 다른 서브넷을 가진다.

도커 컨테이너 끼리는 통신을 하지 못한다
-> 컨테이너끼리 연결해주는 작업이 도커 안에 네트워크를 다는 것

사실 컨테이너 가상화가 각자 분리된 영역을 만들어 주기 위함이었으니 서로 통신도 분리를 해둔 것이라고 생각하면 편하다.(필요할 경우에 통신을 연결하면 되도록 만든 것)

 

도커 네트워크

하지만 컨테이너가 분리가 되어있다 하더라도 통신은 필요하다. 예를 들자면 내 springboot 서비스에 대한 컨테이너 A와 DB에 대한 컨테이너 B 둘사이에서 통신이 일어나는 경우를 생각할 수 있을 것이다.

따라서 컨테이너끼리 통신을 위해 도커 네트워크를 만들어서 여러개의 컨테이너를 하나의 가상의 망 아래에 묶을 수 있다.

이런식으로 네트워크를 만들고

docker network create test_network

만들어진 네트워크에 컨테이너를 등록하는 형태로 지정하면 컨테이너간 통신이 가능해진다.

docker network connect web_server_container

 

이때 문제는 컨테이너의 아이피가 랜덤으로 만들어진다는 점이다. (아이피가 컨테이너마다 부여된다) 컨테이너가 뜰 때마다 아이피가 랜덤이다.

아이피가 랜덤으로 부여되면, 아이피를 이용해서 통신을 할 수 없다. 따라서 통신을 하기위해서는 컨테이너 네임으로 지정하는 방식을 이용한다.

컨테이너 네임이 Container network interface(CNI)이라는 도커 내부의 가상망을 만들어주는 곳에 등록되어 있어 ip 대신 컨테이너 네임을 DNS 서버에 등록한다.

도커 네트워크에 컨테이너가 등록될 때 컨테이너의 ip는 랜덤하게 부여되지만, CNI 덕분에 컨테이너 네임을 이용할 수 있다.

결국 도커에서 네트워크 통신을 할 때 DNS 검색 우선 순위에서 CNI가 1순위이기 때문에 컨테이너 이름으로 호출이 가능한 것이다.

컨테이너 이름을 아이피 대신 사용한다.

 

그래서 웹앱 컨테이너 하나랑 db 컨테이너하나 띄우고 두 컨테이너를 하나의 네트워크로 묶어서 앱에서 DB를 호출할 때.

  네임스페이스 포트
웹앱 컨테이너 web_app_container 9090
DB 컨테이너 DB_container 3306

다음과 같은 정보를 갖고있다면, 그저 springboot 에서 DB 컨테이너에 연결을 하고자 한다면 DB_container:3306 이런식으로 적어도 작동이 된다는 것이다.

 

이해를 위한 예제

Q. 집에 공유기가 있고, 그 안에 서버가 있고, 그안에 컨테이너 한경에서 9090 포트로 서비스하는앱이 있다. 이때 포트 포워딩은 몇 번 일어날까?

A. 2번일어난다 : 공유기에서 한번, 도커에서 한번

Q. 클라우드 환경에서 nginx를 사용한 포트포워딩을 할 때, nginx는 springboot 로 연결 연결, springboot에서는 db로 연결할 때 도커 네트워크 통신은 몇번 일어날까?

A. 2번 일어난다 : nginx의 proxy_pass 1번, spring에서 mysql로의 1번

container_name:portNumber 의 규칙을 사용하여 도커 네트워크 통신을 한다

[연결 글]

2020/07/12 - [Develop/DevOps] - 알아두면 언젠간 깨달을 도커지식 1 - 가상화

 

알아두면 언젠간 깨달을 도커지식 1 - 가상화

이글은 야매로 작성된 글이며 필자가 깨달아갈 수록 추가되는 글입니다 필자가 도커를 공부하며 깨달은 지극히 주관적인 관점일 수 있으니 이상한 점은 친절한 댓글 부탁드립니다! 그리고 항상

jyami.tistory.com

 

This post is written in a rough-and-ready style and gets updated as I learn more
This may reflect a highly subjective perspective gained while studying Docker, so please leave a kind comment if anything seems off!
And thanks as always to Youngwoo for guiding me down the path of Docker!

 

Virtual Network

The number of IP addresses is limited. The router we use typically assigns an IP to our computer in the 192.168.x.x range. However, this IP is not accessible from outside our router.

So, to access a specific server behind the router from the outside, we use port forwarding. The reason port forwarding is needed is that while there's only one router (only one IP), there can be multiple internal servers, and there's no way to know which server's port to route the connection to — that's why we use port forwarding. 
When an external request comes in on port 8080, if all the servers behind the router are using 8080, there's no way to tell which server's 8080 it should go to — so the purpose is to specify the server. As a bonus, you can also change the port while specifying the server!

Commonly used HTTP and TCP requests specify targets using IP and port. When making a request from the outside:

The IP will point to the public IP of our home router,
and since there's no way to know which of the multiple servers behind the router should receive the request, we use port forwarding to predetermine and route it.

 

Port Forwarding in Docker

Docker works the same way — you can specify port forwarding using the -p flag. Since Docker also has multiple containers on a single server, it's ambiguous which container should be assigned to the server's port. For the same reason it was ambiguous when specifying servers on a router, port forwarding is needed.

In a Docker environment, the host machine with the OS installed acts as the router, and Docker forms its own virtual network.

Think of the IP as referring to the machine where Docker is installed,
and the port as indicating which container to connect to within the Docker network (-p).

 

Inside Docker, every container has a completely different subnet.

Docker containers cannot communicate with each other
-> The process of connecting containers is adding a network within Docker

Since container virtualization was designed to create isolated areas in the first place, it makes sense that communication is also separated. (It's built so that you connect communication only when needed.)

 

Docker Network

However, even though containers are isolated, communication is still necessary. For example, you can think of a case where container A running your Spring Boot service needs to communicate with container B running the DB.

So, for inter-container communication, you can create a Docker network to group multiple containers under a single virtual network.

You create a network like this:

docker network create test_network

Then you register containers to the created network, and communication between containers becomes possible.

docker network connect web_server_container

 

The problem here is that container IPs are assigned randomly. (Each container gets its own IP.) Every time a container starts up, the IP is random.

If IPs are assigned randomly, you can't rely on IPs for communication. Therefore, to communicate, we use the container name instead.

The container name is registered in Docker's internal virtual network manager called the Container Network Interface (CNI), and the container name is registered in the DNS server instead of the IP.

When a container is registered to a Docker network, its IP is assigned randomly, but thanks to CNI, you can use the container name instead.

Ultimately, when Docker handles network communication, the CNI has the highest priority in DNS lookup, which is why you can call containers by their name.

Container names are used in place of IPs.

 

So when you spin up one web app container and one DB container, group them under a single network, and the app calls the DB:

  Namespace Port
Web App Container web_app_container 9090
DB Container DB_container 3306

If you have the information above, then to connect from Spring Boot to the DB container, you can simply write DB_container:3306 and it will work.

 

Examples for Understanding

Q. You have a router at home, a server behind it, and inside that server, an app serving on port 9090 in a container environment. How many times does port forwarding occur?

A. It happens 2 times: once at the router, once at Docker

Q. In a cloud environment using nginx for port forwarding, where nginx connects to Spring Boot, and Spring Boot connects to the DB — how many times does Docker network communication occur?

A. It happens 2 times: once for nginx's proxy_pass, once for Spring to MySQL

Docker network communication follows the rule of container_name:portNumber

[Related Posts]

2020/07/12 - [Develop/DevOps] - Docker Knowledge You'll Eventually Appreciate 1 - Virtualization

 

Docker Knowledge You'll Eventually Appreciate 1 - Virtualization

This post is written in a rough-and-ready style and gets updated as I learn more. This may reflect a highly subjective perspective gained while studying Docker, so please leave a kind comment if anything seems off! And thanks as always

jyami.tistory.com

 

댓글

Comments

Develop/DevOps

알아두면 언젠간 깨달을 도커지식 1 - 가상화 | Docker Knowledge You'll Eventually Appreciate 1 - Virtualization

이글은 야매로 작성된 글이며 필자가 깨달아갈 수록 추가되는 글입니다필자가 도커를 공부하며 깨달은 지극히 주관적인 관점일 수 있으니 이상한 점은 친절한 댓글 부탁드립니다!그리고 항상 도커의 길로 인도해주는 영우찡 감사여! 서버 가상화가상화에는 두가지 종류가 있다.하이퍼 바이저 가상화컨테이너 가상화 하이퍼바이저 가상화하이퍼바이저 기반의 가상화를 사용할 경우에는 내 서버 위에 host OS를 설치하고, 사용할 만큼만 자원을 분할하여 가상머신을 생성한 후에 그 위에 Guest OS를 설치하여 그 위에서 어플리케이션을 사용한다.하지만 하이퍼바이저 기반 가상화의 경우엔 vm마다 자원을 따로따로 분리해서 사용을하는데, 중복된 자원이 있을 경우엔 사실 불필요한 용량(자원)을 소모한다고 볼 수 있다. 컨테이너 가상화따라..

알아두면 언젠간 깨달을 도커지식 1 - 가상화 | Docker Knowledge You'll Eventually Appreciate 1 - Virtualization

728x90

이글은 야매로 작성된 글이며 필자가 깨달아갈 수록 추가되는 글입니다
필자가 도커를 공부하며 깨달은 지극히 주관적인 관점일 수 있으니 이상한 점은 친절한 댓글 부탁드립니다!
그리고 항상 도커의 길로 인도해주는 영우찡 감사여!

 

서버 가상화

가상화에는 두가지 종류가 있다.

  1. 하이퍼 바이저 가상화
  2. 컨테이너 가상화

 

하이퍼바이저 가상화

하이퍼바이저 기반의 가상화를 사용할 경우에는 내 서버 위에 host OS를 설치하고, 사용할 만큼만 자원을 분할하여 가상머신을 생성한 후에 그 위에 Guest OS를 설치하여 그 위에서 어플리케이션을 사용한다.

하지만 하이퍼바이저 기반 가상화의 경우엔 vm마다 자원을 따로따로 분리해서 사용을하는데, 중복된 자원이 있을 경우엔 사실 불필요한 용량(자원)을 소모한다고 볼 수 있다.

 

컨테이너 가상화

따라서 이 OS 기반 가상화 기술을 대체할 수 있게 나온것이 Container 기술이다 이 컨테이너 기술은 LXC(linux Containers)라고 부르며 이 LXC 기술을 잘 활용해서 만든 것이 Docker이다.

 

도커환경에서는 위 그림처럼 Guest OS가 필요하지 않다. 쉬운 이해를 위해 예를 들어보자

 

Host OS가 Cent OS이고, Ubuntu에서 application을 실행하고 싶다.

1. 하이퍼 바이저에서는, GuestOS인 Ubuntu를 설치하고 그 위에서 app을 실행한다. 그러면 Guest OS에 있는 Kernel이 하이퍼 바이저에 명령을 전달하고 하이퍼 바이저가 다시 Cent OS의 Kernel로 명령어를 전달한다.

사실 이때만 봐도 커널과, OS가 각각 2개이다 (많은 리소스를 소모한다)

이때 하이퍼 바이저는 논리적으로 CPU와 Memory 자원을 분리하는 역할을 한다.

 

2. 반면 도커엔진은 Guest OS인 Ubuntu와 Ubuntu의 Kernel 없이 앱의 명령을 내 host OS인 Cent OS의 kernel에 전달 할 수 있다.

이는 도커엔진이 Ubuntu(guest OS) 위에서 실행되는 app의 명령어를 Cent OS(host OS) kernel이 이해할 수 있게 바꿔주는 역할을 하기 때문이다.

하이퍼 바이저와 비교해서 리소스 소모가 훨씬 적다.

 

하이퍼 바이저와는 다르게 도커는 커널을 설치하지 않아, 베이스 이미지에도 커널이 없다. 따라서 하이퍼바이저에서 서버를 설치할 때 쓰는 윈도우나 리눅스 이미지 파일(iso)에 비해서 도커 이미지는 (베이스 이미지 조차도) 훨씬 가볍다

가벼운만큼 설치가 엄청 빠르다!!! (컨테이너 환경은 가볍다)

 

LXC(Linux Containers)

컨테이너 환경이 실제 메모리 상에 어떤식으로 저장될지를 생각해보면,

리눅스에 프로세스의 네임스페이스를 다르게 해서 분리해둔 것이라고 생각하면된다. 리눅스에 격리 구역을 만들어서 서로 엉키지 않게 분리해놓고 그냥 프로세스를 실행하는 것이다

이 작업들을 LXC가 해준다

 

LCX는 리눅스 커널 컨테이너 기능을 위한 사용자 영역 인터페이스다. 리눅스 사용자가 쉽게 시스템과 어플리케이션의 컨테이너들을 생성/관리 할 수 있게 해준다.

공식문서에 따르면 아래와 같은 기능이 있다 한다. (근데 읽어도 솔직히 잘 모르겠다)

  • 커널 네임스페이스 (ipc, uts, 마운트, pid, 네트워크, 사용자)
  • Seccomp 정책
  • Chroots (pivot_root 사용)
  • 커널 캐퍼빌리티
  • CGroups (컨트롤 그룹)
  • Apparmor와 SELinux 프로파일

그래서 그냥 컨테이너 환경에서는 도커 위에 올라간 코드를 돌린다는 것은

  1. LCX를 통해 리눅스 커널안에 Cgroup이라는 구분자와 NameSpace라는 구분자를 결합하여 격리된 구역을 만든다.
  2. 해당 격리 구역에 있는 코드를 도커엔진은 LXC를 사용하여 격리된 구역 안에서 프로그램을 실행한다
  3. 즉, 도커엔진이 리눅스에서 돌 때 LXC를 사용하여 위에 말한 host OS kernel 명령어로의 치환이 이루어 지고 구역을 격리시킨다는 것이다

첨언하자면 옛날의 윈도우의 도커는

하이퍼바이저위에 geust OS로 리눅스를 설치하고 그위에서 도커엔진을 돌렸다고한다 (리소스 절약이 없었을 것)

요즘엔 그냥 된다고 한다.(최근 윈도우에 리눅스 커널[WSL]이 들어왔다고..mac유저라 관심이 없었음)

 

[연결 글]

2020/07/12 - [Develop/DevOps] - 알아두면 언젠간 깨달을 도커지식 2 - 도커 네트워크

 

알아두면 언젠간 깨달을 도커지식 2 - 도커 네트워크

이글은 야매로 작성된 글이며 필자가 깨달아갈 수록 추가되는 글입니다 필자가 도커를 공부하며 깨달은 지극히 주관적인 관점일 수 있으니 이상한 점은 친절한 댓글 부탁드립니다! 그리고 항상

jyami.tistory.com

 

This post is written in a rough-and-ready style and will be updated as I learn more
This may reflect a highly subjective perspective from my experience studying Docker, so please leave a kind comment if anything seems off!
And shoutout to Youngwoo for always guiding me down the path of Docker!

 

Server Virtualization

There are two types of virtualization.

  1. Hypervisor Virtualization
  2. Container Virtualization

 

Hypervisor Virtualization

Hypervisor-based virtualization works by installing a host OS on your server, partitioning resources as needed to create virtual machines, then installing a Guest OS on each VM to run applications on top of it.

However, with hypervisor-based virtualization, each VM uses its own separately allocated resources — and when there are duplicate resources across VMs, it essentially wastes unnecessary capacity (resources).

 

Container Virtualization

So what came along to replace this OS-based virtualization technology is Container technology. This container technology is called LXC (Linux Containers), and Docker is something built by leveraging this LXC technology really well.

 

In a Docker environment, as shown in the diagram above, a Guest OS is not needed. Let's walk through an example for easier understanding.

 

The Host OS is CentOS, and you want to run an application on Ubuntu.

1. With a hypervisor, you install Ubuntu as the Guest OS and run the app on top of it. Then the kernel in the Guest OS passes commands to the hypervisor, and the hypervisor relays them to the CentOS kernel.

Just looking at this, there are already two kernels and two OSes (consuming a lot of resources).

Here, the hypervisor's role is to logically separate CPU and memory resources.

 

2. On the other hand, the Docker engine can deliver app commands directly to the host OS (CentOS) kernel without needing the Guest OS (Ubuntu) or Ubuntu's kernel.

This is because the Docker engine translates the commands from apps running on Ubuntu (Guest OS) into something the CentOS (host OS) kernel can understand.

Compared to a hypervisor, resource consumption is significantly lower.

 

Unlike a hypervisor, Docker doesn't install a kernel, so even the base image has no kernel. Therefore, Docker images (even base images) are much lighter compared to the Windows or Linux image files (ISO) used when setting up servers in a hypervisor.

Being lightweight means installation is incredibly fast!!! (Container environments are lightweight)

 

LXC (Linux Containers)

If you think about how a container environment is actually stored in memory,

you can think of it as processes separated by different namespaces in Linux. It creates isolated zones in Linux so things don't get tangled up, and simply runs processes within those zones.

This is what LXC does for us.

 

LXC is a userspace interface for the Linux kernel containment features. It lets Linux users easily create and manage system and application containers.

According to the official documentation, it has the following features. (Though honestly, I still don't fully get it even after reading it)

  • Kernel namespaces (ipc, uts, mount, pid, network, user)
  • Seccomp policies
  • Chroots (using pivot_root)
  • Kernel capabilities
  • CGroups (control groups)
  • Apparmor and SELinux profiles

So, running code on top of Docker in a container environment basically means:

  1. Through LXC, isolated zones are created inside the Linux kernel by combining identifiers called Cgroups and Namespaces.
  2. The Docker engine uses LXC to run programs inside those isolated zones.
  3. In other words, when the Docker engine runs on Linux, it uses LXC to translate commands into host OS kernel commands (as mentioned above) and isolate the zones.

As a side note, Docker on Windows in the old days

used to install Linux as a Guest OS on top of a hypervisor and run the Docker engine on that (so there were no resource savings).

Nowadays it just works natively, they say. (Apparently a Linux kernel [WSL] was recently added to Windows..I'm a Mac user so I didn't really care)

 

[Related Post]

2020/07/12 - [Develop/DevOps] - Docker Knowledge You'll Eventually Appreciate 2 - Docker Networking

 

Docker Knowledge You'll Eventually Appreciate 2 - Docker Networking

이글은 야매로 작성된 글이며 필자가 깨달아갈 수록 추가되는 글입니다 필자가 도커를 공부하며 깨달은 지극히 주관적인 관점일 수 있으니 이상한 점은 친절한 댓글 부탁드립니다! 그리고 항상

jyami.tistory.com

 

댓글

Comments

Develop/JAVA

volatile을 사용한 쓰레드간 통신 동기화 | Thread Synchronization for Inter-Thread Communication Using volatile

이펙티브 자바를 읽으면서 짜릿한 단일검사(racy single-check)에 대해 찾아보던 중, 알게된 내용이다. 동기화의 기능자바의 쓰레드 프로그래밍을 해보았다면 synchronized 키워드를 몇번 접해볼 수 있을 것이다. 동기화에서 synchronized 를 이용해 한가지 자원을 동시에 접근할 때 thread safe하게 자원의 내용을 변경할 수 있어, 이 기능만 동기화의 기능이라고 보기 쉽다. 즉 synchronized 가 걸려있는 블록 혹은 메서드에서 한번에 한 쓰레드씩 수행하도록 한다.그러나 사실 동기화의 기능은 총 2가지이다. a. 배타적 실행위에 말한 대로 한 쓰레드가 변경하는 중이라서, 상태가 일관되지 않는 (공유하는 자원의) 객체를 현재 사용중인 쓰레드만 접근이 가능하고, 다른 쓰레드가..

volatile을 사용한 쓰레드간 통신 동기화 | Thread Synchronization for Inter-Thread Communication Using volatile

728x90

이펙티브 자바를 읽으면서 짜릿한 단일검사(racy single-check)에 대해 찾아보던 중, 알게된 내용이다.

 

동기화의 기능

자바의 쓰레드 프로그래밍을 해보았다면 synchronized 키워드를 몇번 접해볼 수 있을 것이다. 동기화에서 synchronized 를 이용해 한가지 자원을 동시에 접근할 때 thread safe하게 자원의 내용을 변경할 수 있어, 이 기능만 동기화의 기능이라고 보기 쉽다. 즉 synchronized 가 걸려있는 블록 혹은 메서드에서 한번에 한 쓰레드씩 수행하도록 한다.

그러나 사실 동기화의 기능은 총 2가지이다.

 

a. 배타적 실행

위에 말한 대로 한 쓰레드가 변경하는 중이라서, 상태가 일관되지 않는 (공유하는 자원의) 객체를 현재 사용중인 쓰레드만 접근이 가능하고, 다른 쓰레드가 보지 못하게 막는 용도를 말한다.

이때 락에 대한 개념이 나온다. 락을 건 메서드에서 객체의 상태를 확인하고 필요하면 수정하도록 작성했을 때, 한 쓰레드에서 해당 메서드를 사용하게 되면 객체에 락이 걸리게 되고, 해당 객체는 다른 쓰레드에서 동시에 접근이 불가능하다.

즉 배타적 실행은 객체를 하나의 일관된 상태에서 다른 일관된 상태로 변화시키는 것이다.

 

b. 쓰레드 사이의 안정적 통신

나는 이 a번만 이전에 알고있었는데, 동기화의 중요한 기능이 하나 더 있다.

동기화 없이는 한 스레드가 만든 변화를 다른 스레드에서 확인하지 못할 수 있다.
동기화덕분에 한 스레드에서 락의 보호하에 수행된 수정사항을 다른 쓰레드에서 최종 결과를 볼 수 있다.

자바 언어에서 long과 double을 제외한 변수를 읽고 쓰는 동작은 원자적이다. 여러 쓰레드가 primitive 타입의 변수를 동기화 없이 수정하더라도, 각 스레드에서는 정상적으로 그 값을 온전하게 (연산중간에 끼어들지 않고 온전히) 읽어온다

 

원자적 연산

위에서 읽고 쓰는 동작이 원자적이라 했는데, 원자적 연산은 중단이 불가능한 연산을 이야기한다
여러 자바의 연산은 바이트코드로 이루어져 있는데, 하나의 연산을 수행하기 위해  바이트코드가 수행될 때 중간에 다른 쓰레드가 끼어들어서 연산의 결과가 올바르지 않게 변한다면 해당 연산은 원자적 연산이 아니다.

원자적이지 않은 동작의 예시로는 a++(증가 연산자)이 있다. cleancode책의 동시성 부록에서는 아래와 같은 설명이 나온다

lastId값이 42였다고 가정하자. 다음은 getNextId 메서드의 바이트 코드다.
예를 들어 첫째 스레드가 ALOAD 0, DUP, GETFIELD lastId까지 실행한 후 중단 되었다고 가정하자.
둘째 스레드가 끼어들어 모든 명령을 실행했다. 즉, lastId를 하나 증가해 43을 얻어갔다.
이제 첫째 스레드가 중단했던 실행을 재개한다.
첫째 스레드가 GETFIELD lastId를 실행한 당시 lastId 값은 42였다. 그래서 피연산자 스택에도 42가 들어있었다. 여기에 1을 더해 43을 얻은 후 결과를 저장한다.
첫째 스레드가 반환하는 값 역시 43이다. 둘째 스레드가 증가한 값은 잃어 버린다.
둘째 쓰레드가 첫째 스레드를 중단한 후 다시 실행된 첫째 스레드가 둘째스레드의 작업을 덮어썼기 때문이다.

즉, 여기서의 문제는 연산을 수행할 때 JVM에서 사용하는 프레임, 지연변수, 피연산자 스택에 저장하는 과정에서 원자적 연산이 아닌경우, 연산 중간 과정이 덮어씌워져 올바르지 않은 값을 갖는다는 것이다.

좀더 쉽게는 두개의 쓰레드에서 ++ 연산을 했으니 +2가 되어야하는데, ++ 연산이 원자적이지 않아 +1만 되었다는 것이다.

 

원자적 데이터에서의 동기화

위의 원자성에 대한 이야기를 들으면 원자적 데이터를 읽고 쓸 때는 (할당 연산은 원자적이다) 동기화를 하지 않아도 괜찮다고 생각 할 수 있다. (중단이 불가능하기 때문에!)

하지만 원자적 데이터라도 동기화가 필요하다

Java언어에서 스레드가 (원자적 데이터 값을 가지더라도) 필드를 읽을 때 '수정이 완전히 반영된' 값을 얻는다고 보장하지 않는다.
즉 A 쓰레드에서 필드를 수정했더라도, B 쓰레드에서 수정된 필드를 반드시 볼 수 있는 것은 아니라는 것이다.

따라서 한 쓰레드에서 수정이된 필드값을 다른 쓰레드에서 '잘 읽기' 위해서라도 동기화의 안정적인 통신이 필요하다
이는 자바 메모리 모델 때문이다.

 

동기화의 관점에서의 자바의 메모리 모델

동기화를 하지 않으면 스레드가 변수를 읽어올 때 각 쓰레드가 변수를 cached한 영역에서 읽어오게 된다. 그래서 한 쓰레드로 인해 해당 변수가 값이 변화해도, 다른 쓰레드에서는 이전에 읽었던 cached된 변수의 값을 읽기 때문에 변경된 사항을 볼 수 없다.

따라서 각 쓰레드에서 변경한 값을 값을 통신하기 위해 동기화가 필요하며. 이때 통신을 위한 동기화를 사용하기 위해서는 volatile 한정자를 사용하는 방법이 있다. (Synchronized는 배타적수행, 안정적 통신을 모두수행하는 것이고, volatile은 안정적 통신만을 수행한다고 생각하면 편하다)

즉, 여러 스레드가 공유하는 변수값을 읽어오기 위해서 volatile 키워드를 붙이면 그 변수를 읽어올때 각 쓰레드의 cached한 영역이 아닌 메인 메모리에서 직접 읽어오기 때문에 안정적인 통신을 보장할 수 있다.

 

공식문서에 있는 자바 메모리 모델에 대한 설명

volatile 변수의 경우에는 inter-thread action에 해당하여, synchronized된 경우의 자바 메모리 모델 reordering 규칙이 적용된다. (Reordering은 다른 쓰레드의 변수값을 읽어오기 위한 작업으로, 한 쓰레드의 변경사항이 다른 쓰레드에 표시될 수 있게 하기 위한 작업이라 생각하자.)
이 규칙은 volatile 변수가 쓰기가 일어날 경우에는, 항상 임의의 읽기 쓰레드에 의해서 동기화가 되도록 reordering되는 것을 의미하며, reordering이 된다는 것은 다른 쓰레드에서 변수를 읽을 때 최신 변경사항을 읽을 수 있다는 것이다.

좀더 자세한 내용을 보고싶다면 더보기 클릭

더보기

https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4

 

Chapter 17. Threads and Locks

class A { final int x; A() { x = 1; } int f() { return d(this,this); } int d(A a1, A a2) { int i = a1.x; g(a1); int j = a2.x; return j - i; } static void g(A a) { // uses reflection to change a.x to 2 } } In the d method, the compiler is allowed to reorder

docs.oracle.com

위 글에서는 자바의 메모리 모델에 대해 설명하며, 동기화 되지 않은 프로그램이 어떻게 놀라운 결과를 나타내는지를 보여주고 있다.

자바의 메모리 모델은 한 실행에 대해서 각각의 읽기를 검사하고, 특정 규칙에 따라 읽기가 쓰기를 옵저빙하면서 쓰기가 유효한지를 확인하여 작동한다.

격리된 각 쓰레드의 동작은 표시되는 값이 메모리 모델에 의해 결정되는 경우를 제외하고, 각 쓰레드의 의미에 따라 제어되는 방식으로 작동한다. (intra-thread semantics). 

즉, 격리된 각 쓰레드의 동작이 메모리 모델에 의해 결정되는 경우는 멀티쓰레드에서 표시되는 값을 기반으로 이해해야하는 경우를 이야기 한다. 

intra-thread semantics : 싱글 쓰레드에서 스레드 안에서만 표시되는 값을 기반으로 스레드 동작을 예측 가능하다
inter-thread action : 한 스레드에서 수행되어 다른 스레드에의해 직접 감지되거나 영향을 받는 작업

 

자바의 메모리 모델의 reordering

왼쪽 : reordering되기 전 / 오른쪽 : reordering된 후

왼쪽의 경우에 r2 == 2, r1 == 1이 되는것이 불가능해 보인다. 하지만, 컴파일러는 스레드의 실행에 영향을 미치지 않는 경우 두 스레드에서 명령어를 다시 정렬할 수 있다 (오른쪽 사진처럼 정렬). d

불가능한 이유 : 1번이 먼저오면 4번에 의한 쓰기 결과를 볼 수없고, 3번이 먼저오면 2번에 의한 쓰기 결과를 볼 수 없기 때문이다.

하지만 오른쪽의 경우에는 동기화가 되지 않았다.

  • 한개의 thread에서는 쓰기를 하고있고
  • 같은 변수값을 다른 쓰레드에서 읽고있고
  • 쓰기와 읽기는 동기화에 의해 정렬되지 않았다 : 동기화에 의한 정렬에 대한 설명은 17.4.4에 있다.

 

동기화의 경우에 자바 메모모리 모델의 reordering

volatile 변수에 대한 쓰기는 임의의 쓰레드에 의해 해당 변수를 subsequent read(동기화 순서에 의해 정의된 read)를 하여 동기화된다. -> volatile 변수의 변경 사항은 항상 다른 쓰레드에 표시된다.

 

long과 double

위에서 "자바 언어에서 long과 double을 제외한 변수를 읽고 쓰는 동작은 원자적이다." 라고 언급했다. 그렇다면 long과 double은 왜 변수를 읽고 쓰는 동작이 원자적이지 않을까?

JVM 비트수와 관련이 있다. 자바 메모리 모델에 의하면 32비트 메모리에 값을 할당하는 연산은 중단이 불가능하다. (원자적이다)
그렇지만 long과 double의 경우에는 64비트의 메모리 공간을 갖고있기 때문이다.

https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.7

 

Chapter 17. Threads and Locks

class A { final int x; A() { x = 1; } int f() { return d(this,this); } int d(A a1, A a2) { int i = a1.x; g(a1); int j = a2.x; return j - i; } static void g(A a) { // uses reflection to change a.x to 2 } } In the d method, the compiler is allowed to reorder

docs.oracle.com

해당 글을 읽어보면, 3가지로 요약이 된다.

  • volatile이 설정되지 않은 long, doulbe에 대한 쓰기는 두번에 이루어진다
    => 먼저 첫번째 32비트를 쓰고 다음 쓰기에서 두번째 32비트를 쓴다.
  • volatile이 설정된 long, double이라면 항상 원자적이다
  • 프로그래머는 shared 되는 62bit 값은 volatile이나 synchronize 로 선언하는게 좋다. complication을 피하기 위해!

즉 첫 비트 32비트 값을 할당한 직후에, 즉 둘째 32비트를 할당하기 직전에 다른 쓰레드가 끼어들어 두 32비트 값중 하나를 변경할 수 있기 때문에 long, double은 원자적 연산이 될 수 없다.

하지만 volatile을 사용을 한다는 것은 여러 쓰레드에서 하나의 변수가 같은 값을 읽도록 보장하는 것이기 때문에, 메모리를 2번 접근을 하더라도 같은 값을 읽도록하는. 변수에 접근하는 연산을 원자적으로 수행하게 보장한다는 것이다.

in which case the Java memory model ensures that all threads see a consistent value for the variable

따라서 long, double 변수를 원자적으로 사용하고 싶다면 volatile로 선언하는게 좋다.

I came across this while reading Effective Java and looking into the racy single-check idiom.

 

Functions of Synchronization

If you've done any thread programming in Java, you've probably encountered the synchronized keyword a few times. In synchronization, synchronized lets you safely modify a shared resource when multiple threads access it simultaneously, so it's easy to think that's all synchronization does. In other words, it ensures that only one thread at a time can execute a synchronized block or method.

But in fact, synchronization has two functions in total.

 

a. Mutual Exclusion

As mentioned above, this refers to preventing other threads from seeing a shared object while one thread is modifying it and its state is inconsistent — only the thread currently using the object can access it.

This is where the concept of a lock comes in. When you write a method that acquires a lock to check and potentially modify an object's state, once a thread enters that method, the object becomes locked, and other threads cannot access it simultaneously.

In short, mutual exclusion is about transitioning an object from one consistent state to another consistent state.

 

b. Reliable Communication Between Threads

I previously only knew about point (a), but there's another important function of synchronization.

Without synchronization, changes made by one thread may not be visible to other threads.
Thanks to synchronization, modifications performed under the protection of a lock in one thread can be seen as the final result by other threads.

In the Java language, reading and writing variables is atomic for all types except long and double. Even if multiple threads modify a primitive variable without synchronization, each thread will read the value correctly and completely (without being interrupted mid-operation).

 

Atomic Operations

I mentioned above that read and write operations are atomic. An atomic operation is one that cannot be interrupted.
Many Java operations are composed of bytecode instructions, and if another thread can intervene during the bytecode execution of an operation and cause incorrect results, then that operation is not atomic.

A classic example of a non-atomic operation is a++ (the increment operator). The concurrency appendix of the Clean Code book explains it like this:

Assume lastId had a value of 42. Here is the bytecode for the getNextId method.
For example, suppose the first thread executes up to ALOAD 0, DUP, GETFIELD lastId and then gets interrupted.
The second thread cuts in and executes all the instructions — it increments lastId and gets 43.
Now the first thread resumes execution from where it was interrupted.
When the first thread executed GETFIELD lastId, the value of lastId was 42. So 42 was on the operand stack. It adds 1 to get 43 and stores the result.
The first thread also returns 43. The value incremented by the second thread is lost.
This happened because the second thread interrupted the first thread, and when the first thread resumed, it overwrote the second thread's work.

The problem here is that when performing operations, if the operation is not atomic during the process of storing values in the JVM's frame, local variables, and operand stack, intermediate results can get overwritten, leading to incorrect values.

To put it more simply: two threads each performed a ++ operation, so the result should have been +2, but since the ++ operation is not atomic, only +1 was applied.

 

Synchronization with Atomic Data

After hearing about atomicity above, you might think that you don't need synchronization when reading and writing atomic data (since assignment operations are atomic). (Because they can't be interrupted!)

However, even with atomic data, synchronization is necessary.

The Java language does not guarantee that when a thread reads a field (even if the data is atomic), it will get a 'fully updated' value.
In other words, even if thread A modifies a field, thread B is not guaranteed to see the modified value.

Therefore, reliable communication through synchronization is needed even just to ensure that a field value modified by one thread is 'properly read' by another thread.
This is due to the Java Memory Model.

 

Java Memory Model from a Synchronization Perspective

Without synchronization, when a thread reads a variable, it reads from its own cached area. So even if one thread changes the variable's value, other threads still read the previously cached value and cannot see the change.

Therefore, synchronization is needed for threads to communicate changed values. One way to achieve communication-only synchronization is by using the volatile modifier. (Think of it this way: synchronized provides both mutual exclusion and reliable communication, while volatile only provides reliable communication.)

In other words, when you add the volatile keyword to a shared variable, reading that variable goes directly to main memory instead of each thread's cached area, which guarantees reliable communication.

 

Java Memory Model Explained in the Official Documentation

Volatile variables are considered inter-thread actions, so the Java Memory Model's reordering rules for synchronized cases apply. (Think of reordering as the mechanism for reading variable values from other threads — it's what makes changes in one thread visible to other threads.)
This rule means that writes to volatile variables are always reordered so that they are synchronized by any reading thread through subsequent reads (as defined by synchronization order). Being reordered means that other threads can read the latest changes when they access the variable.

Click "Show More" for more details

더보기

https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4

 

Chapter 17. Threads and Locks

class A { final int x; A() { x = 1; } int f() { return d(this,this); } int d(A a1, A a2) { int i = a1.x; g(a1); int j = a2.x; return j - i; } static void g(A a) { // uses reflection to change a.x to 2 } } In the d method, the compiler is allowed to reorder

docs.oracle.com

The article above explains Java's memory model and demonstrates how unsynchronized programs can produce surprising results.

Java's memory model works by examining each read in an execution and checking whether the write being observed by the read is valid according to specific rules.

The behavior of each isolated thread operates in a manner controlled by that thread's semantics, except when the values it sees are determined by the memory model (intra-thread semantics). 

In other words, when the behavior of an isolated thread is determined by the memory model, it needs to be understood based on values visible in a multithreaded context. 

intra-thread semantics: In a single thread, the thread's behavior is predictable based on values visible only within that thread.
inter-thread action: An action performed by one thread that can be directly detected or affected by another thread.

 

Reordering in the Java Memory Model with Synchronization

Left: Before reordering / Right: After reordering

In the left case, it seems impossible for r2 == 2 and r1 == 1. However, the compiler can reorder instructions in both threads if it doesn't affect each thread's execution (as shown in the right image).

Why it seems impossible: If instruction 1 comes first, it can't see the write result from instruction 4. If instruction 3 comes first, it can't see the write result from instruction 2.

But in the right case, there is no synchronization.

  • One thread is writing
  • Another thread is reading the same variable
  • The writes and reads are not ordered by synchronization: the explanation of synchronization ordering is in section 17.4.4.

 

Reordering in the Java Memory Model with Synchronization

A write to a volatile variable is synchronized with any subsequent read (a read defined by synchronization order) of that variable by any thread. → Changes to a volatile variable are always visible to other threads.

 

long and double

I mentioned above that "in the Java language, reading and writing variables is atomic for all types except long and double." So why aren't read and write operations atomic for long and double?

It's related to the JVM's bit width. According to the Java memory model, assigning a value to 32-bit memory is an uninterruptible operation (i.e., atomic).
However, long and double occupy 64-bit memory space.

https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.7

 

Chapter 17. Threads and Locks

class A { final int x; A() { x = 1; } int f() { return d(this,this); } int d(A a1, A a2) { int i = a1.x; g(a1); int j = a2.x; return j - i; } static void g(A a) { // uses reflection to change a.x to 2 } } In the d method, the compiler is allowed to reorder

docs.oracle.com

Reading through that article, it can be summarized in three points:

  • Writes to non-volatile long and double are done in two steps.
    => The first 32 bits are written first, then the second 32 bits are written next.
  • If long or double is declared volatile, the operation is always atomic.
  • Programmers should declare shared 64-bit values as volatile or synchronized to avoid complications.

In other words, right after assigning the first 32 bits — just before assigning the second 32 bits — another thread can cut in and modify one of the two 32-bit values. That's why long and double cannot be atomic operations.

However, using volatile guarantees that multiple threads read the same value from a single variable. So even though memory is accessed twice, it ensures the same value is read — meaning the variable access operation is guaranteed to be performed atomically.

in which case the Java memory model ensures that all threads see a consistent value for the variable

Therefore, if you want to use long or double variables atomically, it's best to declare them as volatile.

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] Chapter6: 열거 타입과 애너테이션

item 34. int 상수 대신 열거 타입을 사용하라 열거 타입은 확실히 정수 상수보다 뛰어나다. 더 알기쉽고 강력하다 대다수 열거 타입이 명시적 생성자나 메서드 없이 쓰이지만, 각 상수를 특정 데이터와 연결짓거나 상수마다 다르게 동작하게 할 때는 필요하다 하나의 메서드가 상수별로 다르게 동작해야할 때에는 switch문 대신 상수별 메서드 구현을 사용하자 열거 타입 상수 일부가 같은 동작을 공유한다면 전략 열거 타입 패턴을 사용하자. Link : jyami.tistory.com/102 [Effective Java] item 34. int 상수 대신 열거 타입을 사용하라 열거타입 (enum) : 일정 개수의 상수 값을 정의한 다음 그외의 값은 허용하지 않는 타입 정수 열거 패턴 (int enum patt..

[Effective Java] Chapter6: 열거 타입과 애너테이션

728x90

item 34. int 상수 대신 열거 타입을 사용하라

  • 열거 타입은 확실히 정수 상수보다 뛰어나다. 더 알기쉽고 강력하다
  • 대다수 열거 타입이 명시적 생성자나 메서드 없이 쓰이지만, 각 상수를 특정 데이터와 연결짓거나 상수마다 다르게 동작하게 할 때는 필요하다
  • 하나의 메서드가 상수별로 다르게 동작해야할 때에는 switch문 대신 상수별 메서드 구현을 사용하자
  • 열거 타입 상수 일부가 같은 동작을 공유한다면 전략 열거 타입 패턴을 사용하자.
  • Link : jyami.tistory.com/102
 

[Effective Java] item 34. int 상수 대신 열거 타입을 사용하라

열거타입 (enum) : 일정 개수의 상수 값을 정의한 다음 그외의 값은 허용하지 않는 타입 정수 열거 패턴 (int enum pattern) : 이전까지 사용하던 패턴 1. 정수 열거 패턴 (int enum pattern)의 단점 public stati..

jyami.tistory.com

 

item 35. ordinal 메서드 대신 인스턴스 필드를 사용하라

  • ordinal 메서드는 EnumSet과 EnumMap과 같이 열거 타입 기반의 범용 자료 구조에 쓸 목적으로 설계 되었다. 이 경우가 아니면 사용하지 말자
  • 열거 타입 상수에 연결 된 값을 ordinal 메서드로 얻지 말고 인스턴스 필드에 저장하라.
  • Link : https://jyami.tistory.com/103
 

[Effective Java] item 35. ordinal 메서드 대신 인스턴스 필드를 사용하라

1. ordinal 메서드를 잘못쓸 때 ordinal 메서드 : 해당 상수가 그 열거 타입에서 몇 번째 위치인지 반환하는 메서드 상수 선언 순서를 바꾸면 오동작한다. 이미 사용중인 정수와 값이 같은 상수는 추��

jyami.tistory.com

 

item 36. 비트 필드 대신 EnumSet을 사용하라

  • 열거할 수 있는 타입을 한데 모아 집합 형태로 사용한다고 해도 비트 필드를 사용할 이유는 없다
  • EnumSet 클래스가 비트 필드 수준의 명료함과 성능을 제공하고 아이템 34에서 설명한 열거 타입의 장점까지 선사한다
  • EnumSet의 유일한 단점은 (자바 11까지는 아직) 불변 EnumSet을 만들 수 없다는 것이다.
  • 향후 릴리즈에서 수정될 때 까지는 (명확성과 성능이 조금 희생되지만) Collections.unmodifiableSet으로 EnumSet을 감싸 사용할 수 있다.
  • Link : https://jyami.tistory.com/104
 

[Effective Java] item 36. 비트 필드 대신 EnumSet을 사용하라

1. 비트 필드란? 비트 필드 : 비트별 OR를 사용해 여러 상수를 하나의 집합으로 모을 수 있는 집합 public class Text{ public static final int STYLE_BOLD = 1 << 0; // 1 public static final int STYLE_ITALIC..

jyami.tistory.com

 

 

item 37. ordinal 인덱싱 대신 EnumMap을 사용하라

  • 배열의 인덱스를 위해 ordinal을 쓰는 것은 일반적으로 좋지 않으니 EnumMap을 사용하라
  • 다차원 관계는 EnumMap<..., EnumMap<...>> 으로 표현하라
  • 애플리케이션 프로그래머는 Enum.ordinal을 (왠만해서는) 사용하지 말아야한다 (아이템 35)는 일반원칙의 특수한 사례다
  • Link : https://jyami.tistory.com/105
 

[Effective Java] item 37. ordinal 인덱싱 대신 EnumMap을 사용하라

1. 올바르지 않은 방법 : ordinal()을 배열 인덱스로 사용 Set [] plant ByLisfeCycle = (Set []) new Set[Plant.LifeCycle.values().length]; for(int i =0; i < plantsByLifeCycle.length; i++) plantsByLifeCyc..

jyami.tistory.com

 

item 38. 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라

  • 열거 타입 자체는 확장할 수 없지만, 인터페이스와 그 인터페이스를 구현하는 기본 열거 타입을 함께 사용해 같은 효과를 낼 수 있다.
  • 이렇게 하면 클라이언트는 이 인터페이스를 구현해 자신만의 열거 타입(혹은 다른 타입)을 만들 수 있다.
  • API가 (기본 열거 타입을 직접 명시하지 않고) 인터페이스 기반으로 작성되었다면 기본 열거 타입의 인스턴스가 쓰이는 모든 곳을 새로 확장한 열거 타입의 인스턴스로 대체해 사용 할 수 있다.
  • Link : https://jyami.tistory.com/106
 

[Effective Java] item 38. 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라

1. 열거타입 확장은 하지 말자 열거 타입은 거의 모든 상황에서 타입 안전 열거 패턴(typesafe enum pattern)보다 우수하다. 단점 : 타입 안전 열거 패턴은 확장할 수 있으나 열거 타입은 그렇지 못하다

jyami.tistory.com

 

Item 39. 명명패턴보다 애너테이션을 사용하라

  • 애너테이션으로 할 수 있는 일을 명명패턴으로 처리할 이유는 없다.
  • 자바 프로그래머라면 예외 없이 자바가 제공하는 애너테이션 타입들은 사용해야한다.
  • 마커애너테이션을 이용하여 애너테이션에 관심있는 프로그램에 추가 정보를 제공한다.
  • 매개변수를 받는 애너테이션 타입, 반복가능한 애너테이션 등을 사용하여 소스 코드에 추가 정보를 제공할 수 있는 도구를 제공하자.
  • Link : https://jyami.tistory.com/107
 

[Effective Java] item 39. 명명 패턴보다 애너테이션을 사용하라

1. 명명 패턴의 단점 ex) junit3 : 테스트 메서드의 시작을 test로 시작하게 하였다. 오타가 나면 안된다. 올바른 프로그램 요소에서만 사용되리라 보증할 방법이 없다. 메서드가 아닌 클래스 명을 Tes

jyami.tistory.com

 

item 40. @Override 애너테이션을 일관되게 사용하라

  • 재정의한 모든 메서드에 @Override 애너테이션을 의식적으로 달면 실수했을 때 컴파일러가 바로 알려준다.
  • 예외는 하나다. 구체 클래스에서 상위 클래스의 추상 메서드를 재정의한 경우엔 이 애너테이션을 달지 않아도 된다. (단다고 해서 해롭진 않다.)
  • Link : https://jyami.tistory.com/108
 

[Effective Java] item 40. @Override 애너테이션을 일관되게 사용하라

1. @Override를 사용했을 때 장점 @Override : 메서드 선언에만 달 수 있다. 상위 타입의 메서드를 재정의했음을 뜻한다. Overriding을 Overloading로 잘못 작성할 수 있는 오류를 방지 할 수 있다. 잘못 작성 �

jyami.tistory.com

 

item 41. 정의하려는 것이 타입이라면 마커 인터페이스를 사용하라

  • 마커 인터페이스와 마커 애너테이션은 각자의 쓰임이 있다.
  • 새로 추가하는 메서드 없이 단지 타입 정의가 목적이라면 마커 인터페이스를 선택하자
  • 클래스나 인터페이스의 프로그램요소에 마킹해야하거나 애너테이션을 적극 활용하는 프레임워크의 일부로 그 마커를 편입시키고자 한다면 마커 애너테이션이 올바른 선택이다.
  • 적용 대상이 ElementType.TYPE인 마커 애너테이션을 작성하고 있다면, 잠시 여유를 갖고 정말 애너테이션으로 구현하는게 옳은지, 혹은 마커 인터페이스가 낫지는 않을지 곰곰히 생각해보자
  • Link : https://jyami.tistory.com/109
 

[Effective Java] item 41. 정의하려는 것이 타입이라면 마커 인터페이스를 사용하라

1. 마커 인터페이스 (marker interface) 마커 인터페이스(marker interface) : 아무 메서드도 담고 있지 않고, 단지 자신을 구현하는 클래스가 특정 속성을 가짐을 표시해주는 것 실제로 아무런 메서드도 담

jyami.tistory.com

 

댓글

Comments