JBoss & Tomcat – MySQL 이중화
<Jboss와 Tomcat을 통한 MySQL 이중화 연동 TEST>
1. JBOSS – Mysql 이중화
standalone-ha.xml에
다음과 같이 설정한다
<datasources>
<datasource jndi-name="java:jboss/MySqlDS" pool-name="MySqlDS">
<connection-url>
jdbc:mysql:loadbalance://[DB1_IP]:3306,[DB2_IP]:3306/mysql?useSSL=false&serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true
</connection-url>
<driver>mysql</driver>
<security>
<user-name>mysql</user-name>
<password>Root#1234!</password>
</security>
<validation>
<check-valid-connection-sql>SELECT 1</check-valid-connection-sql>
<validate-on-match>true</validate-on-match>
<background-validation>false</background-validation>
<valid-connection-checker class-name="org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLValidConnectionChecker"/>
</validation>
</datasource>
user-name, password, url의 내용은 DB 설정에따라 바꾸어야한다.
그리고 추가적으로 DB와 연동하려면 모듈 경로에 mysql-connector-j-8.3.0.jar, module.xml이 필요하다.

module.xml
<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.9" name="com.mysql">
<resources>
<resource-root path="mysql-connector-j-8.3.0.jar"/>
</resources>
<dependencies>
<module name="javax.api"/>
<module name="javax.transaction.api"/>
</dependencies>
</module>
다음으로 테스트를 진행한다.
먼저 DB 연결을 생성하기위해 jboss-cli.sh 로 test 요청을 보낸다.

그리고 연결 확인.

JBoss instance 하나에 두개의 DB가 연동된 상태
29번 DB shutdown

연결 확인. 23만 ESTABLISHED 되어있다.

23만 ESTABLISHED 되어있다.
다시 29번살리고 23번 다운,
위과정과 똑같이 test 연결 보낸후에 연결확인

29번만 ESTABLISHED 되어있다.
[옵션 정리]
<validate-on-match>true</validate-on-match>
– 풀에서 커넥션을 꺼낼 때마다 유효성 검사 , 장애 발생 시 깨진 커넥션 즉시 제거
<background-validation>false</background-validation>
– 백그라운드에서 주기적으로 커넥션 검사 여부, false → 실제 사용 시에만 검사
<check-valid-connection-sql>SELECT 1</check-valid-connection-sql>
– 쿼리를 보내 커넥션이 살아있는지 확인하는 SQL
<valid-connection-checker
class-name=”org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLValidConnectionChecker”/>
– MySQL 전용 커넥션 상태 검사 클래스, 단순 SQL 검사보다 정확하고 빠름 , 네트워크 단절/DB 다운 감지에 효과적
– 해당 클래스가 없을때 ,유효성 검사 x, 죽은 커넥션 그대로 반환, 풀은 “아 이 커넥션 썼다 실패했네” 정도만 인식
Replication 모드로 이중화 했을때 Master노드 Down 하고
jboss-cli 로 DB 연결 체크 했을때 멈춤현상
test-connection-in-pool()은 읽기/쓰기 구분 없이 “기본 커넥션”을 검사 하는데
Slave는 기본적으로 read-only
Master가 반드시 있어야 “쓰기 커넥션”이 유효
Master가 죽어 있으면 드라이버는 쓰기 커넥션 생성 실패
하지만 JBoss의 “헬스체크 방식”이 replication 구조와 안 맞는 것
* <connection-url> 옵션
autoReconnect=true → 접속 끊기면 자동 재연결
failOverReadOnly=false → failover 시에도 쓰기 가능
2. Tomcat – Mysql 이중화
vi [TOMCAT_HOME]/conf/server.xml
...
<GlobalNamingResources>
<Resource name="jdbc/JNDI_MYSQL"
auth="Container"
type="javax.sql.DataSource"
driverClassName="cohttp://m.mysql.cj.jdbc.Driver"
url="jdbc:mysql://[DB1_IP]:3306,[DB2_IP]:3306/mysql?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul"
username="mysql"
password="Root#1234!"
initialSize="5"
maxTotal="20"
maxIdle="20"
minIdle="5"
maxWaitMillis="5000"
testOnBorrow="true"
validationQuery="SELECT 1" />
</GlobalNamingResources>
...
위에서 url에 스키마네임( mysql) , username(mysql), password(Root#1234!) 값은
DB 설정에 따라 변경해주어야 하며,
Resource name( jdbc/JNDI_MYSQL)은 context.xml 의 값과 애플리케이션 소스에서 JNDI 과 맞춰주어야한다.
vi [TOMCAT_HOME]/conf/context.xml
...
<ResourceLink name="jdbc/JNDI_MYSQL" global="jdbc/JNDI_MYSQL" type="javax.sql.DataSource" />
<Loader jakartaConverter="TOMCAT" />
</Context>
그리고 mysql-connector-j 파일이 lib 폴더안에 있어야 한다.

추가적으로 DB 테스트용 소스이다.
conn.jsp
<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%@ page import="java.io.*" %>
<%@ page import="java.sql.*" %>
<%@ page import="java.util.*" %>
<%@ page import="javax.naming.*" %>
<%@ page import="javax.sql.DataSource" %>
<html>
<%!
String getStackTraceAsString(Exception e) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
e.printStackTrace(new PrintStream(baos));
return(baos.toString());
} %>
<body>
<%
Context ctx = null;
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
// [수정] @@hostname을 추가하여 현재 어느 DB 서버에 붙었는지 확인합니다.
// VERSION(): 버전, @@hostname: DB 호스트명, NOW(): 현재시간
String query = "SELECT @@hostname AS 'DB_HOST', VERSION() AS 'VERSION', NOW() AS 'TIME'";
try {
ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup("java:/comp/env/jdbc/JNDI_MYSQL");
conn = ds.getConnection();
stmt = conn.createStatement();
rs = stmt.executeQuery(query);
ResultSetMetaData rsmd = rs.getMetaData();
int numCols = rsmd.getColumnCount(); %>
<h3>MySQL DB Connection Test Success</h3>
<p>
<table border=1 cellspacing=1 cellpadding=5>
<tr bgcolor="#eeeeee">
<%
for (int i = 1; i <= numCols; i++) { %>
<td align=center>
<b> <%= rsmd.getColumnLabel(i) %> </b>
</td>
<%
} %>
</tr>
<%
while (rs.next()) { %>
<tr>
<%
for (int i = 1; i <= numCols; i++) { %>
<td align=center>
<%= rs.getString(i) %>
</td>
<%
} %>
</tr>
<%
}
} catch (Exception e) { %>
<br>
<h3 style="color:red;">Connection Failed!</h3>
<b>Error Message:</b> <%= e.getMessage() %>
<pre><%= getStackTraceAsString(e) %></pre>
<%
} finally {
try {
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();
} catch (Exception e) {
out.print("<br>Close Exception: " + e);
}
} %>
</table>
<p>
<b>연결된 JNDI 리소스:</b> java:/comp/env/jdbc/JNDI_MYSQL
</body>
</html>
위와 같이 셋팅이 되어있다면
JBOSS 와 똑같이 테스트하면 된다.
DB 연동후 페이지 호출


![]()
DB1번 기동중지 후 페이지 호출

DB2 번으로 연동된 모습을 확인
다시 DB1번 기동후 DB2번 shutdown 하면 똑같이 페일오버 되는것을 확인할수 있다.
자유롭게 댓글을 달아주세요! 언제나 환영합니다.
기타 문의: info@neoclova.co.kr
네오클로바 기술블로그 홈 바로가기: https://neoclova.net
네오클로바 홈페이지: http://neoclova.co.kr

url = jdbc:mysql:[loadbalance:]//[DB1_IP]:3306,[DB2_IP]:3306/mysql 인데. mysql 두 서버는 멀티 write 가 되는 상황인가요?
master, replication 이라고 하면, 어느쪽이 master 인지 어떻게 구분 되나요?
JNDI 로 연동하면 어플리케이션 입장에서는 하나의 DataSource 로 인식해서 CUD, R 구분해서 쿼리 하지 않을것 같습니다.
WAS 설정 전에 DB 설정부터 있어야 할것 같은데요. 혹시 미리 작성된 다른 포스트가 있나요?
안녕하세요.
질문주신 내용을 아래와 같이 답변드립니다.
1. url = jdbc:mysql:[loadbalance:]//[DB1_IP]:3306,[DB2_IP]:3306/mysql 인데. mysql 두 서버는 멀티 write 가 되는 상황인가요?
말씀 주신 멀티 write에 대한 DB 설정은 따로 하지 않았습니다.
2. master, replication 이라고 하면, 어느쪽이 master 인지 어떻게 구분 되나요?
기본적으로 앞에 적은 IP가 우선되어 사용됩니다.
Default
무조건 리스트의 첫 번째 IP(IP1)를 우선적으로 사용합니다.
Loadbalance 모드
Master라는 개념 자체가 희박합니다. 드라이버는 모든 서버를 대등한 작업자로 봅니다.
Replication 모드
리스트의 첫 번째 IP를 Master, 그 뒤에 오는 IP를 Slave로 고정해서 인식합니다.
3. JNDI 로 연동하면 어플리케이션 입장에서는 하나의 DataSource 로 인식해서 CUD, R 구분해서 쿼리 하지 않을것 같습니다
맞습니다. JNDI로 연동하면 애플리케이션 입장에서는 하나의 DataSource로 보이지만, replication 프로토콜을 사용하면 드라이버가 내부적으로 트랜잭션의 ReadOnly 속성을 감지하여 CUD는 Master로, R은 Slave로 지능적으로 분기해 줍니다. 따라서 실질적인 부하 분산을 위해서는 JNDI 설정과 더불어 애플리케이션 단의 ReadOnly 설정이 병행되어야 합니다.
1. Default 모드
– URL: jdbc:mysql://DB1,DB2/db
– 동작: 애플리케이션이 CUD를 하든 R을 하든, 드라이버는 현재 연결된 DB1 서버로 모든 쿼리를 던집니다.
– 구분 여부: 전혀 구분하지 않습니다. DB2는 Standby 상태입니다.
2. Loadbalance 모드
– URL: jdbc:mysql:loadbalance://DB1,DB2/db
– 동작: 애플리케이션이 커넥션을 요청할 때마다 드라이버가 DB1과 DB2를 번갈아 가며 연결해 줍니다.
– 구분 여부: 구분하지 않지만 분산은 됩니다. 하지만 쿼리 성격(CUD/R)을 보고 나누는 것이 아니라, 단순히 커넥션 단위로 나누는 것입니다.
운 좋게 Select 쿼리가 DB2 커넥션에 걸릴 수도 있지만, 중요한 Insert 쿼리도 똑같은 확률로 DB2에 걸립니다. (이때 DB2가 Slave라면 에러가 발생합니다.)
3. Replication 모드
– URL: jdbc:mysql:replication://DB1,DB2/db
– 동작: 이 모드에서도 애플리케이션은 하나의 DataSource를 보지만, 드라이버가 ‘상태 값’을 체크합니다.
– 구분 여부: 애플리케이션의 ‘명시적 요청’이 있을 때만 구분합니다.
Replication 모드에서는 Connection의 ReadOnly 설정이 핵심입니다. CUD는 기본적으로 첫 번째 서버(Source)로 가고, Read는 개발자가 코드에서 ‘읽기 전용’임을 명시했을 때만 두 번째 이후 서버(Replica)들로 분산됩니다. 따라서 부하 분산 효과를 보시려면 반드시 애플리케이션 소스 코드의 트랜잭션 설정이 병행되어야 합니다.
또한 현재 게시된 글은 미들웨어 담당 인력이 WAS 중심으로 작성한 내용으로, DB 설정 관련 주제는 아직 당사 블로그에 다뤄진 바가 없습니다.
Jboss 혹은 Tomcat 환경에서의 DB 설정 관련 주제는 향후 콘텐츠 논의 과정에서 하나의 의견으로 공유하고자 합니다.
관심을 가지고 의견 주셔서 감사합니다. 아직 해소되지 않은 부분이 있으시면 부담없이 댓글, 또는 별도 문의 주시기 바랍니다. ^^
설명보고 이해한 내용을 정리해 보았습니다. 감사합니다.
1. Default 모드
– URL: jdbc:mysql://DB1,DB2/db, DB2는 Standby 상태
– DB1 장애시 다른 솔루션이 DB2 가용상태(primary)로 전환
2. Loadbalance 모드
– URL: jdbc:mysql:loadbalance://DB1,DB2/db, 드라이버는 모든 서버를 대등한 작업자로 봄
– 멀티 wirte 가능.(오라클RAC 처럼)
– replication 고려대상 아님
3. Replication 모드
– URL: jdbc:mysql:replication://DB1,DB2/db
– 어플리케이션에서 connection.setReadonly(true) 등 명시적 설정 필요.
맞…게 이해한거죠?
네, 맞습니다.
다만 Loadbalance 모드는 멀티 write를 만들어주지 못합니다.
멀티 wirte는 DB에서 설정하고 구현하는 기능이고,
JBoss에서 Loadbalance 모드란
부하분산을 위해서 여러 개의 DB와 커넥션을 나눠서 연결해주는 기능입니다.
연결할 때 동시 다발적이지 않고 번갈아가면서 연결을 해줍니다.
더 궁금한 점이 있으시다면 언제든 말씀해주세요 🙂
앞으로의 다른 포스트에도 많은 관심 부탁드립니다!