1. Jedis 연결 풀(JedisPool) 기본 활용
Jedis를 통해 Redis와 통신할 때, 매번 새로운 연결을 생성하고 종료하는 것은 성능 저하의 원인이 됩니다. 이를 해결하기 위해 JedisPool을 사용하여 연결을 재사용합니다.
1.1 기본 연결 풀 생성 및 사용
가장 간단한 형태로 JedisPool을 초기화하고 연결을 가져오는 방법입니다.
package com.example.redis.pool;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class BasicJedisPoolTest {
@Test
public void testBasicPoolUsage() {
// 기본 설정으로 JedisPool 인스턴스 생성 (localhost:6379)
JedisPool connectionPool = new JedisPool();
// 풀에서 Jedis 연결 객체 획득
Jedis jedisClient = connectionPool.getResource();
try {
// Redis 명령 실행
jedisClient.set("testKey", "testValue");
System.out.println("저장된 값: " + jedisClient.get("testKey"));
} finally {
// 사용 후 반드시 close()를 호출하여 풀로 반환
if (jedisClient != null) {
jedisClient.close();
}
}
}
}
1.2 커스텀 설정을 통한 연결 풀 생성
JedisPoolConfig를 사용하여 최대 연결 수, 유휴 연결 수 등의 파라미터를 세부적으로 조정할 수 있습니다.
package com.example.redis.pool;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class CustomJedisPoolTest {
@Test
public void testCustomPoolConfig() {
// 연결 풀 설정 객체 생성 및 속성 정의
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(30); // 최대 활성 연결 수
poolConfig.setMaxIdle(10); // 최대 유휴 연결 수
poolConfig.setMinIdle(5); // 최소 유휴 연결 수
// 설정과 서버 정보를 바탕으로 풀 생성
JedisPool customPool = new JedisPool(poolConfig, "127.0.0.1", 6379);
Jedis jedisConn = customPool.getResource();
try {
jedisConn.set("regionCode", "SEOUL");
} finally {
jedisConn.close();
}
}
}
1.3 주요 연결 풀 설정 파라미터
운영 환경에 맞게 연결 풀을 튜닝하기 위해 다음과 같은 설정 값을 활용할 수 있습니다.
# 최대 활성 연결 수
redis.pool.maxTotal=200
# 최대 유휴 연결 수
redis.pool.maxIdle=50
# 최소 유휴 연결 수
redis.pool.minIdle=20
# 연결 획득 시 최대 대기 시간 (ms)
redis.pool.maxWaitMillis=5000
# 연결 대여 시 유효성 검사 여부
redis.pool.testOnBorrow=true
# 연결 반납 시 유휴성 검사 여부
redis.pool.testOnReturn=false
# 유휴 연결 검사 스레드 실행 주기 (ms)
redis.pool.timeBetweenEvictionRunsMillis=60000
# 유휴 상태일 때 유효성 검사 여부
redis.pool.testWhileIdle=true
# Redis 서버 호스트
redis.server.host=192.168.0.100
# Redis 서버 포트
redis.server.port=6379
2. 연결 풀 유틸리티 클래스 구현
설정 파일을 읽어와 JedisPool을 싱글톤으로 관리하는 유틸리티 클래스를 구현하면 코드 재사용성과 유지보수성이 향상됩니다.
package com.example.redis.util;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class RedisConnectionManager {
private static final JedisPool jedisPool;
static {
Properties props = new Properties();
try (InputStream inputStream = RedisConnectionManager.class.getClassLoader()
.getResourceAsStream("redis-config.properties")) {
props.load(inputStream);
} catch (IOException e) {
throw new ExceptionInInitializerError(e);
}
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(Integer.parseInt(props.getProperty("maxTotal")));
config.setMaxIdle(Integer.parseInt(props.getProperty("maxIdle")));
config.setTestOnBorrow(Boolean.parseBoolean(props.getProperty("testOnBorrow", "true")));
String host = props.getProperty("host");
int port = Integer.parseInt(props.getProperty("port"));
jedisPool = new JedisPool(config, host, port);
}
public static Jedis getConnection() {
return jedisPool.getResource();
}
}
위 클래스에서 참조하는 redis-config.properties 파일의 내용입니다.
host=127.0.0.1
port=6379
maxTotal=50
maxIdle=15
testOnBorrow=true
3. Redis 캐시를 활용한 지역 정보 조회 사례
데이터베이스의 부하를 줄이고 응답 속도를 높이기 위해, 변경 빈도가 낮은 데이터는 Redis에 캐싱하는 것이 효과적입니다. 다음은 지역(Region) 목록을 조회할 때 Redis 캐시를 적용한 예제입니다.
3.1 데이터베이스 및 테이블 구성
CREATE DATABASE IF NOT EXISTS app_db;
USE app_db;
CREATE TABLE region (
region_id INT AUTO_INCREMENT PRIMARY KEY,
region_name VARCHAR(50) NOT NULL
);
INSERT INTO region (region_name) VALUES ('Seoul'), ('Busan'), ('Incheon'), ('Daegu');
3.2 데이터베이스 연결 풀(Druid) 설정
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/app_db
username=root
password=secret
initialSize=5
maxActive=20
3.3 JDBC 및 Redis 유틸리티 클래스
package com.example.db.util;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class DatabasePoolManager {
private static final DataSource dataSource;
static {
try {
Properties dbProps = new Properties();
InputStream is = DatabasePoolManager.class.getClassLoader().getResourceAsStream("db-config.properties");
dbProps.load(is);
dataSource = DruidDataSourceFactory.createDataSource(dbProps);
} catch (Exception e) {
throw new RuntimeException("DB 연결 풀 초기화 실패", e);
}
}
public static DataSource getDataSource() { return dataSource; }
public static Connection getConnection() throws SQLException { return dataSource.getConnection(); }
}
3.4 도메인, DAO, Service 계층 구현
package com.example.domain;
public class Region {
private int id;
private String name;
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
package com.example.dao;
import com.example.domain.Region;
import com.example.db.util.DatabasePoolManager;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.List;
public class RegionDao {
private final JdbcTemplate jdbcTemplate = new JdbcTemplate(DatabasePoolManager.getDataSource());
public List<Region> getAllRegions() {
String sql = "SELECT region_id AS id, region_name AS name FROM region";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Region.class));
}
}
package com.example.service;
import com.example.domain.Region;
import com.example.dao.RegionDao;
import com.example.redis.util.RedisConnectionManager;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import redis.clients.jedis.Jedis;
import java.util.List;
public class RegionService {
private final RegionDao regionDao = new RegionDao();
private final ObjectMapper objectMapper = new ObjectMapper();
private static final String CACHE_KEY = "region_list_cache";
public String getRegionsAsJson() {
Jedis jedis = RedisConnectionManager.getConnection();
String cachedJson = null;
try {
cachedJson = jedis.get(CACHE_KEY);
if (cachedJson == null || cachedJson.isEmpty()) {
List<Region> regions = regionDao.getAllRegions();
cachedJson = objectMapper.writeValueAsString(regions);
jedis.set(CACHE_KEY, cachedJson);
}
} catch (JsonProcessingException e) {
throw new RuntimeException("JSON 변환 중 오류 발생", e);
} finally {
if (jedis != null) jedis.close();
}
return cachedJson;
}
public void invalidateCache() {
try (Jedis jedis = RedisConnectionManager.getConnection()) {
jedis.del(CACHE_KEY);
}
}
}
3.5 Servlet 컨트롤러 및 프론트엔드 연동
package com.example.web;
import com.example.service.RegionService;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/api/regions")
public class RegionServlet extends HttpServlet {
private final RegionService regionService = new RegionService();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("application/json;charset=UTF-8");
String jsonResponse = regionService.getRegionsAsJson();
resp.getWriter().write(jsonResponse);
}
}
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>지역 선택</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<h2>지역 목록 조회</h2>
<select id="regionSelect">
<option value="">-- 지역을 선택하세요 --</option>
</select>
<script>
$(document).ready(function() {
$.ajax({
url: '/api/regions',
method: 'GET',
dataType: 'json',
success: function(regions) {
const selectBox = $('#regionSelect');
$.each(regions, function(index, region) {
selectBox.append(
$('<option></option>').val(region.id).text(region.name)
);
});
},
error: function() {
alert('데이터를 불러오는 중 오류가 발생했습니다.');
}
});
});
</script>
</body>
</html>