Redis——缓存设计与优化

讲解Redis的缓存设计与优化,以及在生产环境中遇到的Redis常见问题,例如缓存雪崩和缓存穿透,还讲解了相关问题的解决方案。

1、Redis缓存的优点和缺点

1.1、缓存优点:

  • 高速读写:Redis可以帮助解决由于数据库压力造成的延迟现象,针对很少改变的数据并且经常使用的数据,我们可以把这些数据放入内存中。这样一方面可以减小数据库压力,另一方面可以提高读写效率。
  • 降低后端负载:后端服务器通过缓存降低负载,业务端使用Redis可以降低后端数据库MySQL的负载等。

1.2、缓存缺点:

  • 数据不一致:程序的缓存层和数据层有时会不一致,这和更新数据策略有关。
  • 代码维护成本:原本只需要读写MySQL就能实现功能,但加入了Redis缓存之后就要去维护缓存中的数据,增加了代码复杂度。
  • 堆内缓存可能带来内存溢出的风险,从而影响用户进程:在Java虚拟机的EhCache、LoadingCache、Java虚拟机栈、方法区、本地方法栈、程序计数器中,堆内缓存可能会带来内存溢出的风险,从而影响用户进程。

2、缓存雪崩

2.1、什么是缓存雪崩

缓存雪崩是指数据未加载到缓存中,或者缓存在同一时间大面积失效,导致所有请求都查询数据库,从而导致数据库CPU和内存负载过高,甚至数据库宕机。

2.2、有什么解决方案来防止缓存雪崩

(1)使用互斥锁(mutex)。使用互斥锁来防止缓存雪崩,使用Redis的SETNX命令去设置一个mutex key,当操作返回成功时,再执行查询数据库操作并回设Redis缓存。否则,就重试执行缓存的GET方法。

(2)缓存预热。缓存预热就是应用上线后,将相关的缓存数据直接加载到缓存系统中。这样用户就可以直接查询事先被预热的缓存数据。

(3)双层缓存策略。Cache 1为原始缓存,Cache 2为复制缓存。Cache 1失效时,可以访问Cache 2。Cache 1缓存失效时间设置为短期,Cache 2缓存失效时间设置为长期。

(4)定时更新缓存策略。对失效性要求不高的缓存,在容器启动初始化加载时采用定时任务更新或移除缓存。

(5)设置不同的过期时间,让缓存失效的时间点尽量均匀。

3、缓存穿透

3.1、什么是缓存穿透

缓存就是数据交换的缓冲区。缓存的主要作用是提高查询效率。在企业开发的软件系统中常常使用Redis作为缓存中间件,当请求到达服务器端时,优先查询缓存中的数据,当缓存中不存在时,再查询数据库,如果在数据库中查询到数据会将数据写回缓存,使得下一次同样的请求能够在缓存中直接查询到数据。一些攻击性请求会特意查询缓存中不存在的数据,产生缓存穿透。

缓存穿透是指查询一个不存在的数据。例如,Redis在缓存中没有查询到要查询的数据,需要去数据库查询,如果查询不到数据则不写入缓存,这将导致这个不存在的数据在每次请求时都到数据库查询,进而对数据库产生流量冲击造成缓存穿透。

3.2、有什么解决方案来防止缓存穿透

(1)采用布隆过滤器。

(2)缓存空值。如果一个查询返回的数据为空值,那么不管是数据不存在,还是系统故障,程序仍然会把这个空值进行缓存处理,但它的过期时间会很短,可能不超过5min。通过设置的默认值将该数据直接存放到缓存中,这样第二次在缓存中就可以查询到值了,而不会继续访问数据库。

4、布隆过滤器

4.1、布隆过滤器简介

布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率高和查询时间短,缺点是有一定的误识别率和元素删除困难。

布隆过滤器是一种空间效率很高的随机数据结构,它利用位数组很简洁地表示一个集合,并能判断一个元素是否属于这个集合。布隆过滤器由一个很长的位数组和一系列散列函数组成,数组的每个元素都只占1 bit空间,并且每个元素只能为0或1。

布隆过滤器还拥有k个散列函数,当一个元素加入布隆过滤器时,会使用k个散列函数对其进行k次计算,得到k个散列值,并且根据得到的k个散列值,在位数组中把对应位置的值置为1。判断某个元素是否在布隆过滤器中,就对该元素进行k次散列计算,判断得到的值在位数组中对应位置的值是否都为1,如果每个元素都为1,就说明这个元素在布隆过滤器中。

将数据库中需要查询的数据放入系统缓存中的布隆过滤器中,当请求向后台系统查询数据时,先去系统缓存中的布隆过滤器中进行查找,如果查询的数据在布隆过滤器中不存在,就不用查询数据库了,直接给请求返回一个未查询到数据的结果,从而避免了对数据库的频繁查询。

布隆过滤器是一个判断元素是否属于集合的快速的概率算法。布隆过滤器有可能会出现错误判断,但不会漏掉判断。也就是说,如果布隆过滤器判断元素不在集合中,那么肯定不在;如果判断元素在集合中,那么会有一定的概率判断错误。因此,布隆过滤器不适合那些零错误的应用场景。而在能容忍低错误率的应用场景中,布隆过滤器比其他常见的算法(如散列函数、折半查找)极大节省了空间,如下图所示:
Redis——缓存设计与优化_第1张图片
布隆过滤器很常用的一个功能是去重,比如在爬虫中有一个常见的需求:目标网站的URL可以有成千上万个,怎么判断某个URL是否被爬虫爬取过呢?一个简单的方法是,可以把爬虫爬取过的每个URL存入数据库中,每次一个新的URL过来就到数据库查询是否爬取过。例如,SELECT*FROM spider WHERE url=‘http://www.163.com’。

但是随着爬虫爬取过的URL越来越多,每次请求查询时都要访问数据库一次,判断某个URL是否访问过使用SQL查询效率并不高。除了数据库之外,还可以使用Redis的Set数据类型满足这个需求,并且其性能优于数据库。但是Redis也存在一个问题,它会耗费过多的内存,这时候就可以使用布隆过滤器来解决去重问题。相比于数据库和Redis,使用布隆过滤器可以很好地避免性能和内存占用的问题。

我们通常使用Redis作为数据缓存,当收到请求时先通过key去Redis缓存中查询,如果查询的数据在Redis缓存中不存在,就会去查询数据库中的数据。如果这种请求量很大,会给数据库造成很大的查询压力,从而影响系统的性能,这时就需要用到布隆过滤器来解决缓存穿透问题了。

解决缓存穿透的方法:

方法一:当数据库和Redis中都不存在key,查询数据库会返回null。需要在Redis中使用SETEX key null expireTime设置一个过期时间expireTime,这样当再次请求key时Redis将直接返回null,而不用再次查询数据库。

方法二:使用Redis提供的布隆过滤器模块RedisBloom,同样是将存在的key放入布隆过滤器中。当收到请求时先在布隆过滤器中查询key是否存在,如果key不存在直接返回null,不必再次查询数据库。

布隆过滤器的用途是判断过滤器中是否存在该数据,从而减少没有必要的数据库请求。

4.2、Redis加载布隆过滤器模块

Redis官方提供的布隆过滤器在Redis 4.0发布以后才正式推出。布隆过滤器可作为一个插件加载到Redis服务器中,给Redis提供强大的布隆去重功能。在本小节中,我们将学习如何在Redis服务器上加载布隆过滤器模块。

在GitHub搜索RedisBloom下载最新发布的源代码,单击页面的“Clone or download”按钮后选择“Download ZIP”,下载RedisBloom-master.zip到本地硬盘,如下图所示:
Redis——缓存设计与优化_第2张图片
上传RedisBloom-master.zip到Linux服务器,在Linux服务器上进行解压缩和编译:

$ unzip RedisBloom-master.zip 
$ cd RedisBloom-master/ 
$ make

4.3、在项目中使用布隆过滤器

pom.xml文件中引入以下类库:

 
      
        com.redislabs</groupId> 
        jrebloom</artifactId> 
        1.0.1</version> 
     </dependency> 
 
      
        redis.clients</groupId> 
        jedis</artifactId> 
        3.1.0</version> 
     </dependency> 
   </dependencies>

新建测试类RedisbloomDemo。本实例使用“RedisbloomDemo.java”,内容如下:

 
import io.rebloom.client.Client; 
 
public class RedisbloomDemo { 
    public static void main(String[] args) { 
       // 创建客户端,Jedis实例 
       Client client = new Client("192.168.11.15", 6379); 
 
       String urlsBloomKey = "urls"; 
 
       // 创建一个有初始值和出错率的布隆过滤器 
       client.createFilter(urlsBloomKey,1000,0.01); 
       // 在布隆过滤器新增一个key-value键值对 
       boolean url1 = client.add(urlsBloomKey,"http://www.163.com"); 
       System.out.println("url1 add :" + url1); 
 
       boolean url2 = client.add(urlsBloomKey,"http://www.cnblogs.com"); 
       System.out.println("url2 add :" + url1); 
 
       // 某个value是否在布隆过滤器中存在 
       boolean exists = client.exists(urlsBloomKey, "http://www.163.com"); 
       System.out.println("http://www.163.com 是否存在: " + exists); 
    } 
}

该程序输出如下:

url1 add :true 
url2 add :true 
http://www.163.com 是否存在: true

你可能感兴趣的:(#,NoSQL+NewSQL,缓存,redis,数据库)