[Spring] Redis 캐시를 활용해 데이터 주기적으로 반영하기
현재 진행중인 프로젝트에서 DB에 영향을 주는 API를 굉장히 자주 호출해주는 경우가 있었는데, 이 횟수를 줄여보기 위해보자는 취지
에서 Redis 캐시를 활용해보고 싶었다. key-value
로 저장하기 때문에 API 호출을 하면서 처리되는 작업 내용을 식별해줘야 했는데
다행히도 이 부분이 비교적 간단한 편이어서(=key로 표현하기가 쉬워서) 어느정도 구현이 수월했다.
Redis
설치 및 의존성 추가
Docker 컨테이너로 redis를 띄워서 연결했다.
여기를 참고!
application.yml
다음 내용을 추가해주자. Redis 설정에 필요한 host
, port
를 세팅해줘야 한다.
spring:
cache:
type: redis
data:
redis:
host: 127.0.0.1
port: 6379
RedisConfig
RedisTemplate
를 빈으로 등록해서 다른 클래스에서도 주입받아 활용할 것이다!
이 때 직렬화/역직렬화를 위해 KeySerializer
, ValueSerializer
를 반드시 등록해줘야 한다.
설정해주지 않으면 SerializationException
이 발생하니 주의하자.
@Configuration
public class RedisConfig {
@Value("${spring.data.redis.host}")
private String host;
@Value("${spring.data.redis.port}")
private int port;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(new RedisStandaloneConfiguration(host, port));
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
}
Domain
Yoon
테스트용 엔티티 Yoon
이다. 필드 count
, subCount
는 단순 카운팅을 위한 변수다.
public class Yoon {
@Id @GeneratedValue
@Column(name = "yoon_id")
private Long id;
private Integer count;
private Integer subCount;
public void addCount(Integer value) {
count += value;
}
public void addSubCount(Integer value) {
subCount += value;
}
}
YoonRepository
스프링 데이터 JPA로 Yoon
엔티티를 관리할 수 있도록 하는 클래스 YoonRepository
를 만들어주자.
public interface YoonRepository extends JpaRepository<Yoon, Long> {
}
테스트
RedisService
redisTemplate
을 통해서 redis 캐시에 접근할 수 있다. 여기서는 카운팅을 위해 increment()
를 사용했으나 바로 저장하고
싶다면 set(String key, Object value)
를 사용하면 된다.
@Service
@RequiredArgsConstructor
public class RedisService {
private final RedisTemplate<String, Object> redisTemplate;
private final YoonRepository yoonRepository;
//value 1 증가 (key : count_{id})
public void incrementCount(Integer id) {
redisTemplate.opsForValue().increment("count_" + id);
}
//value 1 증가 (key : subCount_{id})
public void incrementSubCount(Integer id) {
redisTemplate.opsForValue().increment("subCount_" + id);
}
/*
<key, value>
- {count_1, 50}
- {count_2, 30}
- {subCount_1, 40}
*/
public void saveTest() {
for(int i=0; i<50; i++) {
incrementCount(1);
}
for(int i=0; i<30; i++) {
incrementCount(2);
}
for(int i=0; i<40; i++) {
incrementSubCount(1);
}
}
/*
Scheduler에 의해 주기적으로 호출될 함수로, id, count를 파싱해서 업데이트
count_, subCount_로 시작하는 key를 모두 불러와서 처리
처리 후에는 redis 캐시에서 <id, count> 삭제
*/
@Transactional
public void schedulerTest() {
redisTemplate.keys("count_*").forEach(key -> {
Long id = Long.parseLong(key.split("_")[1]);
Integer count = Integer.parseInt(redisTemplate.opsForValue().get(key).toString());
yoonRepository.findById(id).ifPresent(yoon -> {
yoon.addCount(count); //count 갱신
});
redisTemplate.delete(key);
});
redisTemplate.keys("subCount_*").forEach(key -> {
Long id = Long.parseLong(key.split("_")[1]);
Integer count = Integer.parseInt(redisTemplate.opsForValue().get(key).toString());
yoonRepository.findById(id).ifPresent(yoon -> {
yoon.addTimeCount(count); //subCount 갱신
});
redisTemplate.delete(key);
});
}
}
Yoon
엔티티에서 업데이트된count
,subCount
는 변경감지때문에 트랜잭션이 끝나면 DB에 반영됨
TestController
localhost:8080/test
로 GET
요청을 보내면 saveTest()
를 호출하면서 redis 캐시에 key-value
를 저장할 것이다!
@RestController
@RequiredArgsConstructor
public class TestController {
private final RedisService redisService;
@GetMapping("/test")
public String test() {
redisService.saveTest();
return "test function is called. check redis.";
}
}
실제로 저장됐는지 확인하고 싶으면 (docker 컨테이너로 redis를 띄웠다면) 다음 명령어를 입력해보자.
# redis 컨테이너 접속
> docker exec -it {redis_container_id} /bin/sh
# redis-cli 실행
> redis cli
# key 목록 조회
> keys *
# value 조회
> get {key 정보}
CountScheduler
위에서 schedulerTest()
를 scheduler에 의해 주기적으로 호출된다고 설명했다. 아래는 이를 담당하는 CountScheduler
이다.
@Scheduled
에 1분을 주기로 설정했으니 test()
를 1분마다 호출할 것이다.
그러면 schedulerTest()
에 의해 redis 캐시에 등록된 값들을 DB에 반영해주고 초기화하는 작업을 진행한다.
@Component
@RequiredArgsConstructor
public class CountScheduler {
private final ProductService productService;
private final RedisService redisService;
private final int minute = 1000 * 60;
@Scheduled(fixedDelay = minute)
public void test() {
redisService.schedulerTest();
}
}
서버를 실행하면 1분마다 test()
가 호출되면서 여러 작업을 수행하고 그 결과가 반영된걸 DB에서 확인해볼 수 있을 것이다.
로그에도 쿼리가 날라가니 꼭 확인해보자!
댓글남기기