Redis(开发与运维):55---缓存设计之(无底洞问题及优化)
2020-06-04 15:36
633 查看
一、无底洞问题
- 2010年,Facebook的Memcache节点已经达到了3000个,承载着TB级别的缓存数据。但开发和运维人员发现了一个问题,为了满足业务要求添加了大量新Memcache节点,但是发现性能不但没有好转反而下降了,当时将这 种现象称为缓存的“无底洞”现象。
- 那么为什么会产生这种现象呢,通常来说添加节点使得Memcache集群性能应该更强了,但事实并非如此。键值数据库由于通常采用哈希函数将 key映射到各个节点上,造成key的分布与业务无关,但是由于数据量和访问量的持续增长,造成需要添加大量节点做水平扩容,导致键值分布到更多的节点上,所以无论是Memcache还是Redis的分布式,批量操作通常需要从不同节点上获取,相比于单机批量操作只涉及一次网络操作,分布式批量操作会涉及多次网络时间
- 下图展示了在分布式条件下,一次mget操作需要访问多个Redis节点, 需要多次网络时间
- 而下图由于所有键值都集中在一个节点上,所以一次批量操作只需要 一次网络时间
- 无底洞问题分析: 客户端一次批量操作会涉及多次网络操作,也就意味着批量操作会随 着节点的增多,耗时会不断增大
- 网络连接数变多,对节点的性能也有一定影响
优化思路
- 下面介绍如何在分布式条件下优化批量操作。在介绍具体的方法之前, 我们来看一下常见的IO优化思路: 命令本身的优化,例如优化SQL语句等
- 减少网络通信次数
- 降低接入成本,例如客户端使用长连/连接池、NIO等
这里我们假设命令、客户端连接已经为最优,重点讨论减少网络操作次数 以Redis批量获取n个字符串为例,有三种实现方法,如下图所示: 客户端n次get:n次网络+n次get命令本身
客户端1次pipeline get:1次网络+n次get命令本身 客户端1次mget:1次网络+1次mget命令本身
- 上面已经给出了IO的优化思路以及单个节点的批量操作优化方式,下面我们将结合Redis Cluster的一些特性对四种分布式的批量操作方式进行说明
二、串行命令
- 由于n个key是比较均匀地分布在Redis Cluster的各个节点上,因此无法使用mget命令一次性获取,所以通常来讲要获取n个key的值,最简单的方法就是逐次执行n个get命令,这种操作时间复杂度较高,它的操作时间=n次网络时间+n次命令时间,网络次数是n
- 很显然这种方案不是最优的,但是实现起来比较简单,如下图所示
- Jedis客户端示例代码如下:
[code]List<String> serialMGet(List<String> keys) { // 结果集 List<String> values = new ArrayList<String>(); // n次串行get for (String key : keys) { String value = jedisCluster.get(key); values.add(value); } return values; }
三、串行IO
- Redis Cluster使用CRC16算法计算出散列值,再取对16383的余数就可以算出slot值,同时前面文章https://www.geek-share.com/detail/2801939965.html我们提到过Smart客户端会保存slot和节点的对应关系,有了这两个数据就可以将属于同一个节点的key进行归档,得到每个节点的key子列表,之后对每个节点执行mget或者Pipeline操作
- 它的操作时间=node次网络时间+n次命令时间,网络次数是node的个数
- 整个过程如下图所示,很明显这种方案比第一种要好很多,但是如果节点数太多,还是有一定的性能问题
- Jedis客户端示例代码如下:
[code]Map<String, String> serialIOMget(List<String> keys) { // 结果集 Map<String, String> keyValueMap = new HashMap<String, String>(); // 属于各个节点的key列表,JedisPool要提供基于ip和port的hashcode方法 Map<JedisPool, List<String>> nodeKeyListMap = new HashMap<JedisPool, List<String>>(); // 遍历所有的key for (String key : keys) { // 使用CRC16本地计算每个key的slot int slot = JedisClusterCRC16.getSlot(key); // 通过jedisCluster本地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 (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),这种方案会增加编程的复杂度
- 它的操作时间为:
[code]max_slow(node网络时间)+n次命令时间
- 整个过程如下图所示:
五、hash_tag实现
- 前面文章https://www.geek-share.com/detail/2801939965.html介绍了Redis Cluster的hash_tag功能,它可以将多个key强制分配到 一个节点上,它的操作时间=1次网络时间+n次命令时间,如下图所示
- 如下图所示,所有key属于node2节点
- Jedis客户端示例代码如下:
[code]List<String> hashTagMget(String[] hashTagKeys) { return jedisCluster.mget(hashTagKeys); }
六、总结
- 上面已经对批量操作的四种方案进行了介绍,最后通过下图来对四种方案的优缺点、网络IO次数进行一个总结:
- 实际开发中可以根据上图给出的优缺点进行分析,没有最好的方案只有最合适的方案
相关文章推荐
- Redis(开发与运维):56---缓存设计之(雪崩问题及优化)
- 运维角度浅谈MySQL数据库优化一个成熟的数据库架构并不是一开始设计就具备高可用、高伸缩等特性的,它是随着用户量的增加,基础架构才逐渐完善。这篇博文主要谈MySQL数据库发展周期中所面临的问题及优化方
- Redis(开发与运维):58---开发运维的陷阱之(Linux配置优化:内存分配控制(overcommit、swappiness)、THP、OOM killer、NTP、TCP backlog
- ASP.NET设计中的性能优化问题,.net开发,
- JAVAWEB开发之Hibernate详解(三)——Hibernate的检索方式、抓取策略以及利用二级缓存进行优化、解决数据库事务并发问题
- JAVAWEB开发之Hibernate详解(三)——Hibernate的检索方式、抓取策略以及利用二级缓存进行优化、解决数据库事务并发问题
- APP后台开发运维与架构实践 7 :Redis---App后台高性能的缓存系统
- 数据库历险记(三) | 缓存框架的连环炮 数据库历险记(二) | Redis 和 Mecached 到底哪个好? 数据库历险记(一) | MySQL这么好,为什么还有人用Oracle? 面对海量请求,缓存设计还应该考虑哪些问题?
- Redis(开发与运维):45---Sentinel之(开发与运维中的问题:故障转移日志、节点运维(节点下线/上线/配置)、高可用读写分离)
- 记录一个关于互联网、网页设计、Web开发、服务器运维优化、项目管理、网站运营、网站安全的网站
- Camstar开发:缓存的设计与实现(整合Redis实例)
- web开发中的缓存问题的研(一)
- ASP.NET设计中的性能优化问题
- 漫谈ASP.NET设计中的性能优化问题
- 论ASP.NET设计中的性能优化问题
- web开发中的缓存问题的研究(二)
- ASP.NET设计中的性能优化问题
- Oracle数据库设计开发阶段性能优化策略
- web开发中的缓存问题的研究(二)
- 漫谈ASP.NET设计中的性能优化问题