Merge remote-tracking branch 'mrniko/master'
commit
9f664bbaba
@ -0,0 +1,126 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
|
||||||
|
*
|
||||||
|
* 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.redisson;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.AbstractMap;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
import org.redisson.client.protocol.decoder.MapScanResult;
|
||||||
|
import org.redisson.client.protocol.decoder.ScanObjectEntry;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
|
abstract class RedissonBaseMapIterator<K, V, M> implements Iterator<M> {
|
||||||
|
|
||||||
|
private Map<ByteBuf, ByteBuf> firstValues;
|
||||||
|
private Iterator<Map.Entry<ScanObjectEntry, ScanObjectEntry>> iter;
|
||||||
|
protected long iterPos = 0;
|
||||||
|
protected InetSocketAddress client;
|
||||||
|
|
||||||
|
private boolean finished;
|
||||||
|
private boolean removeExecuted;
|
||||||
|
protected Map.Entry<ScanObjectEntry, ScanObjectEntry> entry;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
if (finished) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (iter == null || !iter.hasNext()) {
|
||||||
|
MapScanResult<ScanObjectEntry, ScanObjectEntry> res = iterator();
|
||||||
|
client = res.getRedisClient();
|
||||||
|
if (iterPos == 0 && firstValues == null) {
|
||||||
|
firstValues = convert(res.getMap());
|
||||||
|
} else {
|
||||||
|
Map<ByteBuf, ByteBuf> newValues = convert(res.getMap());
|
||||||
|
if (newValues.equals(firstValues)) {
|
||||||
|
finished = true;
|
||||||
|
free(firstValues);
|
||||||
|
free(newValues);
|
||||||
|
firstValues = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
free(newValues);
|
||||||
|
}
|
||||||
|
iter = res.getMap().entrySet().iterator();
|
||||||
|
iterPos = res.getPos();
|
||||||
|
}
|
||||||
|
return iter.hasNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract MapScanResult<ScanObjectEntry, ScanObjectEntry> iterator();
|
||||||
|
|
||||||
|
private void free(Map<ByteBuf, ByteBuf> map) {
|
||||||
|
for (Entry<ByteBuf, ByteBuf> entry : map.entrySet()) {
|
||||||
|
entry.getKey().release();
|
||||||
|
entry.getValue().release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<ByteBuf, ByteBuf> convert(Map<ScanObjectEntry, ScanObjectEntry> map) {
|
||||||
|
Map<ByteBuf, ByteBuf> result = new HashMap<ByteBuf, ByteBuf>(map.size());
|
||||||
|
for (Entry<ScanObjectEntry, ScanObjectEntry> entry : map.entrySet()) {
|
||||||
|
result.put(entry.getKey().getBuf(), entry.getValue().getBuf());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public M next() {
|
||||||
|
if (!hasNext()) {
|
||||||
|
throw new NoSuchElementException("No such element at index");
|
||||||
|
}
|
||||||
|
|
||||||
|
entry = iter.next();
|
||||||
|
removeExecuted = false;
|
||||||
|
return getValue(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
M getValue(final Entry<ScanObjectEntry, ScanObjectEntry> entry) {
|
||||||
|
return (M)new AbstractMap.SimpleEntry<K, V>((K)entry.getKey().getObj(), (V)entry.getValue().getObj()) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V setValue(V value) {
|
||||||
|
return put(entry, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
if (removeExecuted) {
|
||||||
|
throw new IllegalStateException("Element been already deleted");
|
||||||
|
}
|
||||||
|
|
||||||
|
// lazy init iterator
|
||||||
|
hasNext();
|
||||||
|
iter.remove();
|
||||||
|
removeKey();
|
||||||
|
removeExecuted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void removeKey();
|
||||||
|
|
||||||
|
protected abstract V put(Entry<ScanObjectEntry, ScanObjectEntry> entry, V value);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,292 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
|
||||||
|
*
|
||||||
|
* 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.redisson;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import org.redisson.client.codec.Codec;
|
||||||
|
import org.redisson.client.protocol.RedisCommands;
|
||||||
|
import org.redisson.client.protocol.RedisStrictCommand;
|
||||||
|
import org.redisson.client.protocol.convertor.BooleanAmountReplayConvertor;
|
||||||
|
import org.redisson.command.CommandAsyncExecutor;
|
||||||
|
import org.redisson.core.RListMultimap;
|
||||||
|
|
||||||
|
import io.netty.util.concurrent.Future;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Nikita Koksharov
|
||||||
|
*
|
||||||
|
* @param <K> key
|
||||||
|
* @param <V> value
|
||||||
|
*/
|
||||||
|
public class RedissonListMultimap<K, V> extends RedissonMultimap<K, V> implements RListMultimap<K, V> {
|
||||||
|
|
||||||
|
private static final RedisStrictCommand<Boolean> LLEN_VALUE = new RedisStrictCommand<Boolean>("LLEN", new BooleanAmountReplayConvertor());
|
||||||
|
|
||||||
|
RedissonListMultimap(CommandAsyncExecutor connectionManager, String name) {
|
||||||
|
super(connectionManager, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
RedissonListMultimap(Codec codec, CommandAsyncExecutor connectionManager, String name) {
|
||||||
|
super(codec, connectionManager, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<Integer> sizeAsync() {
|
||||||
|
return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_INTEGER,
|
||||||
|
"local keys = redis.call('hgetall', KEYS[1]); " +
|
||||||
|
"local size = 0; " +
|
||||||
|
"for i, v in ipairs(keys) do " +
|
||||||
|
"if i % 2 == 0 then " +
|
||||||
|
"local name = '{' .. KEYS[1] .. '}:' .. v; " +
|
||||||
|
"size = size + redis.call('llen', name); " +
|
||||||
|
"end;" +
|
||||||
|
"end; " +
|
||||||
|
"return size; ",
|
||||||
|
Arrays.<Object>asList(getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<Boolean> containsKeyAsync(Object key) {
|
||||||
|
try {
|
||||||
|
byte[] keyState = codec.getMapKeyEncoder().encode(key);
|
||||||
|
String keyHash = hash(keyState);
|
||||||
|
|
||||||
|
String setName = getValuesName(keyHash);
|
||||||
|
return commandExecutor.readAsync(getName(), codec, LLEN_VALUE, setName);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<Boolean> containsValueAsync(Object value) {
|
||||||
|
try {
|
||||||
|
byte[] valueState = codec.getMapValueEncoder().encode(value);
|
||||||
|
|
||||||
|
return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN,
|
||||||
|
"local keys = redis.call('hgetall', KEYS[1]); " +
|
||||||
|
"for i, v in ipairs(keys) do " +
|
||||||
|
"if i % 2 == 0 then " +
|
||||||
|
"local name = '{' .. KEYS[1] .. '}:' .. v; " +
|
||||||
|
|
||||||
|
"local items = redis.call('lrange', name, 0, -1) " +
|
||||||
|
"for i=1,#items do " +
|
||||||
|
"if items[i] == ARGV[1] then " +
|
||||||
|
"return 1; " +
|
||||||
|
"end; " +
|
||||||
|
"end; " +
|
||||||
|
"end;" +
|
||||||
|
"end; " +
|
||||||
|
"return 0; ",
|
||||||
|
Arrays.<Object>asList(getName()), valueState);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsEntry(Object key, Object value) {
|
||||||
|
return get(containsEntryAsync(key, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<Boolean> containsEntryAsync(Object key, Object value) {
|
||||||
|
try {
|
||||||
|
byte[] keyState = codec.getMapKeyEncoder().encode(key);
|
||||||
|
String keyHash = hash(keyState);
|
||||||
|
byte[] valueState = codec.getMapValueEncoder().encode(value);
|
||||||
|
|
||||||
|
String setName = getValuesName(keyHash);
|
||||||
|
|
||||||
|
return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN,
|
||||||
|
"local items = redis.call('lrange', KEYS[1], 0, -1) " +
|
||||||
|
"for i=1,#items do " +
|
||||||
|
"if items[i] == ARGV[1] then " +
|
||||||
|
"return 1; " +
|
||||||
|
"end; " +
|
||||||
|
"end; " +
|
||||||
|
"return 0; ",
|
||||||
|
Collections.<Object>singletonList(setName), valueState);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean put(K key, V value) {
|
||||||
|
return get(putAsync(key, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<Boolean> putAsync(K key, V value) {
|
||||||
|
try {
|
||||||
|
byte[] keyState = codec.getMapKeyEncoder().encode(key);
|
||||||
|
String keyHash = hash(keyState);
|
||||||
|
byte[] valueState = codec.getMapValueEncoder().encode(value);
|
||||||
|
|
||||||
|
String setName = getValuesName(keyHash);
|
||||||
|
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN,
|
||||||
|
"redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); " +
|
||||||
|
"redis.call('rpush', KEYS[2], ARGV[3]); " +
|
||||||
|
"return 1; ",
|
||||||
|
Arrays.<Object>asList(getName(), setName), keyState, keyHash, valueState);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<Boolean> removeAsync(Object key, Object value) {
|
||||||
|
try {
|
||||||
|
byte[] keyState = codec.getMapKeyEncoder().encode(key);
|
||||||
|
String keyHash = hash(keyState);
|
||||||
|
byte[] valueState = codec.getMapValueEncoder().encode(value);
|
||||||
|
|
||||||
|
String setName = getValuesName(keyHash);
|
||||||
|
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN,
|
||||||
|
"local res = redis.call('lrem', KEYS[2], 1, ARGV[2]); "
|
||||||
|
+ "if res == 1 and redis.call('llen', KEYS[2]) == 0 then "
|
||||||
|
+ "redis.call('hdel', KEYS[1], ARGV[1]); "
|
||||||
|
+ "end; "
|
||||||
|
+ "return res; ",
|
||||||
|
Arrays.<Object>asList(getName(), setName), keyState, valueState);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<Boolean> putAllAsync(K key, Iterable<? extends V> values) {
|
||||||
|
try {
|
||||||
|
List<Object> params = new ArrayList<Object>();
|
||||||
|
byte[] keyState = codec.getMapKeyEncoder().encode(key);
|
||||||
|
params.add(keyState);
|
||||||
|
String keyHash = hash(keyState);
|
||||||
|
params.add(keyHash);
|
||||||
|
for (Object value : values) {
|
||||||
|
byte[] valueState = codec.getMapValueEncoder().encode(value);
|
||||||
|
params.add(valueState);
|
||||||
|
}
|
||||||
|
|
||||||
|
String setName = getValuesName(keyHash);
|
||||||
|
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_AMOUNT,
|
||||||
|
"redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); " +
|
||||||
|
"return redis.call('rpush', KEYS[2], unpack(ARGV, 3, #ARGV)); ",
|
||||||
|
Arrays.<Object>asList(getName(), setName), params.toArray());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<V> get(K key) {
|
||||||
|
try {
|
||||||
|
byte[] keyState = codec.getMapKeyEncoder().encode(key);
|
||||||
|
String keyHash = hash(keyState);
|
||||||
|
String setName = getValuesName(keyHash);
|
||||||
|
|
||||||
|
return new RedissonList<V>(codec, commandExecutor, setName);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<V> getAll(K key) {
|
||||||
|
return (List<V>) get(getAllAsync(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<Collection<V>> getAllAsync(K key) {
|
||||||
|
try {
|
||||||
|
byte[] keyState = codec.getMapKeyEncoder().encode(key);
|
||||||
|
String keyHash = hash(keyState);
|
||||||
|
String setName = getValuesName(keyHash);
|
||||||
|
|
||||||
|
return commandExecutor.readAsync(getName(), codec, RedisCommands.LRANGE, setName, 0, -1);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<V> removeAll(Object key) {
|
||||||
|
return (List<V>) get(removeAllAsync(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<Collection<V>> removeAllAsync(Object key) {
|
||||||
|
try {
|
||||||
|
byte[] keyState = codec.getMapKeyEncoder().encode(key);
|
||||||
|
String keyHash = hash(keyState);
|
||||||
|
|
||||||
|
String setName = getValuesName(keyHash);
|
||||||
|
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_LIST,
|
||||||
|
"redis.call('hdel', KEYS[1], ARGV[1]); " +
|
||||||
|
"local members = redis.call('lrange', KEYS[2], 0, -1); " +
|
||||||
|
"redis.call('del', KEYS[2]); " +
|
||||||
|
"return members; ",
|
||||||
|
Arrays.<Object>asList(getName(), setName), keyState);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<V> replaceValues(K key, Iterable<? extends V> values) {
|
||||||
|
return (List<V>) get(replaceValuesAsync(key, values));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<Collection<V>> replaceValuesAsync(K key, Iterable<? extends V> values) {
|
||||||
|
try {
|
||||||
|
List<Object> params = new ArrayList<Object>();
|
||||||
|
byte[] keyState = codec.getMapKeyEncoder().encode(key);
|
||||||
|
params.add(keyState);
|
||||||
|
String keyHash = hash(keyState);
|
||||||
|
params.add(keyHash);
|
||||||
|
for (Object value : values) {
|
||||||
|
byte[] valueState = codec.getMapValueEncoder().encode(value);
|
||||||
|
params.add(valueState);
|
||||||
|
}
|
||||||
|
|
||||||
|
String setName = getValuesName(keyHash);
|
||||||
|
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_LIST,
|
||||||
|
"redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); " +
|
||||||
|
"local members = redis.call('lrange', KEYS[2], 0, -1); " +
|
||||||
|
"redis.call('del', KEYS[2]); " +
|
||||||
|
"redis.call('rpush', KEYS[2], unpack(ARGV, 3, #ARGV)); " +
|
||||||
|
"return members; ",
|
||||||
|
Arrays.<Object>asList(getName(), setName), params.toArray());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<V> valuesIterator() {
|
||||||
|
return new RedissonListMultimapIterator<K, V, V>(this, commandExecutor, codec) {
|
||||||
|
@Override
|
||||||
|
V getValue(V entry) {
|
||||||
|
return (V) entry;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
RedissonMultiMapIterator<K, V, Entry<K, V>> entryIterator() {
|
||||||
|
return new RedissonListMultimapIterator<K, V, Map.Entry<K, V>>(this, commandExecutor, codec);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
|
||||||
|
*
|
||||||
|
* 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.redisson;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import org.redisson.client.codec.Codec;
|
||||||
|
import org.redisson.command.CommandAsyncExecutor;
|
||||||
|
|
||||||
|
public class RedissonListMultimapIterator<K, V, M> extends RedissonMultiMapIterator<K, V, M> {
|
||||||
|
|
||||||
|
public RedissonListMultimapIterator(RedissonMultimap<K, V> map, CommandAsyncExecutor commandExecutor, Codec codec) {
|
||||||
|
super(map, commandExecutor, codec);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Iterator<V> getIterator(String name) {
|
||||||
|
RedissonList<V> set = new RedissonList<V>(codec, commandExecutor, map.getValuesName(name));
|
||||||
|
return set.iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,160 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
|
||||||
|
*
|
||||||
|
* 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.redisson;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.AbstractMap;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
import org.redisson.client.codec.Codec;
|
||||||
|
import org.redisson.client.protocol.decoder.MapScanResult;
|
||||||
|
import org.redisson.client.protocol.decoder.ScanObjectEntry;
|
||||||
|
import org.redisson.command.CommandAsyncExecutor;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
|
abstract class RedissonMultiMapIterator<K, V, M> implements Iterator<M> {
|
||||||
|
|
||||||
|
private Map<ByteBuf, ByteBuf> firstKeys;
|
||||||
|
private Iterator<Map.Entry<ScanObjectEntry, ScanObjectEntry>> keysIter;
|
||||||
|
protected long keysIterPos = 0;
|
||||||
|
|
||||||
|
private K currentKey;
|
||||||
|
private Iterator<V> valuesIter;
|
||||||
|
protected long valuesIterPos = 0;
|
||||||
|
|
||||||
|
protected InetSocketAddress client;
|
||||||
|
|
||||||
|
private boolean finished;
|
||||||
|
private boolean removeExecuted;
|
||||||
|
protected V entry;
|
||||||
|
|
||||||
|
final RedissonMultimap<K, V> map;
|
||||||
|
|
||||||
|
final CommandAsyncExecutor commandExecutor;
|
||||||
|
final Codec codec;
|
||||||
|
|
||||||
|
|
||||||
|
public RedissonMultiMapIterator(RedissonMultimap<K, V> map, CommandAsyncExecutor commandExecutor, Codec codec) {
|
||||||
|
this.map = map;
|
||||||
|
this.commandExecutor = commandExecutor;
|
||||||
|
this.codec = codec;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
if (finished) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valuesIter != null && valuesIter.hasNext()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keysIter == null || !keysIter.hasNext()) {
|
||||||
|
MapScanResult<ScanObjectEntry, ScanObjectEntry> res = map.scanIterator(client, keysIterPos);
|
||||||
|
client = res.getRedisClient();
|
||||||
|
if (keysIterPos == 0 && firstKeys == null) {
|
||||||
|
firstKeys = convert(res.getMap());
|
||||||
|
} else {
|
||||||
|
Map<ByteBuf, ByteBuf> newValues = convert(res.getMap());
|
||||||
|
if (newValues.equals(firstKeys)) {
|
||||||
|
finished = true;
|
||||||
|
free(firstKeys);
|
||||||
|
free(newValues);
|
||||||
|
firstKeys = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
free(newValues);
|
||||||
|
}
|
||||||
|
keysIter = res.getMap().entrySet().iterator();
|
||||||
|
keysIterPos = res.getPos();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (keysIter.hasNext()) {
|
||||||
|
Entry<ScanObjectEntry, ScanObjectEntry> e = keysIter.next();
|
||||||
|
currentKey = (K) e.getKey().getObj();
|
||||||
|
String name = e.getValue().getObj().toString();
|
||||||
|
valuesIter = getIterator(name);
|
||||||
|
if (valuesIter.hasNext()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Iterator<V> getIterator(String name);
|
||||||
|
|
||||||
|
private void free(Map<ByteBuf, ByteBuf> map) {
|
||||||
|
for (Entry<ByteBuf, ByteBuf> entry : map.entrySet()) {
|
||||||
|
entry.getKey().release();
|
||||||
|
entry.getValue().release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<ByteBuf, ByteBuf> convert(Map<ScanObjectEntry, ScanObjectEntry> map) {
|
||||||
|
Map<ByteBuf, ByteBuf> result = new HashMap<ByteBuf, ByteBuf>(map.size());
|
||||||
|
for (Entry<ScanObjectEntry, ScanObjectEntry> entry : map.entrySet()) {
|
||||||
|
result.put(entry.getKey().getBuf(), entry.getValue().getBuf());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public M next() {
|
||||||
|
if (!hasNext()) {
|
||||||
|
throw new NoSuchElementException("No such element at index");
|
||||||
|
}
|
||||||
|
|
||||||
|
entry = valuesIter.next();
|
||||||
|
removeExecuted = false;
|
||||||
|
return getValue(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
M getValue(V entry) {
|
||||||
|
return (M)new AbstractMap.SimpleEntry<K, V>(currentKey, entry) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V setValue(V value) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
if (removeExecuted) {
|
||||||
|
throw new IllegalStateException("Element been already deleted");
|
||||||
|
}
|
||||||
|
|
||||||
|
// lazy init iterator
|
||||||
|
hasNext();
|
||||||
|
keysIter.remove();
|
||||||
|
map.remove(currentKey, entry);
|
||||||
|
removeExecuted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
|
||||||
|
*
|
||||||
|
* 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.redisson;
|
||||||
|
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import org.redisson.client.protocol.decoder.MapScanResult;
|
||||||
|
import org.redisson.client.protocol.decoder.ScanObjectEntry;
|
||||||
|
|
||||||
|
public class RedissonMultiMapKeysIterator<K, V, M> extends RedissonBaseMapIterator<K, V, M> {
|
||||||
|
|
||||||
|
private final RedissonMultimap<K, V> map;
|
||||||
|
|
||||||
|
public RedissonMultiMapKeysIterator(RedissonMultimap<K, V> map) {
|
||||||
|
this.map = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MapScanResult<ScanObjectEntry, ScanObjectEntry> iterator() {
|
||||||
|
return map.scanIterator(client, iterPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void removeKey() {
|
||||||
|
map.fastRemove((K)entry.getKey().getObj());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected V put(Entry<ScanObjectEntry, ScanObjectEntry> entry, V value) {
|
||||||
|
map.put((K) entry.getKey().getObj(), value);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,289 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
|
||||||
|
*
|
||||||
|
* 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.redisson;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.AbstractCollection;
|
||||||
|
import java.util.AbstractSet;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.redisson.client.codec.Codec;
|
||||||
|
import org.redisson.client.codec.ScanCodec;
|
||||||
|
import org.redisson.client.codec.StringCodec;
|
||||||
|
import org.redisson.client.protocol.RedisCommands;
|
||||||
|
import org.redisson.client.protocol.decoder.MapScanResult;
|
||||||
|
import org.redisson.client.protocol.decoder.ScanObjectEntry;
|
||||||
|
import org.redisson.command.CommandAsyncExecutor;
|
||||||
|
import org.redisson.core.RMultimap;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import io.netty.handler.codec.base64.Base64;
|
||||||
|
import io.netty.util.CharsetUtil;
|
||||||
|
import io.netty.util.concurrent.Future;
|
||||||
|
import net.openhft.hashing.LongHashFunction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Nikita Koksharov
|
||||||
|
*
|
||||||
|
* @param <K> key
|
||||||
|
* @param <V> value
|
||||||
|
*/
|
||||||
|
public abstract class RedissonMultimap<K, V> extends RedissonExpirable implements RMultimap<K, V> {
|
||||||
|
|
||||||
|
RedissonMultimap(CommandAsyncExecutor connectionManager, String name) {
|
||||||
|
super(connectionManager, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
RedissonMultimap(Codec codec, CommandAsyncExecutor connectionManager, String name) {
|
||||||
|
super(codec, connectionManager, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String hash(byte[] objectState) {
|
||||||
|
long h1 = LongHashFunction.farmUo().hashBytes(objectState);
|
||||||
|
long h2 = LongHashFunction.xx_r39().hashBytes(objectState);
|
||||||
|
|
||||||
|
ByteBuf buf = Unpooled.buffer((2 * Long.SIZE) / Byte.SIZE).writeLong(h1).writeLong(h2);
|
||||||
|
|
||||||
|
ByteBuf b = Base64.encode(buf);
|
||||||
|
String s = b.toString(CharsetUtil.UTF_8);
|
||||||
|
b.release();
|
||||||
|
buf.release();
|
||||||
|
return s.substring(0, s.length() - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return get(sizeAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return size() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsKey(Object key) {
|
||||||
|
return get(containsKeyAsync(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsValue(Object value) {
|
||||||
|
return get(containsValueAsync(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsEntry(Object key, Object value) {
|
||||||
|
return get(containsEntryAsync(key, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean put(K key, V value) {
|
||||||
|
return get(putAsync(key, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
String getValuesName(String hash) {
|
||||||
|
return "{" + getName() + "}:" + hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean remove(Object key, Object value) {
|
||||||
|
return get(removeAsync(key, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean putAll(K key, Iterable<? extends V> values) {
|
||||||
|
return get(putAllAsync(key, values));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<K> keySet() {
|
||||||
|
return new KeySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<V> values() {
|
||||||
|
return new Values();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<V> getAll(K key) {
|
||||||
|
return get(getAllAsync(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<V> removeAll(Object key) {
|
||||||
|
return get(removeAllAsync(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<V> replaceValues(K key, Iterable<? extends V> values) {
|
||||||
|
return get(replaceValuesAsync(key, values));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Entry<K, V>> entries() {
|
||||||
|
return new EntrySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long fastRemove(K ... keys) {
|
||||||
|
return get(fastRemoveAsync(keys));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<Long> fastRemoveAsync(K ... keys) {
|
||||||
|
if (keys == null || keys.length == 0) {
|
||||||
|
return newSucceededFuture(0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<Object> args = new ArrayList<Object>(keys.length*2);
|
||||||
|
List<Object> hashes = new ArrayList<Object>();
|
||||||
|
for (K key : keys) {
|
||||||
|
byte[] keyState = codec.getMapKeyEncoder().encode(key);
|
||||||
|
args.add(keyState);
|
||||||
|
String keyHash = hash(keyState);
|
||||||
|
String name = getValuesName(keyHash);
|
||||||
|
hashes.add(name);
|
||||||
|
}
|
||||||
|
args.addAll(hashes);
|
||||||
|
|
||||||
|
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_LONG,
|
||||||
|
"local res = redis.call('hdel', KEYS[1], unpack(ARGV, 1, #ARGV/2)); " +
|
||||||
|
"if res > 0 then " +
|
||||||
|
"redis.call('del', unpack(ARGV, #ARGV/2, #ARGV)); " +
|
||||||
|
"end; " +
|
||||||
|
"return res; ",
|
||||||
|
Collections.<Object>singletonList(getName()), args.toArray());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MapScanResult<ScanObjectEntry, ScanObjectEntry> scanIterator(InetSocketAddress client, long startPos) {
|
||||||
|
Future<MapScanResult<ScanObjectEntry, ScanObjectEntry>> f = commandExecutor.readAsync(client, getName(), new ScanCodec(codec, StringCodec.INSTANCE), RedisCommands.HSCAN, getName(), startPos);
|
||||||
|
return get(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract Iterator<V> valuesIterator();
|
||||||
|
|
||||||
|
abstract RedissonMultiMapIterator<K, V, Entry<K, V>> entryIterator();
|
||||||
|
|
||||||
|
final class KeySet extends AbstractSet<K> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<K> iterator() {
|
||||||
|
return new RedissonMultiMapKeysIterator<K, V, K>(RedissonMultimap.this) {
|
||||||
|
@Override
|
||||||
|
K getValue(java.util.Map.Entry<ScanObjectEntry, ScanObjectEntry> entry) {
|
||||||
|
return (K) entry.getKey().getObj();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(Object o) {
|
||||||
|
return RedissonMultimap.this.containsKey(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean remove(Object o) {
|
||||||
|
return RedissonMultimap.this.fastRemove((K)o) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return RedissonMultimap.this.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
RedissonMultimap.this.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
final class Values extends AbstractCollection<V> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<V> iterator() {
|
||||||
|
return valuesIterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(Object o) {
|
||||||
|
return RedissonMultimap.this.containsValue(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return RedissonMultimap.this.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
RedissonMultimap.this.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
|
||||||
|
|
||||||
|
public final Iterator<Map.Entry<K,V>> iterator() {
|
||||||
|
return entryIterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean contains(Object o) {
|
||||||
|
if (!(o instanceof Map.Entry))
|
||||||
|
return false;
|
||||||
|
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
|
||||||
|
return containsEntry(e.getKey(), e.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean remove(Object o) {
|
||||||
|
if (o instanceof Map.Entry) {
|
||||||
|
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
|
||||||
|
Object key = e.getKey();
|
||||||
|
Object value = e.getValue();
|
||||||
|
return RedissonMultimap.this.remove(key, value);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int size() {
|
||||||
|
return RedissonMultimap.this.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void clear() {
|
||||||
|
RedissonMultimap.this.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,275 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
|
||||||
|
*
|
||||||
|
* 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.redisson;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.redisson.client.codec.Codec;
|
||||||
|
import org.redisson.client.protocol.RedisCommand;
|
||||||
|
import org.redisson.client.protocol.RedisCommands;
|
||||||
|
import org.redisson.client.protocol.RedisStrictCommand;
|
||||||
|
import org.redisson.client.protocol.convertor.BooleanAmountReplayConvertor;
|
||||||
|
import org.redisson.client.protocol.convertor.BooleanReplayConvertor;
|
||||||
|
import org.redisson.command.CommandAsyncExecutor;
|
||||||
|
import org.redisson.core.RSetMultimap;
|
||||||
|
|
||||||
|
import io.netty.util.concurrent.Future;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Nikita Koksharov
|
||||||
|
*
|
||||||
|
* @param <K> key
|
||||||
|
* @param <V> value
|
||||||
|
*/
|
||||||
|
public class RedissonSetMultimap<K, V> extends RedissonMultimap<K, V> implements RSetMultimap<K, V> {
|
||||||
|
|
||||||
|
private static final RedisStrictCommand<Boolean> SCARD_VALUE = new RedisStrictCommand<Boolean>("SCARD", new BooleanAmountReplayConvertor());
|
||||||
|
private static final RedisCommand<Boolean> SISMEMBER_VALUE = new RedisCommand<Boolean>("SISMEMBER", new BooleanReplayConvertor());
|
||||||
|
|
||||||
|
RedissonSetMultimap(CommandAsyncExecutor connectionManager, String name) {
|
||||||
|
super(connectionManager, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
RedissonSetMultimap(Codec codec, CommandAsyncExecutor connectionManager, String name) {
|
||||||
|
super(codec, connectionManager, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<Integer> sizeAsync() {
|
||||||
|
return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_INTEGER,
|
||||||
|
"local keys = redis.call('hgetall', KEYS[1]); " +
|
||||||
|
"local size = 0; " +
|
||||||
|
"for i, v in ipairs(keys) do " +
|
||||||
|
"if i % 2 == 0 then " +
|
||||||
|
"local name = '{' .. KEYS[1] .. '}:' .. v; " +
|
||||||
|
"size = size + redis.call('scard', name); " +
|
||||||
|
"end;" +
|
||||||
|
"end; " +
|
||||||
|
"return size; ",
|
||||||
|
Arrays.<Object>asList(getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<Boolean> containsKeyAsync(Object key) {
|
||||||
|
try {
|
||||||
|
byte[] keyState = codec.getMapKeyEncoder().encode(key);
|
||||||
|
String keyHash = hash(keyState);
|
||||||
|
|
||||||
|
String setName = getValuesName(keyHash);
|
||||||
|
return commandExecutor.readAsync(getName(), codec, SCARD_VALUE, setName);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<Boolean> containsValueAsync(Object value) {
|
||||||
|
try {
|
||||||
|
byte[] valueState = codec.getMapValueEncoder().encode(value);
|
||||||
|
|
||||||
|
return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN,
|
||||||
|
"local keys = redis.call('hgetall', KEYS[1]); " +
|
||||||
|
"for i, v in ipairs(keys) do " +
|
||||||
|
"if i % 2 == 0 then " +
|
||||||
|
"local name = '{' .. KEYS[1] .. '}:' .. v; " +
|
||||||
|
"if redis.call('sismember', name, ARGV[1]) == 1 then "
|
||||||
|
+ "return 1; " +
|
||||||
|
"end;" +
|
||||||
|
"end;" +
|
||||||
|
"end; " +
|
||||||
|
"return 0; ",
|
||||||
|
Arrays.<Object>asList(getName()), valueState);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<Boolean> containsEntryAsync(Object key, Object value) {
|
||||||
|
try {
|
||||||
|
byte[] keyState = codec.getMapKeyEncoder().encode(key);
|
||||||
|
String keyHash = hash(keyState);
|
||||||
|
byte[] valueState = codec.getMapValueEncoder().encode(value);
|
||||||
|
|
||||||
|
String setName = getValuesName(keyHash);
|
||||||
|
return commandExecutor.readAsync(getName(), codec, SISMEMBER_VALUE, setName, valueState);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<Boolean> putAsync(K key, V value) {
|
||||||
|
try {
|
||||||
|
byte[] keyState = codec.getMapKeyEncoder().encode(key);
|
||||||
|
String keyHash = hash(keyState);
|
||||||
|
byte[] valueState = codec.getMapValueEncoder().encode(value);
|
||||||
|
|
||||||
|
String setName = getValuesName(keyHash);
|
||||||
|
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN,
|
||||||
|
"redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); " +
|
||||||
|
"return redis.call('sadd', KEYS[2], ARGV[3]); ",
|
||||||
|
Arrays.<Object>asList(getName(), setName), keyState, keyHash, valueState);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<Boolean> removeAsync(Object key, Object value) {
|
||||||
|
try {
|
||||||
|
byte[] keyState = codec.getMapKeyEncoder().encode(key);
|
||||||
|
String keyHash = hash(keyState);
|
||||||
|
byte[] valueState = codec.getMapValueEncoder().encode(value);
|
||||||
|
|
||||||
|
String setName = getValuesName(keyHash);
|
||||||
|
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN,
|
||||||
|
"local res = redis.call('srem', KEYS[2], ARGV[2]); "
|
||||||
|
+ "if res == 1 and redis.call('scard', KEYS[2]) == 0 then "
|
||||||
|
+ "redis.call('hdel', KEYS[1], ARGV[1]); "
|
||||||
|
+ "end; "
|
||||||
|
+ "return res; ",
|
||||||
|
Arrays.<Object>asList(getName(), setName), keyState, valueState);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<Boolean> putAllAsync(K key, Iterable<? extends V> values) {
|
||||||
|
try {
|
||||||
|
List<Object> params = new ArrayList<Object>();
|
||||||
|
byte[] keyState = codec.getMapKeyEncoder().encode(key);
|
||||||
|
params.add(keyState);
|
||||||
|
String keyHash = hash(keyState);
|
||||||
|
params.add(keyHash);
|
||||||
|
for (Object value : values) {
|
||||||
|
byte[] valueState = codec.getMapValueEncoder().encode(value);
|
||||||
|
params.add(valueState);
|
||||||
|
}
|
||||||
|
|
||||||
|
String setName = getValuesName(keyHash);
|
||||||
|
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_AMOUNT,
|
||||||
|
"redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); " +
|
||||||
|
"return redis.call('sadd', KEYS[2], unpack(ARGV, 3, #ARGV)); ",
|
||||||
|
Arrays.<Object>asList(getName(), setName), params.toArray());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<V> get(K key) {
|
||||||
|
try {
|
||||||
|
byte[] keyState = codec.getMapKeyEncoder().encode(key);
|
||||||
|
String keyHash = hash(keyState);
|
||||||
|
String setName = getValuesName(keyHash);
|
||||||
|
|
||||||
|
return new RedissonSet<V>(codec, commandExecutor, setName);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<V> getAll(K key) {
|
||||||
|
return (Set<V>) super.getAll(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<Collection<V>> getAllAsync(K key) {
|
||||||
|
try {
|
||||||
|
byte[] keyState = codec.getMapKeyEncoder().encode(key);
|
||||||
|
String keyHash = hash(keyState);
|
||||||
|
String setName = getValuesName(keyHash);
|
||||||
|
|
||||||
|
return commandExecutor.readAsync(getName(), codec, RedisCommands.SMEMBERS, setName);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<V> removeAll(Object key) {
|
||||||
|
return (Set<V>) get(removeAllAsync(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<Collection<V>> removeAllAsync(Object key) {
|
||||||
|
try {
|
||||||
|
byte[] keyState = codec.getMapKeyEncoder().encode(key);
|
||||||
|
String keyHash = hash(keyState);
|
||||||
|
|
||||||
|
String setName = getValuesName(keyHash);
|
||||||
|
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_SET,
|
||||||
|
"redis.call('hdel', KEYS[1], ARGV[1]); " +
|
||||||
|
"local members = redis.call('smembers', KEYS[2]); " +
|
||||||
|
"redis.call('del', KEYS[2]); " +
|
||||||
|
"return members; ",
|
||||||
|
Arrays.<Object>asList(getName(), setName), keyState);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Entry<K, V>> entries() {
|
||||||
|
return (Set<Entry<K, V>>) super.entries();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<V> replaceValues(K key, Iterable<? extends V> values) {
|
||||||
|
return (Set<V>) get(replaceValuesAsync(key, values));
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<V> valuesIterator() {
|
||||||
|
return new RedissonSetMultimapIterator<K, V, V>(RedissonSetMultimap.this, commandExecutor, codec) {
|
||||||
|
@Override
|
||||||
|
V getValue(V entry) {
|
||||||
|
return (V) entry;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
RedissonSetMultimapIterator<K, V, Entry<K, V>> entryIterator() {
|
||||||
|
return new RedissonSetMultimapIterator<K, V, Map.Entry<K, V>>(RedissonSetMultimap.this, commandExecutor, codec);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<Collection<V>> replaceValuesAsync(K key, Iterable<? extends V> values) {
|
||||||
|
try {
|
||||||
|
List<Object> params = new ArrayList<Object>();
|
||||||
|
byte[] keyState = codec.getMapKeyEncoder().encode(key);
|
||||||
|
params.add(keyState);
|
||||||
|
String keyHash = hash(keyState);
|
||||||
|
params.add(keyHash);
|
||||||
|
for (Object value : values) {
|
||||||
|
byte[] valueState = codec.getMapValueEncoder().encode(value);
|
||||||
|
params.add(valueState);
|
||||||
|
}
|
||||||
|
|
||||||
|
String setName = getValuesName(keyHash);
|
||||||
|
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_SET,
|
||||||
|
"redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); " +
|
||||||
|
"local members = redis.call('smembers', KEYS[2]); " +
|
||||||
|
"redis.call('del', KEYS[2]); " +
|
||||||
|
"redis.call('sadd', KEYS[2], unpack(ARGV, 3, #ARGV)); " +
|
||||||
|
"return members; ",
|
||||||
|
Arrays.<Object>asList(getName(), setName), params.toArray());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
|
||||||
|
*
|
||||||
|
* 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.redisson;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import org.redisson.client.codec.Codec;
|
||||||
|
import org.redisson.command.CommandAsyncExecutor;
|
||||||
|
|
||||||
|
public class RedissonSetMultimapIterator<K, V, M> extends RedissonMultiMapIterator<K, V, M> {
|
||||||
|
|
||||||
|
public RedissonSetMultimapIterator(RedissonMultimap<K, V> map, CommandAsyncExecutor commandExecutor, Codec codec) {
|
||||||
|
super(map, commandExecutor, codec);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Iterator<V> getIterator(String name) {
|
||||||
|
RedissonSet<V> set = new RedissonSet<V>(codec, commandExecutor, map.getValuesName(name));
|
||||||
|
return set.iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
|
||||||
|
*
|
||||||
|
* 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.redisson.client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This error occurs in case then Redis server free memory has been exhausted.
|
||||||
|
*
|
||||||
|
* @author Nikita Koksharov
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class RedisOutOfMemoryException extends RedisException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -2565335188503354660L;
|
||||||
|
|
||||||
|
public RedisOutOfMemoryException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
|
||||||
|
*
|
||||||
|
* 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.redisson.core;
|
||||||
|
|
||||||
|
public enum NodeType {
|
||||||
|
|
||||||
|
MASTER, SLAVE
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
|
||||||
|
*
|
||||||
|
* 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.redisson.core;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List based Multimap. Stores insertion order and allows duplicates for values mapped to key.
|
||||||
|
*
|
||||||
|
* @author Nikita Koksharov
|
||||||
|
*
|
||||||
|
* @param <K> key
|
||||||
|
* @param <V> value
|
||||||
|
*/
|
||||||
|
public interface RListMultimap<K, V> extends RMultimap<K, V> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* <p>Because a {@code RListMultimap} may has duplicates among values mapped by key and stores insertion order
|
||||||
|
* method returns a {@link List}, instead of the {@link java.util.Collection}
|
||||||
|
* specified in the {@link RMultimap} interface.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
List<V> get(K key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* <p>Because a {@code RListMultimap} may has duplicates among values mapped by key and stores insertion order
|
||||||
|
* method returns a {@link List}, instead of the {@link java.util.Collection}
|
||||||
|
* specified in the {@link RMultimap} interface.
|
||||||
|
*/
|
||||||
|
List<V> getAll(K key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* <p>Because a {@code RListMultimap} may has duplicates among values mapped by key and stores insertion order
|
||||||
|
* method returns a {@link List}, instead of the {@link java.util.Collection}
|
||||||
|
* specified in the {@link RMultimap} interface.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
List<V> removeAll(Object key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* <p>Because a {@code RListMultimap} may has duplicates among values mapped by key and stores insertion order
|
||||||
|
* method returns a {@link List}, instead of the {@link java.util.Collection}
|
||||||
|
* specified in the {@link RMultimap} interface.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
List<V> replaceValues(K key, Iterable<? extends V> values);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,198 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
|
||||||
|
*
|
||||||
|
* 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.redisson.core;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base Multimap interface. Allows to map multiple values per key.
|
||||||
|
*
|
||||||
|
* @author Nikita Koksharov
|
||||||
|
*
|
||||||
|
* @param <K> key
|
||||||
|
* @param <V> value
|
||||||
|
*/
|
||||||
|
public interface RMultimap<K, V> extends RExpirable, RMultimapAsync<K, V> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of key-value pairs in this multimap.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
int size();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check is map empty
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
boolean isEmpty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if this multimap contains at least one key-value pair
|
||||||
|
* with the key {@code key}.
|
||||||
|
*/
|
||||||
|
boolean containsKey(Object key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if this multimap contains at least one key-value pair
|
||||||
|
* with the value {@code value}.
|
||||||
|
*/
|
||||||
|
boolean containsValue(Object value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if this multimap contains at least one key-value pair
|
||||||
|
* with the key {@code key} and the value {@code value}.
|
||||||
|
*/
|
||||||
|
boolean containsEntry(Object key, Object value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a key-value pair in this multimap.
|
||||||
|
*
|
||||||
|
* <p>Some multimap implementations allow duplicate key-value pairs, in which
|
||||||
|
* case {@code put} always adds a new key-value pair and increases the
|
||||||
|
* multimap size by 1. Other implementations prohibit duplicates, and storing
|
||||||
|
* a key-value pair that's already in the multimap has no effect.
|
||||||
|
*
|
||||||
|
* @return {@code true} if the method increased the size of the multimap, or
|
||||||
|
* {@code false} if the multimap already contained the key-value pair and
|
||||||
|
* doesn't allow duplicates
|
||||||
|
*/
|
||||||
|
boolean put(K key, V value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a single key-value pair with the key {@code key} and the value
|
||||||
|
* {@code value} from this multimap, if such exists. If multiple key-value
|
||||||
|
* pairs in the multimap fit this description, which one is removed is
|
||||||
|
* unspecified.
|
||||||
|
*
|
||||||
|
* @return {@code true} if the multimap changed
|
||||||
|
*/
|
||||||
|
boolean remove(Object key, Object value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a key-value pair in this multimap for each of {@code values}, all
|
||||||
|
* using the same key, {@code key}. Equivalent to (but expected to be more
|
||||||
|
* efficient than): <pre> {@code
|
||||||
|
*
|
||||||
|
* for (V value : values) {
|
||||||
|
* put(key, value);
|
||||||
|
* }}</pre>
|
||||||
|
*
|
||||||
|
* <p>In particular, this is a no-op if {@code values} is empty.
|
||||||
|
*
|
||||||
|
* @return {@code true} if the multimap changed
|
||||||
|
*/
|
||||||
|
boolean putAll(K key, Iterable<? extends V> values);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a collection of values with the same key, replacing any existing
|
||||||
|
* values for that key.
|
||||||
|
*
|
||||||
|
* <p>If {@code values} is empty, this is equivalent to
|
||||||
|
* {@link #removeAll(Object) removeAll(key)}.
|
||||||
|
*
|
||||||
|
* @return the collection of replaced values, or an empty collection if no
|
||||||
|
* values were previously associated with the key. The collection
|
||||||
|
* <i>may</i> be modifiable, but updating it will have no effect on the
|
||||||
|
* multimap.
|
||||||
|
*/
|
||||||
|
Collection<V> replaceValues(K key, Iterable<? extends V> values);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all values associated with the key {@code key}.
|
||||||
|
*
|
||||||
|
* <p>Once this method returns, {@code key} will not be mapped to any values,
|
||||||
|
* so it will not appear in {@link #keySet()}, {@link #asMap()}, or any other
|
||||||
|
* views.
|
||||||
|
*
|
||||||
|
* @return the values that were removed (possibly empty). The returned
|
||||||
|
* collection <i>may</i> be modifiable, but updating it will have no
|
||||||
|
* effect on the multimap.
|
||||||
|
*/
|
||||||
|
Collection<V> removeAll(Object key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all key-value pairs from the multimap, leaving it {@linkplain
|
||||||
|
* #isEmpty empty}.
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a view collection of the values associated with {@code key} in this
|
||||||
|
* multimap, if any. Note that when {@code containsKey(key)} is false, this
|
||||||
|
* returns an empty collection, not {@code null}.
|
||||||
|
*
|
||||||
|
* <p>Changes to the returned collection will update the underlying multimap,
|
||||||
|
* and vice versa.
|
||||||
|
*/
|
||||||
|
Collection<V> get(K key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all elements at once. Result collection is <b>NOT</b> backed by map,
|
||||||
|
* so changes are not reflected in map.
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Collection<V> getAll(K key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a view collection of all <i>distinct</i> keys contained in this
|
||||||
|
* multimap. Note that the key set contains a key if and only if this multimap
|
||||||
|
* maps that key to at least one value.
|
||||||
|
*
|
||||||
|
* <p>Changes to the returned set will update the underlying multimap, and
|
||||||
|
* vice versa. However, <i>adding</i> to the returned set is not possible.
|
||||||
|
*/
|
||||||
|
Set<K> keySet();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a view collection containing the <i>value</i> from each key-value
|
||||||
|
* pair contained in this multimap, without collapsing duplicates (so {@code
|
||||||
|
* values().size() == size()}).
|
||||||
|
*
|
||||||
|
* <p>Changes to the returned collection will update the underlying multimap,
|
||||||
|
* and vice versa. However, <i>adding</i> to the returned collection is not
|
||||||
|
* possible.
|
||||||
|
*/
|
||||||
|
Collection<V> values();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a view collection of all key-value pairs contained in this
|
||||||
|
* multimap, as {@link Map.Entry} instances.
|
||||||
|
*
|
||||||
|
* <p>Changes to the returned collection or the entries it contains will
|
||||||
|
* update the underlying multimap, and vice versa. However, <i>adding</i> to
|
||||||
|
* the returned collection is not possible.
|
||||||
|
*/
|
||||||
|
Collection<Map.Entry<K, V>> entries();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes <code>keys</code> from map by one operation
|
||||||
|
*
|
||||||
|
* Works faster than <code>RMap.remove</code> but not returning
|
||||||
|
* the value associated with <code>key</code>
|
||||||
|
*
|
||||||
|
* @param keys
|
||||||
|
* @return the number of keys that were removed from the hash, not including specified but non existing keys
|
||||||
|
*/
|
||||||
|
long fastRemove(K ... keys);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,139 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
|
||||||
|
*
|
||||||
|
* 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.redisson.core;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import io.netty.util.concurrent.Future;
|
||||||
|
/**
|
||||||
|
* Base asynchronous MultiMap interface. A collection that maps multiple values per one key.
|
||||||
|
*
|
||||||
|
* @author Nikita Koksharov
|
||||||
|
*
|
||||||
|
* @param <K> key
|
||||||
|
* @param <V> value
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface RMultimapAsync<K, V> extends RExpirableAsync {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of key-value pairs in this multimap.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Future<Integer> sizeAsync();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if this multimap contains at least one key-value pair
|
||||||
|
* with the key {@code key}.
|
||||||
|
*/
|
||||||
|
Future<Boolean> containsKeyAsync(Object key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if this multimap contains at least one key-value pair
|
||||||
|
* with the value {@code value}.
|
||||||
|
*/
|
||||||
|
Future<Boolean> containsValueAsync(Object value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if this multimap contains at least one key-value pair
|
||||||
|
* with the key {@code key} and the value {@code value}.
|
||||||
|
*/
|
||||||
|
Future<Boolean> containsEntryAsync(Object key, Object value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a key-value pair in this multimap.
|
||||||
|
*
|
||||||
|
* <p>Some multimap implementations allow duplicate key-value pairs, in which
|
||||||
|
* case {@code put} always adds a new key-value pair and increases the
|
||||||
|
* multimap size by 1. Other implementations prohibit duplicates, and storing
|
||||||
|
* a key-value pair that's already in the multimap has no effect.
|
||||||
|
*
|
||||||
|
* @return {@code true} if the method increased the size of the multimap, or
|
||||||
|
* {@code false} if the multimap already contained the key-value pair and
|
||||||
|
* doesn't allow duplicates
|
||||||
|
*/
|
||||||
|
Future<Boolean> putAsync(K key, V value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a single key-value pair with the key {@code key} and the value
|
||||||
|
* {@code value} from this multimap, if such exists. If multiple key-value
|
||||||
|
* pairs in the multimap fit this description, which one is removed is
|
||||||
|
* unspecified.
|
||||||
|
*
|
||||||
|
* @return {@code true} if the multimap changed
|
||||||
|
*/
|
||||||
|
Future<Boolean> removeAsync(Object key, Object value);
|
||||||
|
|
||||||
|
// Bulk Operations
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a key-value pair in this multimap for each of {@code values}, all
|
||||||
|
* using the same key, {@code key}. Equivalent to (but expected to be more
|
||||||
|
* efficient than): <pre> {@code
|
||||||
|
*
|
||||||
|
* for (V value : values) {
|
||||||
|
* put(key, value);
|
||||||
|
* }}</pre>
|
||||||
|
*
|
||||||
|
* <p>In particular, this is a no-op if {@code values} is empty.
|
||||||
|
*
|
||||||
|
* @return {@code true} if the multimap changed
|
||||||
|
*/
|
||||||
|
Future<Boolean> putAllAsync(K key, Iterable<? extends V> values);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a collection of values with the same key, replacing any existing
|
||||||
|
* values for that key.
|
||||||
|
*
|
||||||
|
* <p>If {@code values} is empty, this is equivalent to
|
||||||
|
* {@link #removeAll(Object) removeAll(key)}.
|
||||||
|
*
|
||||||
|
* @return the collection of replaced values, or an empty collection if no
|
||||||
|
* values were previously associated with the key. The collection
|
||||||
|
* <i>may</i> be modifiable, but updating it will have no effect on the
|
||||||
|
* multimap.
|
||||||
|
*/
|
||||||
|
Future<Collection<V>> replaceValuesAsync(K key, Iterable<? extends V> values);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all values associated with the key {@code key}.
|
||||||
|
*
|
||||||
|
* <p>Once this method returns, {@code key} will not be mapped to any values,
|
||||||
|
* so it will not appear in {@link #keySet()}, {@link #asMap()}, or any other
|
||||||
|
* views.
|
||||||
|
*
|
||||||
|
* @return the values that were removed (possibly empty). The returned
|
||||||
|
* collection <i>may</i> be modifiable, but updating it will have no
|
||||||
|
* effect on the multimap.
|
||||||
|
*/
|
||||||
|
Future<Collection<V>> removeAllAsync(Object key);
|
||||||
|
|
||||||
|
Future<Collection<V>> getAllAsync(K key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes <code>keys</code> from map by one operation
|
||||||
|
*
|
||||||
|
* Works faster than <code>removeAll</code> but not returning
|
||||||
|
* the value associated with <code>key</code>
|
||||||
|
*
|
||||||
|
* @param keys
|
||||||
|
* @return the number of keys that were removed from the hash, not including specified but non existing keys
|
||||||
|
*/
|
||||||
|
Future<Long> fastRemoveAsync(K ... keys);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
|
||||||
|
*
|
||||||
|
* 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.redisson.core;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set based Multimap. Doesn't allow duplications for values mapped to key.
|
||||||
|
*
|
||||||
|
* @author Nikita Koksharov
|
||||||
|
*
|
||||||
|
* @param <K> key
|
||||||
|
* @param <V> value
|
||||||
|
*/
|
||||||
|
public interface RSetMultimap<K, V> extends RMultimap<K, V> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* <p>Because a {@code RSetMultiMap} has unique values for a given key, this
|
||||||
|
* method returns a {@link Set}, instead of the {@link java.util.Collection}
|
||||||
|
* specified in the {@link RMultimap} interface.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
Set<V> get(K key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* <p>Because a {@code RSetMultiMap} has unique values for a given key, this
|
||||||
|
* method returns a {@link Set}, instead of the {@link java.util.Collection}
|
||||||
|
* specified in the {@link RMultimap} interface.
|
||||||
|
*/
|
||||||
|
Set<V> getAll(K key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* <p>Because a {@code RSetMultiMap} has unique values for a given key, this
|
||||||
|
* method returns a {@link Set}, instead of the {@link java.util.Collection}
|
||||||
|
* specified in the {@link RMultimap} interface.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
Set<V> removeAll(Object key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* <p>Because a {@code RSetMultiMap} has unique values for a given key, this
|
||||||
|
* method returns a {@link Set}, instead of the {@link java.util.Collection}
|
||||||
|
* specified in the {@link RMultimap} interface.
|
||||||
|
*
|
||||||
|
* <p>Any duplicates in {@code values} will be stored in the multimap once.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
Set<V> replaceValues(K key, Iterable<? extends V> values);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* <p>Because a {@code RSetMultiMap} has unique values for a given key, this
|
||||||
|
* method returns a {@link Set}, instead of the {@link java.util.Collection}
|
||||||
|
* specified in the {@link RMultimap} interface.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
Set<Map.Entry<K, V>> entries();
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,288 @@
|
|||||||
|
package org.redisson;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.AbstractMap;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.redisson.core.RListMultimap;
|
||||||
|
|
||||||
|
public class RedissonListMultimapTest extends BaseTest {
|
||||||
|
|
||||||
|
public static class SimpleKey implements Serializable {
|
||||||
|
|
||||||
|
private String key;
|
||||||
|
|
||||||
|
public SimpleKey() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleKey(String field) {
|
||||||
|
this.key = field;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKey(String key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "key: " + key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + ((key == null) ? 0 : key.hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj)
|
||||||
|
return true;
|
||||||
|
if (obj == null)
|
||||||
|
return false;
|
||||||
|
if (getClass() != obj.getClass())
|
||||||
|
return false;
|
||||||
|
SimpleKey other = (SimpleKey) obj;
|
||||||
|
if (key == null) {
|
||||||
|
if (other.key != null)
|
||||||
|
return false;
|
||||||
|
} else if (!key.equals(other.key))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SimpleValue implements Serializable {
|
||||||
|
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
public SimpleValue() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleValue(String field) {
|
||||||
|
this.value = field;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(String field) {
|
||||||
|
this.value = field;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "value: " + value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + ((value == null) ? 0 : value.hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj)
|
||||||
|
return true;
|
||||||
|
if (obj == null)
|
||||||
|
return false;
|
||||||
|
if (getClass() != obj.getClass())
|
||||||
|
return false;
|
||||||
|
SimpleValue other = (SimpleValue) obj;
|
||||||
|
if (value == null) {
|
||||||
|
if (other.value != null)
|
||||||
|
return false;
|
||||||
|
} else if (!value.equals(other.value))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSize() {
|
||||||
|
RListMultimap<SimpleKey, SimpleValue> map = redisson.getListMultimap("test1");
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("1"));
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("2"));
|
||||||
|
map.put(new SimpleKey("1"), new SimpleValue("4"));
|
||||||
|
|
||||||
|
assertThat(map.size()).isEqualTo(3);
|
||||||
|
|
||||||
|
assertThat(map.fastRemove(new SimpleKey("0"))).isEqualTo(1);
|
||||||
|
|
||||||
|
List<SimpleValue> s = map.get(new SimpleKey("0"));
|
||||||
|
assertThat(s).isEmpty();
|
||||||
|
assertThat(map.size()).isEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPut() {
|
||||||
|
RListMultimap<SimpleKey, SimpleValue> map = redisson.getListMultimap("test1");
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("1"));
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("2"));
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("3"));
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("3"));
|
||||||
|
map.put(new SimpleKey("3"), new SimpleValue("4"));
|
||||||
|
|
||||||
|
assertThat(map.size()).isEqualTo(5);
|
||||||
|
|
||||||
|
List<SimpleValue> s1 = map.get(new SimpleKey("0"));
|
||||||
|
assertThat(s1).containsExactly(new SimpleValue("1"), new SimpleValue("2"), new SimpleValue("3"), new SimpleValue("3"));
|
||||||
|
|
||||||
|
List<SimpleValue> allValues = map.getAll(new SimpleKey("0"));
|
||||||
|
assertThat(allValues).containsExactly(new SimpleValue("1"), new SimpleValue("2"), new SimpleValue("3"), new SimpleValue("3"));
|
||||||
|
|
||||||
|
List<SimpleValue> s2 = map.get(new SimpleKey("3"));
|
||||||
|
assertThat(s2).containsExactly(new SimpleValue("4"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemoveAll() {
|
||||||
|
RListMultimap<SimpleKey, SimpleValue> map = redisson.getListMultimap("test1");
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("1"));
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("1"));
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("2"));
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("3"));
|
||||||
|
|
||||||
|
List<SimpleValue> values = map.removeAll(new SimpleKey("0"));
|
||||||
|
assertThat(values).containsExactly(new SimpleValue("1"), new SimpleValue("1"), new SimpleValue("2"), new SimpleValue("3"));
|
||||||
|
assertThat(map.size()).isZero();
|
||||||
|
|
||||||
|
List<SimpleValue> values2 = map.removeAll(new SimpleKey("0"));
|
||||||
|
assertThat(values2).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFastRemove() {
|
||||||
|
RListMultimap<SimpleKey, SimpleValue> map = redisson.getListMultimap("test1");
|
||||||
|
assertThat(map.put(new SimpleKey("0"), new SimpleValue("1"))).isTrue();
|
||||||
|
assertThat(map.put(new SimpleKey("0"), new SimpleValue("2"))).isTrue();
|
||||||
|
assertThat(map.put(new SimpleKey("0"), new SimpleValue("2"))).isTrue();
|
||||||
|
assertThat(map.put(new SimpleKey("0"), new SimpleValue("3"))).isTrue();
|
||||||
|
|
||||||
|
long removed = map.fastRemove(new SimpleKey("0"), new SimpleKey("1"));
|
||||||
|
assertThat(removed).isEqualTo(1);
|
||||||
|
assertThat(map.size()).isZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testContainsKey() {
|
||||||
|
RListMultimap<SimpleKey, SimpleValue> map = redisson.getListMultimap("test1");
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("1"));
|
||||||
|
assertThat(map.containsKey(new SimpleKey("0"))).isTrue();
|
||||||
|
assertThat(map.containsKey(new SimpleKey("1"))).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testContainsValue() {
|
||||||
|
RListMultimap<SimpleKey, SimpleValue> map = redisson.getListMultimap("test1");
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("1"));
|
||||||
|
|
||||||
|
assertThat(map.containsValue(new SimpleValue("1"))).isTrue();
|
||||||
|
assertThat(map.containsValue(new SimpleValue("0"))).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testContainsEntry() {
|
||||||
|
RListMultimap<SimpleKey, SimpleValue> map = redisson.getListMultimap("test1");
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("1"));
|
||||||
|
|
||||||
|
assertThat(map.containsEntry(new SimpleKey("0"), new SimpleValue("1"))).isTrue();
|
||||||
|
assertThat(map.containsEntry(new SimpleKey("0"), new SimpleValue("2"))).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemove() {
|
||||||
|
RListMultimap<SimpleKey, SimpleValue> map = redisson.getListMultimap("test1");
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("1"));
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("2"));
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("3"));
|
||||||
|
|
||||||
|
assertThat(map.remove(new SimpleKey("0"), new SimpleValue("2"))).isTrue();
|
||||||
|
assertThat(map.remove(new SimpleKey("0"), new SimpleValue("5"))).isFalse();
|
||||||
|
assertThat(map.get(new SimpleKey("0")).size()).isEqualTo(2);
|
||||||
|
assertThat(map.getAll(new SimpleKey("0")).size()).isEqualTo(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPutAll() {
|
||||||
|
RListMultimap<SimpleKey, SimpleValue> map = redisson.getListMultimap("test1");
|
||||||
|
List<SimpleValue> values = Arrays.asList(new SimpleValue("1"), new SimpleValue("2"), new SimpleValue("3"), new SimpleValue("3"));
|
||||||
|
assertThat(map.putAll(new SimpleKey("0"), values)).isTrue();
|
||||||
|
assertThat(map.putAll(new SimpleKey("0"), Arrays.asList(new SimpleValue("1")))).isTrue();
|
||||||
|
|
||||||
|
List<SimpleValue> testValues = Arrays.asList(new SimpleValue("1"), new SimpleValue("2"), new SimpleValue("3"), new SimpleValue("3"), new SimpleValue("1"));
|
||||||
|
assertThat(map.get(new SimpleKey("0"))).containsExactlyElementsOf(testValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testKeySet() {
|
||||||
|
RListMultimap<SimpleKey, SimpleValue> map = redisson.getListMultimap("test1");
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("1"));
|
||||||
|
map.put(new SimpleKey("3"), new SimpleValue("4"));
|
||||||
|
|
||||||
|
assertThat(map.keySet()).containsOnly(new SimpleKey("0"), new SimpleKey("3"));
|
||||||
|
assertThat(map.keySet().size()).isEqualTo(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValues() {
|
||||||
|
RListMultimap<SimpleKey, SimpleValue> map = redisson.getListMultimap("test1");
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("1"));
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("1"));
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("3"));
|
||||||
|
map.put(new SimpleKey("2"), new SimpleValue("5"));
|
||||||
|
map.put(new SimpleKey("3"), new SimpleValue("4"));
|
||||||
|
|
||||||
|
assertThat(map.values().size()).isEqualTo(5);
|
||||||
|
assertThat(map.values()).containsOnly(new SimpleValue("1"), new SimpleValue("1"), new SimpleValue("3"), new SimpleValue("5"), new SimpleValue("4"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEntrySet() {
|
||||||
|
RListMultimap<SimpleKey, SimpleValue> map = redisson.getListMultimap("test1");
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("1"));
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("1"));
|
||||||
|
map.put(new SimpleKey("3"), new SimpleValue("4"));
|
||||||
|
|
||||||
|
assertThat(map.entries().size()).isEqualTo(3);
|
||||||
|
List<Map.Entry<SimpleKey, SimpleValue>> testMap = new ArrayList<Map.Entry<SimpleKey, SimpleValue>>();
|
||||||
|
testMap.add(new AbstractMap.SimpleEntry(new SimpleKey("0"), new SimpleValue("1")));
|
||||||
|
testMap.add(new AbstractMap.SimpleEntry(new SimpleKey("0"), new SimpleValue("1")));
|
||||||
|
testMap.add(new AbstractMap.SimpleEntry(new SimpleKey("3"), new SimpleValue("4")));
|
||||||
|
assertThat(map.entries()).containsOnlyElementsOf(testMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReplaceValues() {
|
||||||
|
RListMultimap<SimpleKey, SimpleValue> map = redisson.getListMultimap("test1");
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("1"));
|
||||||
|
map.put(new SimpleKey("3"), new SimpleValue("4"));
|
||||||
|
|
||||||
|
List<SimpleValue> values = Arrays.asList(new SimpleValue("11"), new SimpleValue("12"), new SimpleValue("12"));
|
||||||
|
List<SimpleValue> oldValues = map.replaceValues(new SimpleKey("0"), values);
|
||||||
|
assertThat(oldValues).containsExactly(new SimpleValue("1"));
|
||||||
|
|
||||||
|
List<SimpleValue> allValues = map.getAll(new SimpleKey("0"));
|
||||||
|
assertThat(allValues).containsExactlyElementsOf(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,277 @@
|
|||||||
|
package org.redisson;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.redisson.core.RSetMultimap;
|
||||||
|
import static org.assertj.core.api.Assertions.*;
|
||||||
|
|
||||||
|
public class RedissonSetMultimapTest extends BaseTest {
|
||||||
|
|
||||||
|
public static class SimpleKey implements Serializable {
|
||||||
|
|
||||||
|
private String key;
|
||||||
|
|
||||||
|
public SimpleKey() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleKey(String field) {
|
||||||
|
this.key = field;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKey(String key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "key: " + key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + ((key == null) ? 0 : key.hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj)
|
||||||
|
return true;
|
||||||
|
if (obj == null)
|
||||||
|
return false;
|
||||||
|
if (getClass() != obj.getClass())
|
||||||
|
return false;
|
||||||
|
SimpleKey other = (SimpleKey) obj;
|
||||||
|
if (key == null) {
|
||||||
|
if (other.key != null)
|
||||||
|
return false;
|
||||||
|
} else if (!key.equals(other.key))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SimpleValue implements Serializable {
|
||||||
|
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
public SimpleValue() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleValue(String field) {
|
||||||
|
this.value = field;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(String field) {
|
||||||
|
this.value = field;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "value: " + value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + ((value == null) ? 0 : value.hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj)
|
||||||
|
return true;
|
||||||
|
if (obj == null)
|
||||||
|
return false;
|
||||||
|
if (getClass() != obj.getClass())
|
||||||
|
return false;
|
||||||
|
SimpleValue other = (SimpleValue) obj;
|
||||||
|
if (value == null) {
|
||||||
|
if (other.value != null)
|
||||||
|
return false;
|
||||||
|
} else if (!value.equals(other.value))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSize() {
|
||||||
|
RSetMultimap<SimpleKey, SimpleValue> map = redisson.getSetMultimap("test1");
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("1"));
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("2"));
|
||||||
|
|
||||||
|
assertThat(map.size()).isEqualTo(2);
|
||||||
|
|
||||||
|
map.fastRemove(new SimpleKey("0"));
|
||||||
|
|
||||||
|
Set<SimpleValue> s = map.get(new SimpleKey("0"));
|
||||||
|
assertThat(s).isEmpty();
|
||||||
|
assertThat(map.size()).isEqualTo(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPut() {
|
||||||
|
RSetMultimap<SimpleKey, SimpleValue> map = redisson.getSetMultimap("test1");
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("1"));
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("2"));
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("3"));
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("3"));
|
||||||
|
map.put(new SimpleKey("3"), new SimpleValue("4"));
|
||||||
|
|
||||||
|
assertThat(map.size()).isEqualTo(4);
|
||||||
|
|
||||||
|
Set<SimpleValue> s1 = map.get(new SimpleKey("0"));
|
||||||
|
assertThat(s1).containsOnly(new SimpleValue("1"), new SimpleValue("2"), new SimpleValue("3"));
|
||||||
|
Set<SimpleValue> allValues = map.getAll(new SimpleKey("0"));
|
||||||
|
assertThat(allValues).containsOnly(new SimpleValue("1"), new SimpleValue("2"), new SimpleValue("3"));
|
||||||
|
|
||||||
|
Set<SimpleValue> s2 = map.get(new SimpleKey("3"));
|
||||||
|
assertThat(s2).containsOnly(new SimpleValue("4"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemoveAll() {
|
||||||
|
RSetMultimap<SimpleKey, SimpleValue> map = redisson.getSetMultimap("test1");
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("1"));
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("2"));
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("3"));
|
||||||
|
|
||||||
|
Set<SimpleValue> values = map.removeAll(new SimpleKey("0"));
|
||||||
|
assertThat(values).containsOnly(new SimpleValue("1"), new SimpleValue("2"), new SimpleValue("3"));
|
||||||
|
assertThat(map.size()).isZero();
|
||||||
|
|
||||||
|
Set<SimpleValue> values2 = map.removeAll(new SimpleKey("0"));
|
||||||
|
assertThat(values2).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFastRemove() {
|
||||||
|
RSetMultimap<SimpleKey, SimpleValue> map = redisson.getSetMultimap("test1");
|
||||||
|
assertThat(map.put(new SimpleKey("0"), new SimpleValue("1"))).isTrue();
|
||||||
|
assertThat(map.put(new SimpleKey("0"), new SimpleValue("2"))).isTrue();
|
||||||
|
assertThat(map.put(new SimpleKey("0"), new SimpleValue("2"))).isFalse();
|
||||||
|
assertThat(map.put(new SimpleKey("0"), new SimpleValue("3"))).isTrue();
|
||||||
|
|
||||||
|
long removed = map.fastRemove(new SimpleKey("0"), new SimpleKey("1"));
|
||||||
|
assertThat(removed).isEqualTo(1);
|
||||||
|
assertThat(map.size()).isZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testContainsKey() {
|
||||||
|
RSetMultimap<SimpleKey, SimpleValue> map = redisson.getSetMultimap("test1");
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("1"));
|
||||||
|
assertThat(map.containsKey(new SimpleKey("0"))).isTrue();
|
||||||
|
assertThat(map.containsKey(new SimpleKey("1"))).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testContainsValue() {
|
||||||
|
RSetMultimap<SimpleKey, SimpleValue> map = redisson.getSetMultimap("test1");
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("1"));
|
||||||
|
|
||||||
|
assertThat(map.containsValue(new SimpleValue("1"))).isTrue();
|
||||||
|
assertThat(map.containsValue(new SimpleValue("0"))).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testContainsEntry() {
|
||||||
|
RSetMultimap<SimpleKey, SimpleValue> map = redisson.getSetMultimap("test1");
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("1"));
|
||||||
|
|
||||||
|
assertThat(map.containsEntry(new SimpleKey("0"), new SimpleValue("1"))).isTrue();
|
||||||
|
assertThat(map.containsEntry(new SimpleKey("0"), new SimpleValue("2"))).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemove() {
|
||||||
|
RSetMultimap<SimpleKey, SimpleValue> map = redisson.getSetMultimap("test1");
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("1"));
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("2"));
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("3"));
|
||||||
|
|
||||||
|
assertThat(map.remove(new SimpleKey("0"), new SimpleValue("2"))).isTrue();
|
||||||
|
assertThat(map.remove(new SimpleKey("0"), new SimpleValue("5"))).isFalse();
|
||||||
|
assertThat(map.get(new SimpleKey("0")).size()).isEqualTo(2);
|
||||||
|
assertThat(map.getAll(new SimpleKey("0")).size()).isEqualTo(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPutAll() {
|
||||||
|
RSetMultimap<SimpleKey, SimpleValue> map = redisson.getSetMultimap("test1");
|
||||||
|
List<SimpleValue> values = Arrays.asList(new SimpleValue("1"), new SimpleValue("2"), new SimpleValue("3"));
|
||||||
|
assertThat(map.putAll(new SimpleKey("0"), values)).isTrue();
|
||||||
|
assertThat(map.putAll(new SimpleKey("0"), Arrays.asList(new SimpleValue("1")))).isFalse();
|
||||||
|
|
||||||
|
assertThat(map.get(new SimpleKey("0"))).containsOnlyElementsOf(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testKeySet() {
|
||||||
|
RSetMultimap<SimpleKey, SimpleValue> map = redisson.getSetMultimap("test1");
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("1"));
|
||||||
|
map.put(new SimpleKey("3"), new SimpleValue("4"));
|
||||||
|
|
||||||
|
assertThat(map.keySet()).containsOnly(new SimpleKey("0"), new SimpleKey("3"));
|
||||||
|
assertThat(map.keySet().size()).isEqualTo(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValues() {
|
||||||
|
RSetMultimap<SimpleKey, SimpleValue> map = redisson.getSetMultimap("test1");
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("1"));
|
||||||
|
map.put(new SimpleKey("3"), new SimpleValue("4"));
|
||||||
|
|
||||||
|
assertThat(map.values()).containsOnly(new SimpleValue("1"), new SimpleValue("4"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEntrySet() {
|
||||||
|
RSetMultimap<SimpleKey, SimpleValue> map = redisson.getSetMultimap("test1");
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("1"));
|
||||||
|
map.put(new SimpleKey("3"), new SimpleValue("4"));
|
||||||
|
|
||||||
|
assertThat(map.entries().size()).isEqualTo(2);
|
||||||
|
Map<SimpleKey, SimpleValue> testMap = new HashMap<SimpleKey, SimpleValue>();
|
||||||
|
testMap.put(new SimpleKey("0"), new SimpleValue("1"));
|
||||||
|
testMap.put(new SimpleKey("3"), new SimpleValue("4"));
|
||||||
|
assertThat(map.entries()).containsOnlyElementsOf(testMap.entrySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReplaceValues() {
|
||||||
|
RSetMultimap<SimpleKey, SimpleValue> map = redisson.getSetMultimap("test1");
|
||||||
|
map.put(new SimpleKey("0"), new SimpleValue("1"));
|
||||||
|
map.put(new SimpleKey("3"), new SimpleValue("4"));
|
||||||
|
|
||||||
|
List<SimpleValue> values = Arrays.asList(new SimpleValue("11"), new SimpleValue("12"));
|
||||||
|
Set<SimpleValue> oldValues = map.replaceValues(new SimpleKey("0"), values);
|
||||||
|
assertThat(oldValues).containsOnly(new SimpleValue("1"));
|
||||||
|
|
||||||
|
Set<SimpleValue> allValues = map.getAll(new SimpleKey("0"));
|
||||||
|
assertThat(allValues).containsOnlyElementsOf(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,622 @@
|
|||||||
|
# Redis configuration file example
|
||||||
|
|
||||||
|
# Note on units: when memory size is needed, it is possible to specify
|
||||||
|
# it in the usual form of 1k 5GB 4M and so forth:
|
||||||
|
#
|
||||||
|
# 1k => 1000 bytes
|
||||||
|
# 1kb => 1024 bytes
|
||||||
|
# 1m => 1000000 bytes
|
||||||
|
# 1mb => 1024*1024 bytes
|
||||||
|
# 1g => 1000000000 bytes
|
||||||
|
# 1gb => 1024*1024*1024 bytes
|
||||||
|
#
|
||||||
|
# units are case insensitive so 1GB 1Gb 1gB are all the same.
|
||||||
|
|
||||||
|
# By default Redis does not run as a daemon. Use 'yes' if you need it.
|
||||||
|
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
|
||||||
|
daemonize no
|
||||||
|
|
||||||
|
# When running daemonized, Redis writes a pid file in /var/run/redis.pid by
|
||||||
|
# default. You can specify a custom pid file location here.
|
||||||
|
#pidfile /var/run/redis.pid
|
||||||
|
|
||||||
|
# Accept connections on the specified port, default is 6379.
|
||||||
|
# If port 0 is specified Redis will not listen on a TCP socket.
|
||||||
|
port 6319
|
||||||
|
|
||||||
|
# If you want you can bind a single interface, if the bind option is not
|
||||||
|
# specified all the interfaces will listen for incoming connections.
|
||||||
|
#
|
||||||
|
# bind 127.0.0.1
|
||||||
|
|
||||||
|
# Specify the path for the unix socket that will be used to listen for
|
||||||
|
# incoming connections. There is no default, so Redis will not listen
|
||||||
|
# on a unix socket when not specified.
|
||||||
|
#
|
||||||
|
# unixsocket /tmp/redis.sock
|
||||||
|
# unixsocketperm 755
|
||||||
|
|
||||||
|
# Close the connection after a client is idle for N seconds (0 to disable)
|
||||||
|
timeout 0
|
||||||
|
|
||||||
|
# TCP keepalive.
|
||||||
|
#
|
||||||
|
# If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence
|
||||||
|
# of communication. This is useful for two reasons:
|
||||||
|
#
|
||||||
|
# 1) Detect dead peers.
|
||||||
|
# 2) Take the connection alive from the point of view of network
|
||||||
|
# equipment in the middle.
|
||||||
|
#
|
||||||
|
# On Linux, the specified value (in seconds) is the period used to send ACKs.
|
||||||
|
# Note that to close the connection the double of the time is needed.
|
||||||
|
# On other kernels the period depends on the kernel configuration.
|
||||||
|
#
|
||||||
|
# A reasonable value for this option is 60 seconds.
|
||||||
|
tcp-keepalive 0
|
||||||
|
|
||||||
|
# Specify the server verbosity level.
|
||||||
|
# This can be one of:
|
||||||
|
# debug (a lot of information, useful for development/testing)
|
||||||
|
# verbose (many rarely useful info, but not a mess like the debug level)
|
||||||
|
# notice (moderately verbose, what you want in production probably)
|
||||||
|
# warning (only very important / critical messages are logged)
|
||||||
|
loglevel debug
|
||||||
|
|
||||||
|
# Specify the log file name. Also 'stdout' can be used to force
|
||||||
|
# Redis to log on the standard output. Note that if you use standard
|
||||||
|
# output for logging but daemonize, logs will be sent to /dev/null
|
||||||
|
logfile "stdout"
|
||||||
|
|
||||||
|
# To enable logging to the system logger, just set 'syslog-enabled' to yes,
|
||||||
|
# and optionally update the other syslog parameters to suit your needs.
|
||||||
|
# syslog-enabled no
|
||||||
|
|
||||||
|
# Specify the syslog identity.
|
||||||
|
# syslog-ident redis
|
||||||
|
|
||||||
|
# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.
|
||||||
|
# syslog-facility local0
|
||||||
|
|
||||||
|
# Set the number of databases. The default database is DB 0, you can select
|
||||||
|
# a different one on a per-connection basis using SELECT <dbid> where
|
||||||
|
# dbid is a number between 0 and 'databases'-1
|
||||||
|
databases 16
|
||||||
|
|
||||||
|
################################ SNAPSHOTTING #################################
|
||||||
|
#
|
||||||
|
# Save the DB on disk:
|
||||||
|
#
|
||||||
|
# save <seconds> <changes>
|
||||||
|
#
|
||||||
|
# Will save the DB if both the given number of seconds and the given
|
||||||
|
# number of write operations against the DB occurred.
|
||||||
|
#
|
||||||
|
# In the example below the behaviour will be to save:
|
||||||
|
# after 900 sec (15 min) if at least 1 key changed
|
||||||
|
# after 300 sec (5 min) if at least 10 keys changed
|
||||||
|
# after 60 sec if at least 10000 keys changed
|
||||||
|
#
|
||||||
|
# Note: you can disable saving at all commenting all the "save" lines.
|
||||||
|
#
|
||||||
|
# It is also possible to remove all the previously configured save
|
||||||
|
# points by adding a save directive with a single empty string argument
|
||||||
|
# like in the following example:
|
||||||
|
#
|
||||||
|
# save ""
|
||||||
|
|
||||||
|
#save 900 1
|
||||||
|
#save 300 10
|
||||||
|
#save 60 10000
|
||||||
|
|
||||||
|
# By default Redis will stop accepting writes if RDB snapshots are enabled
|
||||||
|
# (at least one save point) and the latest background save failed.
|
||||||
|
# This will make the user aware (in an hard way) that data is not persisting
|
||||||
|
# on disk properly, otherwise chances are that no one will notice and some
|
||||||
|
# distater will happen.
|
||||||
|
#
|
||||||
|
# If the background saving process will start working again Redis will
|
||||||
|
# automatically allow writes again.
|
||||||
|
#
|
||||||
|
# However if you have setup your proper monitoring of the Redis server
|
||||||
|
# and persistence, you may want to disable this feature so that Redis will
|
||||||
|
# continue to work as usually even if there are problems with disk,
|
||||||
|
# permissions, and so forth.
|
||||||
|
stop-writes-on-bgsave-error yes
|
||||||
|
|
||||||
|
# Compress string objects using LZF when dump .rdb databases?
|
||||||
|
# For default that's set to 'yes' as it's almost always a win.
|
||||||
|
# If you want to save some CPU in the saving child set it to 'no' but
|
||||||
|
# the dataset will likely be bigger if you have compressible values or keys.
|
||||||
|
rdbcompression yes
|
||||||
|
|
||||||
|
# Since version 5 of RDB a CRC64 checksum is placed at the end of the file.
|
||||||
|
# This makes the format more resistant to corruption but there is a performance
|
||||||
|
# hit to pay (around 10%) when saving and loading RDB files, so you can disable it
|
||||||
|
# for maximum performances.
|
||||||
|
#
|
||||||
|
# RDB files created with checksum disabled have a checksum of zero that will
|
||||||
|
# tell the loading code to skip the check.
|
||||||
|
rdbchecksum yes
|
||||||
|
|
||||||
|
# The filename where to dump the DB
|
||||||
|
#dbfilename "dump.rdb"
|
||||||
|
|
||||||
|
# The working directory.
|
||||||
|
#
|
||||||
|
# The DB will be written inside this directory, with the filename specified
|
||||||
|
# above using the 'dbfilename' configuration directive.
|
||||||
|
#
|
||||||
|
# The Append Only File will also be created inside this directory.
|
||||||
|
#
|
||||||
|
# Note that you must specify a directory here, not a file name.
|
||||||
|
dir "C:\\Devel\\projects\\redis"
|
||||||
|
|
||||||
|
################################# REPLICATION #################################
|
||||||
|
|
||||||
|
# Master-Slave replication. Use slaveof to make a Redis instance a copy of
|
||||||
|
# another Redis server. Note that the configuration is local to the slave
|
||||||
|
# so for example it is possible to configure the slave to save the DB with a
|
||||||
|
# different interval, or to listen to another port, and so on.
|
||||||
|
#
|
||||||
|
# slaveof <masterip> <masterport>
|
||||||
|
|
||||||
|
# If the master is password protected (using the "requirepass" configuration
|
||||||
|
# directive below) it is possible to tell the slave to authenticate before
|
||||||
|
# starting the replication synchronization process, otherwise the master will
|
||||||
|
# refuse the slave request.
|
||||||
|
#
|
||||||
|
# masterauth <master-password>
|
||||||
|
|
||||||
|
# When a slave loses its connection with the master, or when the replication
|
||||||
|
# is still in progress, the slave can act in two different ways:
|
||||||
|
#
|
||||||
|
# 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will
|
||||||
|
# still reply to client requests, possibly with out of date data, or the
|
||||||
|
# data set may just be empty if this is the first synchronization.
|
||||||
|
#
|
||||||
|
# 2) if slave-serve-stale-data is set to 'no' the slave will reply with
|
||||||
|
# an error "SYNC with master in progress" to all the kind of commands
|
||||||
|
# but to INFO and SLAVEOF.
|
||||||
|
#
|
||||||
|
slave-serve-stale-data yes
|
||||||
|
|
||||||
|
# You can configure a slave instance to accept writes or not. Writing against
|
||||||
|
# a slave instance may be useful to store some ephemeral data (because data
|
||||||
|
# written on a slave will be easily deleted after resync with the master) but
|
||||||
|
# may also cause problems if clients are writing to it because of a
|
||||||
|
# misconfiguration.
|
||||||
|
#
|
||||||
|
# Since Redis 2.6 by default slaves are read-only.
|
||||||
|
#
|
||||||
|
# Note: read only slaves are not designed to be exposed to untrusted clients
|
||||||
|
# on the internet. It's just a protection layer against misuse of the instance.
|
||||||
|
# Still a read only slave exports by default all the administrative commands
|
||||||
|
# such as CONFIG, DEBUG, and so forth. To a limited extend you can improve
|
||||||
|
# security of read only slaves using 'rename-command' to shadow all the
|
||||||
|
# administrative / dangerous commands.
|
||||||
|
slave-read-only yes
|
||||||
|
|
||||||
|
# Slaves send PINGs to server in a predefined interval. It's possible to change
|
||||||
|
# this interval with the repl_ping_slave_period option. The default value is 10
|
||||||
|
# seconds.
|
||||||
|
#
|
||||||
|
# repl-ping-slave-period 10
|
||||||
|
|
||||||
|
# The following option sets a timeout for both Bulk transfer I/O timeout and
|
||||||
|
# master data or ping response timeout. The default value is 60 seconds.
|
||||||
|
#
|
||||||
|
# It is important to make sure that this value is greater than the value
|
||||||
|
# specified for repl-ping-slave-period otherwise a timeout will be detected
|
||||||
|
# every time there is low traffic between the master and the slave.
|
||||||
|
#
|
||||||
|
# repl-timeout 60
|
||||||
|
|
||||||
|
# Disable TCP_NODELAY on the slave socket after SYNC?
|
||||||
|
#
|
||||||
|
# If you select "yes" Redis will use a smaller number of TCP packets and
|
||||||
|
# less bandwidth to send data to slaves. But this can add a delay for
|
||||||
|
# the data to appear on the slave side, up to 40 milliseconds with
|
||||||
|
# Linux kernels using a default configuration.
|
||||||
|
#
|
||||||
|
# If you select "no" the delay for data to appear on the slave side will
|
||||||
|
# be reduced but more bandwidth will be used for replication.
|
||||||
|
#
|
||||||
|
# By default we optimize for low latency, but in very high traffic conditions
|
||||||
|
# or when the master and slaves are many hops away, turning this to "yes" may
|
||||||
|
# be a good idea.
|
||||||
|
repl-disable-tcp-nodelay no
|
||||||
|
|
||||||
|
# The slave priority is an integer number published by Redis in the INFO output.
|
||||||
|
# It is used by Redis Sentinel in order to select a slave to promote into a
|
||||||
|
# master if the master is no longer working correctly.
|
||||||
|
#
|
||||||
|
# A slave with a low priority number is considered better for promotion, so
|
||||||
|
# for instance if there are three slaves with priority 10, 100, 25 Sentinel will
|
||||||
|
# pick the one wtih priority 10, that is the lowest.
|
||||||
|
#
|
||||||
|
# However a special priority of 0 marks the slave as not able to perform the
|
||||||
|
# role of master, so a slave with priority of 0 will never be selected by
|
||||||
|
# Redis Sentinel for promotion.
|
||||||
|
#
|
||||||
|
# By default the priority is 100.
|
||||||
|
slave-priority 100
|
||||||
|
|
||||||
|
################################## SECURITY ###################################
|
||||||
|
|
||||||
|
# Require clients to issue AUTH <PASSWORD> before processing any other
|
||||||
|
# commands. This might be useful in environments in which you do not trust
|
||||||
|
# others with access to the host running redis-server.
|
||||||
|
#
|
||||||
|
# This should stay commented out for backward compatibility and because most
|
||||||
|
# people do not need auth (e.g. they run their own servers).
|
||||||
|
#
|
||||||
|
# Warning: since Redis is pretty fast an outside user can try up to
|
||||||
|
# 150k passwords per second against a good box. This means that you should
|
||||||
|
# use a very strong password otherwise it will be very easy to break.
|
||||||
|
#
|
||||||
|
#requirepass mypass
|
||||||
|
|
||||||
|
# Command renaming.
|
||||||
|
#
|
||||||
|
# It is possible to change the name of dangerous commands in a shared
|
||||||
|
# environment. For instance the CONFIG command may be renamed into something
|
||||||
|
# hard to guess so that it will still be available for internal-use tools
|
||||||
|
# but not available for general clients.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
#
|
||||||
|
# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
|
||||||
|
#
|
||||||
|
# It is also possible to completely kill a command by renaming it into
|
||||||
|
# an empty string:
|
||||||
|
#
|
||||||
|
# rename-command CONFIG ""
|
||||||
|
#
|
||||||
|
# Please note that changing the name of commands that are logged into the
|
||||||
|
# AOF file or transmitted to slaves may cause problems.
|
||||||
|
|
||||||
|
################################### LIMITS ####################################
|
||||||
|
|
||||||
|
# Set the max number of connected clients at the same time. By default
|
||||||
|
# this limit is set to 10000 clients, however if the Redis server is not
|
||||||
|
# able to configure the process file limit to allow for the specified limit
|
||||||
|
# the max number of allowed clients is set to the current file limit
|
||||||
|
# minus 32 (as Redis reserves a few file descriptors for internal uses).
|
||||||
|
#
|
||||||
|
# Once the limit is reached Redis will close all the new connections sending
|
||||||
|
# an error 'max number of clients reached'.
|
||||||
|
#
|
||||||
|
# maxclients 10000
|
||||||
|
|
||||||
|
# Don't use more memory than the specified amount of bytes.
|
||||||
|
# When the memory limit is reached Redis will try to remove keys
|
||||||
|
# accordingly to the eviction policy selected (see maxmemmory-policy).
|
||||||
|
#
|
||||||
|
# If Redis can't remove keys according to the policy, or if the policy is
|
||||||
|
# set to 'noeviction', Redis will start to reply with errors to commands
|
||||||
|
# that would use more memory, like SET, LPUSH, and so on, and will continue
|
||||||
|
# to reply to read-only commands like GET.
|
||||||
|
#
|
||||||
|
# This option is usually useful when using Redis as an LRU cache, or to set
|
||||||
|
# an hard memory limit for an instance (using the 'noeviction' policy).
|
||||||
|
#
|
||||||
|
# WARNING: If you have slaves attached to an instance with maxmemory on,
|
||||||
|
# the size of the output buffers needed to feed the slaves are subtracted
|
||||||
|
# from the used memory count, so that network problems / resyncs will
|
||||||
|
# not trigger a loop where keys are evicted, and in turn the output
|
||||||
|
# buffer of slaves is full with DELs of keys evicted triggering the deletion
|
||||||
|
# of more keys, and so forth until the database is completely emptied.
|
||||||
|
#
|
||||||
|
# In short... if you have slaves attached it is suggested that you set a lower
|
||||||
|
# limit for maxmemory so that there is some free RAM on the system for slave
|
||||||
|
# output buffers (but this is not needed if the policy is 'noeviction').
|
||||||
|
#
|
||||||
|
maxmemory 1mb
|
||||||
|
|
||||||
|
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
|
||||||
|
# is reached. You can select among five behaviors:
|
||||||
|
#
|
||||||
|
# volatile-lru -> remove the key with an expire set using an LRU algorithm
|
||||||
|
# allkeys-lru -> remove any key accordingly to the LRU algorithm
|
||||||
|
# volatile-random -> remove a random key with an expire set
|
||||||
|
# allkeys-random -> remove a random key, any key
|
||||||
|
# volatile-ttl -> remove the key with the nearest expire time (minor TTL)
|
||||||
|
# noeviction -> don't expire at all, just return an error on write operations
|
||||||
|
#
|
||||||
|
# Note: with any of the above policies, Redis will return an error on write
|
||||||
|
# operations, when there are not suitable keys for eviction.
|
||||||
|
#
|
||||||
|
# At the date of writing this commands are: set setnx setex append
|
||||||
|
# incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
|
||||||
|
# sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
|
||||||
|
# zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
|
||||||
|
# getset mset msetnx exec sort
|
||||||
|
#
|
||||||
|
# The default is:
|
||||||
|
#
|
||||||
|
# maxmemory-policy volatile-lru
|
||||||
|
|
||||||
|
# LRU and minimal TTL algorithms are not precise algorithms but approximated
|
||||||
|
# algorithms (in order to save memory), so you can select as well the sample
|
||||||
|
# size to check. For instance for default Redis will check three keys and
|
||||||
|
# pick the one that was used less recently, you can change the sample size
|
||||||
|
# using the following configuration directive.
|
||||||
|
#
|
||||||
|
# maxmemory-samples 3
|
||||||
|
|
||||||
|
############################## APPEND ONLY MODE ###############################
|
||||||
|
|
||||||
|
# By default Redis asynchronously dumps the dataset on disk. This mode is
|
||||||
|
# good enough in many applications, but an issue with the Redis process or
|
||||||
|
# a power outage may result into a few minutes of writes lost (depending on
|
||||||
|
# the configured save points).
|
||||||
|
#
|
||||||
|
# The Append Only File is an alternative persistence mode that provides
|
||||||
|
# much better durability. For instance using the default data fsync policy
|
||||||
|
# (see later in the config file) Redis can lose just one second of writes in a
|
||||||
|
# dramatic event like a server power outage, or a single write if something
|
||||||
|
# wrong with the Redis process itself happens, but the operating system is
|
||||||
|
# still running correctly.
|
||||||
|
#
|
||||||
|
# AOF and RDB persistence can be enabled at the same time without problems.
|
||||||
|
# If the AOF is enabled on startup Redis will load the AOF, that is the file
|
||||||
|
# with the better durability guarantees.
|
||||||
|
#
|
||||||
|
# Please check http://redis.io/topics/persistence for more information.
|
||||||
|
|
||||||
|
appendonly no
|
||||||
|
|
||||||
|
# The name of the append only file (default: "appendonly.aof")
|
||||||
|
# appendfilename appendonly.aof
|
||||||
|
|
||||||
|
# The fsync() call tells the Operating System to actually write data on disk
|
||||||
|
# instead to wait for more data in the output buffer. Some OS will really flush
|
||||||
|
# data on disk, some other OS will just try to do it ASAP.
|
||||||
|
#
|
||||||
|
# Redis supports three different modes:
|
||||||
|
#
|
||||||
|
# no: don't fsync, just let the OS flush the data when it wants. Faster.
|
||||||
|
# always: fsync after every write to the append only log . Slow, Safest.
|
||||||
|
# everysec: fsync only one time every second. Compromise.
|
||||||
|
#
|
||||||
|
# The default is "everysec", as that's usually the right compromise between
|
||||||
|
# speed and data safety. It's up to you to understand if you can relax this to
|
||||||
|
# "no" that will let the operating system flush the output buffer when
|
||||||
|
# it wants, for better performances (but if you can live with the idea of
|
||||||
|
# some data loss consider the default persistence mode that's snapshotting),
|
||||||
|
# or on the contrary, use "always" that's very slow but a bit safer than
|
||||||
|
# everysec.
|
||||||
|
#
|
||||||
|
# More details please check the following article:
|
||||||
|
# http://antirez.com/post/redis-persistence-demystified.html
|
||||||
|
#
|
||||||
|
# If unsure, use "everysec".
|
||||||
|
|
||||||
|
# appendfsync always
|
||||||
|
appendfsync everysec
|
||||||
|
# appendfsync no
|
||||||
|
|
||||||
|
# When the AOF fsync policy is set to always or everysec, and a background
|
||||||
|
# saving process (a background save or AOF log background rewriting) is
|
||||||
|
# performing a lot of I/O against the disk, in some Linux configurations
|
||||||
|
# Redis may block too long on the fsync() call. Note that there is no fix for
|
||||||
|
# this currently, as even performing fsync in a different thread will block
|
||||||
|
# our synchronous write(2) call.
|
||||||
|
#
|
||||||
|
# In order to mitigate this problem it's possible to use the following option
|
||||||
|
# that will prevent fsync() from being called in the main process while a
|
||||||
|
# BGSAVE or BGREWRITEAOF is in progress.
|
||||||
|
#
|
||||||
|
# This means that while another child is saving, the durability of Redis is
|
||||||
|
# the same as "appendfsync none". In practical terms, this means that it is
|
||||||
|
# possible to lose up to 30 seconds of log in the worst scenario (with the
|
||||||
|
# default Linux settings).
|
||||||
|
#
|
||||||
|
# If you have latency problems turn this to "yes". Otherwise leave it as
|
||||||
|
# "no" that is the safest pick from the point of view of durability.
|
||||||
|
no-appendfsync-on-rewrite no
|
||||||
|
|
||||||
|
# Automatic rewrite of the append only file.
|
||||||
|
# Redis is able to automatically rewrite the log file implicitly calling
|
||||||
|
# BGREWRITEAOF when the AOF log size grows by the specified percentage.
|
||||||
|
#
|
||||||
|
# This is how it works: Redis remembers the size of the AOF file after the
|
||||||
|
# latest rewrite (if no rewrite has happened since the restart, the size of
|
||||||
|
# the AOF at startup is used).
|
||||||
|
#
|
||||||
|
# This base size is compared to the current size. If the current size is
|
||||||
|
# bigger than the specified percentage, the rewrite is triggered. Also
|
||||||
|
# you need to specify a minimal size for the AOF file to be rewritten, this
|
||||||
|
# is useful to avoid rewriting the AOF file even if the percentage increase
|
||||||
|
# is reached but it is still pretty small.
|
||||||
|
#
|
||||||
|
# Specify a percentage of zero in order to disable the automatic AOF
|
||||||
|
# rewrite feature.
|
||||||
|
|
||||||
|
auto-aof-rewrite-percentage 100
|
||||||
|
auto-aof-rewrite-min-size 64mb
|
||||||
|
|
||||||
|
################################ LUA SCRIPTING ###############################
|
||||||
|
|
||||||
|
# Max execution time of a Lua script in milliseconds.
|
||||||
|
#
|
||||||
|
# If the maximum execution time is reached Redis will log that a script is
|
||||||
|
# still in execution after the maximum allowed time and will start to
|
||||||
|
# reply to queries with an error.
|
||||||
|
#
|
||||||
|
# When a long running script exceed the maximum execution time only the
|
||||||
|
# SCRIPT KILL and SHUTDOWN NOSAVE commands are available. The first can be
|
||||||
|
# used to stop a script that did not yet called write commands. The second
|
||||||
|
# is the only way to shut down the server in the case a write commands was
|
||||||
|
# already issue by the script but the user don't want to wait for the natural
|
||||||
|
# termination of the script.
|
||||||
|
#
|
||||||
|
# Set it to 0 or a negative value for unlimited execution without warnings.
|
||||||
|
lua-time-limit 5000
|
||||||
|
|
||||||
|
################################## SLOW LOG ###################################
|
||||||
|
|
||||||
|
# The Redis Slow Log is a system to log queries that exceeded a specified
|
||||||
|
# execution time. The execution time does not include the I/O operations
|
||||||
|
# like talking with the client, sending the reply and so forth,
|
||||||
|
# but just the time needed to actually execute the command (this is the only
|
||||||
|
# stage of command execution where the thread is blocked and can not serve
|
||||||
|
# other requests in the meantime).
|
||||||
|
#
|
||||||
|
# You can configure the slow log with two parameters: one tells Redis
|
||||||
|
# what is the execution time, in microseconds, to exceed in order for the
|
||||||
|
# command to get logged, and the other parameter is the length of the
|
||||||
|
# slow log. When a new command is logged the oldest one is removed from the
|
||||||
|
# queue of logged commands.
|
||||||
|
|
||||||
|
# The following time is expressed in microseconds, so 1000000 is equivalent
|
||||||
|
# to one second. Note that a negative number disables the slow log, while
|
||||||
|
# a value of zero forces the logging of every command.
|
||||||
|
slowlog-log-slower-than 10000
|
||||||
|
|
||||||
|
# There is no limit to this length. Just be aware that it will consume memory.
|
||||||
|
# You can reclaim memory used by the slow log with SLOWLOG RESET.
|
||||||
|
slowlog-max-len 128
|
||||||
|
|
||||||
|
############################### ADVANCED CONFIG ###############################
|
||||||
|
|
||||||
|
# Hashes are encoded using a memory efficient data structure when they have a
|
||||||
|
# small number of entries, and the biggest entry does not exceed a given
|
||||||
|
# threshold. These thresholds can be configured using the following directives.
|
||||||
|
hash-max-ziplist-entries 512
|
||||||
|
hash-max-ziplist-value 64
|
||||||
|
|
||||||
|
# Similarly to hashes, small lists are also encoded in a special way in order
|
||||||
|
# to save a lot of space. The special representation is only used when
|
||||||
|
# you are under the following limits:
|
||||||
|
list-max-ziplist-entries 512
|
||||||
|
list-max-ziplist-value 64
|
||||||
|
|
||||||
|
# Sets have a special encoding in just one case: when a set is composed
|
||||||
|
# of just strings that happens to be integers in radix 10 in the range
|
||||||
|
# of 64 bit signed integers.
|
||||||
|
# The following configuration setting sets the limit in the size of the
|
||||||
|
# set in order to use this special memory saving encoding.
|
||||||
|
set-max-intset-entries 512
|
||||||
|
|
||||||
|
# Similarly to hashes and lists, sorted sets are also specially encoded in
|
||||||
|
# order to save a lot of space. This encoding is only used when the length and
|
||||||
|
# elements of a sorted set are below the following limits:
|
||||||
|
zset-max-ziplist-entries 128
|
||||||
|
zset-max-ziplist-value 64
|
||||||
|
|
||||||
|
# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
|
||||||
|
# order to help rehashing the main Redis hash table (the one mapping top-level
|
||||||
|
# keys to values). The hash table implementation Redis uses (see dict.c)
|
||||||
|
# performs a lazy rehashing: the more operation you run into an hash table
|
||||||
|
# that is rehashing, the more rehashing "steps" are performed, so if the
|
||||||
|
# server is idle the rehashing is never complete and some more memory is used
|
||||||
|
# by the hash table.
|
||||||
|
#
|
||||||
|
# The default is to use this millisecond 10 times every second in order to
|
||||||
|
# active rehashing the main dictionaries, freeing memory when possible.
|
||||||
|
#
|
||||||
|
# If unsure:
|
||||||
|
# use "activerehashing no" if you have hard latency requirements and it is
|
||||||
|
# not a good thing in your environment that Redis can reply form time to time
|
||||||
|
# to queries with 2 milliseconds delay.
|
||||||
|
#
|
||||||
|
# use "activerehashing yes" if you don't have such hard requirements but
|
||||||
|
# want to free memory asap when possible.
|
||||||
|
activerehashing yes
|
||||||
|
|
||||||
|
# The client output buffer limits can be used to force disconnection of clients
|
||||||
|
# that are not reading data from the server fast enough for some reason (a
|
||||||
|
# common reason is that a Pub/Sub client can't consume messages as fast as the
|
||||||
|
# publisher can produce them).
|
||||||
|
#
|
||||||
|
# The limit can be set differently for the three different classes of clients:
|
||||||
|
#
|
||||||
|
# normal -> normal clients
|
||||||
|
# slave -> slave clients and MONITOR clients
|
||||||
|
# pubsub -> clients subcribed to at least one pubsub channel or pattern
|
||||||
|
#
|
||||||
|
# The syntax of every client-output-buffer-limit directive is the following:
|
||||||
|
#
|
||||||
|
# client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>
|
||||||
|
#
|
||||||
|
# A client is immediately disconnected once the hard limit is reached, or if
|
||||||
|
# the soft limit is reached and remains reached for the specified number of
|
||||||
|
# seconds (continuously).
|
||||||
|
# So for instance if the hard limit is 32 megabytes and the soft limit is
|
||||||
|
# 16 megabytes / 10 seconds, the client will get disconnected immediately
|
||||||
|
# if the size of the output buffers reach 32 megabytes, but will also get
|
||||||
|
# disconnected if the client reaches 16 megabytes and continuously overcomes
|
||||||
|
# the limit for 10 seconds.
|
||||||
|
#
|
||||||
|
# By default normal clients are not limited because they don't receive data
|
||||||
|
# without asking (in a push way), but just after a request, so only
|
||||||
|
# asynchronous clients may create a scenario where data is requested faster
|
||||||
|
# than it can read.
|
||||||
|
#
|
||||||
|
# Instead there is a default limit for pubsub and slave clients, since
|
||||||
|
# subscribers and slaves receive data in a push fashion.
|
||||||
|
#
|
||||||
|
# Both the hard or the soft limit can be disabled by setting them to zero.
|
||||||
|
client-output-buffer-limit normal 0 0 0
|
||||||
|
client-output-buffer-limit slave 256mb 64mb 60
|
||||||
|
client-output-buffer-limit pubsub 32mb 8mb 60
|
||||||
|
|
||||||
|
# Redis calls an internal function to perform many background tasks, like
|
||||||
|
# closing connections of clients in timeot, purging expired keys that are
|
||||||
|
# never requested, and so forth.
|
||||||
|
#
|
||||||
|
# Not all tasks are perforemd with the same frequency, but Redis checks for
|
||||||
|
# tasks to perform accordingly to the specified "hz" value.
|
||||||
|
#
|
||||||
|
# By default "hz" is set to 10. Raising the value will use more CPU when
|
||||||
|
# Redis is idle, but at the same time will make Redis more responsive when
|
||||||
|
# there are many keys expiring at the same time, and timeouts may be
|
||||||
|
# handled with more precision.
|
||||||
|
#
|
||||||
|
# The range is between 1 and 500, however a value over 100 is usually not
|
||||||
|
# a good idea. Most users should use the default of 10 and raise this up to
|
||||||
|
# 100 only in environments where very low latency is required.
|
||||||
|
hz 10
|
||||||
|
|
||||||
|
# When a child rewrites the AOF file, if the following option is enabled
|
||||||
|
# the file will be fsync-ed every 32 MB of data generated. This is useful
|
||||||
|
# in order to commit the file to the disk more incrementally and avoid
|
||||||
|
# big latency spikes.
|
||||||
|
#aof-rewrite-incremental-fsync yes
|
||||||
|
|
||||||
|
################################## INCLUDES ###################################
|
||||||
|
|
||||||
|
# Include one or more other config files here. This is useful if you
|
||||||
|
# have a standard template that goes to all Redis server but also need
|
||||||
|
# to customize a few per-server settings. Include files can include
|
||||||
|
# other files, so use this wisely.
|
||||||
|
#
|
||||||
|
# include /path/to/local.conf
|
||||||
|
# include /path/to/other.conf
|
||||||
|
# Generated by CONFIG REWRITE
|
||||||
|
|
||||||
|
#slaveof 127.0.0.1 6399
|
||||||
|
#slaveof 127.0.0.1 6399
|
||||||
|
|
||||||
|
#notify-keyspace-events "xE"
|
||||||
|
#slaveof 127.0.0.1 6399
|
||||||
|
#slaveof 127.0.0.1 6389
|
||||||
|
#slaveof 127.0.0.1 6389
|
||||||
|
|
||||||
|
#slaveof 127.0.0.1 6399
|
||||||
|
#slaveof 127.0.0.1 6399
|
||||||
|
#slaveof 127.0.0.1 6399
|
||||||
|
#slaveof 127.0.0.1 6399
|
||||||
|
#slaveof 127.0.0.1 6399
|
||||||
|
#slaveof 127.0.0.1 6399
|
||||||
|
#slaveof 127.0.0.1 6399
|
||||||
|
#slaveof 127.0.0.1 6399
|
||||||
|
|
||||||
|
#slaveof 127.0.0.1 6389
|
||||||
|
#slaveof 127.0.0.1 6389
|
||||||
|
|
||||||
|
#slaveof 127.0.0.1 6399
|
||||||
|
|
||||||
|
notify-keyspace-events "xE"
|
Loading…
Reference in New Issue