Nov 11, 2020 - jmatter

JMeter 설치

https://jmeter.apache.org/download_jmeter.cgi

현재 버전, Apache JMeter 5.3 (Requires Java 8+) 을 다운받았습니다.

Apache Jmeter 실행

압축을 풀고 bin 폴더의 jmeter.bat 을 클릭하여 실행합니다.
기본적인 테스트를 위해 플러그인을 2개 설치합니다.

jmeter-plugins-manager-1.4.jar 와 jmeter-plugins-graphs-basic-2.0.jar

이 두개입니다.

플러그인 추가

https://jmeter-plugins.org/wiki/PluginsManager/

외의 플러그인을 다운받으면 현재 1.4 버전의 파일을 다운받을 수 있습니다.

jmeter-plugins-manager-1.4.jar

위의 페이지에서 설명하는데로, lib/ext 경로에 파일을 넣고 실행하면

Option > Plugins Manager 를 클릭하면, Installed Plugins 을 할 수 있으며, 거기서 3 Basic Graphs를 클릭합니다.

1차 테스트 시해 예상하지 못했던 2가지 에러가 발생했습니다.
하나는 시간이 지난 후에 갑작스럽게 발생하는,

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.util.ConcurrentModificationException

에러와,

500 Internal Server Error: [{"timestamp":"2020-11-10T09:03:50.900+0000","status":500,"error":"Internal Server Error","message":"Request processing failed; nested exception is org.springframework.core.task.TaskRejectedException: ... (440 bytes)]

500 에러 2가지가 부하 테스트를 진행하면서 발생했습니다.

  1. TCP_FIN_TIMEOUT 변경설정
cat /proc/sys/net/ipv4/tcp_fin_timeout

default 60 에서 8로 변경. 통신완료 후, 대기했다가 커넥션을 끊는 시간으로 알고 있는데, 해당 커넥션을 굳이 길게 잡을 필요 없어 보여 과감하게 낮췄습니다.
(실제 제가 운영하는 다른 서비스는 3초로 설정한 서비스도 존재합니다!

  1. IP_LOCAL_PORT_RANGE
    cat /proc/sys/net/ipv4/ip_local_port_range
    

    default 값이 32768 ~ 60999 로 되어있었는데요. 해당 내부 증가되는 포트 수를 15000 ~ 65000 정도로 늘렸습니다. 내부 소켓 통신이 빈번하게 늘어남에 따라 발생하는 이슈로,
    내부 증가포트를 늘려보았는데요.

위 2가지 수행시,

2차 테스트 진행시, 에러는 확연히 줄어들었지만,

500 Internal Server Error: [{"timestamp":"2020-11-10T09:03:50.900+0000","status":500,"error":"Internal Server Error","message":"Request processing failed; nested exception is org.springframework.core.task.TaskRejectedException: ... (440 bytes)]

는 여전히 발생하고 있었습니다. 원인 분석을 위해 소스를 분석해보니, 의미없는 소스 라인에 Aync 가 걸려있는 것을 확인하였고,
담당자에게 문의시, 해당 서비스에 굳이 Aync를 걸 필요없다는 답변을 받아, 해당 기능을 제거했습니다.

추후 서비스에 비동기 기능들이 필요한 곳에 구현되어있는 부분이 있는데, 서비스가 늘어남에 따라 해당 부분은 같이 고민해보기로 했습니다.

3차 테스트 진행시 1초당 10000건의 유입에서 fail이 발생하지 않는 것을 확인하였고,
실제 유입 수 5만건, ngnix 처리 건 5만건, 통계 데이터 4만 9천998건으로 2건의 유실로 최종 결론나, 서비스 안정화에 성공했다고 클로즈했습니다.

팀별 개발 시, 혹은 레거시와 리팩토링 작업시 양쪽에 데이터를 확인하여, 리팩토링시 장점이 명확한지를 확인하기에는 꼭 jmatter 등의 스트레스 테스트 등이 꼭 필요하다고 생각됩니다.

Nov 10, 2020 - spring config

spring config sever 설정값입니다.

application.yml

server:
  port: 9999

spring:
  cloud:
    config:
      server:
        git:
          uri: http://ip:port/xxx/config.git

dependencies 설정입니다. (gradle 기준)

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.cloud:spring-cloud-config-server'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}

어플리케이션은 아래와 같이 하나만 처리하시면됩니다.

@SpringBootApplication
@EnableConfigServer
public class SpringCloudConfigServerApplication {

  public static void main(String[] args) {
    SpringApplication.run(SpringCloudConfigServerApplication.class, args);
  }

}

어플리케이션 구동에 @EnableConfigServer 를 추가하시면 서버 구성이 완료 됩니다.

spring config client 설정값입니다.

server:
  port: 8080

spring:
  profiles:
    active: dev

  cloud:
    config:
      uri: http://xxx:9999
  application:
    name: aaaa

올려진 서버 cofig 정볼르 바라봅니다. 해당 git 서버에 올라간 값이 aaaa-dev, aaaa-prod 형태여야하며, application:name 이 없다면, application 을 자동으로 찾도록 되어있습니다.

@Component
@ConfigurationProperties(prefix="xxxx")
public class XXXConfiguration {
  String message;

  public String getMessage() {
    return message;
  }

  public void setMessage(String message) {
    this.message = message;
  }
}

configuration 을 정의하고, 해당값은 aaaa-prod 의 xxxx 란 속성 밑의 message라는 값을 가져오게 되어있습니다.

dependencies는 다음과 같습니다.

    implementation('org.springframework.cloud:spring-cloud-starter-config')
    implementation('org.springframework.boot:spring-boot-starter-actuator')

해당 값이 서비스에 설정되어야지만 정상적으로 동작하며, 프로퍼티 리로드를 적용하기 위해서는, actuator 를 설치한 후,

yml 파일에 추가합니다.

management:  #actuator
  endpoints:
    web:
      # base-path: / #default is /actuator prefix, can be modified here
      exposure:
        include: "refresh"

위와같이 설정하는 이유는 refresh 만 처리하기 위해서입니다.

curl -X POST xxx:port/actuator/refresh

시에 리플레쉬가 되는 것을 확인할 수 있습니다.

예전에는 프로퍼티 리로드라는 기능을 직접 구현해본적이 있습니다.

private static void _loadPropertiesUsingCommonsConfiguration(Properties props, File file) throws ConfigurationException {
  final PropertiesConfiguration config = new PropertiesConfiguration();
  config.setDelimiterParsingDisabled(true);
  config.setFile(file);
  config.load();
  final Iterator<String> it = config.getKeys();
  while (it.hasNext()) {
    final String sKey = it.next();
    props.put(sKey, config.getString(sKey));
  }
}

로 우선 서비스가 시작할때, Properties 에 등록을 한 후,
수정 사항이 있을 때마다,
별도의 api가,

PropertyLoader.getInstance().loadProperties(asFile);

해당 인스턴스의 프로퍼티를 리로드 하도록 구현한 것입니다.
이 버전의 가장 큰 문제는, 각 서버마다 직접 들어가서 properties 파일을 수정해야한다는 문제가 있었지요.

그게 번거로워, 해당 기능에 DB_CONFIG 라는 테이블을 만들어,
1차는 DB에서, DB에 데이터가 없을시에는 프로퍼티로 가져오도록 구현하는 기능을 추가한 적이 있습니다.

예전부터 spring config 에 대해서는 알고 있었으나,
예전 레거시 소스로도 충분히 대응가능 적용해보지 않았지만,
이번에 신규프로젝트 진행하면서 욕심을 내보았는데,

역시 새로운 기술은 항상 즐거운거 같습니다!

2020-11-12 추가 내용.. 현재 서비스 적용중에 잠시 보류하게 되었습니다.
처음 시작 시에 해당 config 값이 잘못되어있는 경우, (ex : boolean 변수에 String 값 boolean a = “1234”) 같은 값이 들어갈 경우, 서비스가 재시작이 안되게,

  cloud:
    config:
      uri: http://xxxx:9999
      fail-fast: true

fail-fast : true를 주어서, 에러가 나게 적용했습니다.
그러나 중간에 값을 변경후, 해당 클라이언트에 /actuator/refresh 할때 boolean 변수에 String 값을 강제로 넣을 경우, 적용은 되대, 해당 값을 get 할때 타입에러가 나는 문제가 생겼습니다.

검증을 하기 위한 별도의 소스를 추가 개발을 하던가, 에러시 defalut 값이 나갈 수 있게 하여 전체 변수를 String으로 변경하여, 초기 에러가 안나게 막든지, 아니면 이럴 경우 에러가 난 후, 적용이 안되게하든지 다양한 방법을 찾아봐야할 것으로 보입니다.
(기존 테스트 시에 에러가 나서 변경이 안되었던걸로 보이는데, 조금 더 테스트가 필요해보여, 실 적용은 우선 보류했습니다. Toy project로 올려야할듯..)

2020-12-28 추가 내용..

[WARN ][2020/12/28 17:10:10] o.s.c.c.c.ConfigServicePropertySourceLocator [140] 
Could not locate PropertySource: I/O error on GET request for 
"http://localhost:8888/xxx/ymkim": Connection refused: connect; nested exception is java.net.ConnectException: Connection refused: connect 

bootstrap.yml 파일의 경우 스프링부트 앱 기동시 application.yml 보다 먼저 로드되므로 src/main/resources/bootstrap.yml 파일의 설정정보를 아래와 같이 수정해줘야합니다.

server:
  port: 8081

spring:
  application:
    name: xxx
  cloud:
    config:
      uri: http://localhost:9999

아래와 같이 설정하면 굳이 중복되는 부분이 필요없어 application.yml 을 제거합니다.

Nov 3, 2020 - 시간 동기화 문제

* * * * * root /usr/sbin/ntpdate -b time.bora.net
* * * * * rdate -s time.bora.net

rdate - 원격타임서버로 부터 날짜시간정보 가져오기
rdate는 지정한 원격지의 타임서버로 부터 날짜시간정보를 받아와 보여주거나 날짜시간설정을 하는 명령어입니다.

NTP는 Network Time Protocol 약자로서 rdate와 비슷한 기능을 제공합니다.
ntpdate는 rdate를 이용한 방법보다 시간을 0.01초 이하의 오차로 맞출 수 있습니다.

각 서비스에서 테스트를 진행하는데,
시간 오차가 발생하여 확인해본 결과,
특정 서버에서 rdate 가 설치가 안되어있는 이슈를 발견하여 이력을 남깁니다.