中间件之Redis应用场景及常见问题

一、数据一致性

1.缓存使用场景

针对读多写少的高并发场景,我们可以使用缓存来提升查询速度。

使用Redis作为缓存的流程

  1. 如果数据在Redis中存在,客户端可以直接从Redis中拿到数据,不用直接访问数据库。


    在这里插入图片描述
  2. 客户端新增数据,只保存到数据库中,如果Redis中没有,先到数据库查询然后写入到缓存,再返回给客户端


    在这里插入图片描述

2.一致性问题

因为数据一定是以数据库为准的,如果Redis中没有数据,就不存在这个问题。当Redis和数据库都有同一条记录,而这条记录发生变化的时候,就可能出现一致性问题。

当缓存的数据发生变化(修改、删除)时,我们纪要操作数据库中的数据也要操作Redis缓存的数据,才能保证缓存和数据库的数据一致。所以问题出现了:

  1. Redis中的旧数据删除还是更新?
  2. 先操作Redis再操作数据库
  3. 先操作数据库再操作Redis

(1)删除/更新

当存储的数据发生变化,Redis的数据也要更新的时候,我们有两种方案,一直是直接更新Redis数据,调用set;还有一种时直接删除Redis中的数据,客户端下次查询的时候重新写入。

首先需要考虑,更新缓存前是否要经过其他的各种操作,最后才能得到最新的数据,而不是从数据库直接拿到的值?基于这种复杂场景的考量,建议还是直接删除缓存,这种方案更加简单,而且避免了数据库的数据和缓存不一致的情况。

(2)先数据库后缓存

正常情况
更新数据库:成功
更新缓存:成功

异常情况

  1. 更新数据库失败,抛出异常,缓存不更新,不会出现数据不一致的额情况
  2. 更新数据库成功,删除缓存失败。数据库是新数据,缓存是旧数据,发生了不一致情况。

(3)先缓存后数据库

正常情况
更新缓存:成功
更新数据库:成功

异常情况

  • 删除缓存,程序捕获异常,不会走到下一步,不会出现数据不一致的情况
  • 删除缓存成功,更新数据库失败。因为以数据库的数据为准,所以不存在数据不一致的情况。
    但是当并发情况下:
    • 线程A需要更新数据,首先删除了缓存

    • 线程B需要查询数据,发现缓存不存在,到数据库查询旧数据,然后写入Redis返回

    • 线程A更新数据库
      这时候,缓存中是旧数据,数据库中是新数据,造成了数据不一致


      在这里插入图片描述

      最终方案
      双删,先删除Redis,再更新数据库,再删除Redis。

二、高并发问题

再Redis存储的所有数据中,有一部分是被频繁访问的。有两种情况可能导致热点问题。一是用户集中访问的数据,比如抢购商品,热点新闻等。还有一个是再数据进行分片的情况下,负载不均衡,超过了单个服务的承受能力。热点问题的出现可能会引起服务的不可用,最终造成数据库压力过大。

1.热点数据发现

首先Redis中有缓存淘汰机制,能够留下一些热点的Key,不管是LRU还是LFU

除了自动的缓存淘汰机制,如何找出访问频率高的key?

(1)客户端

在客户端,我们可以统计热点数据的访问频率。比如再所有调用get、set方法的地方加上key 的计数。
会导致的问题:

  1. 对客户端的代码造成入侵
  2. 不知道要存多少个key,可能会出现内存泄漏的问题OOM
  3. 只能统计当前客户端热点key

(2)代理层

可以使用Spring的AOP动态代理,来完成上面客户端的操作。但是如果项目中没有用到支持代理相关功能的框架呢?

(3)服务层

Redis中有一个monitor命令,可以监控所有Redis执行的命令


在这里插入图片描述

Jedis中也封装了相关API

jedis.monitor(new JedisMonitor(){
    @Override
    public void onCommand(String command){
        System.out.println("#monitor:"+command);
    }
})

(4)机器层面

通过TCP协议进行抓包,也有一些开源的方案供使用。

2.缓存雪崩

(1)什么是缓存雪崩?

当Redis大量热点数据同时过期,此时请求量又特别大,就会导致所有请求打到数据库中。

(2)解决方案

  1. 查询写入Redis时,相同的查询操作只允许一条请求访问到数据库,其他的等待,并访问Redis。
  2. 缓存定时预先更新,避免同时失效
  3. 通过加随机数,使key再不同的事件过期(梯度过期)
  4. 热点缓存不设置过期时间

3. 缓存穿透

当请求访问不存在的数据,会直接跳过Redis访问数据库,此时数据库中也不存在。

解决方案:1. 缓存空数据。2.缓存自定义特殊字符串
当这个请求再次进入系统时,会访问到自定义的特殊字符串,这时候系统就知道访问的是不存在的数据,直接返回静态资源404给客户端。

如果有恶意请求,发送大量不重复请求到服务端,虽然我们已经要写入自定义特殊字符到Redis,但由于它每次请求的key都是不同的,还是会给数据库造成很大的压力,占用数据库资源。

(1)经典面试题

如何在海量数据中,快速判断一个key是否存在?

首先我们想到的是先放入客户端缓存,避免请求服务端数据库及Redis。那么我们的基本数据结构能支撑这么大数据量吗,会发生OOM,内存溢出。

最节省空间的数据结构—位图。位图是一个有序数组,只有两个值,0和1。0代表不存在,1代表存在。


在这里插入图片描述

如何使用位图来做这个需求呢?

  1. 可以先将key做hash,再取模,获取到一个索引下标,将位图中对应的下标元素变成1
  2. 如果这样做,哈希碰撞的几率相当大,比如多个key哈希取模后下标一致,那么这些key是否真正存在呢?
  3. 可以将存在的key多个哈希函数运算取模放入位图中,比如key=java,第一次取模放在index=1,第二次取模放在index=5,第三次取模放在index=23,当这三个index对应的元素都为1时,我才大概率确认这个key确实是存在的。
  4. 当一个key经过三次哈希函数的运算后,生成的三个index中只要有一个index对应元素为0,那这个key必不存在
  5. 当一个key经过三次哈希函数运算后,生成的三个index对应元素都为1,那这个key大概率存在(不是百分百存在)

布隆过滤器
谷歌的Guava中提供了一个现成布隆过滤器,实现原理大致如上所述。可以直接拿来使用。

使用在访问Redis之前:

在这里插入图片描述

你可能感兴趣的:(中间件之Redis应用场景及常见问题)