这段时间在完成一个类似于美团一样的外卖项目,这个项目的业务涉及到大量的redis操作,有些操作让我觉得非常巧妙,特地记录一下这个项目带给我的redis使用经验

一、什么情况下使用redis

对于目前的我而言,使用redis的情况有以下两种

  1. 用来做数据缓存,减少对数据库的访问,一般key会采取 实体名:id:id值 来存储对应的实体信息,或者记录实体的某字段信息,一般采取 实体名:字段名:id值 作为key。
  2. 用redis来更方便的实现某些业务,比如根据点赞时间的先后展示头几个点赞用户的头像,根据当前地理位置筛选附件的商户,排行榜等等,用redis来处理这些业务的时候,redis通常不会记录这些实体的详细内容,只记录这些实体的id,这样更加节省内存和方便修改

二、关于redis更新缓存的策略问题

当我们更新数据库数据时,如何保证缓存与数据库的一致性呢?有redis项目经验的伙伴都知道,先更新数据库,再删除对应的key。等到下次用户读取数据时发现缓存中没有,再去数据库中查询数据并刷新缓存。
这样做的好处有两点,一是可以避免对缓存无效的写操作,二是可以最大程度的保证数据一致性,当然我们需要设置一个过期时间来兜底,虽然数据不一致性的概率很低

三、关于redis更新缓存的问题

(一) 缓存穿透

指的是恶意用户频繁访问不存在的数据,这样可以无视缓存,对数据库进行请求,增加服务器的压力。处理的方式有多种,我举几个例子

  1. 若数据库中查不到该数据,则在缓存中设置为空字符串,这样当它下次访问时就可以通过缓存是否为空字符串来校验它,防止它穿透到数据库。 该方式的优点是实现简单,不会误判。缺点是耗费内存
  2. 采用布隆过滤器的方式,布隆过滤器内部采用按位校验的方式判断数据库中是否存在该数据,但它存在误判的可能
  3. 将经常被查询的字段比如id,格式设置的复杂些,这样可以直接通过格式判断是否为恶意请求

(二) 缓存击穿

当缓存的key过期的时候,突然大量的请求访问该key,发现key不存在,便一起访问数据库,给数据库带来极大的压力。这种情况的解决思路很简单,就是当发现缓存中key不存在的时候,获取锁,只允许一个请求去访问数据库,访问完刷新缓存。到了这一步后,其他请求怎么办呢?有两个选择:

  1. 互斥锁:如果需求对数据一致性要求较高,其他请求则等待访问数据库的请求将数据更新到缓存中,再从缓存中拿数据,这样的优点是保证数据一致性,缺点是性能太低,用户需要等很久,而且对cpu和内存的消耗都很大
  2. 逻辑过期:设置一个逻辑过期时间,该key并不会真的过期,请求访问到该key时判断当前时间是否过期,如果没过期直接返回对应值,过期则获取锁,在锁的范围内将更新缓存的事情交给线程池去做,当前线程直接返回那个过期的值,如果没有获取到锁,说明缓存被更新了,直接返回过期结果即可

注意:逻辑过期的方法无法与缓存穿透的空字符方式合作,但互斥锁方式可以

四、关于更新缓存的一些胡思乱想

在一开始学习redis的时候将缓存更新的步骤当成公式去用,结果发现在很多业务上用起来很麻烦很痛苦,最后慢慢才悟出来,所谓的公式(写:先操作数据库,再删key 读:先读缓存,缓存没有再从数据库找) 一般适用于redis做数据缓存的时候,而很多时候我们只是使用redis的特性用来解决业务问题,这种时候我们就应该根据实际情况灵活运用了,而不是套用公式