GeoIP 자동 업데이트 구성


GeoIP 데이터베이스 파일을 이용하면 접근한
IP 주소의 국가, 도시 등의 정보를 얻을 수 있다. 먼저 이 파일을 다운로드 해보자.
또한, 갱신되는 파일을 
주기적으로 업데이트해 줄 필요가 있으므로 자동으로 업데이트 가능하도록 해보자.

GeoIP update 설치
geoipupdate 다운로드 링크에서 geoipupdate-3.1.1.tar.gz  파일을 다운로드하여 해당 경로에서 아래 명령들을 순차적으로 수행한다.

$ tar -zxvf geoipupdate-3.1.1.tar.gz
$ cd geoipupdate-3.1.1
$ ./configure
$ make
$ make install

명령 실행중 curl 이나 zlib이 설치되어 있지 않으면 에러가 발생한다. 해당 경우, 설치 후 명령을 다시 수행하면 된다.
# curl 설치
$ yum install curl-devel

# zlib 설치
$ yum install zlib-devel 

GeoIP.conf 설정
GeoIP.conf 파일에 계정 정보와 필요한 에디션을 설정한다.
유료 이용자에게는 GeoIP2와 GeoIP Legacy DB를 지원하고, 무료 이용자에게는 GeoLite2 DB만 지원한다. (유료 이용 라이센스 발급)

/usr/local/etc/GeoIP.conf

# Paid
AccountID YOUR_ACCOUNT_ID_HERE
LicenseKey YOUR_LICENSE_KEY_HERE
EditionIDs YOUR_EDITION_IDS_HERE
# Free
AccountID 0
LicenseKey 000000000000
EditionIDs GeoLite2-City GeoLite2-Country 

GeoIP update 실행

$ /usr/local/bin/geoipupdate 

위 명령을 수행하면 /usr/local/share/GeoIP 경로에 설정한 Edition이 생성된다.
예를 들어, EditionIDs GeoIP2-City 이면 GeoIP2-City.mmdb 파일이 생성된다.
(단, 방화벽을 사용하는 경우에는 DNS와 443 포트를 열어줘야 한다.)

실행 스크립트를 crontab에 설정하여 주기적으로 동작시키고 결과 값을 메일로 전송할 수 있다.
# top of crontab
MAILTO=mirotic91@tistory.com
2 22 * * 4 /usr/local/bin/geoipupdate2
# end of crontab

GeoIP 자동 업데이트
자동으로 업데이트 받는 shell을 간단히 작성했다. 실행하면 최신 GeoIP2-City.mmdb 파일을 원하는 경로로 이동시킨다.
배포 스크립트에 아래 shell 실행문을 추가해주면 최신 GeoIP를 유지할 수 있다.
#!/bin/bash
/usr/local/bin/geoipupdate
DOWNLOAD_DIR=/usr/local/share/GeoIP
TARGET_DIR=/DATA/GeoIP

mv -f $DOWNLOAD_DIR/GeoIP2-City.mmdb $TARGET_DIR/
chown -R tomcat:tomcat $TARGET_DIR

참고 링크 - https://dev.maxmind.com/geoip/geoipupdate/

'Development > Etc' 카테고리의 다른 글

ssh 접속 불가 현상 해결  (0) 2018.07.22

SSHProtocolException 에러 해결

서버에서 다른 서버로 API 호출하던 중에 아래와 같은 에러가 발생했다.

Caused by: javax.net.ssl.SSLProtocolException: handshake alert:  unrecognized_name 

수신 서버가 송신 서버의 name을 알지 못한다는 내용이다.

해당 에러를 해결하기 위해서는 두가지 방법이 있다.
- System 환경변수를 설정한다. System.setProperty("jsse.enableSNIExtension", false);
- JVM 옵션을  설정한다. -Djsse.enableSNIExtension=false

한가지 방법을 선택해 설정하면 정상적으로 API 호출되는 것을 확인 할 수 있다.

'Development > Java' 카테고리의 다른 글

UnsupportedClassVersionError 에러 해결  (0) 2018.07.16
Garbage Collection 방식  (0) 2018.05.12
Garbage Collection 과정  (0) 2018.04.28
Garbage Collection 용어 정리  (0) 2018.04.26
AES256 암호화 오류 해결  (4) 2017.10.06

JSR-303 Annotation Customizing

데이터를 저장하기 전에는 데이터에 대한 검증이 요구된다.
서비스에 정의한 값들이 매우 반복적이여서 검증하는 코드를 숨기고 명시적으로 보여줄 수 있는 것 같아서 JSR-303 어노테이션을 커스텀하는 방식을 사용해봤다.

우선 필드에서 검증할 어노테이션을 생성해준다. 이 때, message, groups, payload 메소드는 반드시 존재해야 한다.
그리고 values 라는 속성을 추가하여 주로 사용되는 값을 디폴트로 설정했다. 다른 값을 검증하려면 필드에 설정한 어노테이션에 values 속성을 설정해주면 된다.
@Constraint 어노테이션에는 직접 생성할 ConstraintValidator를 구현하는 Validator를 지정해줘야 한다.

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = AllowedValueValidator.class)
public @interface AllowedValue {
  String[] values() default { “S”, “M”, “L" };
  String message() default “{com.validator.constraints.AllowedValue.message}";
  Class<?>[] groups() default {};
  Class<? extends Payload>[] payload() default {};
}

Validator를 생성하여 ConstraintValidator 인터페이스를 상속 받고, 인터페이스에 선언된 메소드를 구현해줘야 한다.
initialize 메소드에서는 초기화할 데이터가 필요한 경우 설정해주면 된다. 앞서 만든 어노테이션을 인자로 하여 디폴트로 설정한 값으로 초기화했다.
isValid 메소드에서는 실제 검증할 내용을 작성해주면 된다. 검증할 필드에 요청된 값이 values에 정의된 값 중에 존재하는지 확인했다.

public class AllowedValueValidator implements ConstraintValidator<AllowedValue, String> {
  private String[] values;
  @Override
  public void initialize(AllowedValue constraintAnnotation) {
    this.values = constraintAnnotation.values();
  }
  @Override
  public boolean isValid(String value, ConstraintValidatorContext context) {
    return ArrayUtils.contains(values, value);
  }
}

검증할 필드에 커스텀한 어노테이션을 표기한다.
특정 필드에서는 기본 검증 값이 아닌 “red”, “blue” 값을 검증하도록 표기했다.

public class Clothing {

  private String id;

  private String name;

  @AllowedValue
  private String topSize;

  @AllowedValue
  private String bottomSize;

  @AllowedValue(values = { "red", "blue" })
  private String color;
}

컨트롤러에서는 기존 처럼 모델에 @Valid와 BindingResult만 표기해준다.
해당 요청이 들어오면 모델 필드에 표기한 커스텀 어노테이션의 Validator에 작성한 isValid 메소드로 검증하게 된다.
조건에 맞지 않는 경우에는 bindingResult의 해당 필드에 대한 에러 내용이 담긴다.

@PostMapping("/clothing/update")
public void update(@Valid Clothing clothing, BindingResult bindingResult) {
  if (bindingResult.hasErrors()) {
    FieldError fieldError = bindingResult.getFieldError();
    throw new RuntimeException(fieldError.getDefaultMessage());
  }
  ...
  clothingManager.update(clothing);
}

매우 반복적인 값들에 대한 검증으로 괜찮다고 생각한다.
하지만 모델 필드에 values 에 표기할 값들이 많아지는 경우는 오히려 코드가 복잡해 보일 수도 있다.
검증하는 방법은 다양하니 상황에 적절하게 잘 사용하면 좋을 것 같다.

DatabaseConfiguration 적용


PropertiesConfiguration을 사용하여 서비스에 사용되는 설정 값들을 실시간으로 변경할 수 있다.
서버가 적을 경우에는 각 서버마다 설정을 변경하는 것이 크게 번거롭지 않지만
서버가 많을 경우에는 모든 서버의 설정을 변경하는 것은 곤혹스럽다.
물론 서버마다 설정을 다르게 지정해야 하는 경우라면 어쩔 수 없는 것 같다.
모든 서버의 설정값을 동일하게 가져간다면 DB편히 관리할 수 있다.

그 뿐만이 아니다. AWS Auto Scaling 같은 것을 사용하고 있다면 서버 사용량에 따라 미리 만들어 놓은 서버 이미지로 서버가 늘고 줄게된다.
해당 서버 이미지의 설정값은 변경되지 않은 기존 이미지의 설정값을 따르기 때문에 스케일링 서버들은 다르게 동작하게 된다.
그렇기 때문에 설정값을 각각 수정하더라도 이미지를 다시 만들어야하기에 번거롭다.
그러므로 DatabaseConfiguration을 적용하면 스케일링에 상관없이 DML 수행만으로 서버에 구애 받지 않고 실시간으로 설정값을 변경할 수 있다. 

#DatabaseConfiguration 빈 등록
@Configuration
public class DatabaseConfig {

  @Bean
  public DataSource dataSource() {
    BasicDataSource dataSource = new BasicDataSource();
    dataSource.setDriverClassName("com.mysql.jdbc.Driver");
    dataSource.setUrl("jdbc:mysql://localhost/test");
    dataSource.setUsername("root");
    dataSource.setPassword("root");
    return dataSource;
  }

  @Bean
  public DatabaseConfiguration databaseConfig() {
    return new DatabaseConfiguration(dataSource(), "config", "key", "value"); // table명, key/value명
  }

}

#config 테이블 정의 (JPA)
@Entity
@Table(name = "config")
@Getter
@Setter
@EqualsAndHashCode(callSuper = false, of = { "key" })
@NoArgsConstructor
public class Config {

  @Id
  @Column
  private String key;

  @Column
  private String value;

}

#프로퍼티 값 설정
@Component
public class ConfigUtil {

  @Autowired
  private DatabaseConfiguration dbConfig;

  // key, default value

  public int getRetryCount() {
    return dbConfig.getInteger("retry.count", 3);
  }

  public boolean isSendAsync() {
    return dbConfig.getBoolean("send.async", false);
  }

  public String getPaymentServerAddress() {
    return dbConfig.getString("payment.server.address", "www.pay.com");
  }

}

동작 방식 예시
1. config 테이블에서 retry.count 라는 key로 DB를 읽는다.
2. DB에서 해당 key의 value 값을 반환한다.
3. DB에 해당 key가 존재하지 않으면 default 값으로 지정된 3을 반환한다.

추가적으로 해당 설정값들의 변경될 가능성을 감안하여 캐시를 적용하면 DB를 읽는 수고를 덜 수 있다.


IntelliJ PMD Plugin 

PMD
: 자바 컴파일러가 잡아내지 않는 버그나 잘못된 코드 패턴을 찾아주는 정적 코드 분석 도구

IntelliJ 에서 먼저 PMD Plugin을 아래와 같이 설치한다.
Preference > Plugins > Browse repositories... > pmd 검색 > PMD Plugin Install > Restart

설치하면 Other Settings에 PMD 항목이 추가된 것을 확인할 수 있다.
기본적으로 정의되어 있는 ruleset을 사용할 수 있지만 커스텀하여 사용할 수 있다.
어떤 코드를 잘못된 오류로 볼 것인지 결정하여 ruleset을 작성하여 아래오 같이 설정 파일을 등록하면 된다.
잘못된 ruleset 파일을 import 하게되면 파일을 점검하라는 알림창이 뜨게 된다.
그러나 문제는 이미 import한 파일을 수정하다가 오타가 생긴 경우이다. PMD 실행시 파일을 스캔하다가 종료되는듯 해보인다. 
그런데 파일이 잘못 되었다는 알림 같은 것이 없어 당황했다. 뒤늦게 오타가 있음을 발견하여 수정 후 정상 실행을 확인했다.

실행방법은 아래와 같다. 검사하고 싶은 패키지나 파일들을 우클릭해 Run PMD 에서 적용할 ruleset을 선택한다.
Pre Defined 는 기본적으로 지원하는 것이고, 커스텀 파일을 등록했다면 Custom Rules 에서 선택한다.
Custom Rules 비활성화 혹은 설정한 커스텀 파일명아 보이지 않는 경우에는 지체없이 재시작한다. IDE Restart!

실행하면 아래와 같이 검출된 항목이 타입별로 분류된다. 
그러나 정렬 기준이 뭔지는 모르겠다.. 검색도 되지 않아 내가 찾고자하는 타입을 찾기가 어려운게 단점이다.
Rerun > 최근 적용한 ruleset 으로 현재 오픈된 클래스 기준으로 재검출. 클래스 단위로 검사시 편함!
Export to Text File > PMD Results 를 html 파일로 export 파일 및 라인별로 위반한 룰을 안내. 보기 불편함..
Details > 위반한 룰에 대한 가이드 링크로 이동. 원인과 해결방안 제공. 이드 링크
가이드 링크의 내용을 번역하여 정리해 놓은 링크 공유합니다. PMD 한글번역

PMD를 통해 검출된 항목들을 검토하다 보면 코드 패턴 및 작성 요령을 깨달을 수 있다. 뿐만 아니라 JVM 내부 동작 같은 것도 알 수 있어 큰 도움이 된다!


'Development > Tool' 카테고리의 다른 글

VirtualBox 해상도 조절  (0) 2017.07.28
IntelliJ 단축키 정리  (0) 2017.03.21

ssh 접속 불가 현상 해결

ssh 명령을 통해 네트워크 상의 다른 컴퓨터에 로그인하거나 원격 시스템에서 명령을 실행할 수 있다.

최초 아래와 같이 명령을 실행하면 해당 서버에 연결되며 인증키를 발급 받는다.
이후 연결시에는 발급된 인증키를 통해 연결된다.

$ ssh root@11.222.33.4

그러나 다음과 같은 오류가 발생하며 접속되지 않는 경우도 있다.
WARNING : REMOTE HOST IDENTIFICATION HAS CHANGED!

원인은 
11.222.33.4 이라는 IP로 기존에 접속한 적이 있는 서버와 이미 공유키를 교환한 상태에서 
11.222.33.4 이라는 서버가 변경되었기 때문이다.

문제가 되는 키를 초기화 해주는 명령이 필요하다.
$ ssh-keygen -R 11.222.33.4

명령을 실행하면 .ssh/known_hosts 파일이 업데이트된다.

그리고 나서 다시 접속 명령을 수행하면 새로운 키가 생성되어 접속할 수 있게 된다.
The authenticity of host '11.222.33.4 (11.222.33.4)' can't be established.
ECDSA key fingerprint is SHA256:sakjfhsdjfhweasfasdffiwfksfasfjksfhdk
Are you sure you want to continue connecting (yes/no)? yes


'Development > Etc' 카테고리의 다른 글

GeoIP 자동 업데이트 구성  (0) 2019.01.16

UnsupportedClassVersionError 에러 해결


Unsupported major.minor version 52.0

위 에러가 발생하여 찾아보니 원인은 빌드한 자바 버전보다 낮은 버전의 자바 컴파일러에서 실행시 발생한다고 한다.

    • Java SE 10 = 54
    • Java SE 9 = 53
    • Java SE 8 = 52
    • Java SE 7 = 51
    • Java SE 6.0 = 50
    • Java SE 5.0 = 49
    • JDK 1.4 = 48
    • JDK 1.3 = 47
    • JDK 1.2 = 46
    • JDK 1.1 = 45

실제로 빌드한 자바 버전은 1.8 이었고, 배포할 서버의 자바 버전은 1.7.x 였다.

$ java -version 

두 가지의 해결 방안이 있어 전자로 선택했다.

1) 배포할 서버의 자바 버전을 1.8.x로 업데이트 한다.
2) 1.7 버전으로 빌드한다.

$ yum install -y java-1.8.0-openjdk

결론빌드/배포 할 자바 버전을 동일하게 설정해야 한다.

'Development > Java' 카테고리의 다른 글

SSHProtocolException 에러 해결  (0) 2019.01.12
Garbage Collection 방식  (0) 2018.05.12
Garbage Collection 과정  (0) 2018.04.28
Garbage Collection 용어 정리  (0) 2018.04.26
AES256 암호화 오류 해결  (4) 2017.10.06

Configuration Condition

조건에 따라 환경을 설정할 수 있다.
RabbitMQ를 사용하는 서버와 사용하지 않는 서버의 환경을 달리 설정하는데 적용했다.

- Example
amqp.enabled 프로퍼티로 amqp 사용여부를 판단한다.
amqp를 사용하는 경우, 해당 클래스에 정의한 빈들을 등록한다.
amqp를 사용하지 않는 경우, 빈을 등록하지 않는다.

@Configuration
@Conditional(AmqpConfig.Condition.class)
public class AmqpConfig {

  @Autowired
  private Environment env;

  public static class Condition implements ConfigurationCondition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
      return context.getEnvironment().getProperty("amqp.enabled", Boolean.class);
    }

    @Override
    public ConfigurationPhase getConfigurationPhase() {
      return ConfigurationPhase.PARSE_CONFIGURATION;
    }
  }

  // @Bean
}

컨텍스트에 특정 빈이 등록되어 있는가? 조건 등으로도 사용할 수 있다.

Garbage Collection 방식

Java 버전별 GC 방식 default value

    • Java 7 - Parallel GC
    • Java 8 - Parallel GC
    • Java 9 - G1 GC

GC 방식
  • Serial GC
  • Parallel GC
  • Parallel Compacting GC
  • Concurrent Mark Sweep GC
  • G1(Garbage First) GC

상세 내용은 하단 링크에 잘 설명되어 있으므로 생략!

GC 방식 상세 링크1 
GC 방식 상세 링크2

'Development > Java' 카테고리의 다른 글

SSHProtocolException 에러 해결  (0) 2019.01.12
UnsupportedClassVersionError 에러 해결  (0) 2018.07.16
Garbage Collection 과정  (0) 2018.04.28
Garbage Collection 용어 정리  (0) 2018.04.26
AES256 암호화 오류 해결  (4) 2017.10.06
가비지 컬렉션 과정

Java에서는 개발자가 메모리를 명시적으로 해제하지 않는다. 대신에 Garbage Collector가 더 이상 필요 없는 객체를 찾아 지우는 작업을 한다. 이 가비지 컬렉터는 두 가지 가설 전제 하에 만들어졌다.

Weak Generational Hypothesis
        1. 대부분의 객체는 금방 접근 불가능 상태가 된다. 할당되고 짧은 시간 내에 사용이 종료되어 불필요한 상태인 것이 거의 대부분이다.
        2. 오래된 객체에서 젊은 객체로의 참조는 거의 발생하지 않는다.

이 가설의 장점을 최대한 살리기 위해서 메모리를 몇가지 영역으로 분리하여 관리하는 것이 효과적이다.

JVM Memory 영역
- Young 영역 : 새롭게 생성한 객체의 대부분이 여기에 위치한다. 대부분의 객체가 금방 접근 불가능 상태가 되기 때문에 매우 많은 객체가 Young 영역에 생성되었다가 사라진다.
Old 영역 : 접근 불가능 상태로 되지 않아 Young 영역에서 살아남은 객체가 여기로 복사된다. 대부분 Young 영역보다 크게 할당하며, 크기가 큰 만큼 Young 영역보다 GC는 적게 발생한다. 
- Perm 영역 : Method 영역이라고도 부른다. Class, Method 등 메타 데이터가 저장된다. 

GC 종류
        - Minor GC : Young 영역에서 발생하는 GC
        - Major(Full) GC : Old 영역에서 발생하는 GC

GC 발생 과정
1. 새로 생성한 대부분의 객체는 Eden 영역에 위치한다.
2. Eden 영역이 가득 차 Minor GC가 발생하면 살아남은 객체가 Survivor 영역 중 하나로 이동해서 Age가 증가한다.
3. 다시 Eden 영역이 가득 차 Minor GC가 발생하면 이미 살아남은 객체가 존재하는 Survivor 영역으로 이동해서 Age가 증가한다.
4. 그 Survivor 영역이 가득 차면 그 중에서 살아남은 객체가 다른 Survivor 영역으로 이동해서 Age가 증가한다.
5. 이 과정을 반복하다가 계속해서 살아남은 객체의 Age가 threshold 설정 값(default 16)이 되면 Old 영역으로 이동한다.
6. 이 과정을 반복하다가 Old 영역이 가득 차면 Major GC가 발생한다.

이 절차를 확인해 보면 두개의 Survivor 영역 중 하나는 반드시 비어 있는 상태로 남아 있어야 한다. 만약 두 Survivor 영역에 모두 데이터가 존재하거나, 두 Survivor 영역 모두 사용량이 0이라면 시스템이 정상적인 상황이 아니라고 생각하면 된다.


참조 링크 - Java Garbage Collection

'Development > Java' 카테고리의 다른 글

UnsupportedClassVersionError 에러 해결  (0) 2018.07.16
Garbage Collection 방식  (0) 2018.05.12
Garbage Collection 용어 정리  (0) 2018.04.26
AES256 암호화 오류 해결  (4) 2017.10.06
반복문 성능 비교  (0) 2017.07.27

+ Recent posts