[Redis] Failover시 redis PubSub channelTopic 삭제 문제 확인

[Redis] Failover시 redis PubSub channelTopic 삭제 문제 확인

AWS elasticache redis 사용중 redis 가 failover 되면서 redis pub/sub 동작이 안되기 시작했다.

redis-cli로 확인해보니 구독하고 있던 채널이 삭제되었다.

redis failover 시에 데이터는 전부 동기화 한 후 failover 하게 되는데 토픽은 왜 동기화 하지 않는걸까?!!

이 상황에서 뇌피셜로 2가지 추측을 해볼 수 있었다.

1. redis topic은 애초에 slave 까지 전파가 되지 않는다.

그래서 failover 되면서 slave to master 승격이 일어나며 topic 이 삭제 되었다.

2. Spring 에서 토픽을 생성할때 ChannelTopic 이라고 생성을 하는데, Channel이라는 의미가 Connection 기반이라는 의미인가..?

테스트를 위해 아래와 같이 간단한 Spring boot 프로젝트를 작성하였다.

@Configuration public class RedisClusterConfig { @Bean RedisMessageListenerContainer container(RedisConnectionFactory redisConnectionFactory, MessageListenerAdapter listenerAdapter) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(redisConnectionFactory); container.addMessageListener(listenerAdapter, channelTopic()); return container; } @Bean ChannelTopic channelTopic() { return new ChannelTopic("failover"); } @Bean public ClientOptions clientOptions() { return ClusterClientOptions.builder() .socketOptions(ClusterClientOptions.DEFAULT_SOCKET_OPTIONS) .topologyRefreshOptions(ClusterTopologyRefreshOptions.builder().refreshPeriod(ClusterClientOptions.DEFAULT_REFRESH_PERIOD_DURATION).build()) .build(); } @Bean public LettuceConnectionFactory redisConnectionFactory() { LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder() .readFrom(ReadFrom.MASTER) .build(); return new LettuceConnectionFactory(new RedisClusterConfiguration( List.of("localhost:7000","localhost:7001","localhost:7002","localhost:7003","localhost:7004","localhost:7005")), clientConfig); } @Bean MessageListenerAdapter listenerAdapter() { return new MessageListenerAdapter(new MessageReceiver()); } @Bean StringRedisTemplate template(RedisConnectionFactory redisConnectionFactory) { return new StringRedisTemplate(redisConnectionFactory); } }

@Slf4j @Service @RequiredArgsConstructor public class MessageSender { private final StringRedisTemplate template; void publish(String message) { log.info("send message : {}", message); template.convertAndSend("failover", message); } }

@Slf4j @Service public class MessageReceiver implements MessageListener { @Override public void onMessage(Message message, byte[] pattern) { log.info("receive message : {}", message.toString()); } }

그리고 redis cluster를 docker로 띄우기 위해 아래 image 사용해서 docker-compose.yml 파일도 생성해주었다.

version: '3' services: rediscluster: image: grokzen/redis-cluster environment: IP: 127.0.0.1 CLUSTER_ONLY: 'true' MASTERS: 3 SLAVES_PER_MASTER: 1 ports: - '7000-7005:7000-7005'

자! 이제 모두 run 시키고 직접 확인해보자!

먼저, redis-cli로 채널이 살아있는지 확인을 해보자

PUBSUB NUMSUB 은 구독하고 있는 채널 숫자를 출력한다.

PUBSUB NUMSUB [channel-1 ... channel-N] Returns the number of subscribers (not counting clients subscribed to patterns) for the specified channels.

# Redis server, cli 설치 brew install redis

최초 상태는 아래처럼 마스터 중 하나인 7000번 노드에만 토픽이 존재하는 상태이다.

pubsub redis-cli -p 7000 cluster nodes 26223e760092a2160deed834faaad2d0612a15ba 127.0.0.1:7005@17005 slave c9ebc0c23bb35b4be1de18cedc817efa205f7e32 0 1627397858000 1 connected 938a20436c11733f81fd819535cc5f701b77a3ef 127.0.0.1:7003@17003 slave e052c7d608262e4f5fb90a703b7a72e3e19f50dd 0 1627397858674 9 connected f98b88b5468429b7545a97a049d19ae6ff71ad33 127.0.0.1:7002@17002 slave 8219e397fa70fac32074fe4db4078f466c04e673 0 1627397858000 8 connected c9ebc0c23bb35b4be1de18cedc817efa205f7e32 127.0.0.1:7000@17000 myself,master - 0 1627397858000 1 connected 0-5460 e052c7d608262e4f5fb90a703b7a72e3e19f50dd 127.0.0.1:7001@17001 master - 0 1627397858878 9 connected 5461-10922 8219e397fa70fac32074fe4db4078f466c04e673 127.0.0.1:7004@17004 master - 0 1627397858000 8 connected 10923-16383 ➜ pubsub redis-cli -p 7000 pubsub channels failover 1) "failover" ➜ pubsub redis-cli -p 7001 pubsub channels failover (empty array) ➜ pubsub redis-cli -p 7002 pubsub channels failover (empty array) ➜ pubsub redis-cli -p 7003 pubsub channels failover (empty array) ➜ pubsub redis-cli -p 7004 pubsub channels failover (empty array) ➜ pubsub redis-cli -p 7005 pubsub channels failover (empty array)

이제 7000의 slave인 7005를 failover 시켜보면,

7000이 slave 7005 가 master가 되었음에도, 여전히 토픽은 7000에만 존재한다. (1번 가정이 fact)

➜ pubsub redis-cli -p 7005 cluster failover OK ➜ pubsub redis-cli -p 7000 pubsub channels failover 1) "failover" ➜ pubsub redis-cli -p 7000 cluster nodes 26223e760092a2160deed834faaad2d0612a15ba 127.0.0.1:7005@17005 master - 0 1627398050433 10 connected 0-5460 938a20436c11733f81fd819535cc5f701b77a3ef 127.0.0.1:7003@17003 slave e052c7d608262e4f5fb90a703b7a72e3e19f50dd 0 1627398051449 9 connected f98b88b5468429b7545a97a049d19ae6ff71ad33 127.0.0.1:7002@17002 slave 8219e397fa70fac32074fe4db4078f466c04e673 0 1627398049510 8 connected c9ebc0c23bb35b4be1de18cedc817efa205f7e32 127.0.0.1:7000@17000 myself,slave 26223e760092a2160deed834faaad2d0612a15ba 0 1627398049000 10 connected e052c7d608262e4f5fb90a703b7a72e3e19f50dd 127.0.0.1:7001@17001 master - 0 1627398051000 9 connected 5461-10922 8219e397fa70fac32074fe4db4078f466c04e673 127.0.0.1:7004@17004 master - 0 1627398050943 8 connected 10923-16383 ➜ pubsub redis-cli -p 7005 pubsub channels failover (empty array)

그리고 일시적으로 redis 컨테이너를 죽인 후 다시 run 시켰다.

Spring boot 가 자동으로 reconnection 을 맺는다.

이 과정에서 채널토픽이 새로운 커넥션으로 재생성 된다.

그런데 반전은 7005가 slave 라는 것... 뭐야 너..

➜ pubsub redis-cli -p 7000 pubsub channels failover (empty array) ➜ pubsub redis-cli -p 7001 pubsub channels failover (empty array) ➜ pubsub redis-cli -p 7002 pubsub channels failover (empty array) ➜ pubsub redis-cli -p 7003 pubsub channels failover (empty array) ➜ pubsub redis-cli -p 7004 pubsub channels failover (empty array) ➜ pubsub redis-cli -p 7005 pubsub channels failover 1) "failover" ➜ pubsub redis-cli -p 7003 cluster nodes b76e606e800d6d1d1b9234e13cccc348d240f610 127.0.0.1:7005@17005 slave 19e56b0583720212560c01c992da6d56e627cf30 0 1627399921000 3 connected 19e56b0583720212560c01c992da6d56e627cf30 127.0.0.1:7002@17002 master - 0 1627399921688 3 connected 10923-16383 e4d47d30402fabe3d07bf585091c0e280f7f9ce9 127.0.0.1:7003@17003 myself,slave 95d9521f78f93734c44fdd2f3efca836c7140c2e 0 1627399919000 1 connected e3203ca4ae4f24783ee84a35a84850cbcc86cd3f 127.0.0.1:7004@17004 slave c85c5d0f5f941980d8789468e4764fcb393bf744 0 1627399921000 2 connected 95d9521f78f93734c44fdd2f3efca836c7140c2e 127.0.0.1:7000@17000 master - 0 1627399920000 1 connected 0-5460 c85c5d0f5f941980d8789468e4764fcb393bf744 127.0.0.1:7001@17001 master - 0 1627399921000 2 connected 5461-10922

음 테스트 하면서 확인한 사실은 아래와 같다.

1. topic은 slave 까지 전파되지 않는다.

2. topic을 가진 master 가 failover 시 토픽을 가진 노드가 slave로 내려갈 수 있다.

(새로운 마스터로 sync 되지 않음)

3. failover 후 spring - redis 간 커넥션이 계속 유지된 경우는 pubsub이 정상작동한다.

4. failover 후 spring - redis 간 커넥션이 계속 끊어지고, reconnection 시 채널 생성 및 리스너 등록 Bean 이 재생성되지 않는 경우,

토픽이 삭제되어 pubsub이 정상동작하지 않을 수 있다.

-> 이 경우는 사실 테스트로 확인하지는 못했다.

이유는 아래와 같다.

1. spring 서버를 죽이면 빈이 재생성 되면서 채널토픽이 무조건 새로 생성된다.

2. redis 컨테이너를 죽였다 살리면 spring이 reconnection 시도하면서 채널토픽을 새로 생성한다.

아리까리 하지만 쨋든 결론은!

Redis pubsub 은 failover 등의 이유로 토픽이 삭제될 경우 정상동작 하지 않으니,

메세지가 무조건 전달되어야 하는 경우에는 사용하지 않는게 좋다!!

참고자료

https://github.com/Grokzen/docker-redis-cluster

https://docs.spring.io/spring-data/redis/docs/2.5.3/reference/html/#pubsub

https://brunch.co.kr/@springboot/374

https://redis.io/topics/cluster-tutorial

from http://surmong.tistory.com/8 by ccl(A) rewrite - 2021-07-28 01:26:13