728x90
반응형
728x90
반응형
반응형

springframework에서 제공하는 spring retry에 대해 알아보자

maven repo: https://mvnrepository.com/artifact/org.springframework.retry/spring-retry

 

spring aspect 라이브러리를 사용할 수도 있는데, 해당 라이브러리는 springboot starter에 있을 수 있기에 한번 확인하고 넣는 게 좋다.

springboot2.7.3 기준 5.3.24 버전이 들어가 있다.

 

관련 어노테이션을 사용하려면 아래처럼 enable 시켜줘야 한다.

import org.springframework.context.annotation.Configuration;
import org.springframework.retry.annotation.EnableRetry;

@Configuration
@EnableRetry
public class AppConfig {
}

실행 원리는 proxy!!

 

재시도를 해야 하는 함수에 @Retryable을 달아주고

재시도 시 실행해야하는 함수에 @Recover를 달아준다.

import org.springframework.retry.annotation.Recover;
import org.springframework.stereotype.Service;

@Service
public class MyService {

    @Retryable(value = { SomeTransientException.class }, maxAttempts = 3)
    public void performTask() throws SomeTransientException {
        // Your logic here
        System.out.println("Trying to perform task...");
        throw new SomeTransientException("Temporary failure");
    }

    @Recover
    public void recover(SomeTransientException e) {
        // Recovery logic here
        System.out.println("Recovering from failure: " + e.getMessage());
    }
}

api에러 뿐만 아니라 아래와 같이 디비 에러에도 가능. IO에러 등등 많이 커버한다.. @Retryable에서 throw 하는 에러 타입과 @Recover에서 받는 에러타입이 반드시 같아야 한다.

@Service
public interface MyService { 

    @Retryable(retryFor = SQLException.class)
    void retryServiceWithRecovery(String sql) throws SQLException; 

    @Recover
    void recover(SQLException e, String sql); 
}

Retryable 어노테이션에는 어떤 exception이 발생할 때 재시도를 할지와 최대 몇 번 실행할지를 지정해 줄 수 있다.

  • maxAttempts: 실패가 n번 나면 recover 함수를 실행한다.
    • default: 3
  • backoff: 재실행하고 n초 쉬었다가 재실행(단위: ms)
    • default:  a fixed delay of 1000ms
  • multiplier: 재시도와 재시도 사이의 시간 간극이 n배로 점점 멀어짐
    • default: 0(무시)
  • BackOffPolicy: 아래처럼 설정 값들에 따라 재시도 간격이 달라진다.
    • With no explicit settings the default is a fixed delay of 1000ms
    • Only the delay() set: the backoff is a fixed delay with that value
    • When delay() and maxDelay() are set the backoff is uniformly distributed between the two values
    • With delay(), maxDelay() and multiplier() the backoff is exponentially growing up to the maximum value
    • If, in addition, the random() flag is set then the multiplier is chosen for each delay from a uniform distribution in [1, multiplier-1]

 

위 값들은 클래스에서 상수 값으로 관리할 수 있지만 공용사용과 환경별 관리를 위헤 프로퍼티 파일로 뺄 수도 있다.

이때 해당 변수 명이 달라지니(ex. maxAttemps -> maxAtempsExpression) 반드시 공식문서를 참고해야 한다!

https://docs.spring.io/spring-retry/docs/api/current/org/springframework/retry/annotation/Retryable.html

 

공통 설정 혹은 더 복잡한 설정을 위해 java config로 설정할 수도 있다.

@Configuration
@EnableRetry
public class RetryConfig {


    @Bean
    public RetryTemplate retryTemplate(){
        RetryTemplate retryTemplate = new RetryTemplate();

        FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
        fixedBackOffPolicy.setBackOffPeriod(3000l);
        
        retryTemplate.setBackOffPolicy(fixedBackOffPolicy);

        Map<Class<? extends Throwable>, Boolean> includeExceptions = new HashMap<>();
        includeExceptions.put(CannotAcquireLockException.class, true);

        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(5, includeExceptions);
        retryTemplate.setRetryPolicy(retryPolicy);

        return retryTemplate;
    }
    
}

 

참고로 delay는 간격은 랜덤으로도 줄 수 있다.

@Retryable(
    value = {CannotAcquireLockException.class},
    maxAttempts = 4,
    backoff = @Backoff(random = true, delay = 1000, maxDelay = 5000, multiplier = 2)

 


기본설명:

https://www.baeldung.com/spring-retry

좀더 복잡하게 사용하기:

https://medium.com/@AlexanderObregon/using-springs-retryable-annotation-for-automatic-retries-c1d197bc199f

https://medium.com/@vmoulds01/springboot-retry-random-backoff-136f41a3211a


이럴 수가.. 적으면서 이거 전에 본적 있는데 싶었는데 무려 정리를 했던 적이 있던 것ㅋㅋㅋㅋ

2022.02.04 - [개발/spring] - [retry] spring-retry for auto retry

 

[retry] spring-retry for auto retry

spring-retry? exception이 난 작업에 대해 자동으로 재시도 해주는 스프링 라이브러리이다. 일시적인 connection error 등에 유용하게 사용할 수 있다. 환경: springboot2.6.2 implementation 'org.springframework.retry:spri

bangpurin.tistory.com

 

728x90
반응형
반응형

2024.05.23 - [개발/spring] - [transaction] isolation level

 

[jpa] transaction isolation level

2024.05.22 - [개발/sql] - DB isolation level DB isolation levelisolation level 이란 무엇인가?디비 동시성을 관리하고 데이터 정합성을 유지하기 위해 서로 다른 트랜젝션끼리의 관계를 정의한 것 존재 이유?언

bangpurin.tistory.com

A transaction is a group of sequential operations on a database that forms a logical unit of working with data.

Transactions are used to maintain the consistency and integrity of data in a database.

 

 It’s important to know that transactions are thread-local, so they apply to the current thread only.

 

propagation: 세션의 트랜젝션을 어떻게 이용할지; 무결성과 정합성을 유지하기 위한 방법

종류 트랜젝션 존재 시 트랜젝션 미존재 시 비고
REQUIRED 기존 트랜잭션 이용 신규 트랜잭션 생성 기본값
SUPPORTS 기존 트랜잭션 이용  트랜잭션 없이 수행  
MANDATORY 기존 트랜잭션 이용 exception 발생 꼭 이전 트랜잭션이 있어야 하는  경우
NEVER exception 발생 정상적으로 트랜잭션 없이 수행 트랜잭션 없을 때만 작업이 진행되어야할 때
NOT_SUPPORTED 기존 트랜젝션은 중지하고 대기, 트랜젝션 없이 실행하다가 끝나면 기존 트랜젝션 실행 트랜잭션 없이 로직 수행 기존 트랜잭션에 영향을 주지 않아야할 때
REQUIRES_NEW 현재 트랜잭션은 중지되고 대기. 새로운 트랜잭션을 생성하고 종료되면 현재 트랜젝션이 다시 진행 신규 트랜잭션을 생성하고 로직을 실행 이전 트랜잭션과 구분하여 새로운 트랜잭션으로 처리가 필요할 때;
락과 함께 사용할 경우 데드락 조심
NESTED 현재 트랜잭션에 Save Point를 걸고 이후 트랜잭션을 수행 REQUIRED와 동일하게 작동
(신규 트랜잭션을 생성하고 로직이 수행)
DBMS특성에 따라 지원 혹은 미지원;
jpa에서 사용 불가
  • Nested is not possible in the JPA dialect because you cannot create a save point here. Nested, unlike Required New, creates a kind of save point. For example, if you are updating a huge batch of data, you won’t have to roll back everything in case of an error; you may roll back just to the save point.

 

두 함수간 트랜젝션을 전파하는 경우(출처: chat gpt..)

부모 함수 -> 자식 함수라고 가정할 때

트랜잭션 전파의 핵심은 부모 메서드에서 트랜잭션이 이미 시작되었는지 여부입니다. parentMethod()에서 트랜잭션이 시작되었다면, 그 안에서 호출되는 모든 자식 메서드(접근 제어자 public이든 private이든 상관없이)는 동일한 트랜잭션 경계 내에서 실행됩니다.

부모 자식 부모 transaction 전파 여부
public + @Transactional private in same class  O
public + @Transactional public in same class, @Transactional유무 상관없이 O
public + @Transactional public in different class, @Transactional유무 상관없이 O

1. Transaction은 public에서 시작, private은 함수의 일부라 판단하여 트랜젝션 이어짐

@Service
public class MyService {

  @Transactional
  public void parentMethod() {
    // Transaction starts here
    privateChildMethod(); // This method is part of the same transaction
  }

  private void privateChildMethod() {
    // This method participates in the transaction started by parentMethod
  }
}

2. 자식 함수가 같은 클래스에 있으면 부모 트랜젝션 전파됨

만약 반대로 부모가 @TRANSACTIONAL이 없고 같은 클래스의 자식에게 @TRANSACTIONAL이 있다면 자식의 트랜젝션은 신규로 생성되지 않음(프록시를 타지 않아서)

@Service
public class MyService {

  @Transactional
  public void parentMethod() {
    // Transaction starts here
    publicChildMethod(); // This call bypasses the proxy
  }

  public void publicChildMethod() {
    // This method does not participate in the transaction started by parentMethod
  }
}

3. 부모와 자식 클래스가 다를 경우, 자식이 기본 Transaction을 사용할 경우

클래스가 다를 경우 자식 함수가 proxy의 영향을 받기 때문에 부모의 트랜젝션이 자식에게 전파된다.

자식 함수가 @Transaction 어노테이션이 있건 없건 전파되는데, 만약 자식 함수도 Transaction 어노테이션이 있고 별다른 propagation 설정이 없다면 기본 전파 옵션이 Propagation.REQUIRED 이기 때문에 기존 트랜젝션을 탄다. 이 경우가 transaction 중첩이 가능한 부분이고, 위 옵션에 따라 달라진다.

@Service
public class ParentService {

  @Autowired
  private ChildService childService;

  @Transactional
  public void parentMethod() {
    // Transaction starts here
    childService.childMethod(); // This call goes through the proxy
  }
}

@Service
public class ChildService {

  @Transactional //있건없건 트랜젝션 영향 받음
  public void childMethod() {
    // This method participates in the transaction started by parentMethod
    // because the call goes through the Spring proxy
  }
}

https://www.baeldung.com/spring-transactional-propagation-isolation

한 번쯤 읽어볼 만한 시행착오

https://technixc.medium.com/how-to-use-transactional-annotation-like-a-pro-4129308ad069

728x90
반응형
반응형

2024.05.22 - [개발/sql] - DB isolation level

 

DB isolation level

isolation level 이란 무엇인가?디비 동시성을 관리하고 데이터 정합성을 유지하기 위해 서로 다른 트랜젝션끼리의 관계를 정의한 것 존재 이유?언제 그리고 어떤 식으로 한 트랜젝션이 만든 변화

bangpurin.tistory.com

Isolation Level은 동시 트랜잭션이 수행될때 다른 트랜잭션이 동일한 데이터에 대해서 어떻게 보일지에 대한 범위를 나타낸다.

​Isolation is the third letter in the ACID acronym. It means that concurrent transactions should not impact each other. 

 

위에서 DB단에서의 isolation level에 대해 살펴보았다. 이제 application 단에서의 isolation 설정을 알아보자.

 

isolation level을 아래와 같이 트랙잭션 별로 설정할 수 있다.

import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void readUncommittedTransaction() {
    // Your code here
}

@Transactional(isolation = Isolation.READ_COMMITTED)
public void readCommittedTransaction() {
    // Your code here
}

@Transactional(isolation = Isolation.REPEATABLE_READ)
public void repeatableReadTransaction() {
    // Your code here
}

@Transactional(isolation = Isolation.SERIALIZABLE)
public void serializableTransaction() {
    // Your code here
}
  • Isolation.DEFAULT: 디비의 default 설정에 따름
  • Isolation.READ_UNCOMMITTED
  • Isolation.READ_COMMITTED: 처음 업데이트 값으로 오버라이드 위험
  • Isolation.REPEATABLE_READ: 오버라이드 할 것 같으면 에러 발생
    • 에러 발생하면 재시도할 수 있음
    • @Retryable(maxAttempts = 15) // This is actually quite a lot. Ideally, 1–3 attempts should be sufficient.
    • 스래드 200개 생성됨..(과다하게 생성됨; 동시성 이슈)
    • 즉각 재시도(디비저장) 보다 다시 큐를 쌓도록하는게 동시성을 낮출 수
  • Isolation.SERIALIZABLE
    • 동시에 저장하려고하면 디비에서 에러발생
    • 그래도 100프로 보장 못함; 실패나면 롤백되는게 있음

 

주의사항

데이터 정합성(data integrity)과 성능(performance)을 고려하여 설정해야 한다.

사용하고자 하는 level 이 DB에서 지원하는 레벨인지 확인해야 한다.

 

애플리케이션 전체 기본 설정을 바꾸려면 아래와 같이 설정값을 추가한다.

(spring boot version 확인하고 넣도록 하자) 

springboot 2.7.3 ~ 3.2에는 아래와 같다.

spring.datasource.hikari.transaction-isolation

https://docs.spring.io/spring-boot/docs/2.7.3/reference/htmlsingle/

 

Spring Boot Reference Documentation

This section goes into more detail about how you should use Spring Boot. It covers topics such as build systems, auto-configuration, and how to run your applications. We also cover some Spring Boot best practices. Although there is nothing particularly spe

docs.spring.io

https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties.data.spring.datasource.hikari

 

Common Application Properties

 

docs.spring.io

 

728x90
반응형
반응형

isolation level 이란 무엇인가?

  • 디비 동시성을 관리하고 데이터 정합성을 유지하기 위해 서로 다른 트랜젝션끼리의 관계를 정의한 것
  • 트랜잭션이 동시에 수행될때 다른 트랜잭션이 동일한 데이터에 대해서 어떻게 보일지에 대한 범위를 나타낸다.

 

존재 이유?

  • 언제 그리고 어떤 식으로 한 트랜젝션이 만든 변화를 보이게 할지(visibility) 조절하면서 정합성(consistency)과 성능(performance) 사이의 균형(balance)을 맞추기 위함

 

isolation level 4가지

Read Uncommited

  • 제일 낮은 레벨
  • 언제? 이슈 위험 높음(정확도 낮음); 성능이 중요할 때
  • 정의: 커밋되지 않은, 수정된 데이터가 다른 트랜젝션에서도 보임(dirty reads)
  • 이슈: dirty read, non repeatable read, phantom read 모두 발생

 

Read Commited

  • 기본 설정인 DB: postgreSQL, oracleDB
  • 언제? 정합성이 중요하나 반복해서 읽었을 때 같을 필요 없을 경우
  • 정의: 커밋된 수정 사항만 다른 트랜젝션에서 보임
  • 이슈: 같은 데이터를 여러번 조회할 경우 다른 결과를 볼 수도 있음; 다른 트렌젝션에서 변경했음(non repeatable reads)
    • non repeatable read, phantom read 모두 발생

 

Repeatable Read

  • 기본 설정인 DB: mySQL
  • 언제? 정합성이 중요하고 한 트랜젝션 내에서 한 데이터를 여러번 조회했을 때 같은 결과가 나와야하는 경우
  • 정의: 같은 트랜젝션 내 한 데이터를 여러번 조회할 경우 항상 같은 결과를 보장함(트랜젝션 시작 전 커밋된 데이터만 보임)
  • 이슈: 다른 트랜젝션에서 삽입/삭제된 row(혹은 변화)를 볼 수 없음(phantom reads)

 

Serializable

  • 제일 높은 레벨
  • 언제? 데이터의 무결성과 정합성이 엄청 중요할 때(금융..)
  • 정의: 마치 동기로 실행하듯 하나 끝나고 다른 하나를 실행한다.(sequentially)
  • 이슈: 성능이 안좋음; 가끔 테이블 전체를 Lock 함

 

728x90
반응형

'개발 > sql' 카테고리의 다른 글

[mysql] basic functions  (0) 2024.09.09
[mysql] collation이란  (0) 2024.06.10
[mysql] merge into..?  (0) 2024.05.17
[mysql] 유저의 등수 구하기 rank under v8  (0) 2024.02.06
[DB] 분산환경에서 데이터 저장소 선택과 활용  (0) 2023.07.24
반응형

환경: mysql 5.7

oracle에는 merge into 가 있어 값이 있을때는 수정하고 없을때는 추가하여 pk 없음 에러 혹은 중복키 저장 에러가 나지 않고 작업을 진행할 수 있었는데, mysql에는 같은 기능을 하는 쿼리가 있는지 알아본다.

oracle

MERGE INTO [UPDATE되거나 INSERT 될 테이블]
USING [MERGE를 진행하고 싶은 대상, 조인, 서브쿼리도 사용 가능]
ON [조건]
WHEN MATCHED THEN [조건에 맞는 데이터가 있을 시 실행할 구문, UPDATE, DELETE]
WHEN NOT MATCHED THEN [조건에 맞는 데이터가 없을 시 실행할 구문, INSERT]
;

 

mysql

INSERT INTO 테이블 (
	[콜롬들...]
)VALUES(
	[값들...]
)
ON DUPLICATE KEY UPDATE
	[PK값들..]

예시

category 테이블 pk가 service_code, category_code 인 경우, category_name을 추가하거나 수정하려고 한다면..

CREATE TABLE `category` (
  `service_code` varchar(20) NOT NULL COMMENT '서비스 코드',
  `category_code` varchar(20) NOT NULL COMMENT '카테고리 코드',
  `category_name` varchar(20) NOT NULL COMMENT '카테고리 명',
  PRIMARY KEY (`service_code`,`category_code`)

 

INSERT INTO category 
	(service_code, category_code, category_name) 
VALUES
	('admin', 'character', '캐릭터')
ON DUPLICATE KEY UPDATE 
	service_code = 'admin' , category_code = 'character'
;

위처럼 쓸 수도 있고 아래처럼 작성해도 동일하다.

INSERT INTO category 
	(service_code, category_code, category_name) 
VALUES
	('admin', 'character', '캐릭터')
ON DUPLICATE KEY UPDATE 
	service_code = VALUES(service_code), category_code = VALUES(category_code)
;

날려보면 아래처럼 성공하는 로그가 찍힌다.

하지만 실제 row는 수정되지 않았다! 그렇다고 신규 row가 생기지도 않았다.

참고로 기존 row를 지우고 날리면 1로 결과가 떨어지고 신규 row가 추가된다. 허나 이 상태에서 category name을 바꾸고 날려보면 1로 떨어져도 반응이 없다..

 

쿼리를 아래처럼 수정하면 

INSERT INTO category (service_code, category_code, category_name) 
VALUES ('admin', 'character', '캐릭터797')
ON DUPLICATE KEY UPDATE 
    service_code = VALUES(service_code),
    category_code = VALUES(category_code),
    category_name = VALUES(category_name);

기존 row가 있을 경우 결과가 2로 떨어지고 데이터도 수정된 것을 확인할 수 있다. 없을 경우 1로 떨어지고 추가된다.

왜?!???

 

With ON DUPLICATE KEY UPDATE, the affected-rows value per row is 1 if the row is inserted as a new row, 2 if an existing row is updated, and 0 if an existing row is set to its current values. If you specify the CLIENT_FOUND_ROWS flag to the mysql_real_connect() C API function when connecting to mysqld, the affected-rows value is 1 (not 0) if an existing row is set to its current values.

결과에 대해 더 찾아보니 아래와 같이 내린다는 것을 알게 되었다.

  • 1: 신규 row로 insert
  • 2: 기존 row update
  • 0: 변경 없음

 참고

mysql 5.7의 경우

https://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html

 

MySQL :: MySQL 5.7 Reference Manual :: 13.2.5.2 INSERT ... ON DUPLICATE KEY UPDATE Statement

13.2.5.2 INSERT ... ON DUPLICATE KEY UPDATE Statement If you specify an ON DUPLICATE KEY UPDATE clause and a row to be inserted would cause a duplicate value in a UNIQUE index or PRIMARY KEY, an UPDATE of the old row occurs. For example, if column a is de

dev.mysql.com

 

mysql 8부터는 아래 문서를 확인해야 한다.

https://dev.mysql.com/doc/refman/8.0/en/insert-on-duplicate.html

 

MySQL :: MySQL 8.0 Reference Manual :: 15.2.7.2 INSERT ... ON DUPLICATE KEY UPDATE Statement

15.2.7.2 INSERT ... ON DUPLICATE KEY UPDATE Statement If you specify an ON DUPLICATE KEY UPDATE clause and a row to be inserted would cause a duplicate value in a UNIQUE index or PRIMARY KEY, an UPDATE of the old row occurs. For example, if column a is de

dev.mysql.com

 

 

728x90
반응형

'개발 > sql' 카테고리의 다른 글

[mysql] collation이란  (0) 2024.06.10
DB isolation level  (0) 2024.05.22
[mysql] 유저의 등수 구하기 rank under v8  (0) 2024.02.06
[DB] 분산환경에서 데이터 저장소 선택과 활용  (0) 2023.07.24
[형상관리] flyway vs liquibase  (0) 2022.07.08
반응형

try with resources

  • from java7; enhanced in java9
  • try 블락 안에 열린 리소스를 예외 여부와 상관없이 자동으로 닫아주는 기법
    • stream, db, network.. 등

 

예시

try (BufferedReader br = new BufferedReader(new FileReader("example.txt"))) {
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    // Exception handling
}

: BufferedReader, FileReader 와 같은 리소스의 close함수를 호출하지 않아도 자동으로 닫아줌

 

어떤 클래스들이 자동으로 닫히나?

AutoClosable interface 혹은 이를 확장한 AutoCloaseable interface를 구현한 리소스

public interface AutoCloseable {
    void close() throws Exception;
}
public interface Closeable extends AutoCloseable {
    public void close() throws IOException;
}

이를 구현한 예시로는

  • 스트림: FileInputStream, FileOutputStream, BufferedReader, BufferedWriter
  • reader/writer: InputStreamReader, OutputStreamWriter
  • sql connection: java.sql.Connection, java.sql.ResultSet, java.sql.Statement
  • 소켓: java.net.Socket
  • 채널: java.nio.channels > FileChannel, SocketChannel, ServerSocketChannel
  • zip: java.util.zip.ZipFile

 

728x90
반응형
반응형

객체를 ordering 하는 방법에 대해 알아본다.

 

Comparable interface

  • 객체의 natural ordering 에 대해 정의할 수 있음
  • 클래스 안에서 클래스끼리 비교할 때 override해두면 Collections.sort, Arrays.sort 등에서 사용됨
  • 하나뿐인 아래 함수를 상속받고 구현하면 됨, 구현해야지만 사용할 수 있음(not functional interface)
public int compareTo(T o);
  • 해당 함수로 현재 객체(this)와 다른 객체(o)를 비교할 수 있으며 반환 값은 아래와 같음
    • this < o : -1    정방향
    • this == o : 0
    • this > o : 1     역방향
  • 참고로 String 객체는 해당 interface의 함수를 이미 구현하고 있어서 별도의 설정을 하지 않아도 알파벳 순 정렬을 할 수 있다.

String.java

import java.util.*;

@Getter
@ToString
@AllArgsConstructor
public class Person implements Comparable<Person> { ///
    private String name;
    private int age;

    @Override
    public int compareTo(Person other) { ///
        return Integer.compare(this.age, other.age); // Natural order by age
    }



    // Main method for testing
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("Alice", 30),
            new Person("Bob", 25),
            new Person("Charlie", 35)
        );

        // Sort using the natural order defined by Comparable
        // 안줘도 자동으로 있는거 사용
        Collections.sort(people);

        System.out.println("Sorted by age (natural order): " + people);
    }
}

 

Comparator Interface

  • Comparator is intended to be used for defining external comparison logic rather than internal natural ordering. However, you can create a Comparator for Person as a separate class, an inner class, or even as a static field in the Person class to provide custom sorting criteria.
  • 객체 자체의 natural ordering 이 없거나, 좀 더 복잡한 정렬 방법이 있을 경우 사용(flexibility)
  • 아래 함수를 상속받고 구현하면 됨(functional interface) ; 람다로 사용가능
int compare(T o1, T o2);
  • int를 반환하도록 되어있는데, 비교 값은 아래와 같다.
    • 앞 < 뒤 : -1    정방향
    • 앞 == 뒤 : 0
    • 앞 > 뒤 : 1     역방햐
  • String 객체에 역시 이미 정의 되어 있다.

String.java

기본함수

 

  • Comparator<T> reversed():
    • Returns a comparator that reverses the order of this comparator.
  • Comparator<T> thenComparing(Comparator<? super T> other):
    • Returns a comparator that first compares using this comparator, and if the comparison is equal, uses the provided comparator. 앞에 비교한 결과가 같으면 추가 사용!

 

import java.util.*;

@Getter
@ToString
@AllArgsConstructor
public class Person { //여기서 comparator implement못하고 별도의 클래스로 빼야 함
    private String name;
    private int age;


    // Main method for testing
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("Alice", 30),
            new Person("Bob", 25),
            new Person("Charlie", 35)
        );

        // Sort by name
        // 별도로 만들어서 사용 , 람다 사용가능
        Comparator<Person> nameComparator = Comparator.comparing(Person::getName);
        Collections.sort(people, nameComparator);
        System.out.println("Sorted by name: " + people);

        // Sort by age in reverse order
        Comparator<Person> ageComparator = Comparator.comparingInt(Person::getAge).reversed();
        Collections.sort(people, ageComparator);
        System.out.println("Sorted by age (reverse order): " + people);
    }
}
//or 아래처럼 함수로 빼고 new AgeComparator() 삽입
   // Comparator for sorting by age
    public static class AgeComparator implements Comparator<Person> {
        @Override
        public int compare(Person p1, Person p2) {
            return Integer.compare(p1.getAge(), p2.getAge());
        }
    }

 

어떤 것을 사용해야 하나

The Comparable interface is a good choice to use for defining the default ordering, or in other words, if it’s the main way of comparing objects.

기본적으로는 Comparable 을 사용하고 아래와 같은 경우 Comparator을 사용

  • 파일이 수정 불가하여 interface를 구현할 수 없는 경우 Comparator 사용
  • 비교 로직을 분리해서 관리해야하는 경우 Comparator 사용
  • 비교 전략이 다양해서 Comparable 로 부족할 경우 Comparator 사용

 


참고

https://www.baeldung.com/java-comparator-comparable

728x90
반응형
반응형
  1. Synchronization: Use the synchronized keyword to ensure that only one thread can access a critical section of code at a time. This helps prevent race conditions where multiple threads might try to modify shared data simultaneously.
  2. Locks: Java provides various lock implementations like ReentrantLock, ReadWriteLock, and StampedLock which offer more flexibility and functionality compared to intrinsic locks (synchronized). They llow finer control over locking mechanisms and can help avoid deadlock situations.
  3. Thread-safe data structures: Instead of using standard collections like ArrayList or HashMap, consider using their thread-safe counterparts from the java.util.concurrent package, such as ConcurrentHashMap, CopyOnWriteArrayList, and ConcurrentLinkedQueue. These data structures are designed to be safely accessed by multiple threads without external synchronization.
  4. Atomic variables: Java's java.util.concurrent.atomic package provides atomic variables like AtomicInteger, AtomicLong, etc., which ensure that read-modify-write operations are performed atomically without the need for explicit locking.
  5. Immutable objects: Immutable objects are inherently thread-safe because their state cannot be modified once created. If an object's state needs to be shared among multiple threads, consider making it immutable.

멀티 스레드 환경에서 안전하게 코딩하는 기본적인 다섯가지 방법에 대해 살펴본다. 자바 기준

1. synchronized 키워드 사용(직접적인 락은 아니지만 락과 같은 것;  monitor lock)

  • 함수 자체 그리고 함수 안에서도 블락으로 지정하여 사용 가능
  • allowing only one thread to execute at any given time.
  • The lock behind the synchronized methods and blocks is a reentrant. This means the current thread can acquire the same synchronized lock over and over again while holding it(https://www.baeldung.com/java-synchronized

2. 명시적으로 lock interface를 구현한 구현체를 사용 

lock()/tryLock() 함수로 락을 걸고 unlock()함수로 반드시 락을 해제해야한다.(아니면 데드락..)

Condition 클래스를 통해 락에 대한 상세한 조절이 가능

  • ReentrantLock implements Lock
    • synchronized와 같은 방법으로 동시성과 메모리를 핸들링하지만 더 섬세한 사용이 가능하다.
  • ReentrantReadWriteLock implements ReadWriteLock
    • Read Lock – 쓰기락이 없거나 쓰기락을 요청하는 스레드가 없다면, 멀티 스레드가 락 소유 가능
    • Write Lock – 쓰기/읽기 락 모두가 없는 경우 반드시 하나의 스레드만 락을 소유한다.
  • StampedLock(자바8 도입, 읽기/쓰기 락 모두 제공)
    • 락을 걸면 long 타입의 stamp를 반환함 해당 값으로 락을 해제하거나 확인 가능
    • optimistic locking임(수정사항이 별로 없다는 가정하에 읽기에 개방적)
    • https://www.baeldung.com/java-concurrent-locks

3. thread safe 한 자료구조 사용

  • java.util.concurrent 패키지 참고(from java 5)
    • ConcurrentHashMap, ConcurentLinkedQueue, ConcurrentLinkedDeque 등
  1. ConcurrentHashMap: This class is a thread-safe implementation of the Map interface. It allows multiple threads to read and modify the map concurrently without blocking each other. It achieves this by dividing the map into segments, each of which is independently locked.
  2. CopyOnWriteArrayList: This class is a thread-safe variant of ArrayList. It creates a new copy of the underlying array every time it is modified, ensuring that iterators won't throw ConcurrentModificationException. This makes it suitable for scenarios where reads are far more frequent than writes.
  3. ConcurrentLinkedQueue: This class is a thread-safe implementation of the Queue interface. It is designed for use in concurrent environments where multiple threads may concurrently add or remove elements from the queue. It uses non-blocking algorithms to ensure thread safety.
  4. BlockingQueue: This is an interface that represents a thread-safe queue with blocking operations. Implementations like LinkedBlockingQueue and ArrayBlockingQueue provide blocking methods like put() and take() which wait until the queue is non-empty or non-full before proceeding.
  5. ConcurrentSkipListMap and ConcurrentSkipListSet: These classes provide thread-safe implementations of sorted maps and sets, respectively. They are based on skip-list data structures and support concurrent access and updates.

4. thread safe 한 변수 사용

  • java.util.concurrent.atomic 패키지 참고(from java 5)
    • AtomicInteger, AtomocLong, AtomicBoolean 등
    • 스레드 끼리 동기화 필요한 변수에 사용(상태 공유 등); 관련 성능 향상 시
    • 락으로 인한 오버헤드나 컨테스트 스위칭 비용 감소
    • non blocking 상황에서(스레드 별 독립적인 작업 시)

5. immutable object 불변 객체 사용

  • String, Integer, Long, Double 등 과 같은 wrapper 클래스 사용
  • private final로 선언
  • 생성자 이용하여 initialize
  • setter 함수나 수정가능 함수 제공하지 않기

6. volatile 

  • 변수를 Main Memory에 저장하겠다고 명시하는 것
  • 각 스레드는 메인 메모리로 부터 값을 복사해 CPU 캐시에 저장하여 작업하는데 volatile은 CPU 캐시 사용 막고 메모리에 접근해서 실제 값을 읽어오도록 설정하여 데이터 불일치를 막음
  • 자원의 가시성: 메인 메모리에 저장된 실제 자원의 값을 볼 수 있는 것
  • 멀티쓰레드 환경에서 하나의 쓰레드만 read&write하고 나머지 쓰레드가 read하는 상황에 사용
    • Read : CPU cache에 저장된 값 X , 메인 메모리에서 읽음
    • Write : 메인 메모리에 작성
    • CPU Cache보다 메인 메모리가 비용이 더 큼(성능 주의)
  • 가장 최신 값 보장

what to choose

synchronized

  • 여러 쓰레드가 write하는 상황에 적합
  • 가시성 문제해결 : synchronized블락 진입 전/후에 메인 메모리와 CPU 캐시 메모리의 값을 동기화 하여 문제 없도록 처리

volatile

  • 하나의 쓰레드만 read&write하고 나머지 쓰레드가 read하는 상황에 적합
  • 가시성 문제해결 : CPU 캐시 사용 막음 → 메모리에 접근해서 실제 값을 읽어오게 함
    • Read : CPU cache에 저장된 값 X , 메인 메모리에서 읽음
    • Write : 메인 메모리에 작성

AtomicIntger

  • 여러 쓰레드가 read&write를 병행
  • 가시성 문제해결: CAS알고리즘
    • 현재 쓰레드에 저장된 값과 메인 메모리에 저장된 값을 비교하여
      • 일치할 경우 새로운 값으로 교체
      • 불일치할 경우 실패하고 재시도
728x90
반응형
반응형

환경: springboot 2.6.2, spring-web

 

아래와 같은 컨트롤러가 있다.

@RequestMapping("/api/member")
@RestController
@RequiredArgsConstructor
public class MemberController {

  private final UserService userService;

 
  @GetMapping("/loss-game-money")
  public BaseResponse<MemberLossGameMoneyResponse> getMemberLossGameMoney(@ModelAttribute MemberLossGameMoneyRequest request) {
    return userService.getMemberLossGameMoney(request);
  }

  
  @GetMapping("/{id}")
  public BaseResponse<MemberInfoResponse> getMember(@PathVariable String id) {
    return new BaseResponse<>(userService.getMemberInfo(id));
  }
}

 

이 상황에서 아래 api를 요청한다면? endpoint가 정의되어 있지 않아 404가 날 것이라 기대했다.

http://localhost:8600/api/member

하지만 200 ok 가 떨어졌다.

 

분명 컨트롤러에 정의되어 있지 않고, 그렇다고 에러 내용이 controller advice에 정의되어 있지도 않은데.. 

그리고 과거에는 404로 떨어졌던 기억도 있던 터라 구글링을 해본다..

 

의심 1. 스프링 버전 이슈?

구글링 하다가 스프링 버전 2.3 이후부터 바뀐 스펙이라고 적힌 것을 봐서.. 스프링 버전의 문제인가 의심했다.(개소리로 판명)

The change in behavior where Spring Boot started returning a 200 OK response with an empty body for unmatched endpoints instead of a 404 response happened around Spring Boot version 2.3.x.
In versions prior to 2.3.x, the default behavior was to return a 404 Not Found response for unmatched endpoints. However, starting from version 2.3.x, the default behavior was changed to return a 200 OK response with an empty body.

공식 문서를 찾다가 실패하여 신규 프로젝트에 버전을 아래와 같이 중간 버전만 하나씩 올려서 테스트해 봤는데

  • 테스트해 본 버전: 2.2.6.RELEASE, 2.3.4.RELEASE, 2.4.5, 2.5.6, 2.6.3, 2.7.3, 2.7.10, 2.7.18, 3.0.0, 3.2.4(현재 최신)

절대 재현되지 않는다.. 버전 문제는 아니고 소스 문제라고 판명..

아래와 같이 모두 동일한 결과를 return 한다. 404 잘만 나는구먼..

없는 주소로 요청할 경우 404

 

의심 2. controllerAdvice...?

혹시 exception handler가 잡아서 200으로 반환하나 싶어 소스를 뒤져봐도 그런 건 없었다.

 

의심 3. 그럼 필터? 인터셉터?

혹시나 싶어 필터나 인터셉터를 하나씩 주석해 보고 실행해 본다.

로그를 살펴보니 필터에 해당하는 로그는 잘 찍히고 있어 필터 이후에서 200을 반환하는 것이라 판단했다.

그리고 인터셉터를 하나씩 보는데.. 잡았다 요놈..

@Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    if (handler instanceof HandlerMethod) {
    	...
      return HandlerInterceptor.super.preHandle(request, response, handler);
    }
    return false; ///
  }

위 함수에서 컨트롤러에 정의된 api는 if (handler instanceof HandlerMethod) 구문에 true를 반환하여 작업을 하고 마지막에 true를 반환하지만 정의되지 않은 api는 false를 타게 된다. false를 타면 상태코드 200에 empty body로 리턴된다..! false를 반환한다는 의미가 controller를 타지 않고 작업을 종료한다는 의미고 그게 잘 되었으니 어쩌면 맞을 수도..

혹시나 싶어서 신규 프로젝트로 기본 세팅만 한 후 재현해 본다.

1. @EnableWebMvc 없고 + return false

@Component
public class TestInterceptor implements HandlerInterceptor {

  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    return false;
  }
}
@Component
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {

  private final TestInterceptor interceptor;
  
  @Override
  public void addInterceptors(InterceptorRegistry registry) {
  //TestInterceptor가 이미 빈으로 등록되어 있으므로 주입하여 사용하기위해 아래 주석;
  //TestInterceptor에 @Component가 없으면 빈이 아니므로 아래 주석이 작동함
//    registry.addInterceptor(new TestInterceptor()).addPathPatterns("/**");
    registry.addInterceptor(interceptor);
  }
}
@RequestMapping("/api")
@RestController
public class TestController {

  @GetMapping("/test")
  public String test( ) {
    return "hello";
  }
}
  • interceptor 설정 전
    • http://localhost:8080/api/test 요청 시
      • 200 hello
    • http://localhost:8080/api/test3 요청 시
      • 404 기본 404 not found 메시지
  • interceptor 설정 후
    • http://localhost:8080/api/test 요청 시
      • 200 empty body
    • http://localhost:8080/api/test3 요청 시
      • 200 empty body

 

200이 아닌 다른 상태 값으로 반환하려면 아래와 같이 수정하면 된다.

empty body에 404로 떨어진다.

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  if (! (handler instanceof HandlerMethod)) {
    response.setStatus(404); //
    return false;
  }
  return true;
}

 

이를 수정해 본다면? 에러를 던져보자.

2. @EnableWebMvc 없고 + throw exception

아래와 같이 false 반환대신 에러를 던지면 throw 절을 만나게 되고 BasicErrorController를 타고 500으로 떨어진다.

(어떤 종류의 exception이건 500으로 떨어진다. 그 이유는 Exception이 처리가 이뤄지지 않은 상태로 WAS에게 전달되었기 때문이다. WAS 입장에서는 처리되지 않은 Exception을 받으면 이를 예상치 못한 문제로 인해 발생했다고 간주하고 status코드 500에 Internal Server Error를 메시지에 설정하게 된다.)

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  if (! (handler instanceof HandlerMethod)) {
    throw new RuntimeException("nono");
  }
  return true;
}

 

@EnableWebMvc 어노테이션의 유무

1. @EnableWebMvc 있고 + 인터셉터에서 return true로 진행시키면

여기서 @EnableWebMvc 어노테이션을 추가하면 (어떤 에러를 던지건) 404로 에러가 바뀐다....

spring-web dependency가 있으면 @EnableWebMvc를 선언하지 않아도 된다고 알고 있는데 결과가 다르다니..(아래에서 계속)

더 확인해 보니 @EnableWebMvc를 선언하면 아래 부분이 true로 내려와서

handler instanceof HandlerMethod == true

결국 preHandler가 true로 return 되어 에러를 만나지 않고, controller 단에 가서 url을 찾다가 없어서 404 -> BasicErrorController를 타는 플로우였다.

2. @EnableWebMvc + 인터셉터 내부에서 에러 발생 시키면

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  throw new HttpClientErrorException(HttpStatus.MULTI_STATUS);
}
2024-04-18 09:56:29.978  WARN 12950 --- [nio-8080-exec-8] o.s.web.servlet.PageNotFound             : No mapping for GET /api/test3
2024-04-18 09:56:29.980 ERROR 12950 --- [nio-8080-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] threw exception

org.springframework.web.client.HttpClientErrorException: 207 MULTI_STATUS
	at com.example.demo.interceptor.TestInterceptor.preHandle(TestInterceptor.java:16) ~[main/:na]
	...

2024-04-18 09:56:29.982 ERROR 12950 --- [nio-8080-exec-8] o.a.c.c.C.[Tomcat].[localhost]           : Exception Processing ErrorPage[errorCode=0, location=/error]

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.web.client.HttpClientErrorException: 207 MULTI_STATUS
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[spring-webmvc-5.3.22.jar:5.3.22]
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.3.22.jar:5.3.22]
    ...

 

해당 부분에서도 항상 에러를 뱉게 하면 에러가 발생해도 404로 떨어진다.

그 의미는 에러가 발생해도 controller 쪽으로 요청이 들어온다는 것인데, 그 이유는 WAS의 에러페이지를 위한 요청 때문이다.

https://cs-ssupport.tistory.com/494

 

interceptor에 에러를 반환하고 exception handler를 통해 에러를 반환하려고 하면 어떻게 해야 할까?

interceptor는 dispatcher servlet 이후 스프링 컨텍스트 안에 있기 때문에 controller advice를 통한 처리가 가능하다.

1. @EnableWebMvc + controllerAdvice

@EnableWebMvc를 선언한 상태에서 interceptor에서 에러를 뱉게 아래와 같이 수정하고

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  throw new RuntimeException("yesyes");
}

controllerAdvice를 통해 exception handler를 작성하면 의도한 대로 상태코드 400에 에러 메시지가 나온다!

@RestControllerAdvice
public class InterceptorExceptionHandler {

  @ExceptionHandler({RuntimeException.class})
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  public String error(RuntimeException exception){
    return "Exception : " + exception.getMessage();
  }
}

WARN 23846 --- [nio-8080-exec-1] o.s.web.servlet.PageNotFound             : No mapping for GET /api/test3

로그에는 page not found 가 적힌다.

 

2. @EnableWebMvc 제거 + controllerAdvice

여기서 @EnableWebMvc를 선언을 제거한다면?

그래도 api는 동일한 결과가 반환된다.

그런데 로그는 다르게 찍힌다. 반환한 에러에 대한 값이 찍힌다.

ERROR 24122 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: yesyes] with root cause

java.lang.RuntimeException: yesyes
	at com.example.demo.interceptor.TestInterceptor.preHandle(TestInterceptor.java:14) ~[main/:na]
	at org.springframework.web.servlet.HandlerExecutionChain.applyPreHandle(HandlerExecutionChain.java:148) ~[spring-webmvc-5.3.22.jar:5.3.22]
	...

 

@EnableWebMvc 어노테이션 관련..

spring-web dependency가 있으면 @EnableWebMvc를 선언하지 않아도 된다고 알고 있는데 결과가 다르다니..

에 대해 좀 더 알아본다.

우선 맞다. springboot에 spring boot starter web 디펜덴시가 있으면 @SpringBootApplicaion 어노테이션에 의해 @EnableAutoConfiguration 어노테이션이 작동하고 웹의 기능(DispatcherServlet 등)을 사용하기 위한 기본 세팅을 자동으로 해준다.

@EnableWebMvc 어노테이션이 사용되면 스프링은 웹 애플리케이션을 위한 준비를 하게 되는데, 기본 설정값으로 아래 클래스를 읽어온다. 

public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
The @EnableWebMvc annotation is used in Spring to enable the Spring MVC framework for a web application. When this annotation is used, it imports the Spring MVC configuration from WebMvcConfigurationSupport
 and helps in setting up the necessary components for handling web requests, such as controllers, views, and more. This annotation is often used in conjunction with the @Configuration annotation to enable Spring MVC configuration in a Java-based configuration approach.

 

우리가 기본 세팅을 커스터마이징 하려면 WebMvcConfigurerAdapter라는 클래스를 extend 해야 하는데, 이 클래스는 spring5.0 이래로 deprecated 되었고, WebMvcConfigurer interface를 implement 해야 한다.

그런데 여기서 주의해야 할 점은 커스텀 세팅을 사용하려면 @EnableWebMvc 어노테이션이 없어야 한다는 것이다.

만약 @EnableWebMvc 어노테이션과 커스텀 세팅을 사용하려면 빈으로 등록된 DelegatingWebMvcConfiguration 클래스를 확장하여 재정의해야 한다. 기본 값을 사용하지 않기에 모든 함수를 재정의해야 함에 주의하자.

https://docs.spring.io/spring-boot/docs/current/reference/html/web.html

https://dev.to/xterm/be-careful-when-using-configuration-classes-with-enablewebmvc-in-spring-boot-2n32

 

Be careful when using @Configuration classes with @EnableWebMvc in Spring Boot

Situation Recently, we have been faced with a strange issue after adding a configuration...

dev.to

위에서 @EnableWebMvc 어노테이션 유무에 따라 결과가 다르게 나온 이유는 아마도,

@EnableWebMvc 어노테이션 + 커스텀 세팅(인터셉터)인데 DelegatingWebMvcConfiguration 클래스의 재정의가 없어 기본 세팅값으로 override 되어 다른 결과가 나온 것이 아닌가 추측된다.

 


위 내용을 학습하다 스프링 버전 3부터 루트 매핑(@RequestMapping("")과 @RequestMapping("/"))에 대한 이슈가 있다는 글을 보아서 참고로 링크 남긴다.. 관련 글 때문에 더 헷갈렸다.

https://github.com/spring-projects/spring-boot/issues/33499

 

404 error occurs with RequestMapping(path="") · Issue #33499 · spring-projects/spring-boot

Used version SpringBoot3.0.0 GA Occurrence event When I specify an empty string in Controller's RequestMapping (@RequestMappint(path="")) and access the root path, The mapping doesn't work properly...

github.com


interceptor in spring context: was 흐름까지 정리 https://cs-ssupport.tistory.com/494

 

[Spring] 스프링 인터셉터

[Spring] Servlet "Filter" 현재 Controller에 의해서 매핑되는 Page가 다음 종류가 있다고 하자 1. 로그인 하지 않고 접근 가능 2. 로그인 해야 접근 가능 >> 여기서 과연 "로그인 해야 접근 가능한 페이지"가

cs-ssupport.tistory.com

https://velog.io/@monkeydugi/Spring-Interceptor%EC%97%90%EC%84%9C-%EC%98%88%EC%99%B8%EB%A5%BC-%EC%9D%91%EB%8B%B5-%ED%95%B4%EC%A3%BC%EB%8A%94-%EB%B0%A9%EB%B2%95

 

Spring Interceptor에서 예외를 응답 해주는 방법

상황이 어떤가 살펴보자.소셜 로그인을 시도한다.authorization code가 유효하지 않으면, 예외를 발생 시킨다.500으로 응답한다.위의 코드는 인터셉터에서 예외를 발생 시킨다.하지만 이렇게 끝내면 5

velog.io

 

728x90
반응형
반응형

기업에서 서버팜을 구축하게 되면 어플리케이션 서버 전에 여러 단계의 라우터나 스위치, 로드발랜서 등등이 구축되어 실제 요청한 사람의 ip를 알기 어려워지게 된다.

소스로 말하자면 아래 값이 로컬이 나오거나 내부 장비의 ip가 찍히는 상황이 생기게 된다.

HttpServletRequest request;
request.getRemoteAddr();

 

그리하여 실제로 요청한 사람/장비의 ip를 알기 어려워지는데.. 아래와 같이 설정하면 해결할 수 있다.

 

1. nginx 설정

1-1. nginx map 설정(생략가능)

map은 다음과 같이 $key라는 변수를 받아 $value라는 결과값을 매핑해준다.
아래 코드에서 $key 값이 a라면, $value 값은 1이다.

map $key $value {
  a 1;
  b 2;
  default 0;
}

map 규칙은 위처럼 쓸 수도 있지만, 다른 파일에 분리해 둔 뒤 받아올 수도 있다.
가령 위의 규칙을 map-rule이라는 파일에 아래와 같이 분리하면:

a 1;
b 2;

다음과 같이 파일의 상대 include할 수 있다:

map $key $value {
  include PATH/TO/map-rule; # map-rule 파일의 절대 경로
  default 0;
}

 

위 map을 사용하여 아래처럼 clientip 라는 변수에 값을 담는다.

    map $http_x_forwarded_for $clientip {
        "" $remote_addr;
        default $http_x_forwarded_for;
    }

 

1-2. server.location 세팅

clientip를 아래와 같이 세팅한다. 

  proxy_set_header   X-Forwarded-For  $clientip;

대략적인 큰 그림은 아래와 같다.

 server {
        listen       80;
        server_name  server.abc.com;
        server_tokens off;
        
        ...
        
        location / {
        	//여기부터
            proxy_set_header   Host             $host;
            proxy_set_header   X-Real-IP        $remote_addr;
            proxy_set_header   X-Forwarded-For  $clientip;
            여기까지//
            proxy_set_header Connection "";
            proxy_http_version 1.1;
            proxy_pass http://localhost:8200;
        }
    }

 

1-3. nginx reload

nginx -s reload

 

2. 소스 변경

애플리케이션에서 X-Forwarded-For 헤더를 사용하도록 아래 두 가지 방법 중 하나를 적용해야 한다.
  1. HttpServletRequestWrapper 를 사용해서 getRemoteAddr 를 상속 -> 별도로 필터 개발 필요
  2. server.tomcat.remoteip.remote-ip-header 프로퍼티 설정

2번 방식이 더 쉽고 빠르다고 생각하여 적용해본다.

적용하기 전에, 버전 별로 키값이 달라지니 스프링 공식 문서는 꼭 확인해보자.

https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.webserver.use-behind-a-proxy-server

 

“How-to” Guides

Spring Boot has no mandatory logging dependency, except for the Commons Logging API, which is typically provided by Spring Framework’s spring-jcl module. To use Logback, you need to include it and spring-jcl on the classpath. The recommended way to do th

docs.spring.io

https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#appendix.application-properties.server

 

Common Application Properties

 

docs.spring.io

지금 상황에서 연관있는 값은 아래 값인 것 같다. 기본 값은 없다.

# springboot2.2 버전 이상이면
server.tomcat.remoteip.remote-ip-header=X-FORWARDED-FOR

# 구버전
#server.tomcat.remote-ip-header=X-FORWARDED-FOR

경우에 따라서는 아래 값도 조정해야할 수 있다.

server.tomcat.remoteip.internal-proxies

이 값은 신뢰할 내부 프록시를 매칭하는 값이다. 정규식으로 작성해야하며 기본값은 아래와 같다. 일반적으로는 기본값으로도 사용가능하지만 프로덕션 환경에서 내부 프록시 ip들이 해당 범위를 넘어갈 수도 있으니 확인이 필요하다. 참고로 공백으로 설정하면 모든 프록시를 신뢰한다는 뜻으로 프로덕션 환경에서는 이렇게 설정하면 위험할 수 있으니 주의해야 한다.

  • 10/8
  • 192.168/16
  • 169.254/16
  • 127/8
  • 등등(공식 문서 확인 필요)

 

728x90
반응형

'서버 세팅 & tool > nginx' 카테고리의 다른 글

라이브환경 인증서 교체  (0) 2024.01.08
[이슈해결][apache] 304 NOT_MODIFIED  (0) 2023.10.12
[nginx] WAF  (0) 2022.03.30
[nginx] API gateway  (0) 2022.03.14
[nginx] 실전 cors 해결하기  (0) 2022.03.14

+ Recent posts