1. DB Connection Pool 의 구현
가용한 연결이 없을 경우, getConnection()에서 최대 2초시 10회 즉, 20초간 루프를 돌려
blocking 이 되도록 프로그래밍 되어 있네요. 몇가지 실수하신 부분도 있는데, loop 를
currConnections 갯수만큼 1차 돌고 나서도 여전히 현재 모든 DB연결이 사용되고 있어서,
아직 maxConns 에 도달하지 않았으니 새로운 DB연결을 맺은 후, 곧바로 그 Connection을
return 해야 할 것을 덩달아 Thread.sleep(2000) 를 기다렸다가 다시 loop 를 들어가서
그제서야 Connection을 return 있습니다.
또한, 대기시간을 위한 구현의 문제인데,Thread.sleep(2000) 와 같이 sleep() 함수를
사용하는 것은 만약 그 sleep(2000) 사이에 가용한 Connection 이 생기더라도 그 시점에
바로 꺼내 갈 수 없다는 단점이 있습니다. wait() 과 notify() 를 사용해야 합니다.
그리고, "최대연결대기시간" 20 초는 실운영환경에서 너무나 긴 시간입니다.
이런 류의 상황은 시스템 폭주시에, 계속적으로 getConnection()을 요청하는 Request가
Queuing 되어, 결국 장애상황을 극복하지 못하고 "Hang현상"을 초래하는 원인이 됩니다.
이 메소드를 만든 분은, 소스 전반적으로 풍기는 사상이 많은 시간을 기다리더라도,
어떻게든, java.sql.Connection 을 return 해 주기를 원하고 있는 듯 합니다.
그러실 필요가 없습니다. 가용한 연결이 없을 경우, 약간의 시간(통상 2-3초) 이내에도
가용한 연결이 없다면, Exception 을 발생시키고 "시스템이 폭주하여 DB연결을 할 수
없습니다"와 같은 예외상황을 만들어 내어야 합니다. 이것이 PPC(Peak Point Control)
기법입니다. 실제 운영되는 시스템의 튜닝과 장애진단 경험을 몇번 해 보시면, 무슨
뜻인지를 이해하실 겁니다.
그리고, 아직 이 분은 Thread.sleep() 과 wait(), notify(), notifyAll() 의 적절한
용법을 아직 완벽하게 이해하고 계시지 않은 듯 합니다.
권장 사항은, Backup 을 만들어 두십시요. 저 역시 프로그래머의 고집과 아집이 있기
때문에, 위의 ConnectionBroker 라는 Connection Pool 을 만든 분 역시, 자신이 만든
클래스가 완벽하고 잘 사용되어지기를 바랄 것이며, 그것이 잘못되었다고 누군가 얘기할
때, 그 수모와 치욕을 참기 어려울 것입니다. 이해 합니다.
저의 기술적 소견으로는 앞서 지적드린 Thread.sleep(2000); 이라는 code 가
getConneciton() 이라는 함수에 들어가 있다는 것 하나만으로도, 남이 만든 Connection
Pool 의 기법을 참고 하셔야 할 것으로 사려 됩니다.
만약, 이 시스템이 "IBM WebSphere, BEA WebLogic" 등과 같은 "Application Server"
제품상에서 운영되는 것이라면, 해당 제품이 제공하는 Connection Pool 을 이용하십시요.
Tomcat 과 같은 ServletContainer 에서 운영되는 것이라면, 두가지 선택이 있는데,
하나는 DB Vendor 가 제공하는 JDBC 2.0 의 Connection Pool 기능을 이용하는 것이
있고, 아니면, 이처럼 손수 Connection Pool 을 제작하여 사용할 수 있습니다.
손수 제작할 경우는 Hans Vergsten 이 만든 DBConnectionManager.java 를 수정하여 사용
하시되, 한가지 기능, 즉, "일정한 시간동안 사용되지 않고 idle 하게 남아 있는
Connection을 pool 에 제거하는 기능"을 추가하여 사용하실 것을 권합니다.
몇가지 참고할 만한 글들을 권장드립니다.
Re: DB Connection Pool & 그 외의 것들
http://www.javaservice.net/~java/bbs/read.cgi?m=devtip&b=servlet&c=r_p&n=960704622
Object Pool Contorl 기법
http://www.javaservice.net/~java/bbs/read.cgi?m=devtip&b=javatip&c=r_p&n=974750350
PPC(Peek Point Control) 기법
http://www.javaservice.net/~java/bbs/read.cgi?m=qna&b=consult&c=r_p&n=996642473
서블렛 + JDBC 연동시 코딩 고려사항 -제2탄-
http://www.javaservice.net/~java/bbs/read.cgi?m=devtip&b=servlet&c=r_p&n=968522077
2. ConnectionBroker 는 Singleton Pattern 으로 운영되어야 합니다. 즉, 내부적으로
java.sql.Connection 을 pool 하는 manager 역할을 하고 있기 때문에, 해당 instance는
JVM상에서 단 하나이어야 합니다.
public abstract class WebbillBaseServlet extends BaseServlet {
protected ConnectionBroker connectionBroker;
public void init(ServletConfig config) throws ServletException {
super.init(config);
try{
connectionBroker = new ConnectionBroker();
} catch (IOException e){
e.printStackTrace();
CommonUtil.printLog(e.getMessage());
}
}
protected void freeConnection(java.sql.Connection con){
if(connectionBroker!=null)
this.connectionBroker.freeConnection(con);
}
public void destroy() {
super.destroy();
if(this.connectionBroker!=null)
this.connectionBroker.destroy();
}
}
위와 같이 WebbillBaseServlet 서블렛에 instance 변수로 선언되어 있고, init()에서
초기화가 된다면, 결국, 이 서블렛을 상속받아 만든 모든 서블렛마다 ConnectionBroker
의 서로다른 instance 를 가지게 되고, 이것은 애초의 Connection Pool 의 기능을
극히 비효율적으로 사용하게 됩니다. 더구나, 진숙씨가 지적했던 것 처럼, 이 서블렛을
상속받은 모든 개별적인 서블렛들이 init()될 때 생성되고, destroy 될 때 사라지지
때문에, 결국, 이 서블렛을 상속받은 서블렛의 갯수만큼 connectionBroker 의 instance
가 생성되게 되고, 이는 각 connectionBroker 당 초기에 currConnections 수치만큼
물리적인 DB연결이 일어나므로, 결국, 운영시점에 필요로 하는 실제 DB연결갯수는
다음과 같이 됩니다.
DB연결갯수 = <connectionBroker당 currConnections 개수> x
<WebbillBaseServlet 서블렛을 상속받아 초기화된 모든 서블렛개수>
이것이 애초에 이 시스템을 설계했던 분의 의도는 분명 아닐 겁니다.
따라서, 다음과 같이 Singleton Pattern 구조로 변경되어야 합니다.
public class ConnectionBroker implements Runnable
{
private static ConnectionBroker broker = null;
public synchronized ConnectionBroker getConnectionBroker() {
if ( broker == null ) broker = new ConnectionBroker();
return broker;
}
....
}
public abstract class WebbillBaseServlet extends BaseServlet {
protected ConnectionBroker connectionBroker;
public void init(ServletConfig config) throws ServletException {
super.init(config);
try{
//connectionBroker = new ConnectionBroker();
connectionBroker = ConnectionBroker.getConnectionBroker();
} catch (IOException e){
e.printStackTrace();
CommonUtil.printLog(e.getMessage());
}
}
....
}
이렇게 함으로서, WebbillBaseServlet 에 선언된 broker에 대한 instance 변수는 이
서블렛을 상속받은 모든 서블렛에 각기 서로다른 reference를 갖게 되는 것은
동일하지만, 결국은 JVM상 단하나 뿐인 ConnectionBroker 의 동일한 instance 를
가리키게 됩니다.
사실, ConnectionBroker 의 instance 를 얻는 과정은 이처럼 굳지 WebbillBaseServlet
의 member 변수로 배치시킬 필요는 전혀 없습니다. 최종적으로 DB Connection 을 필요로
하는 시점에서 다음과 같은 유형의 형식으로 사용하는 것으로 충분합니다.
ConnectionBroker broker = ConnectionBroker.getConnectionBroker(); //--- (1)
Statement stmt = null;
try {
Connection conn = borker.getConnection();
stmt = conn.createStatement();
.....
}
finally{
if ( stmt != null ) try{stmt.close();}catch(Exception e){}
if ( conn != null ) broker.freeConnection(conn);
}
위 처럼 사용하지 않고, broker 의 reference를 WebbillBaseServlet 서블렛에 두는
구조는 단지 위에서 (1) 번 한 라인을 적지 않아도 된다는 시덥잖은 장점 밖에 가지지
않습니다.
3. 서블렛의 instance 변수는 서블렛이 Thread 로 동작하지 때문에 공유됩니다.
public class NonePGResultServlet extends WebbillBaseServlet
{
//PDA결제 여부
private boolean okPda = false;
//PDA결제시 이전 서블릿
private String BEFORE_URL = "";
위에서 BEFORE_URL 과 같은 변수는 서로다른 사용자에 따라 호출시점에 바뀝니다.
이런 류는 이처럼 member 변수로 선언하여 공유시켜서는 안됩니다. 아래 문서에서
입이 마르고 닳도록 얘기를 드렸습니다.
서블렛 + JDBC 연동시 코딩 고려사항 -제1탄-
http://www.javaservice.net/~java/bbs/read.cgi?m=devtip&b=servlet&c=r_p&n=968185187
가용한 연결이 없을 경우, getConnection()에서 최대 2초시 10회 즉, 20초간 루프를 돌려
blocking 이 되도록 프로그래밍 되어 있네요. 몇가지 실수하신 부분도 있는데, loop 를
currConnections 갯수만큼 1차 돌고 나서도 여전히 현재 모든 DB연결이 사용되고 있어서,
아직 maxConns 에 도달하지 않았으니 새로운 DB연결을 맺은 후, 곧바로 그 Connection을
return 해야 할 것을 덩달아 Thread.sleep(2000) 를 기다렸다가 다시 loop 를 들어가서
그제서야 Connection을 return 있습니다.
또한, 대기시간을 위한 구현의 문제인데,Thread.sleep(2000) 와 같이 sleep() 함수를
사용하는 것은 만약 그 sleep(2000) 사이에 가용한 Connection 이 생기더라도 그 시점에
바로 꺼내 갈 수 없다는 단점이 있습니다. wait() 과 notify() 를 사용해야 합니다.
그리고, "최대연결대기시간" 20 초는 실운영환경에서 너무나 긴 시간입니다.
이런 류의 상황은 시스템 폭주시에, 계속적으로 getConnection()을 요청하는 Request가
Queuing 되어, 결국 장애상황을 극복하지 못하고 "Hang현상"을 초래하는 원인이 됩니다.
이 메소드를 만든 분은, 소스 전반적으로 풍기는 사상이 많은 시간을 기다리더라도,
어떻게든, java.sql.Connection 을 return 해 주기를 원하고 있는 듯 합니다.
그러실 필요가 없습니다. 가용한 연결이 없을 경우, 약간의 시간(통상 2-3초) 이내에도
가용한 연결이 없다면, Exception 을 발생시키고 "시스템이 폭주하여 DB연결을 할 수
없습니다"와 같은 예외상황을 만들어 내어야 합니다. 이것이 PPC(Peak Point Control)
기법입니다. 실제 운영되는 시스템의 튜닝과 장애진단 경험을 몇번 해 보시면, 무슨
뜻인지를 이해하실 겁니다.
그리고, 아직 이 분은 Thread.sleep() 과 wait(), notify(), notifyAll() 의 적절한
용법을 아직 완벽하게 이해하고 계시지 않은 듯 합니다.
권장 사항은, Backup 을 만들어 두십시요. 저 역시 프로그래머의 고집과 아집이 있기
때문에, 위의 ConnectionBroker 라는 Connection Pool 을 만든 분 역시, 자신이 만든
클래스가 완벽하고 잘 사용되어지기를 바랄 것이며, 그것이 잘못되었다고 누군가 얘기할
때, 그 수모와 치욕을 참기 어려울 것입니다. 이해 합니다.
저의 기술적 소견으로는 앞서 지적드린 Thread.sleep(2000); 이라는 code 가
getConneciton() 이라는 함수에 들어가 있다는 것 하나만으로도, 남이 만든 Connection
Pool 의 기법을 참고 하셔야 할 것으로 사려 됩니다.
만약, 이 시스템이 "IBM WebSphere, BEA WebLogic" 등과 같은 "Application Server"
제품상에서 운영되는 것이라면, 해당 제품이 제공하는 Connection Pool 을 이용하십시요.
Tomcat 과 같은 ServletContainer 에서 운영되는 것이라면, 두가지 선택이 있는데,
하나는 DB Vendor 가 제공하는 JDBC 2.0 의 Connection Pool 기능을 이용하는 것이
있고, 아니면, 이처럼 손수 Connection Pool 을 제작하여 사용할 수 있습니다.
손수 제작할 경우는 Hans Vergsten 이 만든 DBConnectionManager.java 를 수정하여 사용
하시되, 한가지 기능, 즉, "일정한 시간동안 사용되지 않고 idle 하게 남아 있는
Connection을 pool 에 제거하는 기능"을 추가하여 사용하실 것을 권합니다.
몇가지 참고할 만한 글들을 권장드립니다.
Re: DB Connection Pool & 그 외의 것들
http://www.javaservice.net/~java/bbs/read.cgi?m=devtip&b=servlet&c=r_p&n=960704622
Object Pool Contorl 기법
http://www.javaservice.net/~java/bbs/read.cgi?m=devtip&b=javatip&c=r_p&n=974750350
PPC(Peek Point Control) 기법
http://www.javaservice.net/~java/bbs/read.cgi?m=qna&b=consult&c=r_p&n=996642473
서블렛 + JDBC 연동시 코딩 고려사항 -제2탄-
http://www.javaservice.net/~java/bbs/read.cgi?m=devtip&b=servlet&c=r_p&n=968522077
2. ConnectionBroker 는 Singleton Pattern 으로 운영되어야 합니다. 즉, 내부적으로
java.sql.Connection 을 pool 하는 manager 역할을 하고 있기 때문에, 해당 instance는
JVM상에서 단 하나이어야 합니다.
public abstract class WebbillBaseServlet extends BaseServlet {
protected ConnectionBroker connectionBroker;
public void init(ServletConfig config) throws ServletException {
super.init(config);
try{
connectionBroker = new ConnectionBroker();
} catch (IOException e){
e.printStackTrace();
CommonUtil.printLog(e.getMessage());
}
}
protected void freeConnection(java.sql.Connection con){
if(connectionBroker!=null)
this.connectionBroker.freeConnection(con);
}
public void destroy() {
super.destroy();
if(this.connectionBroker!=null)
this.connectionBroker.destroy();
}
}
위와 같이 WebbillBaseServlet 서블렛에 instance 변수로 선언되어 있고, init()에서
초기화가 된다면, 결국, 이 서블렛을 상속받아 만든 모든 서블렛마다 ConnectionBroker
의 서로다른 instance 를 가지게 되고, 이것은 애초의 Connection Pool 의 기능을
극히 비효율적으로 사용하게 됩니다. 더구나, 진숙씨가 지적했던 것 처럼, 이 서블렛을
상속받은 모든 개별적인 서블렛들이 init()될 때 생성되고, destroy 될 때 사라지지
때문에, 결국, 이 서블렛을 상속받은 서블렛의 갯수만큼 connectionBroker 의 instance
가 생성되게 되고, 이는 각 connectionBroker 당 초기에 currConnections 수치만큼
물리적인 DB연결이 일어나므로, 결국, 운영시점에 필요로 하는 실제 DB연결갯수는
다음과 같이 됩니다.
DB연결갯수 = <connectionBroker당 currConnections 개수> x
<WebbillBaseServlet 서블렛을 상속받아 초기화된 모든 서블렛개수>
이것이 애초에 이 시스템을 설계했던 분의 의도는 분명 아닐 겁니다.
따라서, 다음과 같이 Singleton Pattern 구조로 변경되어야 합니다.
public class ConnectionBroker implements Runnable
{
private static ConnectionBroker broker = null;
public synchronized ConnectionBroker getConnectionBroker() {
if ( broker == null ) broker = new ConnectionBroker();
return broker;
}
....
}
public abstract class WebbillBaseServlet extends BaseServlet {
protected ConnectionBroker connectionBroker;
public void init(ServletConfig config) throws ServletException {
super.init(config);
try{
//connectionBroker = new ConnectionBroker();
connectionBroker = ConnectionBroker.getConnectionBroker();
} catch (IOException e){
e.printStackTrace();
CommonUtil.printLog(e.getMessage());
}
}
....
}
이렇게 함으로서, WebbillBaseServlet 에 선언된 broker에 대한 instance 변수는 이
서블렛을 상속받은 모든 서블렛에 각기 서로다른 reference를 갖게 되는 것은
동일하지만, 결국은 JVM상 단하나 뿐인 ConnectionBroker 의 동일한 instance 를
가리키게 됩니다.
사실, ConnectionBroker 의 instance 를 얻는 과정은 이처럼 굳지 WebbillBaseServlet
의 member 변수로 배치시킬 필요는 전혀 없습니다. 최종적으로 DB Connection 을 필요로
하는 시점에서 다음과 같은 유형의 형식으로 사용하는 것으로 충분합니다.
ConnectionBroker broker = ConnectionBroker.getConnectionBroker(); //--- (1)
Statement stmt = null;
try {
Connection conn = borker.getConnection();
stmt = conn.createStatement();
.....
}
finally{
if ( stmt != null ) try{stmt.close();}catch(Exception e){}
if ( conn != null ) broker.freeConnection(conn);
}
위 처럼 사용하지 않고, broker 의 reference를 WebbillBaseServlet 서블렛에 두는
구조는 단지 위에서 (1) 번 한 라인을 적지 않아도 된다는 시덥잖은 장점 밖에 가지지
않습니다.
3. 서블렛의 instance 변수는 서블렛이 Thread 로 동작하지 때문에 공유됩니다.
public class NonePGResultServlet extends WebbillBaseServlet
{
//PDA결제 여부
private boolean okPda = false;
//PDA결제시 이전 서블릿
private String BEFORE_URL = "";
위에서 BEFORE_URL 과 같은 변수는 서로다른 사용자에 따라 호출시점에 바뀝니다.
이런 류는 이처럼 member 변수로 선언하여 공유시켜서는 안됩니다. 아래 문서에서
입이 마르고 닳도록 얘기를 드렸습니다.
서블렛 + JDBC 연동시 코딩 고려사항 -제1탄-
http://www.javaservice.net/~java/bbs/read.cgi?m=devtip&b=servlet&c=r_p&n=968185187