redis_minds

持久化

复制

缓存设计

哨兵

阻塞

Redis内存

redis内存优化

redis cluster注意的问题 :

  1. ‘cluster-require-full-coverage’参数的设置。该参数是redis配置文件中cluster模式的一个参数,从字面上基本就能看出它的作用:需要全部覆盖!
    具体点是redis cluster需要16384个slot都正常的时候才能对外提供服务,换句话说,只要任何一个slot异常那么整个cluster不对外提供服务。
    redis默认是‘yes’,即需要全覆盖!建议设置成‘no’。
  2. 阻塞命令产生failover。由于一些阻塞命令(flushall, del key1 key2 …)会造成redis在‘cluster-node-timeout’时间内无法响应其他节点的ping请求,
    从而导致其他节点都把该redis标记出了pfail状态,进而产生failover。redis作者计划使用lazy redis解决。
  3. 连接建立。当redis cluster的节点数多了以后,client对每个节点建立一个tcp连接需要花比较多的时间。如果是长连接,用户只需忍受一次连接建立的过程,
    如果是短连接,那么频繁建立连接将会极大的降低效率。但即便是短连接,只要每次请求只涉及到一个key,有些客户端可能只需要与一个节点建立连接。
  4. Jedis。Jedis是redis最流行的Java客户端,支持redis cluster。
    ‘MaxRedirectionsException’异常,出现该异常说明刚刚执行的那条命令经过多次重试,没有执行成功,需要用户再次执行。
    在cluster扩容或者slot迁移的时候比较容易出现。建议捕获该异常并采取相应重试工作。
    Pipeline,Jedis目前不支持cluster模式的pipeline,建议采用多并发代替pipeline。
  5. Multi-key。Redis cluster对多key操作有限,要求命令中所有的key都属于一个slot,才可以被执行。客户端可以对multi-key命令进行拆分,再发给redis。
    另外一个局限是,在slot迁移过程中,multi-key命令特别容易报错(CROSSSLOT Keys in request don’t hash to the same slot)。建议不用multi-key命令。
  6. 扩容速度慢。redis官方提供了redis cluster管理脚本redis-trib.rb。使用该脚本进行扩容cluster的时候,是串行的迁移slot中的每个key,这样导致了
    扩容的速度非常慢,百G的数据要数小时。扩容时间越长,越容易出现异常。

方案一:传统的串行IO操作,也就说n个key,分n次串行操作来获取key,复杂度是o(n)。

方案二:将Mget操作(n个key),利用已知的hash函数算出key对应的节点,这样就可以得到一个这样的关系:Map<node, somekeys>,也就是每个节点对应的一些keys,这样将之前的o(n)的效率降低到o(node.size())。

方案三:在方案二的基础上将串行取数据改为并行取数据,进一步提高效率。

方案四:通过redis自带的hashtag功能,强制一批key分配到某台机器上,不推荐使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/**
* 集群下面Pipeline操作
*/
public List<String> clusterPiplineGet(List<String> keys){
RedisSerializer keySerializer = new StringRedisSerializer();
RedisSerializer<String> valueSerializer = new Jackson2JsonRedisSerializer<>(String.class);
Map<RedisClusterNode,List<String>> nodeKeyMap = new HashMap<>();
List<String> result = new ArrayList<>();

RedisClusterConnection redisClusterConnection = redisTemplate.getConnectionFactory().getClusterConnection();

try {
// 通过计算每个key的槽点,并获取相应节点
Iterable<RedisClusterNode> redisClusterNodes = redisClusterConnection.clusterGetNodes();
for (RedisClusterNode redisClusterNode: redisClusterNodes) {
RedisClusterNode.SlotRange slotRange = redisClusterNode.getSlotRange();
for(String key : keys){
int slot = JedisClusterCRC16.getCRC16(key);
if(slotRange.contains(slot)){
List<String> list = nodeKeyMap.get(redisClusterNode);
if (list == null) {
list = new ArrayList<>();
nodeKeyMap.putIfAbsent(redisClusterNode,list);
}
list.add(key);
}
}

}

for (Map.Entry<RedisClusterNode,List<String>> redisClusterNodeListEntry : nodeKeyMap.entrySet() ) {

RedisClusterNode redisClusterNode = redisClusterNodeListEntry.getKey();
List<String> nodeListEntryValue = redisClusterNodeListEntry.getValue();
JedisPool jedisPool = ((JedisCluster) redisClusterConnection.getNativeConnection()).getClusterNodes().get(new HostAndPort(redisClusterNode.getHost(), redisClusterNode.getPort()).toString());


// byte[][] arr = new byte[nodeListEntryValue.size()][];
int count = 0;
//获取key数据
// for (String nodeKey : nodeListEntryValue) {
// arr[count++] = keySerializer.serialize(nodeKey);
// }
Jedis jedis = jedisPool.getResource();
List<Response<byte[]>> responses = new ArrayList<>();
try {
Pipeline pipeline = jedis.pipelined();
for (String nodeKey : nodeListEntryValue) {
responses.add(pipeline.get(keySerializer.serialize(nodeKey)));
}
pipeline.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
jedis.close();
}
for (Response<byte[]> response : responses) {
byte[] data = response.get();
result.add(valueSerializer.deserialize(data));
}
}
} finally {
RedisConnectionUtils.releaseConnection(redisClusterConnection, redisTemplate.getConnectionFactory());

}
return result;
}