INT-4507: Use UNLINK in RedisMessageStore if any

JIRA: https://jira.spring.io/browse/INT-4507

* Add `RedisUtils` with the `isUnlinkAvailable()` to check the Redis
server version to be sure that `UNLINK` is available or not
* Use `RedisUtils.isUnlinkAvailable()` in the `RedisMessageStore` and
`RedisLockRegistry` when a removal functionality is performed
* Add `AbstractKeyValueMessageStore.doRemoveAll(Collection<Object> ids)`
for optimization
* Implement `doRemoveAll()` in the `RedisMessageStore` and
`GemfireMessageStore`

**Cherry-pick to 5.0.x**
This commit is contained in:
Artem Bilan
2018-07-25 14:04:18 -04:00
committed by Gary Russell
parent d61cd790fe
commit 88c7646ed9
6 changed files with 137 additions and 20 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2007-2017 the original author or authors.
* Copyright 2007-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.integration.redis.util.RedisUtils;
import org.springframework.integration.store.AbstractKeyValueMessageStore;
import org.springframework.integration.store.MessageGroupStore;
import org.springframework.integration.store.MessageStore;
@@ -66,7 +67,7 @@ public class RedisMessageStore extends AbstractKeyValueMessageStore implements B
*/
public RedisMessageStore(RedisConnectionFactory connectionFactory, String prefix) {
super(prefix);
this.redisTemplate = new RedisTemplate<Object, Object>();
this.redisTemplate = new RedisTemplate<>();
this.redisTemplate.setConnectionFactory(connectionFactory);
this.redisTemplate.setKeySerializer(new StringRedisSerializer());
this.redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
@@ -130,11 +131,26 @@ public class RedisMessageStore extends AbstractKeyValueMessageStore implements B
Assert.notNull(id, "'id' must not be null");
Object removedObject = this.doRetrieve(id);
if (removedObject != null) {
this.redisTemplate.delete(id);
if (RedisUtils.isUnlinkAvailable(this.redisTemplate)) {
this.redisTemplate.unlink(id);
}
else {
this.redisTemplate.delete(id);
}
}
return removedObject;
}
@Override
protected void doRemoveAll(Collection<Object> ids) {
if (RedisUtils.isUnlinkAvailable(this.redisTemplate)) {
this.redisTemplate.unlink(ids);
}
else {
this.redisTemplate.delete(ids);
}
}
@Override
protected Collection<?> doListKeys(String keyPattern) {
Assert.hasText(keyPattern, "'keyPattern' must not be empty");

View File

@@ -302,11 +302,10 @@ public final class RedisLockRegistry implements ExpirableLockRegistry, Disposabl
}
try {
if (Thread.currentThread().isInterrupted()) {
RedisLockRegistry.this.executor.execute(() ->
RedisLockRegistry.this.redisTemplate.delete(this.lockKey));
RedisLockRegistry.this.executor.execute(this::removeLockKey);
}
else {
RedisLockRegistry.this.redisTemplate.delete(this.lockKey);
removeLockKey();
}
if (logger.isDebugEnabled()) {
@@ -321,6 +320,15 @@ public final class RedisLockRegistry implements ExpirableLockRegistry, Disposabl
}
}
private void removeLockKey() {
if (RedisUtils.isUnlinkAvailable(RedisLockRegistry.this.redisTemplate)) {
RedisLockRegistry.this.redisTemplate.unlink(this.lockKey);
}
else {
RedisLockRegistry.this.redisTemplate.delete(this.lockKey);
}
}
@Override
public Condition newCondition() {
throw new UnsupportedOperationException("Conditions are not supported");

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.redis.util;
import java.util.Properties;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisOperations;
/**
* A set of utility methods for common Redis functions.
*
* @author Artem Bilan
*
* @since 5.1
*/
public final class RedisUtils {
private static final String SECTION = "server";
private static final String VERSION_PROPERTY = "redis_version";
private static final int MAJOR_VERSION_TO_COMPARE = 4;
private static Boolean unlinkAvailable;
/**
* Perform an {@code INFO} command on the provided {@link RedisOperations} to check
* the Redis server version to be sure that {@code UNLINK} is available or not.
* @param redisOperations the {@link RedisOperations} to perform {@code INFO} command.
* @return true or false if {@code UNLINK} Redis command is available or not.
* @throws IllegalStateException when {@code INFO} returns null from the Redis.
*/
public static boolean isUnlinkAvailable(RedisOperations<?, ?> redisOperations) {
if (unlinkAvailable == null) {
Properties info = redisOperations.execute(
(RedisCallback<Properties>) connection -> connection.serverCommands().info(SECTION));
if (info != null) {
int majorVersion = Integer.parseInt(info.getProperty(VERSION_PROPERTY).split("\\.")[0]);
unlinkAvailable = majorVersion >= MAJOR_VERSION_TO_COMPARE;
}
else {
throw new IllegalStateException("The INFO command cannot be used in pipeline/transaction.");
}
}
return unlinkAvailable;
}
private RedisUtils() {
}
}