redis的性能并不受CPU的运行速度,影响redis性能的是网络带宽和内存大小。
redis常见问题及对应解决方案:
一、缓存穿透:就是查询一个压根就不存在的数据,即缓存中没有,数据库中也没有
解决方案:使用布隆过滤器,把数据先加载到布隆过滤器中,访问前先判断是否存在于布隆过滤器中,不存在代表这笔数据压根就不存在。
缺点:布隆过滤器是不可变的,可能一开始过滤器和数据库数据时一致的,后面数据库数据变了,或变多或变少,而对应的布隆过滤器的数据也要改变,这时会比较麻烦。
二、缓存击穿:数据库中有,缓存中没有。缓存击穿实际就是一个并发问题,一般来说查询数据,先查询缓存,有直接返回,没有再查询数据库并放到缓存中之后返回,但这种场景在并发情况下就会有问题,假设同时又100个请求执行上面
逻辑的代码,则可能会出现多个请求都查询数据库,因为大家同时执行,都查到了缓存中没有数据。
解决方案:加锁。如果是单机部署,则可以使用JVM级别的锁,如lock、synchronized。如果是集群部署,则需要使用分布式锁,如基于redis、zookeeper、mysql等实现的分布式锁。
三、缓存雪崩:大部分数据同时失效、过期,新的缓存又没来,导致大量的请求都去访问数据库而导致的服务器压力过大、宕机、系统崩溃。
解决方案:搭建高可用的redis集群,避免压力集中于一个节点;缓存失效时间错开,避免缓存同时失效而都去请求数据库。
四、双写不一致问题:数据库数据和缓存不一致,例如一个线程A要更新某个数据stock为6,先写进数据库再更新缓存,但是在这个过程中,在已经写进数据库而没有更新到缓存中的这个时间段内,另外一个线程B
也要把这个stock更新为8,也是先写数据库再更新缓存,并且完成了这个操作这时线程A再继续更新缓存,这时数据库中stock的值就是线程B更新的8,而缓存中stock的值就是线程A更新的6,这就是数据库与缓存不一致。
解决方法1:一般在操作缓存的数据库也就是写缓存的时候是先删除缓存数据再更新数据库,可以采用删除缓存再更新数据库,如果删除缓存和更新数据库都成功了就没有不一致问题;
如果删除缓存成功,而更新数据库失败了,那缓存里的数据为空,数据里的数据为旧的,数据没有不一致;
如果缓存删除失败,那就没有必要更新数据库,两边数据都是旧,也没有不一致。
解决方法1之所以要删除数据库是因为如果一个数据在还没有使用的时候就被更新了,那这个更新的操作就显得有些浪费,所以一般更新缓存都是删除缓存,在读取的时候再加载到缓存中,也就是懒加载模式。
但这种先删除缓存再更新数据的解决方法依旧是有问题的,比如一个线程A要操作一个字段stock先删除缓存更新一次数据库为6,这时数据库里stock的值为6,缓存里因为被删除了所以没有,这时线程B去查询这个字段,
先查缓存发现没有再查数据库,查出来的是6,想要去把这个6添加到缓存中,在执行这个操作之前,又有一个线程C来操作这个stock,要更新为10,这时线程C先更新数据库的stock为10,再删除缓存,操作完成之后,
线程B继续把值为6的stock字段更新到缓存中,这时缓存的stock为6,而数据库中的为10,又出现了不一致问题了。
解决方法2:延迟双删,就是让所有的写请求,在删除缓存的时候删除两次,比如上面就是因为线程B的更新缓存的操作在线程C删除缓存再写数据库之后执行导致的问题,这时我在线程B写完数据库之后让当前线程延迟
一点时间再删除一次,这样就有可能让线程B的更新缓存操作在线程C二次删除缓存之前执行,这样数据库中的stock值是线程C更新的10是最新的,而缓存中没有数据,也就没有双写不一致问题。
解决方法2是只能在一定程度上解决不一致问题,而且每个写请求都要延迟,会有性能问题。
造成以上问题的根源是每个线程在执行自己的操作过程中可能会被其他线程“插队”,导致了一个线程的操作不是原子性,所以要解决这个问题就是要保证线程操作的原子性,也就是加锁。
解决方法3:读写锁,对读操作加读锁,对写操作加写锁,读锁是非互斥的,一个请求获得读锁的时候另一个线程也可以得到读锁。写锁是互斥的,一个线程在获得写锁时,另一个线程既不能读也不能写。