LabHub
배우기 러닝패스 코스

ヒープは余っていたのにサービスが止まった

プールが枯れるとサービスが止まる

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

풀 고갈을 직접 만들고 타임아웃으로 빠른 실패로 바꾼 뒤, Tomcat 의 스레드 풀과 DB 커넥션 풀 설정을 별도 CATALINA_BASE 인스턴스에 넣어 실제로 띄운다.

왜 중요한가

서비스는 가장 작은 풀에서 멈춘다. 풀 크기보다 중요한 것이 대기 상한이다 — 상한이 없으면 느린 하류 하나가 모든 처리 스레드를 삼킨다. 자바 코드(Semaphore)와 Tomcat 설정(커넥터·DataSource) 양쪽에서 같은 원리를 손으로 확인한다. 재료 PoolDemo.java 는 127.0.0.1:8086(처리 스레드 4개, 커넥션 풀 2개, 쿼리 3초)과 관리 포트 8087(/stats, 별도 스레드)로 동작한다. Tomcat 은 $CATALINA_HOME 에 설치돼 있다.

단계

  1. /root/jvm/pool 에 PoolDemo.java 를 컴파일하고 nohup java -cp /root/jvm/pool PoolDemo > /root/jvm/pool/server.log 2>&1 & 로 띄운 뒤 pid 를 /root/jvm/pool/server.pid 에 적는다. curl -s http://127.0.0.1:8086/health 가 ok, curl -s http://127.0.0.1:8087/statsserved= 로 시작하는 한 줄을 돌려준다.
  2. /query 를 6개 동시에(curl -s -m 30 http://127.0.0.1:8086/query & 여섯 번) 보내고 1초 뒤 jcmd <pid> Thread.print > /root/jvm/pool/dump-pool.txt. parking to wait forSemaphore 가 있어야 하고, /statsmax_waiting 이 2 이상이어야 한다.
  3. server.pid 의 프로세스를 kill 하고 -Ddb.pool.timeout.ms=500 으로 다시 띄워 server.pid 를 갱신한다. 다시 /query 6개를 동시에 보내되 그중 하나의 결과를 curl -s -m 10 -o /dev/null -w '%{http_code}\n' … >> /root/jvm/pool/after-timeout.txt 로 모은다 — 503 이 섞여 있어야 한다.
  4. CATALINA_BASE 를 만든다: /root/jvm/pool/tc 아래 conf($CATALINA_HOME/conf 를 복사)·logs·temp·webapps·work 디렉터리.
  5. /root/jvm/pool/tc/conf/server.xml 의 8080 <Connector>maxThreads="50", acceptCount="20", connectionTimeout="5000" 을 둔다(기존 connectionTimeout 값은 바꾼다). XML 이 유효해야 한다.
  6. /root/jvm/pool/tc/conf/context.xml<Context> 안에 <Resource name="jdbc/app" auth="Container" type="javax.sql.DataSource" maxTotal="20" maxIdle="5" maxWaitMillis="2000" driverClassName="org.h2.Driver" url="jdbc:h2:mem:app" username="sa" password=""/> 를 넣는다.
  7. CATALINA_BASE=/root/jvm/pool/tc JAVA_OPTS=-Xmx256m catalina.sh start 로 인스턴스를 띄운다. 8080 이 HTTP 상태 줄을 돌려주고, 그 JVM 의 명령줄에 -Dcatalina.base=/root/jvm/pool/tc 가 있어야 한다.
  8. Tomcat JVM 의 덤프를 /root/jvm/pool/dump-tomcat.txt 에 남기고, 덤프에서 "http-nio-8080-exec- 로 시작하는 스레드 수와 server.xml 8080 커넥터의 maxThreads 를 읽어 /root/jvm/pool/sizing.txtexec_threads=<n>max_threads=<n> 두 줄로 적는다.

참고

PoolDemo 를 띄운다

/root/jvm/pool 에 PoolDemo.java 를 컴파일하고 nohup 으로 띄운 뒤 pid 를 /root/jvm/pool/server.pid 에 적는다. 8086 /health 가 ok, 8087 /stats 가 served= 로 시작한다.

javac -d /root/jvm/pool /opt/lab/fixtures/jvm/PoolDemo.java 뒤 nohup java -cp /root/jvm/pool PoolDemo > server.log 2>&1 & 와 echo $! > server.pid 입니다.

커넥션 풀을 말린다

/query 를 6개 동시에 보내고 1초 뒤 jcmd Thread.print > /root/jvm/pool/dump-pool.txt. parking to wait for 와 Semaphore 가 있어야 하고 /stats 의 max_waiting 이 2 이상이어야 한다.

for i in 1 2 3 4 5 6; do curl -s -m 30 http://127.0.0.1:8086/query & done; sleep 1; jcmd $(cat server.pid) Thread.print > dump-pool.txt. 커넥션 2개에 요청 6개면 4개가 기다립니다.

대기에 상한을 둔다

server.pid 의 프로세스를 kill 하고 -Ddb.pool.timeout.ms=500 으로 다시 띄워 server.pid 를 갱신한다. /query 6개를 동시에 보내며 결과 코드를 /root/jvm/pool/after-timeout.txt 에 모은다(503 이 섞여야 한다).

각 curl 에 -o /dev/null -w '%{http_code}\n' 을 주고 >> after-timeout.txt 로 모으세요. 500ms 안에 커넥션을 못 받은 요청이 503 입니다. 백그라운드 curl 을 기다릴 때 인자 없는 wait 를 쓰면 nohup 으로 띄운 java 까지 기다리므로 curl 의 pid 만 wait 하세요.

CATALINA_BASE 를 만든다

/root/jvm/pool/tc 아래 conf($CATALINA_HOME/conf 복사)·logs·temp·webapps·work 를 만든다.

mkdir -p tc/logs tc/temp tc/webapps tc/work && cp -r $CATALINA_HOME/conf tc/conf. 설치본(HOME)은 건드리지 않습니다.

커넥터의 세 숫자

/root/jvm/pool/tc/conf/server.xml 의 8080 Connector 에 maxThreads="50", acceptCount="20", connectionTimeout="5000" 을 둔다. XML 이 유효해야 한다.

배포판 server.xml 의 8080 커넥터에는 connectionTimeout="20000" 이 이미 있습니다 — 값을 바꾸고 두 속성을 더하세요. 속성은 여는 태그 안 어디에 있어도 됩니다.

커넥션 풀에 대기 상한을

/root/jvm/pool/tc/conf/context.xml 의 안에 name="jdbc/app" DataSource Resource 를 maxTotal="20" maxIdle="5" maxWaitMillis="2000" 으로 넣는다.

바로 앞에 한 줄을 넣으면 됩니다. maxWaitMillis 가 -1 이면 무한 대기 — 사건의 원인입니다.

인스턴스를 띄운다

CATALINA_BASE=/root/jvm/pool/tc JAVA_OPTS=-Xmx256m catalina.sh start 로 띄운다. 8080 이 HTTP 상태 줄을 돌려주고 JVM 명령줄에 -Dcatalina.base=/root/jvm/pool/tc 가 있어야 한다.

catalina.sh 는 $CATALINA_HOME/bin 에 있습니다. 기동에 몇 초 걸리니 curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/ 를 반복해 보세요(404 여도 상태 줄이 온 것입니다).

덤프에서 풀 스레드를 센다

Tomcat JVM 의 덤프를 /root/jvm/pool/dump-tomcat.txt 에 남기고 "http-nio-8080-exec- 로 시작하는 스레드 수와 server.xml 8080 커넥터의 maxThreads 를 /root/jvm/pool/sizing.txt 에 exec_threads= 과 max_threads= 으로 적는다.

pid 는 jcmd -l | awk '/Bootstrap/{print $1}'. 세는 법: grep -c '^"http-nio-8080-exec-' dump-tomcat.txt. minSpareThreads 기본 10 이라 기동 직후에는 10개 안팎입니다. max_threads 는 8080 커넥터의 값입니다 — 배포판 server.xml 의 주석에 든 Executor 예시(maxThreads=150)를 잡지 마세요.