Redis expired key monitoring and publish and subscribe function java

Background: publish and subscribe (pub|sub) function is provided after redis2.8

To implement redis expired key monitoring, you only need to bind the message channel of the key expiration event ( keyevent@* :expired) with the listener in the listener container . keyevent@* : the * in expired means matching all db0-db15 databases in redis, keyevent@0 : expired means only listening to the key expiration event of the db0 database

Implementation of spring-data-redis
1. The KeyExpirationEventMessageListener listener is provided in spring-data-redis, and the KeyExpirationEventMessageListener can be directly inherited and the onMessage method
code can be rewritten to realize the key expiration.

package com.wl.redis.listener;

import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;

/**
 * Created by Administrator on 2020/12/10.
 */
@Component
public class KeyExpirationListener extends KeyExpirationEventMessageListener {
    
    

    public KeyExpirationListener(RedisMessageListenerContainer listenerContainer){
    
    
        super(listenerContainer);
    }

    public void onMessage(Message message, @Nullable byte[] pattern) {
    
    
        String key = message.toString();
        System.out.println("监听到key:" + key + "过期");
    }

}

KeyExpirationEventMessageListener has only one constructor with RedisMessageListenerContainer parameters, so we need to inject RedisMessageListenerContainer

package com.wl.redis.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;

/**
 * Created by Administrator on 2020/12/10.
 */
@Configuration
public class RedisConfig {
    
    
    @Bean
    public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
    
    
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        return container;
    }
}

Binding the channel to the listener is implemented
in KeyExpirationEventMessageListener. In KeyExpirationEventMessageListener, the doRegister method of its parent class is overridden. Bind __keyevent@*__:expired to the listener. Part of the source code is as follows

	private static final Topic KEYEVENT_EXPIRED_TOPIC = new PatternTopic("__keyevent@*__:expired");

	@Override
	protected void doRegister(RedisMessageListenerContainer listenerContainer) {
    
    
		listenerContainer.addMessageListener(this, KEYEVENT_EXPIRED_TOPIC);
	}

Test set order 123456 ex 4
console output 监听到key:order过期
2. Custom key expired monitor
Implement MessageListener interface

package com.wl.redis.listener;

import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;

/**
 * Created by Administrator on 2020/12/11.
 */
@Component
public class CustomerKeyExpirationListener implements MessageListener {
    
    

    @Override
    public void onMessage(Message message, @Nullable byte[] pattern) {
    
    
        String key = message.toString();
        System.out.println("监听到key:" + key + "过期");
    }

}

Container binds channel and listener

package com.wl.redis.config;

import com.wl.redis.listener.CustomerKeyExpirationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;

/**
 * Created by Administrator on 2020/12/10.
 */
@Configuration
public class RedisConfig {
    
    
    @Bean
    public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory
            , CustomerKeyExpirationListener customerKeyExpirationListener) {
    
    
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.addMessageListener(customerKeyExpirationListener,new PatternTopic("__keyevent@*__:expired"));
        return container;
    }

}

Recommendations for using redis key expired monitoring
1. The name of the key that needs to be monitored should be different from other normal ones that do not need to be monitored. E.g

	public void onMessage(Message message, @Nullable byte[] pattern) {
    
    
        String key = message.toString();
        if(!key.startsWith("expiration")){
    
    
            return;
        }
        System.out.println("监听到key:" + key + "过期");
    }

2. The key has been deleted when the monitoring is triggered, and only the value of the key can be obtained but the value of its value cannot be obtained. Therefore, the key should contain the information you need, or the information you need can be queried through the key
3. Actively deleting the key before the key expires will not trigger the overdue monitoring event. In the scenario of canceling the order without payment after the order expires, if the order has been paid or cancelled before the key expires, the key should be deleted
4. In a distributed scenario, The listener will listen multiple times, so a lock is needed (to prevent the same key from being monitored and executed multiple times). For example

package com.wl.redis.listener;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Created by Administrator on 2020/12/10.
 */
@Component
public class KeyExpirationListener extends KeyExpirationEventMessageListener {
    
    

    @Autowired
    private StringRedisTemplate redisTemplate;
    
    public KeyExpirationListener(RedisMessageListenerContainer listenerContainer){
    
    
        super(listenerContainer);
    }

    public void onMessage(Message message, @Nullable byte[] pattern) {
    
    
        String key = message.toString();
        if(!key.startsWith("expiration")){
    
    
            return;
        }
        //加锁(不同的key过期获取的锁是不一样的)
        String lockKey = "lock_" + key;
        boolean lock = lock(lockKey);
        if(!lock){
    
    
            System.out.println("===return" + key + "===");
            return;
        }
        try {
    
    
            System.out.println("监听到key:" + key + "过期");
            //释放锁(可以不用释放)
            // 这里睡眠5秒后解锁,防止程序太快,导致服务1已经执行完毕,服务2才刚刚开始获取锁
            Thread.sleep(5000);
            unlock(lockKey);
        } catch (InterruptedException e) {
    
    
            //
        }

    }

    private Boolean lock(String lockKey){
    
    
        Long timeOut = redisTemplate.getExpire(lockKey);
        SessionCallback<Boolean> sessionCallback = new SessionCallback<Boolean>() {
    
    
            List<Object> exec = null;
            @Override
            @SuppressWarnings("unchecked")
            public Boolean execute(RedisOperations operations) throws DataAccessException {
    
    
                operations.multi();
                operations.opsForValue().setIfAbsent(lockKey,"lock");
                if(timeOut == null || timeOut == -2) {
    
    
                    operations.expire(lockKey, 30, TimeUnit.SECONDS);
                }
                exec = operations.exec();
                if(exec.size() > 0) {
    
    
                    return (Boolean) exec.get(0);
                }
                return false;
            }
        };
        return redisTemplate.execute(sessionCallback);
    }

    private void unlock(String lockKey){
    
    
        redisTemplate.delete(lockKey);
    }

}

Start two services and add test data

		for(int i = 1;i<=10;i++){
    
    
            stringRedisTemplate.opsForValue().set("expiration_order_" + i,i + "",6,TimeUnit.SECONDS);
            Thread.sleep(1000);
        }

The two services are output as follows

===returnexpiration_order_1===
===returnexpiration_order_2===
===returnexpiration_order_3===
===returnexpiration_order_4===
===returnexpiration_order_5===
监听到key:expiration_order_6过期
监听到key:expiration_order_7过期
===returnexpiration_order_8===
===returnexpiration_order_9===
===returnexpiration_order_10===
监听到key:expiration_order_1过期
监听到key:expiration_order_2过期
监听到key:expiration_order_3过期
监听到key:expiration_order_4过期
监听到key:expiration_order_5过期
===returnexpiration_order_6===
===returnexpiration_order_7===
监听到key:expiration_order_8过期
监听到key:expiration_order_9过期
监听到key:expiration_order_10过期

5. If your redis is restarted after the publication and subscription become invalid, please modify the configuration file of redis.conf as follows
6.

notify-keyspace-events EA

Use redis to publish and subscribe. The
above example has implemented the binding (subscription) of the channel and the listener. The following only needs to implement the publish message.

package com.wl.redis.publisher;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.Topic;
import org.springframework.stereotype.Component;

/**
 * Created by Administrator on 2020/12/11.
 */
@Component
public class RedisPublisher {
    
    

    @Autowired
    private RedisTemplate redisTemplate;

    public void sendMessage(String channel,String message){
    
    
        redisTemplate.convertAndSend(channel,message);
    }

}

listener

package com.wl.redis.listener;

import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;

/**
 * Created by Administrator on 2020/12/11.
 */
@Component
public class CustomerListener implements MessageListener{
    
    
    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public void onMessage(Message message, @Nullable byte[] pattern) {
    
    
        //反序列化解决message乱码
        RedisSerializer<?> serializer = redisTemplate.getValueSerializer();
        System.out.println("================" + serializer.deserialize(message.getBody()).toString());
    }
}

config

package com.wl.redis.config;

import com.wl.redis.listener.CustomerKeyExpirationListener;
import com.wl.redis.listener.CustomerListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;

/**
 * Created by Administrator on 2020/12/10.
 */
@Configuration
public class RedisConfig {
    
    
    @Bean
    public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory
            ,CustomerListener customerListener) {
    
    
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
//        container.addMessageListener(customerKeyExpirationListener,new PatternTopic("__keyevent@*__:expired"));
        container.addMessageListener(customerListener,new ChannelTopic("customer"));
        return container;
    }

}

test

publisher.sendMessage("customer","你好");

Note that if the RedisSerializer is not deserialized in the CustomerListener, it may cause the received message to be garbled

Implementation of Jedis
1. The listener inherits the JedisPubSub class

package com.wl.redis.jedis;

import org.springframework.stereotype.Component;
import redis.clients.jedis.JedisPubSub;

/**
 * Created by Administrator on 2020/12/11.
 */
@Component
public class JedisKeyExpirationListener extends JedisPubSub {
    
    

    public void onMessage(String channel, String message) {
    
    
        System.out.println("channel:"+ channel + "  message:" + message+":");
    }

}

Subscribe to __keyevent@0__:expired (note that @ is not followed by an asterisk but 0, only db0 can be monitored, otherwise it will not be monitored)

	@Autowired
    private JedisKeyExpirationListener jedisKeyExpirationListener;

    static JedisPool pool = new JedisPool("192.168.92.128",6380);
    @Override
    public void run(String... args) throws Exception {
    
    

        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                pool.getResource().subscribe(jedisKeyExpirationListener,"__keyevent@0__:expired");
            }
        }).start();
        
        Jedis jedis = pool.getResource();
        jedis.set("wl","你好");
        jedis.expire("wl",5);
        jedis.close();
    }

Finally, introduce a free redis client
https://github.com/qishibo/AnotherRedisDesktopManager

Guess you like

Origin blog.csdn.net/name_is_wl/article/details/111037739
Recommended