안녕하세요 오늘은 Redis와 Mysql(JPA)과 이용하는 방법에 대해 설명해보도록 하겠습니다!
왜 Redis와 Mysql을 연동?
Redis는 저장소로 사용될 때도 있지만,
DB의 부하를 줄이기 위해, 혹은 select 를 빠르게 하기 위해 사용될 때도 있습니다
위 그림과 같이 캐시 서버에 조회하려는 데이터가 없는 경우에 DB에서 직접 조회를 하게 되는데,
IO 작업이 필요한 DB와 다르게 캐시 서버인 Redis는 인메모리 저장소이기 때문에 더 빠르게 조회를 할 수 있습니다
redis + springboot + mysql 세팅
springboot와 redis를 연동하는 작업은 지난 번에 해보았으니, 오늘은 지난 번 작업에 이어서 진행을 해보도록 하겠습니다!
일단 맨 처음 mysql을 이용하기 위해 JPA를 연결해주도록 했습니다
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>2.6.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
spring:
jpa:
hibernate:
ddl-auto: create
show-sql: true
datasource:
url: jdbc:mysql://localhost:3306/redis
username: DB 아이디
password : DB 비밀번호
driver-class-name: com.mysql.jdbc.Driver
그리고 지난 번에 만들었던 Student 객체를 entity로 만들어주었습니다
//@RedisHash("Student")
@Entity
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Setter
public class Student implements Serializable {
public enum Gender{
MALE, FEMALE
}
@Id
private String id;
private String name;
private Gender gender;
private int grade;
}
참고로 직렬화를 하면 다음과 같이 저장됩니다
Config 파일에 @EnableCaching을 달아서 캐싱이 가능하도록 했습니다
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
JedisConnectionFactory jedisConnectionFactory() {
RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
redisConfig.setHostName("192.168.0.20");
redisConfig.setPort(6379);
return new JedisConnectionFactory(redisConfig);
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(jedisConnectionFactory());
return template;
}
}
그리고 캐싱을 이용한 select를 하기 위해 Controller을 만들었습니다
@Controller
public class StudentController {
@Autowired
StudentRepository studentRepository;
@Cacheable(value = "Student")
public Student findStudentById(String id){
return studentRepository.findById(id)
.orElseThrow(RuntimeException::new);
}
}
효과가 있나 구경해봅시다
값을 저장하고, select를 하는 test 코드를 만들었습니다
이 때 select를 두번 해서 시간을 비교해보았습니다(첫번째는 DB select, 두번째는 redis select)
@SpringBootTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class RedisWithMysqlTest {
@Autowired
StudentRepository studentRepository;
@Autowired
StudentController studentController;
@Test
@Order(1)
void saveTest(){
Student student1 = new Student("1", "zzarbttoo1", Student.Gender.FEMALE, 1);
Student student2 = new Student("2", "zzarbttoo2", Student.Gender.FEMALE, 2);
Student student3 = new Student("3", "zzarbttoo3", Student.Gender.FEMALE, 3);
Student student4 = new Student("4", "zzarbttoo4", Student.Gender.FEMALE, 4);
Student student5 = new Student("5", "zzarbttoo5", Student.Gender.FEMALE, 5);
studentRepository.save(student1);
studentRepository.save(student2);
studentRepository.save(student3);
studentRepository.save(student4);
studentRepository.save(student5);
}
@Test
@Order(2)
void selectTest(){
long start1 = System.currentTimeMillis();
System.out.println(studentController.findStudentById("1").toString());
System.out.println(studentController.findStudentById("2").toString());
System.out.println(studentController.findStudentById("3").toString());
System.out.println(studentController.findStudentById("4").toString());
System.out.println(studentController.findStudentById("5").toString());
long end1 = System.currentTimeMillis();
System.out.println(end1 - start1);
long start2 = System.currentTimeMillis();
System.out.println(studentController.findStudentById("1").toString());
System.out.println(studentController.findStudentById("2").toString());
System.out.println(studentController.findStudentById("3").toString());
System.out.println(studentController.findStudentById("4").toString());
System.out.println(studentController.findStudentById("5").toString());
long end2 = System.currentTimeMillis();
System.out.println(end2 - start2);
}
}
일단 select를 처음 하면 다음과 같이 redis에도 값이 저장되는 것을 확인할 수 있었습니다
그리고 첫번째와 두번째의 select 시간을 확인해보면
와우~
확실히 두번째에 획기적으로 select 시간 단축이 된 것을 확인할 수 있었습니다
(참고로 여러번 해도 첫번째만 오래 걸리고 두번째부터는 빠르게 진행됩니다)
정말 redis는 만능일까?(mysql과의 동기화 문제)
하지만 redis를 캐시서버로 이용하는 것에는 아주 큰 문제가 있으니
캐시 서버와 DB 간의 동기화가 이루어지지 않을 수 있다는 것!
이를 보이기 위해 아래와 같은 테스트 케이스를 짜보았습니다
@Test
@Order(3)
void updateTest(){
Student updateStudent = studentRepository.findById("1").get();
updateStudent.setName("updated Name");
studentRepository.save(updateStudent);
Student selectStudent = studentRepository.findById("1").get();
System.out.println(selectStudent.toString());
Student redisStudent = studentController.findStudentById("1");
System.out.println(redisStudent.toString());
}
결과를 보면 업데이트가 되지 않은 것을 확인할 수 있습니다
다음 글에는 이러한 문제를 해결하는 방식에 대해 설명해보도록 하겠습니다!
출처
https://www.baeldung.com/spring-boot-redis-cache
https://www.youtube.com/watch?v=92NizoBL4uA