无底洞问题关键点即:
更多的机器 != 更多的性能批量接口需求(mget和mset等):在执行mget和mset等命令时会面对的问题数据增长与水平扩展需求等:随着业务量越来越大,对于缓存和数据源存储的需求也是越来越大,就需要对缓存和数据源进行扩容,即增加缓存节点和数据源节点,但是节点数量增多并不能带来性能的提升,这是一个矛盾的问题

5.3 优化IO的方法

优化命令本身:例如执行慢查询keys,hgetall bigkey等命令时,尽量选择在缓存节点压力不大时执行减少网络通信次数,例如执行mget命令由原来的O(n)次网络时间缩减为O(node)次网络时间,降低接入成本:例如客户端长连接/连接池,NIO等

5.4 四种批量优化方法:

5.4.1 串行mget
串行mget需要n次网络时间
5.4.2 串行IO
由于客户端对key进行重新组装,所以把网络通信时间降低到节点次O(node)
5.4.3 并行IO
并行IO也会在客户端对key进行重新组装,然后执行并行操作,所需要的网络时间为O(1)
5.4.4 hash_tag
hash_tag会把所有的key都分配到一个节点,但是使用这种方法会遇到各种问题

5.5 四种优化方案的优缺点分析

6.执行key重建优化

6.1 缓存重建过程描述

在正常情况下,客户端发送请求,会先到缓存,从缓存中获取需要的数据,如果缓存中并没有需要数据,才会继续向数据源请求,从数据源中获取数据返回给客户端并回写到缓存中,这就是缓存的重建过程

6.2 缓存重建问题描述

如果重建的是一个热点key,用户访问量非常大。很多用户发送请求获取数据,执行线程从缓存中获取数据,但是此时缓存中并有这些数据,就会从数据源中获取数据,然后重建缓存。
当缓存重建完成,后续的访问才会直接读取缓存数制并返回
在这个过程中,会有很多线程同时查询并重建缓存key,一方面会对数据源造成很大压力,另一方面也会加大响应的时间

6.3 解决缓存重建的目标

减少缓存重建次数:不要多次重建缓存 数据尽可能一致:缓存中的数据要尽可能与数据源中的数据保持一致 减少潜在风险:可能造成死锁或者线程池大量被夯住等情况

6.4 缓存重建解决方法

6.4.1 互斥锁(mutex key)
互斥锁是一种比较直观和简单的解决思路
第一个用户从缓存中获取数据,此时缓存中并没有用户需要的数据,会从数据源中重建缓存,
用户在从数据源查询获取数据和重建缓存的过程中加上一把锁,当重建缓存完成以后再把锁解开,并返回
当第二个用户也想从缓存中获取数据时,如果第一个用户重建缓存的过程还没有结束,即锁还没有被解开时,就会等待,同样后续访问的用户也经过这样一个过程
当缓存重建完成,锁被解开,所有的用户请求都从缓存中获取数据并输出
互斥锁解决了缓存大量重建的过程,但是在缓存重建的过程中会有一个等待时间,大量线程被夯住,有可能造成死锁的情况
6.4.2 数据永不过期
在缓存层面,每一个key都不设置过期时间(没有设置expire) 在功能层面中,为每个value添加逻辑过期时间,一旦发现超过逻辑过期时间后,会使用单独的线程去构建缓存
需要注意的是
数据永不过期是一个异步的过程,即使缓存重建失败,也不会造成线程夯住的问题 数据永不过期基本杜绝了热点key的重建问题。 数据永不过期好处是:相比于使用互斥锁的方案,不会使用户产生一个等待的时间,而且可以保证只有一个线程来完成数据源的查询和缓存的重建 数据永不过期的缺点:在缓存重建完成之前,用户从缓存中得到的原来的数据有可能与从数据源中的新数据不一致的情况 数据永不过期中设置逻辑过期时间,会为每一个key设置过期时间,会增加维护成本,占用更多的内存空间。

6.4 缓存重建解决方法对比