๋ฌธ์ ์ํฉ
๋ฐฐํฌ๋ฅผ ํ๊ธฐ ์ํด ์๋ฒ๋ฅผ ๋ด๋ฆด ๋ ํํ ์๋ฒ๊ฐ ์ด๋ค ์์ฒญ์ ์ํํ๊ณ ์๋ค๋ฉด ์ด๋ป๊ฒ ๋ ๊น์?
ํด๋น ์์ฒญ์ ๋๊น์ง ์ํํ์ง ๋ชปํ๊ณ ์คํจํ๊ฒ ๋ ๊ฒ์
๋๋ค.
ํนํ ๋ฐฐํฌ๊ฐ ์ฆ์ ํ๊ฒฝ์ด๋ผ๋ฉด ๋์ฑ ๋ฌธ์ ๊ฐ ์๊ธธ ์ ์์ ๊ฒ์
๋๋ค.
[๋ณดํ] ํ๋ก์ ํธ๋ฅผ ํ๋ฉด์ ์ ๋ง ์งง์ ์ฃผ๊ธฐ๋ก ๋ฐฐํฌ๋ฅผ ํด์๋๋ฐ, ์์ ๊ฐ์ ์ํฉ์ ๋ํด์ ์ด๋ ํ ์ฒ๋ฆฌ๋ ํ์ง ์๊ณ ์์์ต๋๋ค.
๋ํ ๋น๋๊ธฐ ์ด๋ฒคํธ ์ฒ๋ฆฌ๋ฅผ ํ๋ ๋ก์ง์ด ๋์ด๋๋ฉด์, ์๋ฒ๋ฅผ ๋ด๋ฆด ๋ ํํ ๋น๋๊ธฐ ํ์ ๋ค์ด๊ฐ ์๋ ์์ฒญ๋ค์ ๋ชจ๋ ์์ด๋ฒ๋ฆด ๊ฒ์ด๋ผ๋ ๋ฌธ์ ๊ฐ ์์์ต๋๋ค.
์ค์ ๋ก ๊ทธ๋ด๊น?
์ค์ ๋ก ์์ฒญ๋ค์ ๋ชจ๋ ์์ด๋ฒ๋ฆด์ง ํ์ธ์ ํด๋ณด๊ฒ ์ต๋๋ค.
์ฐ์ ๊ฐ๋จํ๊ฒ ์์ฒญ์ด ์์ฒญ ๋๋ฆฌ๊ฒ ์ฒ๋ฆฌ๋๋ api ํ๋๋ฅผ ๋ง๋ค์์ต๋๋ค.
์ค๋ ๋๋ฅผ 20์ด๊ฐ sleep ์ํจ ํ "Request Success!"๋ฅผ ๋ฐํํ๋ api ์
๋๋ค.
@GetMapping("/test")
fun test(): String {
Thread.sleep(20000);
return "Request Success!"
}
์ api๋ฅผ ์์ฒญํ๋ฉด ์๋ต์ด ์ค๊ธฐ๊น์ง ๋ก๋ฉ์ ํฉ๋๋ค.
๊ทธ๋ฆฌ๊ณ 20์ด๊ฐ ์ง๋๊ธฐ ์ ์ ์๋ฒ๋ฅผ ์ข ๋ฃํ๋ฉด ์๋์ ๊ฐ์ด ์๋ต์ ๋ฐ์ง ๋ชปํ๊ณ ์คํจํฉ๋๋ค.
ํด๊ฒฐ ๋ฐฉ๋ฒ
ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์ ๋ง ๊ฐ๋จํฉ๋๋ค.
Spring boot์์ ์ ๊ณตํ๋ graceful shutdown์ ์ฌ์ฉํ๋ฉด ํ์ฌ ์ฒ๋ฆฌ์ค์ธ ์์
์ ๋ชจ๋ ์ข
๋ฃํ ํ ์๋ฒ๊ฐ ์์ ํ๊ฒ ์ค๋จ๋ฉ๋๋ค.
๊ทธ๋ฆฌ๊ณ ์๋ฒ๋ฅผ ๋ด๋ฆฐ ํ์๋ ๋์ด์ ์๋ก์ด ์์ฒญ์ ๋ฐ์ง ์์ต๋๋ค.
์ค์ ํ๋ ๋ฐฉ๋ฒ์ application.yml ํ์ผ์ ์๋์ ๊ฐ์ด ์ถ๊ฐํ๋ฉด ๋ฉ๋๋ค.
server:
shutdown: graceful
๊ทธ๋ผ ๋ค์ ์์ api๋ฅผ ์์ฒญํด์ ์ด๋ป๊ฒ ๋ฌ๋ผ์ก๋์ง ๋ณด๊ฒ ์ต๋๋ค.
์์ฒญ์ ๋ณด๋ด๊ณ 10์ด ํ ์๋ฒ๋ฅผ ์ค๋จ์์ผฐ์ต๋๋ค.
์์ ๋ก๊ทธ์ ๊ฐ์ด, ์์ฒญ์ ์ฒ๋ฆฌํ ํ ์์ฒญ์ ๋ณด๋ธ ์์ ์ผ๋ก๋ถํฐ ์ฝ 20์ด ๋ค์ ์๋ฒ๊ฐ ์ค๋จ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
์๋ต๋ ์ ๋๋ก ์ค๋ ๊ฒ์ ํ์ธํ์ต๋๋ค.
๋น๋๊ธฐ ์ฒ๋ฆฌ๋ ํ์ธํด๋ณด์!
์ ๊ฐ ๊ฐ์ฅ ๊ถ๊ธํ ๊ฒ์ ๋น๋๊ธฐ ์ด๋ฒคํธ ์์ ๋ ์ ๋ง ์ ๋๋ก ์ฒ๋ฆฌํ ํ ์ข ๋ฃ๊ฐ ๋๋์ง ์์ต๋๋ค.
๊ทธ๋์ ๊ฐ๋จํ๊ฒ ์คํ์ ํด๋ดค์ต๋๋ค.
@GetMapping("/test")
fun test(): String {
for (i in 1..5) {
applicationEventPublisher.publishEvent(
EventDto(num = i)
)
}
return "Request Success!"
}
์ปจํธ๋กค๋ฌ ๋ฉ์๋๋ฅผ ์์ ๊ฐ์ด ๋ณ๊ฒฝํ์ต๋๋ค.
์์ฒญ์ด ๋ค์ด์ค๋ฉด 5๊ฐ์ ์ด๋ฒคํธ๋ฅผ ๋ฐํํฉ๋๋ค. ์ด๋ฒคํธ ์ฒ๋ฆฌ๋ ์๋์ ๊ฐ์ด ์ํํฉ๋๋ค.
@Async
@EventListener
fun handleEvent(event: EventDto) {
Thread.sleep((5000 * event.num).toLong())
println("${LocalDateTime.now()} - ${event.num}๋ฒ์งธ ์ด๋ฒคํธ ์ฒ๋ฆฌ ์๋ฃ")
}
์ฐ๋ ๋๋ฅผ sleep ์ํค๋๋ฐ, ์์ฒญ๋ง๋ค ์๊ฐ์ ์กฐ๊ธ์ฉ ๋๋ ธ์ต๋๋ค.
1๋ฒ์งธ ์ด๋ฒคํธ -> 5์ด ๋ค ์๋ฃ
2๋ฒ์งธ ์ด๋ฒคํธ -> 10์ด ๋ค ์๋ฃ
...
๊ทธ๋ฆฌ๊ณ 1๋ฒ์งธ ์ด๋ฒคํธ๊ฐ ์๋ฃ๋ ํ ์๋ฒ๋ฅผ ์ค๋จ์์ผฐ์ต๋๋ค.
๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ์์ต๋๋ค.
์ด๋ฒคํธ ์ฒ๋ฆฌ๊ฐ ๋ชจ๋ ์๋ฃ ๋ค์ ์๋ฒ๊ฐ ์ค๋จ๋๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
์ด๋ ๊ฒ ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ ์์ ํ๊ฒ ๋ชจ๋ ์ํ๋๋ ๊ฒ์ ํ์ธํ์ต๋๋ค.
Graceful shutdown ๋ด๋ถ ๋์ ๊ณผ์
๊ทธ๋ ๋ค๋ฉด graceful shutdown์ ๋ด๋ถ์ ์ผ๋ก ์ด๋ป๊ฒ ๋์ํ๋์ง ์์๋ณด๊ฒ ์ต๋๋ค.
Webserver
public interface WebServer {
void start() throws WebServerException;
void stop() throws WebServerException;
int getPort();
default void shutDownGracefully(GracefulShutdownCallback callback) {
callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
}
}
์ WebServer ์ธํฐํ์ด์ค๋ฅผ Netty, Tomcat, Jetty, Undertow๊ฐ ๊ตฌํ์ ํฉ๋๋ค.
๊ทธ๋ฆฌ๊ณ ๊ฐ์ฅ ์๋์ shutDownGracefully๊ฐ ๋ํดํธ ๋ฉ์๋๋ก ๊ตฌํ๋์ด ์์ต๋๋ค.
ํฐ์บฃ์ ์ด๋ฅผ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ๋์ง ์ดํด๋ด ์๋ค.
TomcatWebserver
๊ฐ๋จํ๊ฒ ํ์ํ ๋ถ๋ถ๋ง ๊ฐ์ ธ์์ต๋๋ค.
public class TomcatWebServer implements WebServer {
private final Tomcat tomcat;
private final boolean autoStart;
private final GracefulShutdown gracefulShutdown;
private volatile boolean started;
public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
this.monitor = new Object();
this.serviceConnectors = new HashMap();
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
// graceful shutdown์ด ์ค์ ๋์ด ์๋ค๋ฉด GracefulShutdown ๊ฐ์ฒด๋ฅผ ์์ฑ
this.gracefulShutdown = shutdown == Shutdown.GRACEFUL ? new GracefulShutdown(tomcat) : null;
this.initialize();
}
public void shutDownGracefully(GracefulShutdownCallback callback) {
if (this.gracefulShutdown == null) {
callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
} else {
this.gracefulShutdown.shutDownGracefully(callback);
}
}
}
TomcatWebServer๋ฅผ ์์ฑํ ๋ graceful shutdown ์ค์ ์ด ๋์ด์๋ค๋ฉด GracefulShutdown ๊ฐ์ฒด๋ฅผ ์์ฑํฉ๋๋ค.
๊ทธ๋ฆฌ๊ณ ์ WebServer์ ์๋ ๋ฉ์๋ shutDownGracefully ๋ฅผ ์ค๋ฒ๋ผ์ด๋ํฉ๋๋ค. ๋ฉ์๋๋ฅผ ์ดํด๋ณด๋ฉด GracefulShutdown ์ธ์คํด์ค๊ฐ shutDownGracefully๋ฅผ ๊ตฌํํ๊ณ ์์ต๋๋ค.
GracefulShutdown์ shutdownGracefully
final class GracefulShutdown {
void shutDownGracefully(GracefulShutdownCallback callback) {
logger.info("Commencing graceful shutdown. Waiting for active requests to complete");
(new Thread(() -> {
this.doShutdown(callback);
}, "tomcat-shutdown")).start();
}
private void doShutdown(GracefulShutdownCallback callback) {
List<Connector> connectors = this.getConnectors();
connectors.forEach(this::close); // ์๋ก์ด ์์ฒญ์ ๋ฐ์ง ์๊ธฐ ์ํด ์ปค๋ฅ์
์ ๋ชจ๋ ์ข
๋ฃ
try {
Container[] var3 = this.tomcat.getEngine().findChildren();
int var4 = var3.length;
for(int var5 = 0; var5 < var4; ++var5) {
Container host = var3[var5];
Container[] var7 = host.findChildren();
int var8 = var7.length;
for(int var9 = 0; var9 < var8; ++var9) {
Container context = var7[var9];
while(this.isActive(context)) { // ์คํ์ค์ธ ์์ฒญ์ด ์๋์ง ํ์ธํ๋ฉฐ ๊ธฐ๋ค๋ฆผ
if (this.aborted) {
logger.info("Graceful shutdown aborted with one or more requests still active");
callback.shutdownComplete(GracefulShutdownResult.REQUESTS_ACTIVE);
return;
}
Thread.sleep(50L);
}
}
}
} catch (InterruptedException var11) {
Thread.currentThread().interrupt();
}
logger.info("Graceful shutdown complete");
callback.shutdownComplete(GracefulShutdownResult.IDLE);
}
}
doShutdown ๋ฉ์๋์์ Connector๋ฅผ ๋ชจ๋ ๊ฐ์ ธ์์ ์ข ๋ฃ์ํต๋๋ค. ์ด๋ ์๋ก์ด ์์ฒญ์ ๋ฐ์ง ์๊ธฐ ์ํ ์ฒ๋ฆฌ์ ๋๋ค.
์คํ์ค์ธ ์์ฒญ์ด ์๋์ง ํ์ธํ๊ณ , ๋ง์ฝ ์๋ค๋ฉด ์ค๋ ๋๋ฅผ sleep ์์ผฐ๋ค๊ฐ ๋ค์ ํ์ธํ๋ ๊ณผ์ ์ ๋ฐ๋ณตํฉ๋๋ค.
๋ชจ๋ ์์ฒญ์ด ์๋ฃ๋๋ฉด ์๋ฒ๋ฅผ ์ค๋จํฉ๋๋ค.
๊ฒฐ๋ก
๊ฒฐ๋ก ์ graceful shutdown์ผ๋ก ์๋ฒ๋ฅผ ์์ ํ๊ฒ ์ข ๋ฃํ๋๋ก ํฉ์๋ค!
'Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[JPA] ์ฐ๊ด๊ด๊ณ ๋งคํ ์ ๋ฆฌ (0) | 2023.07.13 |
---|---|
Dao์ Repository์ ๋ํด์... (0) | 2023.05.29 |
[Spring] Interceptor๋ ์ด๋ป๊ฒ ๋์ํ ๊น? - DispatcherServlet ๋ด๋ถ ์ฝ๋ ์ดํด๋ณด๊ธฐ (0) | 2023.05.08 |
Mockito.mock(), @Mock๊ณผ @MockBean์ ์ฐจ์ด์ (1) | 2023.04.23 |