개발/Spring

[Spring] Redis 연동하기

highright96 2021. 9. 3.

본 글의 예제 코드는 링크에서 확인할 수 있다.

 

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 조회

결과도 올바르게 나온 것을 확인할 수 있었다.

 

참고

댓글