Redis #2 : Redis의 주요 구성요소

2021. 11. 25. 19:23학습일지

Index

  1. Redis Client
  2. Serializer
  3. CrudRepository
  4. RedisTemplate

1. Redis Client

Redis와 Java 애플리케이션의 connection을 형성해주는 conector가 필요하다. 그 connector 역할을 해주는 Redis Client는 크게 두가지가 있다.

1.1. Jedis

Jedis는 예전에는 Java의 표준 Redis Client로 사용되었다. 하지만, thread-safe를 보장하지 않기 때문에, 멀티스레드 환경에서 리소스를 공유하는 기능을 사용하는데 큰 제약이 따르고, TPS 성능이 Lettuce에 비해 저조하고, CPU의 오버헤드가 높다. 하지만, Lettuce에 비해 좀 더 쉽게 사용할 수 있다. 그래서, 보통 Jedis는 확장성과 안정성을 포기하고, 빠른 개발속도가 요구될 때 사용되는 듯 하다.

1.2. Lettuce

Lettuce는 Netty 기반의 Redis Client이다. Netty는 비동기 이벤트 기반의 고성능 네트워크 프레임워크이다. 비동기 요청이 가능하기 때문에, 고성능을 기대해볼 수 있다. 또한 확장성과 thread-safe를 지향하는 클라이언트이고, 활발한 피드백을 통한 개선작업을 꾸준히 해주어서 그런지, spring-data-redis에서는 아예 Lettuce를 기본 ConnectionFactory의 구현체로 지정하였다. (Jedis의 경우, 따로 의존성을 포함하여야 한다.)

2. Serializer

저번 챕터에서도 잠깐 언급했지만, Redis는 매우 다양한 자료구조의 데이터를 받아들인다. 레디스 저장소에 저장된 데이터들은 모두 바이트 배열로 이루어져 있는데, java에서 레디스로 혹은 레디스에서 java로 데이터를 오가는 동안, Java-based serializer를 활용한다.

뒤에서 자세히 이야기 할, redisTemplate를 통해, 데이터를 직렬화/역직렬화 할 때, 사용하는데, spring-data-redis에서 제공하는 serializer는 다음과 같다.

  • JdkSerializationRedisSerializer
    • RedisCacheRedisTemplate에서 기본적으로 사용하는 serializer로, 이름에서 알 수 있듯이, java.io의 ObjectStream을 활용하기 때문에, 모델마다 serializable을 주입하여야 한다.
  • StringRedisSerializer
    • 문자열을 직렬화할 때 사용되는 serializer이다.
  • Jackson2JsonRedisSerializer
    • Json 을 관리해주는데, 특정, 오브젝트 타입에 묶여있다.
  • GenericJackson2JsonRedisSerializer
    • 네이밍을 보고 추측할 수 있듯이, Json을 generic하게 직렬화할 수 있다는 점에서 위의 Jackson2JsonRedisSerializer보다 더 확장성 있는 serializer이다.
    • 그러나 한 번 레디스에 저장된 데이터의 첫번 째 json 필드에 @Class가 추가 되는데, 요청, 응답 메세지에 매핑되는 과정에서는 드러나지 않지만, 만약 한번 저장한 데이터로 다른 serializer로 변경할 경우, 당연하게도 오류가 발생할 수 있다. (확장성 있지만 돌이킬 수 없다.)

3. CrudRepository

spring-data-redis 중에선 가장 간단한, 방법이지 않을까 싶다. Dao인터페이스에 CrudRepository<T, ID>를 확장하여, 사용하기만 하면 되기 때문이다. 공식 문서의 예시 코드의 일부를 참고하자면 다음과 같다.

public interface PersonRepository extends CrudRepository<Person, String> {

}
@Configuration
@EnableRedisRepositories
public class ApplicationConfig {

  @Bean
  public RedisConnectionFactory connectionFactory() {
    return new JedisConnectionFactory();
  }

  @Bean
  public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory redisConnectionFactory) {

    RedisTemplate<byte[], byte[]> template = new RedisTemplate<byte[], byte[]>();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
  }
}
@Autowired PersonRepository repo;

public void basicCrudOperations() {

  Person rand = new Person("rand", "al'thor");
  rand.setAddress(new Address("emond's field", "andor"));

  repo.save(rand);                                       

  repo.findOne(rand.getId());                              

  repo.count();                                            

  repo.delete(rand);                                       
}

CrudRepository를 활용하면, 도메인 주도 개발에 매우 유리할 수 있지만, redis Transaction을 지원하지 않기 때문에, 하나의 요청에 복합적인 redis command를 보내는 그런 디테일한 작업은 불가능하다.

4. RedisTemplate

JDBC와 마찬가지로, Redis Client 역시, Redis 서버에 요청을 하려면, 매 요청마다, connection을 획득하고, 데이터 액세스 로직을 수행하고, 다시 connection을 닫는 작업이 필요하다. 또한 여러가지 상황을 대비하기 위한 예외처리도 해야하는데, 모든 요청에 이러한 코드를 일일이 입력하는 건, 코드 품질을 떨어트린다. 이러한 경우를 개선 시키고자, spring-data-jdbcJdbcTemplate를 제공하여, connection의 획득과 반납, 그리고 여러가지 예외처리를 대신 해주어서, 개발자는 핵심 로직에만 집중할 수 있도록 한다. 같은 맥락으로, spring-data-jdbc 에선RedisTemplate를 제공한다. Redis를 사용하기 위해선 스프링 부트에서는 connectionFactoryredisTemplate 의 serializer 설정을 빈으로 등록 해야하는데, 만약 개발자가 별도의 설정이 없을 경우, LettuceConnectionFactory를 설정해주고, redisTemplate 의 경우, key-value serializer를 모두 StringRedisSerializer로 설정해서 제공 해준다.

만약에 별도로 설정하고 싶은 값이 있다면, 다음과 같이 빈을 등록해주면 된다.

한 번 적용이 되면, 이 redisTemplate는 thread-safe한 객체가 된다.

spring:
  redis:
    host: localhost
    port: 6379
@Configuration
public class RedisConfig {

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory();
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {

        GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(serializer);

        return redisTemplate;
    }
}

4.1. excute()

RedisTemplate을 활용하여, redis 서버에 command를 요청할 수 있는데, excute() 메소드에 파라미터로 콜백 객체를 전달하여, 로직을 수행한다. 템플릿 콜백 패턴이 적용되어 있는데, 자세한 내용은 토비의 스프링을 참고하면 매우 좋다. 대표적으로 두가지의 콜백 함수를 사용할 수 있다.

  • RedisCallback
    • Redis 서버에 단일 command를 요청하는 로직을 수행할 수 있는 콜백함수로, doInRedis(RedisConnection connection)를 구현하여 사용한다.
    • 콜백함수의 RedisConnection 파라미터를 통해 command api를 사용할 수 있는데, 지원하는 command는 https://redis.io/commands 여기를 참고해보자
    • 단순 CRUD의 경우 콜백함수를 구현하는 것보다, Operational views를 활용하는 것이 좋다.
  • SessionCallback
    • 하나의 redis connection 안에서 여러 command를 수행하고 싶을 때 구현하는 콜백함수이다. execute(RedisOperations<K,V> operations)를 구현하여 사용한다.
    • Redis Transaction을 적용할 수 있다.