问题1:懂Redis事务么?
Redis Cluster集群架构,不同的key是有可能分配在不同的Redis节点上的,在这种情况下Redis的事务机制是不生效的。 Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用 hashtag 功能解决)
问题2:Redis的多数据库机制,了解多少?
Redis支持多个数据库,并且每个数据库的数据是隔离的不能共享,单机下的redis可以支持16个数据库(db0 ~ db15)
在Redis Cluster集群架构下只有一个数据库空间,即db0。因此,我们没有使用Redis的多数据库功能!
问题3:Redis集群机制中,你觉得有什么不足的地方吗?
数据通过异步复制,不保证数据的强一致性
假设有一个key,对应的value是Hash类型的。如果Hash对象非常大,是不支持映射到不同节点的!只能映射到集
群中的一个节点上!还有就是做批量操作比较麻烦!
问题4:懂Redis的批量操作么?
我们在生产上采用的是Redis Cluster集群架构,不同的key会划分到不同的slot中,因此直接使用mset或者mget等操作是行不通的。
问题5:那在Redis集群模式下,如何进行批量操作的优化?
redis引入cluster模式后,如果存在的节点异常多的时候,IO的代价已经超过数据传输,在这种情况下再增加节点已经没法再提高效率了。例如批量获取操作mget,RedisCluster将数据按key哈希到16384个slot上,每个redis node负责一部分的slot。mget需要执行的操作就是从redis node获取所有的key-value值,然后进行merge然后返回。
①串行命令:由于n个key是比较均匀地分布在Redis Cluster的各个节点上,因此无法使用mget命令一次性获取,所以通常来讲要获取n个key的值,最简单的方法就是逐次执行n个get命令,这种操作时间复杂度较高,它的操作时间=n次网络时间+n次命令时间,网络次数是n。很显然这种方案不是最优的,但是实现起来比较简单。
②串行IO:Redis Cluster使用CRC16算法计算出散列值,再取对16383的余数就可以算出slot值,同时Smart客户端会保存slot和节点的对应关系,有了这两个数据就可以将属于同一个节点的key进行归档,得到每个节点的key子列表,之后对每个节点执行mget或者Pipeline操作,它的操作时间=node次网络时间+n次命令时间,网络次数是node的个数,很明显这种方案比第一种要好很多,但是如果节点数太多,还是有一定的性能问题。
Map<String, String> serialIOMget (List<String> keys) {
//结果集
Map<String, String> keyValueMap = new HashMap<>();
//属于各个节点的key列表,JedisPool要提供基于ip和port的hashcode方法
Map<JedisPool, List<String>> nodeKeyListMap= new HashMap<>();
//遍历所有的key
for (String key : keys) {
//使用CRC16本地计算每个key的slot
int slot = JedisClusterCRC16.getSlot(key);
//通过iediscluster本地slot->node映射获取slot对应的node
JedisPool jedisPool = jedisCluster.getConnectionHandler().getJedisPoolFromSlot(slot);
//归档
if (nodeKeyListMap.containsKey(jedisPool)) {
nodeKeyListMap.get(jedisPool).add(key);
} else {
List<String> list = new ArrayList<String>();
list.add(key);
nodeKeyListMap.put(jedisPool, list);
}
}
//从每个节点上批量获取,这里使用mget也可以使用pipeline
for (Map.Entry<JedisPool, List<String>> entry : nodeKeyListMap.entrySet()) {
JedisPool jedisPool = entry.getKey();
List<String> nodeKeyList = entry.getValue();
//列表变为数组
String[] nodeKeyArray = nodeKeyList.toArray(new String[nodeKeyList.size()]);
//批量获取,可以使用mget或者Pipeline
List<String> nodeValueList = jedisPool.getResource().mget(nodeKeyArray);
//归档
for (int i = 0; i < nodeKeyList.size(); i++) {
keyValueMap.put(nodeKeyList.get(i), nodeValueList.get(i));
}
}
return keyValueMap;
}
③并行IO:此方案是将方案2中的最后一步改为多线程执行,网络次数虽然还是节点个数,但由于使用多线程网络时间变为O(1),这种方案会增加编程的复杂度。
④hash_tag实现:Redis Cluster的hash_tag功能,它可以将多个key强制分配到一个节点上,它的操作时间=1次网络时间+n次命令时间。hashtag的解决方案:可以使用twitter的 twemproxy
问题6:你们有对Redis做读写分离么?
不做读写分离。我们用的是Redis Cluster的架构,是属于分片集群的架构。而Redis本身在内存上操作,不会涉及IO吞吐,即使读写分离也不会提升太多性能,Redis在生产上的主要问题是考虑容量,单机最多10-20G,key太多降低Redis性能.因此采用分片集群结构,已经能保证了我们的性能。其次,用上了读写分离后,还要考虑主从一致性,主从延迟等问题,徒增业务复杂度。