이전 글 : 채팅 서비스에 Redis 도입 (1) [ https://techforme.tistory.com/16 ]
이전 글 : 채팅 서비스에 Strategy Pattern 적용 [ https://techforme.tistory.com/15 ]
구독 정보 Repository 로서 Redis 도입
이전 글에서 채팅 서비스에 Message Broker 로서 Redis 를 도입했던 글을 썼었는데, 이번에는 구독 정보 Repository 로서 Redis 를 적용한 부분에 대해서 쓰려고 한다. 구독 정보 Repository 는 이전에 인메모리 캐시를 이용한 ConcurrentHashMap 으로 구현하면서 Strategy 패턴을 적용했었는데, 그렇기 때문에 아래와 같이 인터페이스만 구현하고 yml 파일 설정만 변경하면 도입 끝이다.
참고로 Redis 는 싱글스레드로 동작하는 DB 로, 스프링의 멀티스레드 환경과도 잘 통합되어 안전하게 동작한다.
RedisSubscribeRepository 클래스
@Repository
@ConditionalOnProperty(name = "redis-config.websocket", havingValue = "true")
@RequiredArgsConstructor
public class RedisSubscribeRepository implements SubscribeRepository {
private final RedisTemplate<String, Object> redisTemplate;
private final String SESSION_USER = "Session_User:";
private final String SESSION_SUBSCRIPTION = "Session_Subscription:";
private final String SUBSCRIPTION_CHANNEL = "Subscription_Channel:";
private final String CHANNEL_USER = "Channel_User:";
@Override
public void saveUserIdBySessionId(Long userId, String sessionId) {
redisTemplate.opsForHash().putIfAbsent(SESSION_USER, sessionId, userId);
}
@Override
@TransactionTest(pos = 1, value = "subscribe")
public void saveSubscriptionIdBySessionId(String subscriptionId, String sessionId) {
String key = SESSION_SUBSCRIPTION + sessionId ;
redisTemplate.opsForSet().add(key, subscriptionId);
}
@Override
public void setSubscriberToChannel(Long userId, Long channelId) {
String key = CHANNEL_USER + channelId;
redisTemplate.opsForSet().add(key, userId);
}
@Override
public void setSubscriptionIdToChannel(String subscriptionId, Long channelId) {
redisTemplate.opsForHash().put(SUBSCRIPTION_CHANNEL, subscriptionId, channelId);
}
@Override
public Set<Long> findSubscribeChannelIdBySessionId(String sessionId) {
String key = SESSION_SUBSCRIPTION + sessionId;
Set<Object> subscriptions = redisTemplate.opsForSet().members(key);
if(subscriptions == null) return new HashSet<>();
return subscriptions.stream().map(subscription
-> (Long) redisTemplate.opsForHash().get(SUBSCRIPTION_CHANNEL, subscription))
.collect(Collectors.toSet());
}
@Override
public Set<String> findSubscriptionIdBySessionId(String sessionId) {
String key = SESSION_SUBSCRIPTION + sessionId;
return Objects.requireNonNull(redisTemplate.opsForSet().members(key))
.stream().map(obj -> (String)obj).collect(Collectors.toSet());
}
@Override
public Optional<Long> findUserIdBySessionId(String sessionId) {
return Optional.ofNullable((Long) redisTemplate.opsForHash().get(SESSION_USER, sessionId));
}
@Override
public Optional<Long> findChannelIdBySubscriptionId(String subscriptionId) {
return Optional.ofNullable((Long) redisTemplate.opsForHash().get(SUBSCRIPTION_CHANNEL, subscriptionId));
}
@Override
public Set<Long> findSubscribersByChannelId(Long channelId) {
String key = CHANNEL_USER + channelId;
return Objects.requireNonNull(redisTemplate.opsForSet().members(key))
.stream().map(obj -> Long.valueOf((Integer)obj)).collect(Collectors.toSet());
}
@Override
@TransactionTest(value = "disconnect")
public void deleteSessionId(String sessionId) {
redisTemplate.opsForHash().delete(SESSION_USER, sessionId);
}
@Override
public void deleteSubscriberFromChannel(Long userId, Long channelId) {
String key = CHANNEL_USER + channelId;
redisTemplate.opsForSet().remove(key, userId);
}
@Override
public void deleteAllSubscriptionsBySessionId(String sessionId) {
String key = SESSION_SUBSCRIPTION + sessionId;
redisTemplate.delete(key);
}
@Override
public void deleteSubscriptionId(String subscriptionId) {
redisTemplate.opsForHash().delete(SUBSCRIPTION_CHANNEL, subscriptionId);
}
@Override
@TransactionTest(pos = 1, value = "unsubscribe")
public void deleteSubscriptionIdBySessionId(String subscriptionId, String sessionId) {
String key = SESSION_SUBSCRIPTION + sessionId;
redisTemplate.opsForSet().remove(key, subscriptionId);
}
}
Redis 에서 Hash 와 Set 자료형을 이용했다. @TransactionTest 어노테이션은 AOP를 이용해서 테스트 스텁을 자동 생성해주기 위해 붙여놓은 것으로, 관련 글[https://techforme.tistory.com/37] 참고