The project has a business scenario in which a message notification is sent to the user's public number at a fixed point in time.~
Because the time point is not fixed, there is no timed task.
The idea of using redis key failure listener is to save keys.
Calculate the time interval between the current and the time when the notification needs to be sent as the key expiration time, so that the message can be sent in real time on the spot.
In single-machine mode, test push is normal, but when on-line, users receive two push. The reason is that the service has opened the cluster and each service receives notification when the key fails. At this time, the push of the message occurs, so the problem of pushing multiple messages occurs.
At this time, we can consider using redis setNx command to achieve lock competition. At the same time, only one service can preempt the same key, that is, to send messages, and other services that do not get the lock will give up executing message push directly.
The setNx command principle is to return 1 if the key does not exist, or 0 if it is saved successfully.
The redis command is executed in a single thread, so there must be only one person who can get a successful result at the same time.
The implementation code is as follows
@Component public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener { @Autowired private RedisRepository redisRepository; public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer){ super(listenerContainer); } @Override public void onMessage(Message message, byte[] pattern) { String expiredKey = message.toString(); // Get the lock focus, where multiple services or threads call setNx commands that preempt the same key if(redisRepository.lockBySecondsTime(key, 30)){ // Message notification pseudocode push(); } super.onMessage(message, pattern); } }
The logic of the lock BySecondsTime lock method is as follows
public boolean lockBySecondsTime(String key, long expirationTime){ // This method is only applicable to lock contention scenarios during expiration time. If the lock fails automatically beyond the expiration time period, and the thread that acquired the lock before is still running, it loses the meaning of distributed lock, so it should be used carefully according to its own scenario. Long timeStamp = new Date().getTime() + (expirationTime * 1000); // Getting locks through setNx return ifAbsent(key, String.valueOf(timeStamp), expirationTime, TimeUnit.SECONDS); } public boolean ifAbsent(String key, String value, long expirationTime , TimeUnit timeUnit) { Boolean res = (Boolean) redisTemplate.execute(new RedisCallback() { @Override public Boolean doInRedis(RedisConnection connection) throws DataAccessException { return connection.stringCommands().set(key.getBytes(), value.getBytes(), Expiration.from(expirationTime, timeUnit), RedisStringCommands.SetOption.ifAbsent()); } }); return res == null ? false : res; }
The above code relies on the Spring Data Redis module. Take a look at the ifAbsent method, which uses the redisTemplate.execute method here. Why do you do this?
If you are in version 2.1.0 or higher, you can do the ifAbsent operation in the above code directly by redisTemplate.opsForValue().setIfAbsent(key,value,time).
However, only redisTemplate.opsForValue().setIfAbsent(key,value) method is used in the version below 2.1.0, which lacks a set key expiration time. If this method is used, key will always exist in redis, resulting in a waste of memory space resources. Therefore, the author uses redisTemplate.execute to expand and protect it. When the key is used as a lock competing resource, it will disappear.
By using redis locks, the same key operation can only be done by one service at the same time, so my message notification will be normal at this time, and there will be no duplicate message push.~
Recommend a redis-based distributed lock framework redisson interested to learn about~