[Spring] Redis 연동하기
본 글의 예제 코드는 링크에서 확인할 수 있다.
Spring에서 Redis에 접근하는 방법은 RedisTemplate과 Spring Data Redis 두 가지 방법이 있는데, 이 중에서 Spring Data Redis를 사용해 Redis에 접근하는 방법을 정리하려고 한다. 추가적으로 간단하게 MySql과 Redis의 성능을 비교해볼 것이다.
의존성
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
Redis 설정
@Configuration
@EnableRedisRepositories
public class RedisConfig {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private int redisPort;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(redisHost, redisPort);
}
@Bean
public RedisTemplate<?, ?> redisTemplate() {
RedisTemplate<byte[], byte[]> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
return redisTemplate;
}
}
application.yml 설정
spring:
redis:
host: localhost //로컬에 Redis 를 설치하여 진행
port: 6379 // Redis 기본 포트
jpa:
hibernate:
ddl-auto: create-drop
properties:
format_sql: true
datasource:
url: jdbc:mysql://localhost:3306/redis_db //데이터베이스 설정
username: redis_user
password: 1234
driver-class-name: com.mysql.jdbc.Driver
Redis Repositories 사용법
Spring Data Redis 는 Spring Data Jpa처럼 객체를 기반으로 Redis에 접근하는 방법이다.
Domain Object 를 손쉽게 Redis Hash 자료구조로 변환, secondary indexes 적용, TTL(TimeToLive)을 적용시킬 수 있다.
아래의 코드는 테스트에 사용될 PersonRedis 객체이다.
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@RedisHash("person")
public class PersonRedis {
@Id
private String id;
private String firstname;
private String lastname;
private AddressRedis address;
public PersonRedis(String id, String firstname, String lastname,
AddressRedis address) {
this.id = id;
this.firstname = firstname;
this.lastname = lastname;
this.address = address;
}
}
@Getter
public class AddressRedis {
private final String city;
private final String country;
public AddressRedis(String city, String country) {
this.city = city;
this.country = country;
}
}
@RedisHash 과 @Id 어노테이션을 이용해 key를 설정할 수 있다. @RedisHash의 value와 @Id 가 붙어있는 멤버 변수가 저장될 Hash의 key이다.
해당 객체를 기준으로 CrudRepository를 상속받아 Repository 를 생성하면 Redis에 접근해 기본적인 CRUD 기능을 사용할 수 있다.
public interface PersonRedisRepository extends CrudRepository<PersonRedis, String> {
}
간단한 테스트를 진행해보겠다.
@Test
void basicCrudTest() {
AddressRedis addressRedis = new AddressRedis("서울특별시", "대한민국");
PersonRedis person = new PersonRedis("highright96", "상우", "남", addressRedis, 300L);
PersonRedis savedPerson = personRedisRepository.save(person);
Optional<PersonRedis> findPerson = personRedisRepository.findById(savedPerson.getId());
assertThat(findPerson.isPresent()).isEqualTo(true);
assertThat(findPerson.get().getFirstname()).isEqualTo(person.getFirstname());
assertThat(findPerson.get().getAddress().getCity()).isEqualTo(person.getAddress().getCity());
}
위의 테스트는 PersonRedis 객체를 생성해 연결된 Redis 에 해당 객체를 저장하고 조회를 진행한다. 이는 정상적으로 실행되었으며, 실제로 Redis 서버에 어떻게 저장되었는지 확인해보겠다.
조회를 해보니 올바르게 저장된 것을 확인할 수 있었고, 추가적으로 person의 타입은 Set 이고, person 의 highright96 은 Hash 인 것을 확인할 수 있었다.
TimeToLive
아래의 코드와 같이 @TimeToLive 어노테이션을 붙여 저장될 데이터의 유효한 시간을 설정할 수 있다. 설정된 시간이 지나면 삭제된다.
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@RedisHash("person")
public class PersonRedis {
public static final Long DEFAULT_TTL = 1L;
@Id
private String id;
private String firstname;
private String lastname;
private AddressRedis address;
@TimeToLive
private Long expiration = DEFAULT_TTL;
public PersonRedis(String id, String firstname, String lastname,
AddressRedis address, Long expiration) {
this.id = id;
this.firstname = firstname;
this.lastname = lastname;
this.address = address;
this.expiration = expiration;
}
public PersonRedis(String id, String firstname, String lastname,
AddressRedis address) {
this(id, firstname, lastname, address, DEFAULT_TTL);
}
}
테스트를 진행해보겠다.
@Test
void TimeToLiveTest() throws InterruptedException {
AddressRedis addressRedis = new AddressRedis("서울특별시", "대한민국");
PersonRedis person = new PersonRedis("highright96", "상우", "남", addressRedis);
PersonRedis savedPerson = personRedisRepository.save(person);
Thread.sleep(1000);
Optional<PersonRedis> findPerson = personRedisRepository.findById(savedPerson.getId());
assertThat(findPerson.isPresent()).isEqualTo(false);
}
위의 테스트는 Redis에 데이터를 저장한 후 1초를 쉬고 조회를 진행한다. (데이터의 유효 시간은 1초로 설정하였다.) 올바르게 작동하는 것을 확인할 수 있었으며, 실제로 Redis 서버를 확인해보겠다.
유효 시간이 지나 삭제되어 조회되지 않는 것을 확인할 수 있었다.
성능 비교
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/person")
public class PersonController {
private final PersonRepository personRepository;
private final PersonRedisRepository personRedisRepository;
@PostConstruct
public ResponseEntity<Void> createPerson() {
List<Person> personList = new ArrayList<>();
List<PersonRedis> personRedisList = new ArrayList<>();
for (int i = 1; i <= 100000; i++) {
Address address = new Address("서울특별시", "대한민국");
Person person = new Person(null, "상우 " + i, "남", address);
personList.add(person);
}
// DB 에 저장
List<Person> savedPersonList = personRepository.saveAll(personList);
// cache 에 저장
for (Person person : savedPersonList) {
personRedisList.add(PersonRedis.createPersonExp(person, 300L));
}
personRedisRepository.saveAll(personRedisList);
return ResponseEntity.ok().build();
}
@Timer
@GetMapping("/redis/{id}")
public ResponseEntity<PersonRedis> findPersonByRedis(@PathVariable String id) {
PersonRedis personRedis = personRedisRepository.findById(id).get();
return ResponseEntity.ok(personRedis);
}
@Timer
@GetMapping("/database/{id}")
public ResponseEntity<Person> findPersonByDatabase(@PathVariable Long id) {
Person person = personRepository.findById(id).get();
return ResponseEntity.ok(person);
}
}
MySql과 Redis 각각 10만 개의 데이터를
넣고 조회했을 때 걸리는 시간을 로그로 확인해보니 아래와 같이 Redis로 조회했을 때 더 빠른 것을 확인할 수 있었다.
Redis 조회
MySql 조회
결과도 올바르게 나온 것을 확인할 수 있었다.
참고
'개발 > Spring' 카테고리의 다른 글
Spring / 어노테이션으로 Parameter 검증하기 (0) | 2021.08.17 |
---|---|
Spring MVC / Controller에서 요청 데이터를 받는 방법 (0) | 2021.05.28 |
Spring MVC / Controller에서 요청을 매핑하는 방법 (0) | 2021.05.27 |
댓글