API Gateway Filter

클라이언트의 요청에 따라 Gateway에서 어떤 서비스로 갈지 판단한 후 요청을 분기해준다.

https://www.inflearn.com/course/lecture?courseSlug=%EC%8A%A4%ED%94%84%EB%A7%81-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C%EC%84%9C%EB%B9%84%EC%8A%A4&unitId=68416&tab=community&category=questionDetail

  • Gateway Handler Mapping
    • 어떤 요청이 들어왔는지 요청정보를 받는다.
  • Predicate
    • 사전 조건을 분기해주는 역할
  • Pre filter
    • 사전 필터
  • Post filter
    • 사후 필터

Gateway에 filterConfig를 추가하자

package com.example.gateway.config;

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {
    @Bean
    public RouteLocator gatewayRoutes(RouteLocatorBuilder builder){
        return builder.routes()
                .route(r -> r.path("/first-service/**")
                        .filters(f -> f.addRequestHeader("first-request","first-request-header")
                                .addResponseHeader("first-response","first-response-header"))
                        .uri("<http://localhost:8081>"))
                .route(r -> r.path("/second-service/**")
                        .filters(f -> f.addRequestHeader("second-request","second-request-header")
                                .addResponseHeader("second-response","second-response-header"))
                        .uri("<http://localhost:8082>"))
                .build();
    }
}
  • first-service로 오는 모든 요청에 first-request : first-request-header 헤더, 모든 응답에 first-response : first-response-header를 적용한다.
  • second도 마찬가지로 설정

UserService에 헤더 정보를 log로 찍어보자

package com.example.userservice;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

@RestController("/")
@Slf4j
public class UserController {

    @GetMapping("first-service")
    public String first(){
        log.info("first 호출됨");
        return "first-service";
    }
    @GetMapping("first-service/message")
    public String firstMessage(@RequestHeader("first-request") String header){
        log.info(header);
        return "first-service";
    }

    @GetMapping("second-service")
    public String second(){
        log.info("second 호출됨");
        return "second-service";
    }
    @GetMapping("second-service/message")
    public String secondMessage(@RequestHeader("second-request") String header){
        log.info(header);
        return "second-service";
    }
}
  • request 정상적으로 받아오는 것 확인!

 

  • response 또한 확인

 

application.yml 설정정보를 이용해 filter를 적용해보자

server:
    port: 8080

eureka:
    client:
        register-with-eureka: false
        fetch-registry: false
        service-url:
            defaultZone: <http://localhost:8761/eureka>
spring:
    application:
        name: gateway-service
    cloud:
        gateway:
            routes:
              - id: first-service
                uri: <http://localhost:8081/>
                predicates:
                    - Path=/first-service/**
                filters:
                  - AddRequestHeader=first-request, first-request-header2
                  - AddResponseHeader=first-response, first-response-header2
              - id: first-service
                uri: <http://localhost:8082/>
                predicates:
                    - Path=/second-service/**
                filters:
                    - AddRequestHeader=second-request, second-request-header2
                    - AddResponseHeader=second-response, second-response-header2

 

  • 정상작동 확인!!

Spring Cloud Gateway Custom Filter

  • 필터 생성
package com.example.gateway.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

@Component
@Slf4j
public class CustomFilter extends AbstractGatewayFilterFactory<CustomFilter.Config> {
    public CustomFilter() {
        super(Config.class);
    }
    @Override
    public GatewayFilter apply(Config config) {
       //Custom Prefilter
        return ((exchange, chain) -> {
           ServerHttpRequest request = exchange.getRequest();
           ServerHttpResponse response = exchange.getResponse();

           log.info("Custom PRE filter : request id -> {}", request.getId());

           //Custom Post Filter
            return chain.filter(exchange).then(Mono.fromRunnable(()->{
                log.info("Custom POST filter : response code -> {}", response.getStatusCode());
            }));
        });
    }

    public static class Config{
        //configuration 정보를 기입
    }
}
  • yml 파일 수정
server:
    port: 8080

eureka:
    client:
        register-with-eureka: false
        fetch-registry: false
        service-url:
            defaultZone: <http://localhost:8761/eureka>
spring:
    application:
        name: gateway-service
    cloud:
        gateway:
            routes:
              - id: first-service
                uri: <http://localhost:8081/>
                predicates:
                    - Path=/first-service/**
                filters:
                  #- AddRequestHeader=first-request, first-request-header2
                  #- AddResponseHeader=first-response, first-response-header2
                  - CustomFilter
              - id: first-service
                uri: <http://localhost:8082/>
                predicates:
                    - Path=/second-service/**
                filters:
                    #- AddRequestHeader=second-request, second-request-header2
                    #- AddResponseHeader=second-response, second-response-header2
                  - CustomFilter
  • userService의 매핑url 추가
@GetMapping("second-service/check")
    public String checkSecond(){
        return "Hi, there, second check";
    }

    @GetMapping("first-service/check")
    public String checkFirst(){
        return "Hi, there, first check";
    }

정상 작동 확인

Spring Cloud Gateway - Global Filter

  • 위에 사용했던 필터와 차이점
    • 어떠한 라우트 정보가 실행된다 하더라도 공통적으로 수행될 수 있는 필터

  • 개별적으로 등록된 CustomFilter
  • Global Filter 생성
package com.example.gateway.filter;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

@Component
@Slf4j
public class GlobalFilter extends AbstractGatewayFilterFactory<GlobalFilter.Config> {
    public GlobalFilter() {
        super(Config.class);
    }
    @Override
    public GatewayFilter apply(Config config) {
       //Custom Prefilter
        return ((exchange, chain) -> {
           ServerHttpRequest request = exchange.getRequest();
           ServerHttpResponse response = exchange.getResponse();

           log.info("Global Filter baseMessage : request id -> {}", config.getBaseMessage());

           if(config.isPreLogger()){
               log.info("Global Filter Start: request id -> {}", request.getId());
           }
           //Custom Post Filter
            return chain.filter(exchange).then(Mono.fromRunnable(()->{
                if(config.isPostLogger()){
                    log.info("Global Filter end: response code -> {}", response.getStatusCode());
                }
            }));
        });
    }
    @Data
    public static class Config{
        //configuration 정보를 기입
        private String baseMessage;
        private boolean preLogger;
        private boolean postLogger;
    }
}
  • yml 파일 수정
server:
    port: 8080

eureka:
    client:
        register-with-eureka: false
        fetch-registry: false
        service-url:
            defaultZone: <http://localhost:8761/eureka>
spring:
    application:
        name: gateway-service
    cloud:
        gateway:
            default-filters:
              - name: GlobalFilter
                args:
                  baseMessage: Spring Cloud Gateway Filter
                  preLogger: true
                  PostLogger: true
            routes:
              - id: first-service
                uri: <http://localhost:8081/>
                predicates:
                    - Path=/first-service/**
                filters:
                  #- AddRequestHeader=first-request, first-request-header2
                  #- AddResponseHeader=first-response, first-response-header2
                  - CustomFilter
              - id: first-service
                uri: <http://localhost:8082/>
                predicates:
                    - Path=/second-service/**
                filters:
                    #- AddRequestHeader=second-request, second-request-header2
                    #- AddResponseHeader=second-response, second-response-header2
                    - CustomFilter

  • Global filter가 실행되고 Global filter가 종료되기 전 CustomFilter가 수행, 종료되고 그 이후 Global filter가 종료되는 모습을 확인할 수 있다.

'DevOps > MSA' 카테고리의 다른 글

Apache kafka  (0) 2023.05.24
Spring Cloud Gateway - Load Balancer  (0) 2023.05.15
Spring Cloud Gateway  (0) 2023.05.14
Spring Cloud 와 Eureka  (0) 2023.05.14

API Gateway Service

  • API Gateway는 모든 서버로의 요청을 단일지점을 거쳐서 처리하도록 한다. 이를 통해 공통된 로직처리나 인증 및 인가 처리, 라우팅 처리등을 할 수 있다.
  • 시스템의 내부 구조는 숨기고 외부의 요청에 의해 적절한 형태로 응답할 수 있는 장점이 있다.

SpringCloud에서의 MSA간 통신

RestTemplate

  • 전통적으로 다른 서버와의 통신을 위한 방법

FeignClient

  • 특정한 인터페이스를 만들고 이름을 등록user라는 서비스에서 feign client 등록하고 사용하면 직접적인 서버의 주소라던가 포트번호 없이 마이크로 서비스 이름을 가지고 호출할 수 있게된다.
    • 문제는 로드밸런싱을 하기위해 어디에 구축해서 작업할 것인가?
      • Ribbon(netflix 로드밸런싱 기능)
        • 비동기 처리 리액티브 방식과는 호환이 안됨 → 최근에는 잘 사용하지 않는다.
        • 마이크로서비스의 이름만으로 호출할 수 있다.
        • Gateway의 역할을 client side에서 처리하는 방식
    Spring Cloud Gateway
    • Dependencies
      • DevTools, Eureka Discovery Client, Gateway
    • application.yml
    • userService에 Controller를 구현하고 테스트 해보자
server:
    port: 8080

eureka:
    client:
        register-with-eureka: false
        fetch-registry: false
        service-url:
            defaultZone: http://localhost:8761/eureka
spring:
    application:
        name: gateway-service
    cloud:
        gateway:
            routes:
              - id: first-service
                uri: http://localhost:8081/
                predicates:
                    - Path=/first-service/**
              - id: first-service
                uri: http://localhost:8082/
                predicates:
                    - Path=/second-service/**
package com.example.userservice;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController("/")
@Slf4j
public class UserController {

    @GetMapping("first-service")
    public String first(){
        log.info("first 호출됨");
        return "first-service";
    }

    @GetMapping("second-service")
    public String second(){
        log.info("second 호출됨");
        return "second-service";
    }
}

 

각각 다른 서버에서 호출되어야할 api가 호출되는것을 확인!

 

 

'DevOps > MSA' 카테고리의 다른 글

Apache kafka  (0) 2023.05.24
Spring Cloud Gateway - Load Balancer  (0) 2023.05.15
Spring Cloud Gateway - Filter 적용  (0) 2023.05.14
Spring Cloud 와 Eureka  (0) 2023.05.14

Spring Cloud란?

  • MSA 구현을 위한 도구 모음
  • MSA의 개발, 배포, 운영에 필요한 아키텍쳐를 쉽게 구성할 수 있도록 지원하는 Spring Boot 기반 프레임워크
  • 분산 시스템 상에 필요한 여러 패턴들을 표준 패턴화 시켜 손쉽게 개발할 수 있도록 지원함

어떤 서비스가 사용되어야 할까?

  • Spring Cloud Config Server
    • 환경 설정관리를 위함
    • 다양한 마이크로서비스에서 사용하는 정보를 SpringCloudConfigServer를 통해 외부의 저장소에 저장할 수 있다.
    • 유지보수성이 높아진다.
  • Location transparency
    • 서비스 등록과 위치정보 확인 Naming Server(Eureka)
  • Load Distribution(Load Balancing)
    • Ribbon (Client Side)
    • Spring Cloud Gateway(권장)
  • Easier REST Clients
    • 각각의 서비스가 데이터를 주고 받는 방법(RestTemplate , Feign Client)
  • Visibility and monitoring
    • ELK 등등 (로그 추적 및 모니터링)
  • Fault Tolerance
    • Hystrix
      • 서비스의 지연과 장애에 대해 내성을 갖게 해주는 라이브러리

Spring Cloud Netflix Eureka

  • Service Discovery
    • 외부에서 다른 서비스들이 마이크로 서비스를 검색하기 위해 사용되는 일종의 전화번호책이다.
  • API gateway에다가 요청을 보내고 요청 정보가 서비스 디스커버리에 전달되어 필요한 서비스의 위치를 알려준다.

 

  • Eureka 서버 구동 확인 및 application.yml 

server:
  port: 8761

spring:
  application:
    name: discoveryservice

eureka:
  # 이 설정의 default가 true -> 자기 자신을 전화번호부에 등록하는 것은 의미 없는 행위
  client:
    register-with-eureka: false
    fetch-registry: false
  • Eureka 서버에 등록된 userService 구동 확인 및 application.yml 

server:
  port: 9001

spring:
  application:
    name: user-service

eureka:
  client:
    register-with-eureka: true
    # Eureka 서버로 부터 인스턴스들의 정보를 주기적으로 가져올 것인지
    # 설정하는 속성 -> fetch-registry = true 갱신 된 정보를 받겠다는 설정
    fetch-registry: true
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka

같은 서비스를 하나 더 띄워보자

  • application.yml 파일 내부의 port설정을 동적으로 추가해주는 코드를 넣고
  • edit configurations에서 환경변수를 추가해주어야 한다
    • 그렇지 않으면 포트번호가 겹치게 되어 실행 오류가 남
server:
  port: ${PORT}

spring:
  application:
    name: user-service

eureka:
  client:
    register-with-eureka: true
    # Eureka 서버로 부터 인스턴스들의 정보를 주기적으로 가져올 것인지
    # 설정하는 속성 -> fetch-registry = true 갱신 된 정보를 받겠다는 설정
    fetch-registry: true
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka

 

정상작동 확인

랜덤 포트를 이용해보자

  • port값에 0을 넣고 실행시키면 랜덤으로 포트가 지정이 된다.
  • 두 서비스를 돌리고 유레카 서버를 확인해 보니 하나의 서버만 떠있는 상황!
  • 해당 설정을 추가해주고 돌려주면 해결됨!

server:
  # 0의 의미 랜덤 포트를 사용하겠다는 의미! 포트 충돌을 의식하지 않아도 되는 장점이 있다.
  port: 0

spring:
  application:
    name: user-service

eureka:
  ############해당 설정 ###############
  instance:
    instance-id: ${spring.cloud.client.hostname}:${spring.application.instance_id:${random.value}}
  ############해당 설정 ###############
 client:
    register-with-eureka: true
    # Eureka 서버로 부터 인스턴스들의 정보를 주기적으로 가져올 것인지
    # 설정하는 속성 -> fetch-registry = true 갱신 된 정보를 받겠다는 설정
    fetch-registry: true
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka
  • 이러한 기능들을 사용하여 로드밸런싱 등을 쉽게 할 수 있다.

'DevOps > MSA' 카테고리의 다른 글

Apache kafka  (0) 2023.05.24
Spring Cloud Gateway - Load Balancer  (0) 2023.05.15
Spring Cloud Gateway - Filter 적용  (0) 2023.05.14
Spring Cloud Gateway  (0) 2023.05.14

Composition

기존 클래스가 새로운 클래스의 구성요소가 되는것

상속

장점

  • 코드의 재사용을 통해서 중복을 줄인다.
  • 확장성이 증가한다.
  • 클래스 간의 계층적 관계를 구성함으로써 다형성을 구현할 수 있다.
  • 개발 시간이 단축된다.

단점

  • 캡슐화를 깨뜨린다.
    • 상위 클래스의 구현이 하위 클래스에게 노출되기 때문에 캡슐화가 깨진다.
    • 따라서 자식 클래스가 부모 클래스에 강하게 결합 및 의존하게 된다.
    • 부모 클래스의 내부 구현이 달라지면 코드 한줄 건드리지 않은 하위 클래스가 오작동할 수 있다.
    • 부모클래스의 결함이 자식 클래스에게 넘어온다.

컴포지션

  • 상속처럼 기존의 클래스를 확장하는 것이 아닌, 새로운 클래스를 생성하여 private 필드로 기존 클래스의 인스턴스를 참조하는 방식이 바로 컴포지션이다.
    • forwarding이라고도 부른다.
  • 새로운 클래스이기 때문에, 여기서 어떤한 생성 작업이 일어나더라도 기존의 클래스는 전혀 영향을 받지 않는다는 점이 핵심이다.
  • 메서드를 호출하는 방식으로 동작하기 때문에 캡슐화를 깨뜨리지 않는다.
  • 상위클래스에 의존하지 않기 때문에 변화에 유연하다.

일반적으로 상속은 Is -a 관계에 있다고 표현한다.

조합도 이와 비슷하게 Has -a 관계에 있다.

그렇다면 상속은 사용하면 안될까?

답은 아니다. 쓰는 경우가 따로 명확히 존재한다는 뜻!

상속을 써야하는 경우

  • 명확한 Is -a 관계의 경우
  • 상위 클래스가 확장할 목적으로 설계되었고 문서화도 잘되어 있는 경우

'Computer science > JAVA' 카테고리의 다른 글

Interned String in Java  (0) 2023.03.22
Error & Exception  (0) 2023.03.16
GC  (0) 2023.03.16
JVM  (0) 2023.03.16
Instrinsic Lock  (0) 2023.01.08

Interned String in Java

자바의 문자열은 불변이다. String의 함수를 호출을 하면 해당 객체를 직접 수정하는 것이 아니라, 함수의 결과로 해당 객체가 아닌 다른 객체를 반환한다. 그러나 항상 그런 것은 아니다.

  • 대문자로 문자열 생성 후 String의 toUpperCase()를 호출하면 내부 구현에서 lower case의 문자가 발견되지 않으면 기존의 객체를 반환한다.
  • 문자열변수.interen()을 하게되면
    • 해당 문자열과 동일한 값을 가진 문자열을 상수풀에서 찾고 있다면 해당 문자열을 바라보게 하고 없다면 새로 생성하여 반환합니다.
  • 언제 사용될까?
    • 만약 문자열을 == 비교연산으로 비교해야 한다면. intern() 메서드가 사용될 수 있다. 해당 메서드에서 상수풀을 찾아 같은 값을 가지고 있다면 해당 참조를 반환해주기 때문에 속도가 더 빠를 수 있지만 해당 문자열이 상수풀에 없을때는 equals보다 느릴 수 도 있다. 상황에 따라 잘 사용해야할 것 같다!

'Computer science > JAVA' 카테고리의 다른 글

Composition  (0) 2023.03.22
Error & Exception  (0) 2023.03.16
GC  (0) 2023.03.16
JVM  (0) 2023.03.16
Instrinsic Lock  (0) 2023.01.08

개요

도커에 이미지를 빌드하기 위해서 도커파일에 명세를 정의하고 빌드한다.

이미지 파일을 통해 패키징 시켜서 프로그램을 배포를 하게 되는데

도커는 기본적으로 하나의 서버에서만 동작한다.

추후 규모가 커지게되면 하나의 서버에서만 동작시킬 수 없고, 여러개의 서버를 묶어서 하나의 시스템인 것 처럼 사용하는데 그것을 클러스터라고 한다.

클러스터에 필요한 컨테이너를 배포를 하고 배포가 된 컨테이너들이 문제없이 실행될 수 있

도록 조절해주는데 모니터링도하고 문제가 생겼을때 다시 원래 상태로 회복시켜주는 기능도 한다.

이것들을 오케스트레이션 이라고한다.

쿠버네티스란?

  • 오픈소스
    • 필요하면 코드레벨까지 확인이 가능하다.
    • 동작하는 매커니즘, 답답한 부분을 low-level까지 확인할 수 있다!!
  • 컨테이너 관리
    • 컨테이너를 격리시켜주는 기술
      • namespace
        • 하나의 시스템에서 프로세스를 격리시킬 수 있는 가상화 기술
      • cgroup
        • CPU, 메모리, network bandwith, HD i/o 등 프로세스 그룹의 시스템 리소스 사용량을 관리한다. 어떤 애플리케이션의 리소스 사용량이 많다면 그 애플리케이션을 Cgroup에 넣어서 CPU와 메모리 사용 제한 가능.
  • 자동화
    • GitOps
      • git에 원하는 명세 파일을 가져다 놓으면 쿠버네티스에 추가적으로 설정되어 있는 엔진이 내가 선언한 부분을 원하는 상태를 맞춰주기 위해 자동으로 동작한다.
  • 선언적 구성
    • 내가 원하는 상태를 정의를 하면 쿠버네티스 클러스터는 정의한 상태를 맞추려고 내부적으로 수행한다.
    • 원하는 상태를 명령어를 치면서 구성하는 것이 아닌 명세를 파일로 정의 하고 파일을 api호출을 이용해서 실행을 하게되면 클러스터가 맞춰주는 작업을 한다.
  • 명령형 구성
    • 명령어를 통해서 작업이 가능

쿠버네티스 객체의 두 가지 요소

쿠버네티스는 크게 2가지 객체로 존재한다.

쿠버네티스 객체

  • 클러스터 상태를 나타내는 영구항목
    • 객체 사양
      • 사용자가 정의한 바람직한 상태
    • 객체 상태
      • 쿠버네티스가 나타내는 현재 상태

쿠버네티스 아키텍처

여러 개의 서버를 묶어 하나처럼 동작하는것 → 클러스터

https://kubernetes.io/ko/docs/concepts/overview/components/

  • 클러스터
    • 제어영역(컨트롤 플레인)
      • 노드(데이터 플레인)
      • 노드(데이터 플레인)
      • 노드(데이터 플레인)
  • 위와 같은 형태로 구성되어있다.
  • 노드에 문제가 생기면 떠 있는 노드 중 어딘가에 하나를 더 띄운다.
  • 노드 자체를 회복시키는 것은 쿠버네티스의 영역은 아니다!
    • 클라우드를 쓰는 이유 중 하나
      • 노드가 문제가 생겼을때 회복시키는 능력이 필요!
  • 컨트롤 플레인이 죽으면 끝이나기 때문에 2중화 3중화 같은 것들이 필요하다.
  • 사용자는 작업을 kubuctl을 통해서 컨트롤 플레인에게 api 호출만 해주면 된다. 실제 컨테이너가 동작하는 위치는 노드의 파드 단위로 배포되어 동작한다.

컨트롤 플레인

  • kube-APIserver
    • 클러스터의 모든 명령을 수락하는 진입점
  • etcd
    • 클러스터의 상태를 안정적으로 저장하는 역할(키밸류형태로 저장)
  • kube-scheduler
    • 파드를 노드에 예약하는 역할
    • 파드를 분산하고 어디에 배포하면 좋을지 정해주는 역할
  • kube-controller-manager
    • 상태를 지속적으로 모니터링하고 바람직한 상태를 달성하기 위해 변경을 시도
  • kube-cloud-manager
    • 클라우드 제공업체와 상호작용하는 컨트롤러를 관리

노드

  • kubelet
    • 컨테이너 런타임을 사용하여 파드를 시작
  • kube-proxy
    • 클러스터의 파드 간에 네트워크 연결을 유지

왜 파드 단위로 배포를 할까?

  • 애플리케이션이 동작을 하고 있고 동작하면서 어떤 작업을 했는지 로그 등이 남는다고 했을때 다른 컨테이너는 로그를 전송해서 수집하는 작업을 할 수 있다.
  • 마치 파드를 가상의 서버 같은 것이라고 생각을 하면 된다.!
  • 공유된 스토리지를 할 수 있고, 통신을 별도의 인터페이스 없이 할 수 있다.

GCP 실습해보기

create cluster

  • Autopilot
    • 클라우드측에서 데이터 플레인 마저 숨기게 된다.
    • 띄운 파드만큼만 과금이 된다.
  • Standard
    • 클라우드측에서 컨트롤 플레인에 대한 부분은 숨겨놓는다. 접근 불가!
    • 접근 가능한 곳은 데이터 플레인
    • 노드당 과금이 일어난다.
    • 노드들을 사용자가 관리한다.
    • 파드가 떠있건 말건 노드가 떠있으면 과금이된다.

실습을 위해 스탠다드 선택!

location type

  • Zonal
    • zone
      • 쿠버네티스 클러스터 내에서 물리적으로 분리된 영역을 의미. 예를 들어, 클라우드 제공 업체(GCP, AWS 등)에서 제공하는 가용 영역(Availability Zone)과 비슷한 개념입니다. 하나의 클러스터 내에서 여러 개의 Zone을 사용하여 고가용성(High Availability)을 보장할 수 있다.
  • Regional
    • 컨트롤 플레인이 삼중화가 된다.
    • Region
      • 클러스터를 호스팅하는 물리적인 지역을 의미. 예를 들어, GCP의 us-central1, asia-northeast1 등과 같은 지역 개념. 여러 개의 클러스터를 하나의 Region에서 운영할 수 있으며, 이는 지리적으로 분산된 애플리케이션을 구성하는 데 유용하다.

노드가 3개가 뜬 것을 확인할 수 있다! 컨트롤 플레인은 보이지 않고 사용자는 컨트롤 플레인의 apiServer에게 명령만 할 수 있다.

SDN

  • **SDN(Software Defined Network)**이란 소프트웨어를 통해 네트워크 리소스를 가상화하고 추상화하는 네트워크 인프라에 대한 접근 방식을 의미한다.
  • 조금 더 쉽게 설명하자면, 소프트웨어 애플리케이션과 API를 이용하여 네트워크를 프로그래밍하고, 중앙에서 전체 네트워크를 제어하고 관리하는 것이다.

Pod

배포 가능한 가장 작은 쿠버네티스 객체

  • 공유된 네트워킹, 공유된 스토리지를 사용한다.
  • 마치 하나의 pc같다. 컨테이너는 프로그램(엑셀, 등등) 필요한 프로그램이 논리적인 단위로 묶여있는 형태

객체는 yaml 파일에 정의

  • 컨테이너를 띄우기 위한 정보 → 이미지
  • 엔진엑스를 가지고 Pod를 띄우겠다!
  • 내부적으로 이름을 가지고 객체를 구분한다.
    • 똑같은 이름의 Pod를 띄울 수 없음!
    • Pod를 몇개의 중복을 갖도록 지정하는 것을 Deployment라고한다.

파드 및 컨트롤러 객체

  • 컨트롤러 객체 유형
    • Deployment
      • 파드를 복제시켜 배포하는 형태
    • StatefulSet
      • 애플리케이션의 상태정보를 저장하기 위함
    • DaemonSet
      • 각 노드마다 하나씩 띄우고자 할때
    • Job

Deployment를 통해 Pod 집합이 실행되는지 관리

  • kind가 Deployment로 바꼈고, 배포되는 Pod는 3개로 맞춰달라는 명세이다.
  • 쿠버네티스는 숫자를 바꾸지 않는 한 3개로 Pod를 유지한다.
  • 유연한 scale-out 을 위해 등장한 객체가 Deployment이다.
  • 실제로 내부적으로 복제본을 유지하는 것은 ReplicaSet이다.

Error & Exception

Error 와 Exception의 차이

  • Error
    • 시스템이 종료되어야 할 수준과 같이 수습할 수 없는 심각한 문제를 의미한다.
  • Exception
    • 개발자가 구현한 로직에서 발생한 실수나 사용자의 영향에 의해 발생한다.

Exception Handling

  • 잘못된 하나로 인해 전체 시스템이 무너지는 결과를 방지하기 위한 기술적인 처리이다.
  • 예외가 발생하는 주요 원인
    • 사용자의 잘못된 데이터 입력
    • 잘못된 연산
    • 개발자가 로직을 잘못 작성
    • 하드웨어, 네트워크 오작동
    • 시스템 과부하
  • JAVA에서 모든 예외가 발생하면 xxxException 객체를 생성한다. 예외를 처리하는 방법에는 크게 2가지가 있다.
    • 직접 try catch를 이용해서 예외에 대한 최종적인 책임을 지고 처리하는 방식
    • throws Exception을 이용해서 발생한 예외를 호출하는 쪽이 책임지는 방식

Throwable 클래스

https://gyoogle.dev/blog/computer-language/Java/Error%20&%20Exception.html

  • Throwable 클래스는 예외처리를 할 수 있는 최상위 클래스이다. Exception과 Error는 Throwable의 상속을 받는다.

Error

https://gyoogle.dev/blog/computer-language/Java/Error%20&%20Exception.html

  • 시스템 레벨에서 발생하여, 개발자가 어떻게 조취할 수 없는 수준을 의미
    • OutOfMemory : JVM에서 설정된 메모리의 한계를 벗어난 상황일 때 발생. 힙 사이즈가 부족하거나 너무 많은 class를 로드할때, 가용가능한 swap이 없을때 큰 메모리의 native 메서드가 호출될때 등이 있다. 이를 해결하기 위해 dump파일분석, jvm 옵션 수정 등이 있다.

Exception

https://gyoogle.dev/blog/computer-language/Java/Error%20&%20Exception.html

  • 예외는 개발자가 구현한 로직에서 발생하며 개발자가 다른 방식으로 처리가능한 것들로 JVM은 정상 동작한다.

Exception의 2가지 종류

  • Checked Exception
    • 예외처리가 필수이며, 처리하지 않으면 컴파일되지 않는다. JVM 외부와 통신할 때 주로 쓰인다.
      • RuntimeException 이외에 있는 모든 예외
      • IOException, SQLException 등
  • Unchecked Exception
    • 컴파일 때 체크되지 않고, Runtime에 발생하는 Exception을 말한다.
      • RuntimeException 하위의 모든 예외
      • NullPointerException, IndexOutOfBoundException 등

Throw 구문

  • 예외처리를 할때 현재 메서드가 직접 처리하지 않고 호출한 곳에 발생 여부를 통보한다. 호출한 메서드는 이걸 다시 throw할지 처리할지 정해야 한다. return 보다 강력

try-with-resource

  • try()에 자원 객체를 선언해 사용하면, try 블록이 끝날 때 해당 자원을 자동으로 종료 해준다. 다만 AutoCloseable 인터페이스가 구현된 객체여야 사용 가능하다.

'Computer science > JAVA' 카테고리의 다른 글

Composition  (0) 2023.03.22
Interned String in Java  (0) 2023.03.22
GC  (0) 2023.03.16
JVM  (0) 2023.03.16
Instrinsic Lock  (0) 2023.01.08

Garbage Collection

  • 사용하지 않는 객체는 메모리에서 삭제하는 작업을 GC라고 부르며 JVM에서 GC를 수행한다.

stop-the-world

  • GC를 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것.
  • 어떤 GC 알고리즘을 사용하더라도 stop-the-world는 발생하게 된다.
    • 이 시간을 줄이는 것이 바로 GC튜닝!!
  • GC를 해도 더이상 사용 가능한 메모리 영역이 없는데 계속 메모리를 할당하려고 하면
    • OutOfMemoryError가 발생하여 WAS가 다운될 수 도 있다.
  • 따라서 규모있는 JAVA 어플리케이션을 효율적으로 개발하기 위해선 GC에 대해서 잘 알고 있어야 한다.

GC의 대상

  1. 객체가 NULL인 경우
  2. 블럭 실행 종료 후, 블럭 안에서 생성된 객체
  3. 부모 객체가 NULL인 경우, 포함하는 자식 객체

GC의 메모리 해제 과정(Mark and Sweap)

  1. Marking
    • 프로세스는 마킹을 호출 → GC가 메모리가 사용되는지 아닌지를 찾아낸다.
    • 모든 오브젝트는 마킹단계에서 결정을 위해 스캔되어 진다. 모든 오브젝트를 스캔하기 때문에 매우 많은 시간이 소모된다.
  2. Normal Deletion
    • 참조되지 않는 객체를 제거하고, 메모리를 반환한다. 메모리 Allocator는 반환되어 비어진 블럭의 참조 위치를 저장해 두었다가 새로운 오브젝트가 선언되면 할당되도록 한다.
  3. Compacting
    • 퍼포먼스를 향상시키기 위해, 참조되지 않는 객체를 제거하고 또한 남은 참조되어지는 객체들을 묶는다. 이들을 묶음으로서 공간이 생기므로 새로운 메모리 할당 시에 더 쉽고 빠르게 진행할 수 있다.

Generational Garbage Collection 배경

  • 위의 과정과 같이 모든 객체를 Mark&Compact 하는 JVM은 비효율 적이다.
  • Y축은 할당된 바이트 수, X축은 바이트가 할당될 때의 시간. 시간이 갈수록 적은 객체만이 남는 다는 것을 볼 수 있고, 위와같은 그래프에 기반한 것이 Weak Generational Hypothesis이다.

Weak Generational Hypothesis

  • 신규로 생성한 객체의 대부분은 금방 사용하지 않는 상태가 되고, 오래된 객체에서 신규 객체로의 참조는 매우 적게 존재한다는 가설이다.
  • 이 가설에 기반하여 자바는 Young 영역과 Old 영역으로 메모리를 분할하고, 신규로 생성되는 객체는 Young영역에 보관하고, 오랫동안 살아남은 객체는 Old 영역에 보관한다.

Generational Garbage Collection

  1. Young 영역
    • 새롭게 생성한 객체의 대부분이 이곳에 위치한다.
    • 대부분의 객체가 금방 접근 불가능 상태가 되기 때문에 많은 객체가 Young영역에 생성되고 사라진다. 이 영역에서 객체가 사라질때 Minor GC가 발생한다고 말한다.
  2. Old 영역
    • 접근 불가능 상태로 되지 않아 Young 영역에서 살아늠은 객체가 이곳으로 복사된다. 대부분의 Young 영역보다 크게 할당하며, 크기가 큰 만큼 Young 영역보다 GC는 적게 발생한다. 이 영역에서 객체가 사라질때 Major GC가 발생한다고 말한다.
  3. Permanent 영역
    • Method Area라고도 한다. JVM이 클래스들과 메서드들을 설명하기 위해 필요한 메타데이터들을 포함하고 있다. JDK8부터는 PermGen 은 Metaspace로 교체

'Computer science > JAVA' 카테고리의 다른 글

Interned String in Java  (0) 2023.03.22
Error & Exception  (0) 2023.03.16
JVM  (0) 2023.03.16
Instrinsic Lock  (0) 2023.01.08
Thread  (1) 2023.01.02

JVM

JVM 이란?

  • 시스템 메모리를 관리하면서 자바 기반 애플리케이션을 위해 이식 가능한 실행 환경을 제공한다. 한마디로 JVM은 자바 애플리케이션을 실행하는 프로그램이다. 이를 통해 자바는 OS 에 종속되지 않고, JVM이 존재한다면 실행 가능하다. Stack 기반의 가상 머신이다.

실행과정

  1. 프로그램이 실행되면 JVM은 OS로부터 이 프로그램이 필요로하는 메모리를 할당받는다. JVM은 이 메모리를 용도에 따라 여러 영역으로 나누어 관리한다.
  2. 자바 컴파일러가 자바 소스코드를 읽고 바이트 코드로 변환한다.
  3. 변환된 바이트 코드를 클래스 로더를 통해 JVM 메모리 영역으로 로딩된다.
  4. 로딩된 class 파일들은 Excution Engine을 통해 해석되고
  5. 해석된 바이트 코드는 메모리 영역에 배치되어 실질적인 수행이 이뤄진다. 이러한 과정 속 JVM은 필요에 따라 스레드 동기화난 가비지 컬렉션 같은 메모리 관리 작업을 수행한다.

JVM 런타임 데이터 영역

모든 스레드가 공유하는 영역

  • 메서드(스태틱) 영역
    • 크게 두가지 영역으로 나뉠 수 있음
      • 코드 세그먼트
        • 바이트코드로 변환되어 이 영역에 들어간다.
      • 데이터 세그먼트
        • static 데이터가 정의되는 영역
    • 메서드의 바이트코드, static 변수가 할당된다.
  • 힙 영역
    • 배열과 모든 인스턴스 객체가 할당되는 영역으로 자동 초기화가 진행된다.

스레드마다 가지는 영역

  • 스택 영역
    • 지역변수, 매개변수가 할당되는 영역으로 초기화가 진행되지 않는다.
  • PC Resisters
    • 현재 실행되고 있는 주소를 저장하고 있는 영역
  • Native Method Stacks
    • c나 c++로 작성된 메서드를 실행할 때 사용되는 스택

  • 스레드는 하나의 heap, method Area를 공유하고있다.
    그러나 하나의 스레드는 다른 스레드의 내부 데이터에 접근 할 수 없다.
    우리가 하나의 메서드 안에서 지역변수의 동시성 문제를 걱정하지 않아도 되는 이유가 바로 이것!

'Computer science > JAVA' 카테고리의 다른 글

Error & Exception  (0) 2023.03.16
GC  (0) 2023.03.16
Instrinsic Lock  (0) 2023.01.08
Thread  (1) 2023.01.02
Casting  (0) 2023.01.01

Intrinsic Lock

  • 자바는 멀티스레드 환경에서 동기화를 지원하기 위해 가장 기초적인 장치인 ’고유 락(Intrinsic Lock)’을 지원한다. 개발자는 synchronized 키워드를 이용해서 특정 객체의 고유락을 사용해 여러 스레드를 동기화 시킬 수 있다.
  • Synchronized 블록은 Intrinsic Lock을 이용해서, Thread의 접근을 제어한다.

Java의 synchronized

  • 동일한 객체에 대해 synchronized 블록을 사용하는 두 스레드는 한 번에 하나의 스레드만 내부로 들어갈 수 있고, 이 것이 자바가 제공하는 가장 기본적인 ‘상호배제(Mutual Exclusion)’ 장치이다.
  • synchronized에는 4가지의 사용법이 있다.
    • synchronized method
    • synchronized block
    • static synchronized method
    • static synchronized block
      • 4가지 방식의 차이인 lock이 적용되는 범위를 알아보자

1. synchronized method

  • synchronized method는 클래스의 인스턴스에 대하여 lock을 건다.
  • 하나의 인스턴스에 대하여 2개의 thread가 경합하는 상황
public class Main {
    public static void main(String[] args) {
        A a = new A();
        Thread thread1 = new Thread(()->{
            a.run("t1");
        });
        Thread thread2 = new Thread(()->{
            a.run("t2");
        });
        thread1.start();
        thread2.start();
    }
}

public class A {
    public synchronized void run(String name){
        System.out.println(name + "lock");
        try{
            Thread.sleep(1000);
        } catch(InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(name + "unlock");
    }
}
  • 순서대로 lock을 획득하고 반납함
    • 실행 결과
      • t1lock t1unlock t2lock t2unlock
  • 각각의 인스턴스를 만들고 실행 해보자
public class Main {
    public static void main(String[] args) {
        A a = new A();
        A a1 = new A();
        Thread thread1 = new Thread(()->{
            a.run("t1");
        });
        Thread thread2 = new Thread(()->{
            a1.run("t2");
        });
        thread1.start();
        thread2.start();
    }
}
  • lock을 공유하지 않기 때문에 동기화가 발생하지 않음
    • 실행 결과
      • t1lock t2lock t1unlock t2unlock
  • 결과
    • synchronized method는 인스턴스에 대하여 lock을 건다.
    • 인스턴스에 대해서 lock을 건다라는 표현이 인스턴스 접근 자체가 lock이 걸리는 걸까?? 확인해보자
public class Main {
    public static void main(String[] args) throws InterruptedException {
        A a = new A();
        Thread thread1 = new Thread(()->{
            a.run("t1");
        });
        Thread thread2 = new Thread(()->{
            a.print("t2");
        });
        thread1.start();
        Thread.sleep(500);
        thread2.start();
    }
}

public class A {
    public void print(String name){
        System.out.println(name + " hi");
    }
    public synchronized void run(String name){
        System.out.println(name + "lock");
        try{
            Thread.sleep(1000);
        } catch(InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(name + "unlock");
    }
}
  • synchronized가 적용되지 않은 print() 메서드를 추가하고 lock이 걸린 중간에 호출 해보았더니 run() 메서드의 lock과 상관없이 정상적으로 호출됨
    • 실행 결과
      • t1lock t2 hi t1unlock
  • print() 메서드도 sychronized가 적용되어있다면?
    • 동기화 발생
    • 실행 결과
      • t1lock t1unlock t2 hi
  • 결과
    • synchronized 메서드는 인스턴스 단위로 lock을 건다.
    • 인스턴스에 lock을 거는 synchronized 키워드는 synchronized가 적용된 메서드끼리 일괄적으로 lock을 공유한다.

2. static synchronized method

  • static이 포함된 synchronized method방식은 우리가 일반적으로 생각하는 static 성질을 갖는다. 인스턴스가 아닌 클래스 단위로 lock이 발생한다.
public class Main {
    public static void main(String[] args) throws InterruptedException {
        A a1 = new A();
        A a2 = new A();
        Thread thread1 = new Thread(()->{
            a1.run("t1");
        });
        Thread thread2 = new Thread(()->{
            a2.run("t2");
        });
        thread1.start();
        thread2.start();
    }
}
public class A {
    public static synchronized void run(String name){
        System.out.println(name + "lock");
        try{
            Thread.sleep(1000);
        } catch(InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(name + "unlock");
    }
}
  • 실행 결과
    • t1lock t1unlock t2lock t2unlock
  • 다른 인스턴스 이지만 클래스 단위로 lock이 발생했다.
  • 만약 static synchronized method와 synchronized method가 섞여있다면 어떨까?
public class Main {
    public static void main(String[] args) throws InterruptedException {
        A a1 = new A();
        A a2 = new A();
        Thread thread1 = new Thread(()->{
            a1.run("t1");
        });
        Thread thread2 = new Thread(()->{
            a2.print("t2");
        });
        thread1.start();
        thread2.start();
    }
}
public class A {
    public synchronized void print(String name){
        System.out.println(name + " hi");
    }
    public static synchronized void run(String name){
        System.out.println(name + "lock");
        try{
            Thread.sleep(1000);
        } catch(InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(name + "unlock");
    }
}
  • 실행 결과
    • t1lock t2 hi t1unlock
  • 인스턴스 단위의 lock과 클래스 단위의 lock은 공유되지 않았다.
  • static synchronized method를 정리해보면
    • 클래스 단위로 lock을 걸지만
    • 인스턴스 단위의 synchronized method와 lock을 공유하지 않는다.

3. synchronized block

  • synchronized block은 인스턴스의 block단위로 lock을 건다. 이 때, lock 객체를 지정해줘야 한다.
public class Main {
    public static void main(String[] args) throws InterruptedException {
        A a1 = new A();
        Thread thread1 = new Thread(()->{
            a1.run("t1");
        });
        Thread thread2 = new Thread(()->{
            a1.run("t2");
        });
        thread1.start();
        thread2.start();
    }
}
public class A {
    public void run(String name) {
        synchronized (this) {
            System.out.println(name + "lock");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + "unlock");
        }
    }
}
  • 실행 결과
    • t1lock t1unlock t2lock t2unlock
  • block의 인자로 this를 주었다.
    • this는 해당 인스턴스를 의미하고 위 코드에서 method 전체가 block으로 감싸져 있으므로 메서드 선언부에 synchronized 키워드를 붙인 것과 똑같이 동작한다.
    • 하지만 여러 로직이 섞여 있는 사이 부분만 lock을 걸 수 있다. lock은 synchronized block에 진입할 때 획득하고 빠져나오면서 반납하므로 block으로 범위를 지정하는 것이 효율적이다.
    • synchronized block도 method와 동일하게 인스턴스에 대해서 적용된다.
  • 지금까지는 block에 인자로 this를 사용해서 Synchronized를 메서드 선언부에 붙인 것과 별반 다를 것 없이 사용함
    • 이제는 block에 자원을 명시하고 사용
public class Main {
    public static void main(String[] args) throws InterruptedException {
        A a = new A();
        Thread thread1 = new Thread(() -> {
            a.run("thread1");
        });

        Thread thread2 = new Thread(() -> {
            a.run("thread2");
        });

        Thread thread3 = new Thread(() -> {
            a.print("자원 B와 상관 없는 thread3");
        });

        thread1.start();
        thread2.start();
        thread3.start();
    }
}
public class A {
    B b = new B();
    public void run(String name) {
        synchronized (b){
            System.out.println(name + " lock");
            b.run();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + " unlock");
        }
    }

    public synchronized void print(String name) {
        System.out.println(name + " hello");
    }
}
public class B extends Thread{
    @Override
    public synchronized void run() {
        System.out.println("B lock");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("B unlock");
    }
}
  • 실행 결과
    • thread1 lock 자원 B와 상관 없는 thread3 hello B lock B unlock thread1 unlock thread2 lock B lock B unlock thread2 unlock
  • 매우 복잡한 상황이다. lock 객체를 A클래스가 아닌 B클래스의 인스턴스로 사용하고 있다.
  • thread1, thread2는 b를 사용하는 method를 호출하고 있지만
  • thread3은 b와 상관없는 method이기 때문에 b의 lock과 상관없이 출력되었다.
  • 즉 인스턴스 단위 lock과 B를 block한 lock은 공유되지 않고 별도로 관리되는 것을 확인할 수 있다.
  • 이번에는 block에 인스턴스가 아니라 class를 명시해보았다.
public class Main {
    public static void main(String[] args) {
        A a = new A();
        Thread thread1 = new Thread(() -> {
            a.run("thread1");
        });

        Thread thread2 = new Thread(() -> {
            a.run("thread2");
        });

        thread1.start();
        thread2.start();
    }
}
public class A {
    B b = new B();

    public void run(String name) {
        synchronized (B.class){
            System.out.println(name + " lock");
            b.run();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + " unlock");
        }
    }

    public synchronized void print(String name) {
        System.out.println(name + " hello");
    }
}
public class B extends Thread{
    @Override
    public synchronized void run() {
        System.out.println("B lock");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("B unlock");
    }
}
  • 실행 결과
    • thread1 lock B lock B unlock thread1 unlock thread2 lock B lock B unlock thread2 unlock
  • block의 인자로 인스턴스가 아닌 .class를 주었다.
  • 출력 결과를 보면 lock을 공유하고 있는 것을 확인할 수 있다.
  • block에는 객체를 넘기면 인스턴스 단위로 lock을 걸고, .class형식으로 넘기면 클래스 단위의 lock을 건다.

4. static synchronized block

  • static method안에 synchronized block을 지정할 수 있다. static의 특성 상 this같이 현재 객체를 가르키는 표현을 사용할 수 없다. static synchronized method 방식과 차이는 lock 객체를 지정하고 block으로 범위를 한정지을 수 있다는 점이다. 이외에 클래스 단위로 lock을 공유한다는 점은 같다.
public class Main {

    public static void main(String[] args) {
        A a1 = new A();
        A a2 = new A();
        Thread thread1 = new Thread(() -> {
            a1.run("thread1");
        });

        Thread thread2 = new Thread(() -> {
            a2.run("thread2");
        });

        thread1.start();
        thread2.start();
    }
}
public class A {

    public static void run(String name) {
        synchronized (A.class){
            System.out.println(name + " lock");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + " unlock");
        }
    }
}
  • 실행 결과
    • thread1 lock thread1 unlock thread2 lock thread2 unlock

동기화 순서

  • synchronized를 통해 lock을 물고 있을 때 여러 개의 스레드가 접근 요청을 한다고 가정하면
  • 첫 진입 이후에 동기화 순서가 보장되지는 않는다.

정리

  • synchronized method
    • 인스턴스 단위로 lock이 걸림
    • 메서드의 시작→종료 까지 동기화 발생
    • 동일한 인스턴스 내 synchronized 키워드가 적용된 메서드끼리 lock을 공유
  • synchronized block
    • 인스턴스 단위로 lock이 걸림
    • this를 명시하면 synchronized method와 동일하게 동작하면서 synchronized method와 lock을 공유
    • 특정 객체를 명시하면 해당 객체에만 특정 lock을 걸면서 해당 객체에 lock을 거는 block끼리만 lock을 공유
    • .class 형식 명시하면 해당 클래스에만 특정 lock을 걸면서 해당 클래스에 lock을 거는 block끼리만 lock을 공유
  • static synchronized method
    • 클래스 단위로 lock이 걸림
    • 메서드의 시작→종료 까지 동기화 발생
    • static synchronized와 synchronized가 혼용되어있을 때 각자의 lock으로 관리
  • static synchronized block
    • 클래스 단위 lock로 lock이 걸림.
    • block의 인자로 정적 인스턴스나 클래스만 사용
  • synchornized는 Thread의 동기화 순서를 보장하지 않는다.

Atomic

  • 래퍼 클래스의 일종으로 참조 자료형과 기본 자료형 두 종류 변수에 모두 적용 가능
  • 사용시 내부적으로 CAS 알고리즘을 사용해 lock 없이 동기화 처리가 가능

 

https://beststar-1.tistory.com/21#synchronized%EC%99%80_Lock%EC%9D%98_%EC%B0%A8%EC%9D%B4%EC%A0%90_-_%EA%B3%B5%EC%A0%95%EC%84%B1(Fairness)

  • 멀티 스레드 환경, 멀티 코어 환경에서 각 cpu는 메인 메모리에서 변수값을 참조하는 게 아닌 각 cpu의 캐시 영역에서 메모리 값을 참조하게 된다.
  • 이때, 메모리에 저장된 값과 cpu 캐시에 저장된 값이 다른 경우가 존재
    • 이를 가시성 문제라고 한다.
      • 그래서 사용되는 것이 CAS 알고리즘
  • 현재 스레드에서 저장된 값과 메인 메모리에 저장된 값을 비교하여 일치하는 경우 새로운 값으로 교체, 일치하지 않는다면 실패하고 재시도를 한다. 이렇게 처리되면 cpu 캐시에서 잘못된 값을 참조하는 가시성 문제가 해결된다.

Volatile

  • 자바 변수를 메인 메모리에 저장하겠다고 명시하는 키워드이다.
  • 매번 변수의 값을 읽을 때 마다 CPU 캐시에 저장된 값이 아니라 메인 메모리에서 읽는 것이며, 또한 변수의 값을 쓸 때 마다 메인 메모리에 작성하는 것이다.
    • 이 또한 가시성 문제를 해결하는 방법으로 볼 수 있다.

락(lock)

  • 모든 객체에는 lock이 하나씩 있는데 이 lock을 가지고 있는 스레드만 해당 객체의 임계 영역 코드와 관련된 작업을 할 수 있다.
  • 그렇지만 여러 스레드가 경쟁 상태에 있을 때 스레드가 진입권한을 획득할지 순서를 보장하진 않는다. 이를 암시적(Implicit)락 이라고 한다.(Intrinsic Lock) 이라고도 부른다.
  • Lock 클래스는 lock() 메서드와 unlock() 메서드를 호출함으로써 어떤 스레드가 먼저 락을 획득하게 될지 순서를 지정할 수 있다. 이를 명시적(explicit)락 이라고 한다.( Reentrant Lock)

💡 경쟁 상태

  • 공유하는 자원이 있는데 공유하는 자원에 접근하는 여러 스레드 중 어떤 것이 먼저 접근하냐에 따라 결과가 달라질 수 있는 경우가 있다. 이를 경쟁상태에 의해 발생되었다고 하기도 한다.

synchronized와 Lock의 차이점 - 공정성(Fairness)

  • synchronized와 Lock을 구분 짓는 키워드는 공정성(Fairness)이다.
  • 공정성이란 모든 스레드가 자신의 작업을 수행할 기회를 공평하게 갖는 것을 의미한다.
  • 공정한 방법에선 큐 안에서 스레드들이 무조건 순서를 지켜가며 락을 확보한다.
  • 불공정한 방법에선 만약 특정 스레드에 락이 필요한 순간 release가 발생하면 대기열을 건너뛰는 새치기 같은 일이 벌어지게 된다.

💡 기아 상태

  • 다른 스레드들에게 우선순위가 밀려 자원을 계속해서 할당받지 못하는 스레드가 존재하는 상황을 말하며, 이 기아 상태를 해결하기 위해 공정성이 필요하다.
  • synchronized는 공정성을 지원하지 않아서 후순위인 스레드의 실행이 안될 수 있는 반면에
  • ReentrantLock은 생성자의 boolean 인자를 통해 공정/불공정을 설정할 수 있다.

ReentrantLock

  • 가장 일반적인 락이며 재진입이 가능한 락이다.
  • 'Reentrant(재진입할 수 있는)'이라는 단어가 앞에 붙은 이유는
  • 특정 조건에서 락을 풀고 나중에 다시 락을 얻고 임계 영역으로 들어와서 작업을 수행할 수 있기 때문이다.

ReentrantReadWriteLock

  • 읽기에는 공유적이고, 쓰기에는 배타적인 락이다.
  • ReentrantReadWriteLock은 이름에서 알 수 있듯 읽기를 위한 락과 쓰기를 위한 락을 제공한다.
  • ReentrantLock은 배타적인 락이라서 무조건 락이 있어야만 임계 영역의 코드를 수행할 수 있으나,
  • ReentrantReadWriteLock은 읽기 락이 걸려있으면 다른 스레드가 읽기 락을 중복해서 걸고 읽기를 수행할 수 있다.
  • 읽기는 내용을 변경하지 않음으로 동시에 여러 스레드가 읽어도 문제 되지 않는다.
    • 그래서 읽기 락이 걸린 상태에서 쓰기 락은 허용되지 않는다.

StampedLock

  • ReentrantReadWriteLock에 '낙관적 읽기 락(Optimistic Reading Lock)'을 추가한 것이다.
  • StampedLock은 Java 8부터 추가되었으며, 다른 락과 달리 Lock 클래스를 구현하지 않았다.
  • 일반적으론 읽기 락이 걸려있으면 쓰기 락을 얻기 위해서는 읽기 락이 풀릴 때까지 기다려야 하는데,
  • 낙관적 읽기 락은 쓰기 락에 의해 바로 풀린다.
    • 그래서 낙관적 읽기에 실패하면 읽기 락을 얻어서 다시 읽어와야 한다.
  • 무조건 읽기 락을 걸지 않고 쓰기와 읽기가 충돌할 때만 쓰기가 끝난 후에 읽기 락을 거는 것이다.
  • StampedLock은 락을 걸거나 해지할 때 '스탬프(long 타입의 정수 값)'를 사용한다.
  • 스탬프에 해당하는 값이 둘다 long형의 숫자고 사전 연산에서 받아놓은 값이 이후 연산에서 비교 해봤을 때 변경되었다면 이후 연산은 실패가 된다.

'Computer science > JAVA' 카테고리의 다른 글

GC  (0) 2023.03.16
JVM  (0) 2023.03.16
Thread  (1) 2023.01.02
Casting  (0) 2023.01.01
Object Class  (0) 2023.01.01

+ Recent posts