Redis 实践中的问题与优化

总结在常见的在Redis实践中遇到的问题和优化方法

缓存穿透

问题

缓存中存储的是数据库中命中的数据,如果没有命中,则会去数据库中去取然后回溯到缓存中。这个时候如果一直访问一个数据库中也没有的数据,就可能造成大量请求直接访问数据库。

优化方案

  1. 如果从数据库中查询的结果也为空,那么将这个空结果也放到缓存中。这样可能会造成数据不一致的情况(具体取决数据库和缓存的一致性策略),所以要设置一个比较短的过期时间。
  2. 查询数据库的时候使用互斥锁,查询前首先要得到锁再去查询,没有得到锁的要先等待直到锁可用。但是这种方案可能减低其查询的性能,因为只能够有一个查询存在,所以对于不属于缓存穿透的场景的查询性能会收到影响。
  3. 知道key的规则或者合法性,可以在业务层就进行一个过滤(通过规则或者布隆过滤器)

缓存雪崩

问题

缓存中设置了失效时间,如果大量缓存在短时间内一起失效,这样访问的压力就都移到数据库中去了。就像发生雪崩一样。

优化方案

  • 采用加锁或消息队列,采用单线程的方式,防止失效时大量线程请求数据库。
  • 在设置缓存时间的时候,在原来缓存时间的技术上加一个比较小的随机值,避免大量缓存在同一时间失效

热key问题

问题

业务层可能因为一些新闻或者热点事件对一个相同的key在短时间内发出大量请求。如果此时没有命中缓存,就需要构建缓存,但是构建缓存需要时间,在这个时间里面所有对key的请求还是会打到下面的数据库上,造成后端和数据库的压力过大。同时热key本身对缓存系统也会带来非常大的压力。

解决方案

  • 还是可以用互斥锁进行解决,确保同一时间只有一个线程查询数据库
  • 对于大量请求打到Redis上的解决可以采用在本地构建local cache的方法

大key问题

问题

Redis作为缓存通常是以接口为单位进行存储,也就是说Redis中存储的数据就是接口中返回的东西。

这样能够使得每次请求只有一个原子操作,但是带来的问题就是key会非常大,造成了大key存储的问题。这会导致用这个Redis作为缓存的服务非常不稳定,因为单次请求这个key的读写时间过长数据过多导致后面redis的原子操作可能发生I/O超时,从而导致redis实例命中率波动较大。当缓存命中率比较低的时候,瞬间大量数据请求就会打到数据库,导致带宽不足、数据库压力过大、数据库慢查询过多和平均查询时间变长。

因为我们服务的时间=redis超时时间+数据库慢查询时间+接口逻辑时间,所以大key可能大致整个服务的恶性循环。

优化方案

可以采用将存储单位从接口数据变为原子数据。每个接口数据都是由原子数据拼接而成,这个拼接可以在业务层完成。从而减小Redis请求超时的情况。

批量请求问题

问题

接着上面的问题,当把存储单位从接口数据变成了原子数据以后,势必会造成每个接口的请求数据变多。如果这些请求都通过单个连接去处理,就会造成总体的处理事件过长的问题

优化方案

这个时候可以使用到批量请求的相关接口:批量存取字符串类型的方式分别为mget、mset、pipeline。三者都会将多次操作合并到一次,也就是进行以此连接返回所有数据。但是之间的差异还是比较明显的,比如:

  • pipeline:可以支持多个不同类型的操作在一次请求中,但是使用pipeline需要客户端和服务端都支持。当某个命令的执行需要依赖前一个命令的返回结果时,无法使用pipeline。
  • mget:只支持get请求,megt过大的时候会导致内存暴涨,然后一直持有不释放
  • mset:只支持set请求,且不支持设置过期时间

参考: