ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • KeyspaceEventMessageListener 이용 시, RedisCommandExecutionException: ERR unknown command 'CONFIG', with args beginning with: 'GET' 'notify-keyspace-events' 에러 해결
    웹 개발/Spring Framework 2023. 6. 18. 02:48

    기존에 사용하던 레디스를 deprecated 시키고, 사내 인프라 도구를 이용해 새로 레디스를 만들었다.

     

    그랬더니 기존에 없던 에러가 발생했는데, 아래의 에러가 발생했다.

    RedisCommandExecutionException: ERR unknown command 'CONFIG', with args beginning with: 'GET' 'notify-keyspace-events'

     

    근본적인 원인은 레디스 서버에서 CONFIG 명령어를 허용하지 않아서 클라이언트측에서 발생하는 에러다. 클라이언트측이라 함은 결국 내가 운영하는 서버.

     

    spring data redis에서 Redis keyspace notifications 기능을 사용한다치면 서버가 올라올 때 자동으로 redis에다가 CONFIG 명령어를 날린다. 그래서 위의 에러가 발생한다.

     

    결국 해결하려면 레디스 클라이언트 라이브러리에서 레디스 서버로 CONFIG 명령어를 날리지 않도록 비활성화하는 방법을 찾으면 된다.

     

    구글링을 해보면 대부분 아래 같은 해결방법을 제시했다. 나와 같지 않은 이슈를 겪는 사람들을 위해서 남겨둔다.


    1. 

    AWS의 ElastiCache는 기본적으로 CONFIG 명령어를 허용하지 않는다고 한다. 

    The keyspace notification message listener alters notify-keyspace-events settings in Redis, if those are not already set. Existing settings are not overridden, so you must set up those settings correctly (or leave them empty). Note that CONFIG is disabled on AWS ElastiCache, and enabling the listener leads to an error. To work around this behavior, set the keyspaceNotificationsConfigParameter parameter to an empty string. This prevents CONFIG command usage.

    출처: Spring 공식 문서

     

    그리고 RedisRepository를 사용하고 있다면 아래처럼 해결할 수 있다.

    @EnableRedisRepositories(
        enableKeyspaceEvents = RedisKeyValueAdapter.EnableKeyspaceEvents.ON_STARTUP,
        keyspaceNotificationsConfigParameter = ""
    )
    @Configuration
    class RedisConfiguration()

     

    /**
    * Set the configuration string to use for {@literal notify-keyspace-events}.
    *
    * @param keyspaceNotificationsConfigParameter can be {@literal null}.
    * @since 1.8
    */
    public void setKeyspaceNotificationsConfigParameter(String keyspaceNotificationsConfigParameter) {
           this.keyspaceNotificationsConfigParameter = keyspaceNotificationsConfigParameter;
    }

     

    코드를 살짝 까보니 KeyspaceEventMessageListener.class에 setKeyspaceNotificationsConfigParameter를 통해 emptyString을 넣어주는것이다.

     

    /**
    * Initialize the message listener by writing requried redis config for {@literal notify-keyspace-events} and
    * registering the listener within the container.
    */
    public void init() {

            if (StringUtils.hasText(keyspaceNotificationsConfigParameter)) {

                 RedisConnection connection = listenerContainer.getConnectionFactory().getConnection();

                 try {

                       Properties config = connection.getConfig("notify-keyspace-events");

                       if (!StringUtils.hasText(config.getProperty("notify-keyspace-events"))) {
                           connection.setConfig("notify-keyspace-events", keyspaceNotificationsConfigParameter);
                        }

                 } finally {
                        connection.close();
                 }
           }
     
           doRegister(listenerContainer);
    }

     

    KeyspaceEventMessageListener.class의 init() 함수에서 keyspaceNotificationsConfigParameter의 empty 여부를 체크해서  레디스 서버에 notify-keyspace-events를 조회한다. 이때 CONFIG 커맨드를 사용하는것이다.

     

    2.

    Spring Session Data Redis처럼 @EnableRedisHttpSession를 사용하고 있을 경우는 아래처럼 해결할 수 있다고 한다.

    (난 쓰지 않아서 모르겠지만 그렇다고 한다.)

    @Bean
    public ConfigureRedisAction configureRedisAction() {
    	return ConfigureRedisAction.NO_OP;
    }

    내 케이스는 Redisson을 사용하고 RedisRepository를 사용하지 않는다.

    KeyspaceEventMessageListener를 상속받은 KeyExpirationEventMessageListener를 커스텀해서 사용하고 있는데, 위의 케이스들과는 따로노는 코드라 위의 코드들처럼 Spring data redis에서 자체적으로 제공해주는 방식으로 해결하지 못했고 커스텀한 코드로 해결했다.

     

    아래는 회사에서 사용한 Redis Key의 만료 이벤트를 처리하는 리스너 코드이다.

    <처리방법>

    @Component
    class RedisExpirationListener(
        private val listenerContainer: RedisMessageListenerContainer
    ) : KeyExpirationEventMessageListener(listenerContainer) {
        // 커스텀한 오버라이딩 메서드
        override fun init() {
            super.doRegister(listenerContainer)
        }
    
        override fun doHandleMessage(message: Message) {
            // 메세지 처리
        }
    }

     

    커스텀한 리스너 코드도 KeyspaceEventMessageListener의 init() 메서드를 타면서 에러가 발생했기 때문에 init() 메서드의 

    if (StringUtils.hasText(keyspaceNotificationsConfigParameter)) {} 로직을 태우지 않게 만들었다.

     

    (스크롤 올리기 귀찮은 사람들을 위해 다시 복붙해둔다.)

    /**
    * Initialize the message listener by writing requried redis config for {@literal notify-keyspace-events} and
    * registering the listener within the container.
    */
    public void init() {

            if (StringUtils.hasText(keyspaceNotificationsConfigParameter)) {

                 RedisConnection connection = listenerContainer.getConnectionFactory().getConnection();

                 try {

                       Properties config = connection.getConfig("notify-keyspace-events");

                       if (!StringUtils.hasText(config.getProperty("notify-keyspace-events"))) {
                            connection.setConfig("notify-keyspace-events", keyspaceNotificationsConfigParameter);
                       }

                 } finally {
                       connection.close();
                 }
            }

            doRegister(listenerContainer);
    }

     

    내가 만든 커스텀 코드에서 init() 메서드를 따로 오버라이딩 하지 않으면,

    if (StringUtils.hasText(keyspaceNotificationsConfigParameter)) {} 이 로직을 타기 때문에 재정의해서 해당 로직만 안 타도록 만들었다. 왜냐면 KeyspaceEventMessageListener.class의 keyspaceNotificationsConfigParameter 변수가 전역 변수로 초기값이 "EA"로 선언되어있기 때문이다.

     

    요렇게 하니 서버가 잘 올라온다. 끝!

     

Designed by Tistory.