티스토리 뷰

Graceful shutdown이란, 말 그대로 '우아한 끝내기'이다.

프로그램은 잘 돌아가는 것도 중요하지만 잘 죽는 것(?) 또한 중요하다.

 

어떤 코드를 수행하다 종료 명령이 떨어졌을 때 (ctrl + c) 하던 작업을 잘 마무리하고 프로그램이 종료되는 것이다.

가령, DB에 어떤 data를 쭉 저장하다가 갑자기 kill signal이 온다던가, 문제가 발생했을 때, 이전까지 DB에 저장한 것들을 다시 빼내든지(작업을 무효화) 하던 작업을 모두 끝내고 종료하든지 해야 한다. 그렇지 않으면 DB가 꼬일 위험이 있다.

 

한 달 동안 현장실습을 하면서 java로 크롤러를 만들었고, 프로그램 보수 작업을 하던 중 graceful shutdown을 알게 되었고, 구글과 책을 참고하면서 graceful shutdown을 기존 크롤러에 구현해 보았다.

 

 

 

java에서 graceful shutdown을 구현하기 위해서는 thread를 이용해야 한다.

먼저 상위 thread에서 shutDownHook이라는 thread를 생성해 놓는다.

그리고 33번째 줄이 바로 graceful shutdown을 위해 java에서 제공하는 메소드이다.

 

Runtime.getRuntime().addShutdownHook(ThreadName)

 

간단히 말해서, runtime 중 에러가 발생했을 때 parameter로 설정된 이름의 쓰레드를 실행시킨다.

그렇다면 위 예제에서 parameter인 shutDownHook이 정의가 되어있어야겠지?

 

 

ShutDownHook을 선언한 부분이다.

120~125번째 줄은 ShutDownHook의 constructor이다. super()는 thread()에 대응되어 하나의 Thread를 생성한다.

this.target = target 은 super()에서 만든 thread의 target을 parameter로 받은 target으로 설정하는 코드이다.

 

ShutDownHook이 construct되면 run( ) 이 자동으로 실행된다.

먼저 shutdown()은 프로그램 종료를 위해 flag를 setting한다. 

 

 

그리고 133번째 target.join()은 target이라는 쓰레드가 종료될 때까지 프로그램의 진행을 멈춰주는 메소드이다.

 

 


 

graceful shutdown이 걸릴 때 프로그램의 진행을 살펴보면 다음과 같다.

 

1. main thread

2. run thread

shutdown signal 발생

3. shutdown thread

  -> shutdown()        //프로그램 종료 flag setting

  -> target.join()       //shutdown thread 잠시 멈춤

4. run, main thread 종료

5. shutdown thread 종료

 


 

 

전체 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package dajaba;
 
 
 
public class DajabaThread implements Runnable {
 
    private boolean isAlive = true;
    Logger logger;
    private String htmlFilePath;
    private String dataBaseDriverPath;
    private String dataBaseName;
    private int programCycle;
 
    DajabaThread(int _programCycle, String _databaseDriverPath, String _databaseName, String _databasePath,
            String _htmlFilePath, Logger _logger) {
        logger = _logger;
        htmlFilePath = _htmlFilePath;
        dataBaseDriverPath = _databaseDriverPath;
        dataBaseName = _databaseName;
        programCycle = _programCycle;
    }
 
    @Override
    public void run() {
        String thName = Thread.currentThread().getName();
        //shutDownHook을 초기화 해둔다.
        Thread shutDownHook = new ShutDownHook(Thread.currentThread(), "Shutdown");
     
        //runtime 중 에러가 발생하면 shutDownHook 이라는 thread를 실행시킨다.
        Runtime.getRuntime().addShutdownHook(shutDownHook);            
 
        while (isAlive()) {
            try {
                while (isAlive()) {
                    logger.info("process starts");
                    Vector<String> urlList = new Vector<String>();
                    Parser parser = new Parser(htmlFilePath);
                    try {
                        urlList = parser.openDoc();
                    } catch (Exception e) {
                        logger.warn("sth wrong in opening html file", e);
                    }
                    Vector<DataFromDB> dbData = new Vector<DataFromDB>();
 
                    DataBaseManager dbm = new DataBaseManager(dbData, dataBaseDriverPath, dataBaseName);
 
                    dbm.selectAll(dataBaseName);
                    int res;
                    for (String x : urlList) {
                        res = repCheck(x, dbData);
                        if (res == -1) { // if it is new URL
                            try {
                                dbm.insert(urlList, x, dataBaseName);
                            } catch (Exception e) {
                                logger.warn("sth wrong in inserting URL to DB", e);
                            }
                            logger.info("new URL " + x + " has been added to DB");
                        } else { // already in the DB
                            logger.debug("url is already in the DB");
                            try {
                                dbm.update(res, x, dataBaseName);
                                logger.debug("cnt for " + x + " has been updated");
                            } catch (Exception e) {
                                logger.warn("sth wrong in updating DB", e);
                            }
 
                        }
                    }
                    try {
                        dbm.dbClose();
                    } catch (Exception e) {
                        logger.warn("sth wrong in closing DB", e);
                    }
 
                    logger.info("process has been completed");
                    // if (isAlive() == false)
                    // break;
 
                    long timeToSleep = (long) programCycle;
                    TimeUnit time = TimeUnit.SECONDS;
                    long count = 0;
                    try {
                        while (count != timeToSleep && isAlive()) {
                            time.sleep(1);
                            count++;
                        }
                    } catch (InterruptedException e) {
                        logger.error("sth wrong in program sleep", e);
                    }
                }
 
            } catch (Exception e) {
                logger.error("sth wrong in scheduler", e);
            }
        }
        logger.info(thName + " is terminated");
    }
 
    public void shutdown() {
        logger.info("[" + Thread.currentThread().getName() + "] called shutdown");
        //thread의 flag를 false로 해서 프로그램이 종료되도록 한다.
        setAlive(false);                                    
 
    }
 
    public boolean isAlive() {
        return isAlive;
    }
 
    public void setAlive(boolean isAlive) {
        this.isAlive = isAlive;
    }
 
    private class ShutDownHook extends Thread {
        private Thread target;
 
        public ShutDownHook(Thread target, String name) {
            //"shutdown" 이라는 이름으로 Thread를 생성한다.
            super(name);                                    
            //이 thread의 target은 parameter로 받은 것과 동일하게 해준다.
            this.target = target;                            
        }
 
        public void run() {
            //shutdown() 함수를 호출한다.
            shutdown();                                        
 
            try {
                //target 쓰레드가 종료될 때 까지 기다린다.
            } catch (InterruptedException e) {
                logger.error("sth wrong in shutdown process", e);
            }
        }
 
    }
 
    public int repCheck(String url, Vector<DataFromDB> dbData) {
 
        for (int i = 0; i < dbData.size(); i++) {
            if (dbData.elementAt(i).urlDB.contentEquals(url)) {
                return dbData.elementAt(i).id;
            }
        }
        return -1;
    }
 
}
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함