시냅스

Redis 공식문서 기반 정리 ( +Spring 에서의 용례 ) 본문

데이터베이스/Redis

Redis 공식문서 기반 정리 ( +Spring 에서의 용례 )

ted k 2022. 10. 25. 21:08

Redis

  • in-memory 데이터 자료구조 저장소
    • 아래와 같이 쓰일 수 있다. 
    • Database
    • Cache
    • Message broker
    • Streaming engine
  • Redis가 지원하는 자료구조
    • String
    • Hash
    • List
    • Set
    • Sorted Set
    • Bitmap
    • Hyperloglog
    • Geospatial Indexe
    • Stream
  • atomic 한 연산을 지원한다.
  • Redis Model
    • Transactions
    • Pub/Sub
    • Lua scripting
    • Keys with a limited time-to-live
    • LRU eviction of keys
    • Automatic failover
  • 특징
    • Key-Value 구조의 비정형 데이터를 저장하고 관리하기 위한 오픈 소스 기반의 비관계형 데이터 베이스 관리 시스템이다. 
    • RAM에 저장하여 디스크 스캐닝이 필요없어 매우 빠르다.
    • RAM은 휘발성으로 컴퓨터 전원이 꺼지면 모든 정보는 삭제된다.
      • Snapshot을 통해 백업할 수 있다.
      • AOF : 명령(쿼리)들을 저장해두고, 서버가 셧다운 후 재실행 되면 다시 실행한다.
    • 싱글 스레드로 한 번에 하나의 명령만 수행한다.
      • 다만 get, set 명령어의 경우 초당 처리속도를 10만개 까지 지원한다.

 

Transaction

  • 명령의 그룹들을 single step으로 실행하게 한다.
  • MULTI, EXEC, DISCARD, WATCH를 중심으로 한다.
    • MULTI
      • 트랜잭션 블록이 시작됨을 의미한다.
    • EXCEC
      • MULTI 이후 명령어들을 실핸한다. 
      • EXEC을 명령해야 내가 입력한 명령들이 실행된다.
    • DISCARD
      • 입력한 명령어들을 flush 한다.
    • WATCH
      • 트랜잭션의 조건부 실행을 감시할 지정된 키를 표시한다.
  • 모든 트랜잭션 안의 커맨드들은 serialized 되고, 순차적으로 실행된다.
    • 트랜잭션 중에 다른 트랜잭션이 실행될 수 없다.
    • atomic 연산을 지원한다.
  • 필자는 주로 redis에 객체/데이터를 넣어두고 RDBMS를 사용하지 않고 캐싱하는 용도로 자주 쓴다.
> MULTI
OK
> INCR foo
QUEUED
> INCR bar
QUEUED
> EXEC
1) (integer) 1
2) (integer) 1

 

Transaction의 Java에서의 용례

메서드 설명
opsForValue String 을 Redis에 저장(Serialize), 반환(Deserialize) 하게 한다.
opsForList List 를 Redis에 저장(Serialize), 반환(Deserialize) 하게 한다.
opsForSet Set 을 Redis에 저장(Serialize), 반환(Deserialize) 하게 한다.
opsForZSet ZSet 을 Redis에 저장(Serialize), 반환(Deserialize) 하게 한다.
opsForHash Hash 를 Redis에 저장(Serialize), 반환(Deserialize) 하게 한다.

 

  • 아래는 String에서의 use case이다.
  • 사전에 redisconfiguration을 정의해야 한다.
public class UserCaseRepository{
	private final RedisTemplate<String, String> redisTemplate;

    public void setToken(String key, String data) {
        ValueOperations<String, String> values = redisTemplate.opsForValue();
        values.set(key, data);
    }

    public void setToken(String key, String date, Duration duration) {
        ValueOperations<String, String> values = redisTemplate.opsForValue();
        values.set(key, date, duration); // duration을 통해 만료기간을 정한다.
    }

    public Optional<String> getToken(String key) {
        ValueOperations<String, String> values = redisTemplate.opsForValue();
        return Optional.of(values.get(key));
    }
}

 

Pub/Sub

  • 공식문서의 설명
    • SUBSCRIBE, UNSUBSCRIBE 및 PUBSCRIBE는 보낸 사람(발행자)이 특정 수신자(구독자)에게 메시지를 보내도록 프로그래밍되지 않는 게시/구독 메시지 패러다임을 구현한다.
    • 오히려, 게시된 메시지는 구독자가 있을 수 있는 것에 대한 지식 없이 채널로 특성화된다.
    • 구독자는 하나 이상의 채널에 대해 관심을 표현하고 Publisher가 있는지에 대한 지식 없이 관심 있는 메시지만 수신한다.
    • 게시자와 구독자의 이러한 분리는 더 큰 확장성과 더 동적인 네트워크 토폴로지를 가능하게 한다.
  • 필자 각주
    • 클라이언트가 특정 토픽을 Subscribe 하면 Publisher는 어떤 Subscriber가 있는지 고려하지 않고 구독하고 있는 모든 Subscriber에게 메세지를 보낸다.
    • 이후 클라이언트는 Subscribe 로 메세지를 받을 때에 대한 행동을 정의할 수 있다.
    • 참고로 Redis 입장에서 WAS는 client이다.
    • 필자는 여러 WAS를 중개해야 하는 Event가 있을 때에 사용한다.
    • WebSocket이나, SSE와 같은 것이 그렇다.

 

Pub/Sub의 Java에서의 용례

  • 참고
    • RedisAlarmPublisher
    • RedisAlarmSubscriber
    • CommentService : 53L : 58L
    • 을 참고하시면 얼추 보실 수 있다.
    @GetMapping("/subscribe")
    public SseEmitter subscribe(@AuthenticationPrincipal UserPrincipal userPrincipal) {
        ChannelTopic topic = new ChannelTopic("Emitter:UID" + userPrincipal.getId());
        // topic을 통해 특정 topic에 대한 구독을 하고
        // publish가 된다면, redisAlarmSubscriber의 onMessage가 실행된다.
        redisMessageListener.addMessageListener(redisAlarmSubscriber, topic);
        emitterRepository.putTopic(userPrincipal.getId(), topic);
        return alarmService.connectAlarm(userPrincipal.getId());
    }
  • 어떤 컨트롤러에서 sse를 구독하면서 topic을 등록한다고 가정해보자.
  • redisMessageListener를 통해서 subscriber와 topic을 등록하게 된다.
  • subscriber 내부의 onMessage로 message를 받았을 때 행동을 정의하고, 구독할 topic을 정의한다.

 

@RequiredArgsConstructor
@Service
public class RedisAlarmSubscriber implements MessageListener {
    private final ObjectMapper objectMapper;
    private final RedisTemplate<String, String> redisTokenTemplate;
    private final AlarmService alarmService;

    @Override
    public void onMessage(Message message, byte[] pattern) {
        try {
            String publishMessage = redisTokenTemplate.getStringSerializer().deserialize(message.getBody());
            Long userId = objectMapper.readValue(publishMessage, long.class);
            alarmService.send(0L, userId);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }
}
  • Subsriber는 위와 같다.
  • message를 deserialize 하여 원하는 데이터의 형태로 사용할 수 있다.
  • 이후 데이터를 활용하여 원하는 action을 취할 수 있다.

 

@RequiredArgsConstructor
@Service
public class RedisAlarmPublisher {
    private final RedisTemplate<String, String> redisTokenTemplate;

    public void publish(ChannelTopic topic, Long userId) {
        redisTokenTemplate.convertAndSend(topic.getTopic(), userId.toString());
    }
}
  • Publisher는 비교적 간단한데
  • 앞서 말했던 인자에 있는 topic을 통해 pushlish를 하게 되고, 원하는 message를 보낼 수 있다.
  • 이 topic을 토대로 Subscriber가 메세지를 받을 수 있다.

 

참고로 위와 같은 일이 가능한 이유는...

http://redisgate.kr/redis/command/pubsub_intro.php

  • 서버 구조체에는 pubsub_channels 필드와 pubsub_patterns 필드가 있다.
  • dictEntry 의 key field(robj channel)이 channel을 가리킨다(topic)
  • 그리고 linked list로 client를 가지고 있다
  • 따라서 publish 명령은 channel을 hash table에서 찾고 리스트에 저장되어 있는 클라이언트들에게 하나씩 메세지를 보낸다.

'데이터베이스 > Redis' 카테고리의 다른 글

Redis 설치부터 Cluster 구성까지  (0) 2024.01.17
Comments