Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow adding specific keyspace/keyevent topic(s) with KeyspaceEventMessageListener #2757

Open
wbrco opened this issue Oct 25, 2023 · 2 comments
Assignees
Labels
for: team-attention An issue we need to discuss as a team to make progress in:messaging Redis messaging components type: enhancement A general enhancement

Comments

@wbrco
Copy link

wbrco commented Oct 25, 2023

Redis allows for more specific psubscribe filters allowing for subscription to specific keys, or specific events as documented in https://redis.io/docs/manual/keyspace-notifications/

However, the Spring KeyspaceEventMessageListener subscribes to "__keyevent@*", which doesn't include keyspace events, and provides all events.

This requires the programmer to filter every event for both the event type, and the key to see if the event is actionable.

Can a mechanism be added such that a more finite subscription topic be specified, e.g. an overload constructor allows a topic String, or other Setter that can be specified, and then call afterProperties() or init()?

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Oct 25, 2023
@jxblum
Copy link
Contributor

jxblum commented Oct 25, 2023

You should have a look at Issue #2670 as well. Also see PR #2671.

There are good (examples) cases for your argument in this issue, and I generally agree with you.

However, I will point out that the Spring Data Redis KeyspaceEventMessageListener is an abstract class (Javadoc). Therefore, it was meant to be extended.

You can find one such implementation in Spring Data Redis with the KeyExpirationMessageListener class (Javadoc; source, which unfortunately, is still tied to Redis keyevent type messages, though.

Still, it is a simple matter to get what you want through extension. For example:

public class MyApplicatonKeyspaceEventMessageListener extends KeyspaceEventMessageListener {

    @Override
    protected void doRegister(RedisMessageListenerContainer container) {
        listenerContainer.addMessageListener(this, "__keyspace@*");
    }
}

Or even something more specific than * in keyspace@*.

Additionally, the setKeyspaceNotificationsConfigParameter(:String) (Javadoc) can be used to tune your listener for the appropriate events coming from Redis accordingly.

Hope this helps!

@jxblum jxblum added for: team-attention An issue we need to discuss as a team to make progress in:messaging Redis messaging components type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged labels Oct 25, 2023
@jxblum jxblum changed the title Enhancement request - KeyspaceEventMessageListener allow adding specific keyspace/keyevent topic(s) Allow adding specific keyspace/keyevent topic(s) with KeyspaceEventMessageListener Oct 25, 2023
@jxblum jxblum self-assigned this Oct 26, 2023
@wbrco
Copy link
Author

wbrco commented Nov 3, 2023

@jxblum FYI - to fix my problem, I just copy/pasted and modified the KeyspaceEventMessageListener code to allow for setting the topic.

public abstract class CustomKeyspaceEventMessageListener implements MessageListener, InitializingBean, DisposableBean {

    Logger _log = LoggerFactory.getLogger(getClass());
    private Topic _topic;

    private static String KEYEVENT = "__keyevent@";
    private static String KEYSPACE = "__keyspace@";
    private final RedisMessageListenerContainer listenerContainer;

    private String keyspaceNotificationsConfigParameter = "KEA";

    /**
     * Creates new {@link KeyspaceEventMessageListener}.
     *
     * @param listenerContainer must not be {@literal null}.
     * @param topic must start with {@Literal "__keyevent@"} or {@Literal "__keyspace@"}
     */
    public CustomKeyspaceEventMessageListener(RedisMessageListenerContainer listenerContainer, String topic) {

        Assert.notNull(listenerContainer, "RedisMessageListenerContainer to run in must not be null");

        if (!StringUtils.substringMatch(topic, 0, KEYEVENT) && !StringUtils.substringMatch(topic, 0, KEYSPACE)) {
            throw new IllegalArgumentException("Topic must start with __keyevent@ or __keyspace@");
        }
        _topic = new PatternTopic(topic);
        this.listenerContainer = listenerContainer;
    }

    @Override
    public void onMessage(Message message, @Nullable byte[] pattern) {

        if (ObjectUtils.isEmpty(message.getChannel()) || ObjectUtils.isEmpty(message.getBody())) {
            return;
        }

        try {
            doHandleMessage(message);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Handle the actual message
     *
     * @param message never {@literal null}.
     */
    protected abstract void doHandleMessage(Message message) throws Exception;

    /**
     * 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);
    }

    /**
     * Register instance within the container.
     *
     * @param container never {@literal null}.
     */
    protected void doRegister(RedisMessageListenerContainer container) {
        listenerContainer.addMessageListener(this, _topic);
    }


    @Override
    public void destroy() throws Exception {
        listenerContainer.removeMessageListener(this);
    }

    /**
     * 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;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        init();
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
for: team-attention An issue we need to discuss as a team to make progress in:messaging Redis messaging components type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

3 participants