Redis缓存实例超详细讲解

目录
  • 1 前言
    • 1.1 什么是缓存
    • 1.2 缓存的作用及成本
    • 1.3 Redis缓存模型
  • 2 给商户信息添加缓存
  • 3 缓存更新策略
    • 3.1 更新策略介绍
    • 3.2 主动更新策略
    • 3.3 主动更新策略练习
  • 4 缓存穿透及其解决方案
    • 4.1 缓存穿透的概念
    • 4.2 解决方案及实现
  • 5 缓存雪崩的概念及其解决方案
  • 6 缓存击穿及解决方案
    • 6.1 什么是缓存击穿
    • 6.2 缓存击穿解决方法
      • 6.2.1 互斥锁
      • 6.2.2 逻辑过期

1 前言

1.1 什么是缓存

缓存就是数据交换的缓冲区(称作Cache [ kæʃ ] ),是存贮数据的临时地方,一般读写性能较高。

缓存有很多中实现场景:对于web开发,常见的有如下几种:

而我们的Redis缓存功能就是属于在应用层缓存 。

1.2 缓存的作用及成本

作用:毫无疑问,就是提高读写的效率,有效降低后端服务器的负载,有效降低响应时间。

成本:任何东西都有两面性,缓存在带来高效的读写效率的同时,也有着对应的从成本。

比如:数据一致性成本、代码维护成本、运维成本等。

1.3 Redis缓存模型

如下图

原本的模型应该是客户端发送请求给数据库,数据库返回数据给客户端,而Reids的缓存模型就是在原有的基础上,在中间加上一层Redis(经典的中间件思想~)用户每次都会先去redis中查找数据,如果未命中才会去数据库中查找数据,并写入Reis当中,这么一来,用于下次需要相同的数据的时候,就可以在Reis当中进行获取,又因为Redis的高读写效率,实现了缓存的效果~

2 给商户信息添加缓存

基于上述的Redis缓存模型,我们可以得出下面的缓存添加逻辑:

代码实现:(直接看Service层实现)

每次查询到商品用户信息后,添加缓存。

package com.hmdp.service.impl;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
import static com.hmdp.utils.RedisConstants.CACHE_SHOP_KEY;
import static com.hmdp.utils.RedisConstants.CACHE_SHOP_TTL;
/**
 * <p>
 *  服务实现类
 * </p>
 *
 */
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public Result queryShopById(Long id) {
        //1.去redis中查询商品是否存在
        String key = CACHE_SHOP_KEY+id;
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(shopJson)){
            //2.存在,直接返回给用户
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }
        //3.不存在,带着id去数据库查询是否存在商品
        Shop shop = getById(id);
        if (shop == null){
            //4.不存在,返回错误信息
            Result.fail("商品信息不存在!");
        }
        //5.存在,存入redis
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop));
        //6.返回商品信息
        return Result.ok(shop);
    }
}

3 缓存更新策略

3.1 更新策略介绍

使用Redis做缓存时,缓存还是要及时更新的,不然就会出现数据库和缓存数据不一致的情况,也是我们常说的Redis成本——数据一致性成本

缓存大致有以下三种更新策略:

1. 内存淘汰策略:这种策略没有维护成本,这是利用Redis的内存淘汰机制,当内存不足的时候自动淘汰,下次请求时再继续存入数据。

这种策略模型优点在于没有维护成本,但是内存不足这种无法预定的情况就导致了缓存中会有很多旧的数据,数据一致性差。

2. 超时剔除:这种策略就是比较实用的,就是给缓存添加TTL存活时间,下次查询是更新缓存。

这种策略数据一致性一般,维护成本有但是较低,一般用于兜底方案~

3.主动更新策略:就是我们程序员手动的进行数据库和缓存之间的更新,但数据库更新时,缓存也进行更新。

这种策略数据一致性就是最高的(毕竟自己动手,丰衣足食),但同时维护成本也是最高的。

那么,又该如何选择缓存更新策略呢?

我的觉得应该是根据业务场景不同来选择不同的更新策略:

当数据一致性要求低时:l使用内存淘汰机制。例如店铺类型的查询缓存。

当有高一致性需求:使用主动更新,并以超时剔除作为兜底方案。例如店铺详情查询的缓存

3.2 主动更新策略

上述提到的主动更新策略,无疑是维护成本最高的,但具体又有哪些维护方式呢?怎么去做主动更新维护呢?

如下图:主动更新主要分为下面三种:

又因为后两种实现方式过于复杂,所以重点说第一种。

第一种:Cache Aside Pattern,由缓存调用者进行操作,就是在我们数据库进行更新时,对缓存也进行更新。

这又引出了好几个问题了~

1. 缓存更新?是更新缓存还是直接删除缓存?

2. 如何保证数据库更新和缓存更新同时成功或失败?

3. 操作时应该先操作缓存还是先操作数据库?

第一个问题:

我想说,缓存更新如果是数据更新的话,每次更新数据库都要对缓存数据进行更新,有太多无效的读写操作,所以操作缓存时,选择删除缓存~

第二个问题:

要做到两个操作一致性,第一想到的就应该是事务。

解决方案:当我们是单体系统时,将缓存和数据库操作放在同一个事务里。

当我们是分布式系统时,利用TTC等分布式事务方案

最后一个问题:先操作数据库还是先操作缓存?

就只有下面两种情况:

1.先删除缓存,再操作数据库

2.先操作数据库,再删除缓存

我们可以两种情况都来看看~

第一种情况,先删除缓存的情况,我们想的正常的不会出现问题的操作流程(左边)和操作会出现问题的流程(右边)

补充一下:这边两个线程最初的数据库和缓存数据都假定为10~

出现问题的情况为我们线程1先删除缓存,线程2未命中缓存,直接查到数据库中数据为10并写入缓存中,而线程1在线程2后写入缓存后,把数据库更新成了20,这样最后数据库数据为20,而缓存中的数据为10,这就导致数据不一致了。

2.先操作数据库的情况

正确情况(左边)错误情况(右边),数据库和缓存最初数据也都还是10~

第二种先操作数据库的方式,它出现的错误情况主要是,线程1线程查询了缓存,未命中后去查询数据库的同时,线程2更新了数据库,并且删除了缓存,然后线程1才把数据库中查出来的10写入缓存,导致缓存为10,数据库为20。

终上所述,第一种失败的情况是比第二种失败的情况多的,因为第一种情况出现错误的时间是在删除缓存并更新数据库后,线程2有着充足的时间在这段时间内写入缓存,而对于第二种情况来说,出现问题发生在查完数据库到写入缓存这段时间内,这段时间几乎是毫秒级别的,线程2在这段时间内更新数据库并删除缓存,显然几率是很低的(除了高高高并发的状况下),所以我们选择先操作数据库在操作缓存~

总结:

缓存更新策略的最佳实践方案:

1.低一致性需求:使用Redis自带的内存淘汰机制

2.高一致性需求:主动更新,并以超时剔除作为兜底方案

主动更新时

进行读操作:

  • 缓存命中则直接返回
  • 缓存未命中则查询数据库,并写入缓存,设定超时时间

进行写操作:

  • 先写数据库,然后再删除缓存
  • 要确保数据库与缓存操作的原子性

3.3 主动更新策略练习

修改商品缓存

1. 根据id查询店铺时,如果缓存未命中,则查询数据库,将数据库结果写入缓存,并设置超时时间

2. 根据id修改店铺时,先修改数据库,再删除缓存

实现代码如下(Service层实现):

package com.hmdp.service.impl;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
import static com.hmdp.utils.RedisConstants.CACHE_SHOP_KEY;
import static com.hmdp.utils.RedisConstants.CACHE_SHOP_TTL;
/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public Result queryShopById(Long id) {
        //1.去redis中查询商品是否存在
        String key = CACHE_SHOP_KEY+id;
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(shopJson)){
            //2.存在,直接返回给用户
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }
        //3.不存在,带着id去数据库查询是否存在商品
        Shop shop = getById(id);
        if (shop == null){
            //4.不存在,返回错误信息
            Result.fail("商品信息不存在!");
        }
        //5.存在,存入redis(并设置超时时间)
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
        //6.返回商品信息
        return Result.ok(shop);
    }
    @Override
    @Transactional
    public Result updateShop(Shop shop) {
        //1.先更新数据库
        updateById(shop);
        //2.删除redis缓存
        String key = CACHE_SHOP_KEY+shop.getId();
        stringRedisTemplate.delete(key);
        return null;
    }
}

这样就能达到基本的主动缓存更新啦~

4 缓存穿透及其解决方案

4.1 缓存穿透的概念

缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库。

通俗的说,就是请求的数据在数据库和缓存中都没有,最后请求都到了数据库。这个时候,如果有一个恶意的程序员,通过某种方式不断请求一个不存在的数据,这样就会给数据库带来巨大的压力。(就怕用户懂代码)

4.2 解决方案及实现

常见的缓存穿透的解决方案有两种:

1.就是给redis缓存一个空对象并设置TTL存活时间

这种方式优点在于实现简单,维护方便,但也带来了额外的内存消耗和可能的短期的数据不一致。

小知识:这里的数据不一致发生在用户刚从redis中拿到null值恰好数据插入了这个请求需要的值而导致的数据库Redis数据不一致。

2. 就是利用布隆过滤

通俗的说,就是中间件~

这种方式 优点在于内存消耗较小,没有多余的key,缺点就在于实现复杂,而且布隆过滤器有误判的可能...

代码实现:这里实现第一种

就拿上述的写入商品信息为例:

我们只需要在数据库获取数据时,如果取到空值,不直接返回404,而是将空值也存入redis中,并且在判断缓存是否命中时,判断命中的值是不是我们传入的空值。如果是,直接结束,不是就返回商铺信息

package com.hmdp.service.impl;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
import static com.hmdp.utils.RedisConstants.*;
/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public Result queryShopById(Long id) {
        //1.去redis中查询商品是否存在
        String key = CACHE_SHOP_KEY+id;
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(shopJson)){
            //2.存在,直接返回给用户
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }
        //判断命中的是否为空字符串
        /**
         * (这里重点讲一下为什么是不等于:因为我们获取到的shopJson为null是,上面的isNotBlank方法会返回false,导致成为了不命中的效果)
         */
        if (shopJson != null){
            return Result.fail("商品信息不存在!");
        }
        //3.不存在,带着id去数据库查询是否存在商品
        Shop shop = getById(id);
        if (shop == null){
            // 4.将空值存入redis
            stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
            //5.不存在,返回错误信息
            Result.fail("商品信息不存在!");
        }
        //6.存在,存入redis(并设置超时时间)
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
        //7.返回商品信息
        return Result.ok(shop);
    }
}

总结:

缓存穿透产生的原因是什么?

用户请求的数据在缓存中和数据库中都不存在,不断发起这样的请求,给数据库带来巨大压力。

缓存穿透的解决方案有哪些?(3-6点为扩展~)

1.缓存null值

2.布隆过滤

3.增强id的复杂度,避免被猜测id规律

4.做好数据的基础格式校验

5.加强用户权限校验

6.做好热点参数的限流

5 缓存雪崩的概念及其解决方案

缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。

就是说,一群设置了有效期的key同时消失了,或者说redis罢工了,导致所有的或者说大量的请求会给数据库带来巨大压力叫做缓存雪崩~

解决方式也比较的简单~

1. 给不同的key添加随机的TTL存活时间(这种就是最简单的,设置存货时间随机各不相同)

2. 利用Redis集群(这种针对与Redis出现宕机的情况)

3. 给缓存业务添加降级限流策略(SpringCloud知识点)

4. 给业务添加多级缓存

6 缓存击穿及解决方案

6.1 什么是缓存击穿

缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。

大概的奔溃流程是这样子的:

第一个线程,查询redis发现未命中,然后去数据库查询并重建缓存,这个时候因为在缓存重建业务较为复杂的情况下,重建时间较久,又因为高并发的环境下,在线程1重建缓存的时间内,会有其他的大量的其他线程进来,发现查找缓存仍未命中,导致继续重建,如此死循环。

6.2 缓存击穿解决方法

常见的解决方案有两种:

6.2.1 互斥锁

互斥锁的解决方案如下:

就是当线程查询缓存未命中时,尝试去获取互斥锁,然后在重建缓存数据,在这段时间里,其他线程也会去尝试获取互斥锁,如果失败就休眠一段时间,并继续,不断重试,等到数据重建成功,其他线程就可以命中数据了。这样就不会导致缓存击穿。这个方案数据一致性是绝对的,但是相对来说会牺牲性能。

实现方法:

1.获取互斥锁和释放锁的实现:

这里我们获取互斥锁可以使用redis中string类型中的setnx方法 ,因为setnx方法是在key不存在的情况下才可以创建成功的,所以我们重建缓存时,使用setnx来将锁的数据加入到redis中,并且通过判断这个锁的key是否存在,如果存在就是获取锁成功,失败就是获取失败,这样刚好可以实现互斥锁的效果。

而释放锁就更简单了,直接删除我们存入的锁的key来释放锁。

    //获取锁
    public Boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }
    //释放锁方法
    public void unlock(String key){
        stringRedisTemplate.delete(key);
    }

2.实现代码:

具体操作流程如下图:

实现代码如下:

package com.hmdp.service.impl;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisData;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static com.hmdp.utils.RedisConstants.*;
/**
 * <p>
 *  服务实现类
 * </p>
 */
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public Result queryShopById(Long id) {
        //互斥锁缓存击穿
        Shop shop = queryWithMutex(id);
        if (shop == null){
            Result.fail("商品信息不存在!");
        }
        //8.返回商品信息
        return Result.ok(shop);
    }
    // 缓存穿透解决——互斥锁
    public Shop queryWithMutex(Long id){
        //1.去redis中查询商品是否存在
        String key = CACHE_SHOP_KEY+id;
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(shopJson)){
            //2.存在,直接返回给用户
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return shop;
        }
        //判断命中的是否为空字符串
        if (shopJson != null){
            return null;
        }
        Shop shop = null;
        String lockKey = LOCK_SHOP_KEY+id;
        try {
            //3.缓存重建
            //3.1尝试获取锁
            Boolean isLock = tryLock(lockKey);
            //3.2判断获取锁是否成功,如果休眠重试
            if (!isLock){
                //休眠
                Thread.sleep(50);
                //重试用递归
                queryWithMutex(id);
            }
            //3.3如果成功,去数据库查找数据
            shop = getById(id);
            if (shop == null){
                // 4.将空值存入redis
                stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
                //5.不存在,返回错误信息
                Result.fail("商品信息不存在!");
            }
            //6.存在,存入redis(并设置超时时间)
            stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            //7.释放锁
            unlock(lockKey);
        }
        //8.返回商品信息
        return shop;
    }
    //获取锁
    public Boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }
    //释放锁方法
    public void unlock(String key){
        stringRedisTemplate.delete(key);
    }
}

6.2.2 逻辑过期

逻辑过期的处理方法主要为:

给redis缓存字段中添加一个过期时间,然后当线程查询数据库的时候,先判断是否已经过期,如果过期,就获取获取互斥锁,并开启一个子线程进行缓存重建任务,直到子线程完成任务后,释放锁。在这段时间内,其他线程获取互斥锁失败后,并不是继续等待重试,而是直接返回旧数据。这个方法虽然性能较好,但也牺牲了数据一致性。

实现方法:

1.获取互斥锁和释放锁如上

2.给redis数据添加一个过期时间(创建一个RedisData类,并封装数据)

RedisData类:

package com.hmdp.utils;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class RedisData {
    private LocalDateTime expireTime;
    private Object data;
}
//给商品信息添加一个过期时间字段,并存入redis当中
    public void saveShop2Redis(Long id,Long expireSeconds){
        Shop shop = getById(id);
        RedisData redisData = new RedisData();
        redisData.setData(shop);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(redisData));
    }

然后就是业务实现了:

具体代码如下:

package com.hmdp.service.impl;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisData;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static com.hmdp.utils.RedisConstants.*;
/**
 * <p>
 *  服务实现类
 * </p>
 */
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public Result queryShopById(Long id) {
        //逻辑过期解决缓存击穿问题
//        Shop shop = queryWithLogicalExpire(id);
        if (shop == null){
            Result.fail("商品信息不存在!");
        }
        //8.返回商品信息
        return Result.ok(shop);
    }
    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
    public Shop queryWithLogicalExpire(Long id){
        //1.去redis中查询商品是否存在
        String key = CACHE_SHOP_KEY+id;
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否命中
        //3.未命中
        if (StrUtil.isBlank(shopJson)){
            return null;
        }
        //4.命中,先把redis中的数据反序列化成java对象
        RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
        //4.1获取过期时间
        LocalDateTime expireTime = redisData.getExpireTime();
        //4.2获取商品对象
        JSONObject data = (JSONObject) redisData.getData();
        Shop shop = JSONUtil.toBean(data, Shop.class);
        //5.判断是否过期
        if (expireTime.isAfter(LocalDateTime.now())){
            //未过期,直接返回shop
            return shop;
        }
        //6.过期,重建缓存
        //6.1尝试获取锁,并判断
        String lockKey = LOCK_SHOP_KEY + id;
        Boolean isLock = tryLock(lockKey);
        if (isLock){
            //5.2 如果成功,开启一个独立的线程,重建缓存
            CACHE_REBUILD_EXECUTOR.submit(()->{
                try {
                    //重建缓存
                    this.saveShop2Redis(id,20L);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    //释放锁
                    unlock(lockKey);
                }
            });
        }
        //6.2返回旧的商品信息
        return shop;
    }
    //获取锁
    public Boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }
    //释放锁方法
    public void unlock(String key){
        stringRedisTemplate.delete(key);
    }
    //给商品信息添加一个过期时间字段,并存入redis当中
    public void saveShop2Redis(Long id,Long expireSeconds){
        Shop shop = getById(id);
        RedisData redisData = new RedisData();
        redisData.setData(shop);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(redisData));
    }
}

缓存知识结束。

到此这篇关于Redis缓存实例超详细讲解的文章就介绍到这了,更多相关Redis缓存策略内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Redis缓存更新策略详解

    本文实例为大家分享了Redis缓存更新策略的具体代码,供大家参考,具体内容如下 一.缓存的收益与成本 1.1 收益 加速读写:因为缓存通常都是全内存的(例如Redis.Memcache),而存储层通常读写性能不够强悍(例如MySQL),内存读写的速度远远高于磁盘I/O.通过缓存的使用可以有效地加速读写,优化用户体验. 降低后端负载:帮助后端减少访问量(Mysql设置有最大连接数,如果大量的访问同时达到数据库,而磁盘I/O的速度又很慢,很容易造成最大连接数被使用完,但Redis 理论最大)和复杂计

  • Redis缓存策略超详细讲解

    目录 Redis缓存中间件 缓存是什么 缓存的优点 缓存的缺点 Redis缓存已查询数据 redis缓存中间件实践 缓存更新 缓存更新的三个策略 主动更新策略的三种方案 主动更新的代码实现 Redis缓存中间件 缓存是什么   所谓缓存就是数据交换的缓冲区(称作Cache [ kæʃ ] ),是一个临时存贮数据的地方,一般读写性能较高.CPU的运算速度要远远大于内存的读写速度,这样会使CPU花费很长时间等待数据从内存的获取或者写入,因此缓存的出现主要就是为了解决CPU运算速度与内存读写速度不匹配

  • 浅谈Redis缓存更新策略

      内存淘汰 超时剔除 主动更新 说明 不用自己维护,利用Redis的内存淘汰机制,当内存不足时自动淘汰部分数据.下次查询时更新缓存 给缓存数据添加TTL时间,到期后自动删除缓存,下次查询时更新缓存 编写业务逻辑,在修改数据的同时,更新缓存 一致性 差 一般 好 维护成本 无 低 高 业务场景需求: 在基本不会更新数据的情况下可以使用内存淘汰机制 在频繁更新数据的情况下可以使用主动更新,并以超时剔除作为兜底方案. 主动更新的三种方法 Cache Aside Pattern:由缓存的调用者,在更新

  • Redis三种常用的缓存读写策略步骤详解

    目录 一.Redis三种常用的缓存读写策略 二.旁路缓存模式(Cache Aside Pattern) 读写步骤 写: 读: 自我思考 缺点 三.读写穿透(Read/Write Through Pattern) 读写步骤 写: 读: 四.异步缓存写入(Write Behind Pattern) 一.Redis三种常用的缓存读写策略 Redis有三种读写策略分别是:旁路缓存模式策略.读写穿透策略.异步缓存写入策略. 这三种缓存读写策略各有优势,不存在最佳,需要我们根据实际的业务场景选择最合适的.

  • 浅谈Redis缓存有哪些淘汰策略

    目录 Redis过期策略 定时删除 惰性删除 定期删除 Redis的内存淘汰机制 LRU和LFU的区别 LRU LFU Redis重启如何恢复数据呢? 总结 Redis过期策略 我们首先来了解一下Redis的内存淘汰机制. 定时删除 概述     redis默认是每隔 100ms 就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除.注意这里是随机抽取的.为什么要随机呢?你想一想假如 redis 存了几十万个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会

  • 缓存替换策略及应用(以Redis、InnoDB为例)

    1 概述 在操作系统的页面管理中,内存会维护一部分数据以备进程使用,但是由于内存的大小必然是远远小于硬盘的,当某些进程访问到内存中没有的数据时,必然需要从硬盘中读进内存,所以迫于内存容量的压力下迫使操作系统将一些页换出,或者说踢出,而决定将哪些(个)页面踢出就是内存替换策略. 我们考虑内存中的页实际上是整个系统页的子集,所以内存可以当成系统中虚拟内存的缓存(Cache),所以页面置换算法就是缓存替换的方法. 一般意义下,选取页面置换算法即选取一个缓存命中率更高的或者说缺页率更低的算法,但实际上有

  • Redis缓存常用4种策略原理详解

    我们都知道,提高系统性能的最简单也最流行的方法之一其实就是使用缓存.我们引入缓存,相当于对数据进行了复制.每当系统数据更新时,保持缓存和数据源(如 MySQL 数据库)同步至关重要,当然,这也取决于系统本身的要求,看系统是否允许一定的数据延迟. 最常见的几种缓存策略.它们的优缺点以及使用场景,分别是: Cache-Aside Read-Through Write-Through Write-Behind Cache-Aside 策略 Cache-Aside可能是最常用的缓存策略.在这种策略下,应

  • Redis 缓存淘汰策略和事务实现乐观锁详情

    目录 缓存淘汰策略 标题LRU原理 标题Redis缓存淘汰策略 设置最大缓存 淘汰策略 Redis事务 Redis事务介绍 MULTI EXEC DISCARD WATCH Redis 不支持事务回滚(为什么呢) Redis乐观锁 Redis乐观锁实现秒杀 缓存淘汰策略 标题LRU原理 LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”. 最常见的实现是使用一个链表保存缓存数据,

  • Redis缓存实例超详细讲解

    目录 1 前言 1.1 什么是缓存 1.2 缓存的作用及成本 1.3 Redis缓存模型 2 给商户信息添加缓存 3 缓存更新策略 3.1 更新策略介绍 3.2 主动更新策略 3.3 主动更新策略练习 4 缓存穿透及其解决方案 4.1 缓存穿透的概念 4.2 解决方案及实现 5 缓存雪崩的概念及其解决方案 6 缓存击穿及解决方案 6.1 什么是缓存击穿 6.2 缓存击穿解决方法 6.2.1 互斥锁 6.2.2 逻辑过期 1 前言 1.1 什么是缓存 缓存就是数据交换的缓冲区(称作Cache [

  • 四个实例超详细讲解Java 贪心和枚举的特点与使用

    目录 贪心: 枚举: 1.朴素枚举 2.状压枚举    算法题1: 示例 算法题2: 示例 算法题3:  示例1 示例2 算法题4:  示例1 笔试技巧:学会根据数据范围猜知识点          一般1s 时间限制的题目,时间复杂度能跑到 1e8 左右( python 和 java 会少一些,所以建议大家使用c/c++ 做笔试题). n 范围 20 以内: O(n*2^n) 状压搜索 /dfs 暴搜 n 范围 200 以内: O(n^3) 三维 dp n 范围 3000 以内: O(n^2)

  • 四个实例超详细讲解Java 贪心和枚举的特点与使用

    目录 贪心: 枚举: 1.朴素枚举 2.状压枚举 算法题1: 示例 算法题2: 示例 算法题3: 示例1 示例2 算法题4: 示例 笔试技巧:学会根据数据范围猜知识点 一般1s 时间限制的题目,时间复杂度能跑到 1e8 左右( python 和 java 会少一些,所以建议大家使用c/c++ 做笔试题). n 范围 20 以内: O(n*2^n) 状压搜索 /dfs 暴搜 n 范围 200 以内: O(n^3) 三维 dp n 范围 3000 以内: O(n^2) 二维 dp 背包 枚举 二维前

  • Redis超详细讲解高可用主从复制基础与哨兵模式方案

    目录 高可用基础---主从复制 主从复制的原理 主从复制配置 示例 1.创建Redis实例 2.连接数据库并设置主从复制 高可用方案---哨兵模式sentinel 哨兵模式简介 哨兵工作原理 哨兵故障修复原理 sentinel.conf配置讲解 哨兵模式的优点 哨兵模式的缺点 高可用基础---主从复制 Redis的复制功能是支持将多个数据库之间进行数据同步,主数据库可以进行读写操作.当主数据库数据发生改变时会自动同步到从数据库,从数据库一般是只读的,会接收注数据库同步过来的数据. 一个主数据库可

  • C语言超详细讲解栈与队列实现实例

    目录 1.思考-1 2.栈基本操作的实现 2.1 初始化栈 2.2 入栈 2.3 出栈 2.4 获取栈顶数据 2.5 获取栈中有效元素个数 2.6 判断栈是否为空 2.7 销毁栈 3.测试 3.1测试 3.2测试结果 4.思考-2 5.队列的基本操作实现 5.1 初始化队列 5.2 队尾入队列 5.3 队头出队列 5.4 队列中有效元素的个数 5.5 判断队列是否为空 5.6 获取队头数据 5.7 获取队尾的数据 5.8 销毁队列 6.测试 6.1测试 6.2 测试结果 1.思考-1 为什么栈用

  • 超详细讲解SpringBoot参数校验实例

    目录 使用传统方式的弊端 引入依赖 注解说明 一.对实体类进行校验 1.entity 2.controller 3.编写全局统一异常处理 二.针对单个参数进行校验 三.分组校验 1.entity 2.controller 四.自定义分组校验 1.entity 2.CustomSequenceProvider 3.controller 五.自定义校验 1.定义校验注解 2.实现注解 六.嵌套校验 七.快速失败 注意事项 总结 使用传统方式的弊端 public String addUser(User

  • 超详细讲解Java秒杀项目用户验证模块的实现

    目录 一.用户验证 1.在方法内添加请求与反应 2.cookie操作的封装 3.UserServiceImpl 4.跳转界面PathController 二.全局session 1.导入依赖 2.配置yml文件redis 3.开启虚拟机 三.自定义redis实现功能 1.新建RedisConfig文件 2.实现全局session 四.使用参数解析器 1.新建WebConfig文件 2.定义参数解析器 3.PathController 4.访问主界面得到相关信息: 接着上期内容超详细讲解Java秒

  • java反射超详细讲解

    目录 Java反射超详解✌ 1.反射基础 1.1Class类 1.2类加载 2.反射的使用 2.1Class对象的获取 2.2Constructor类及其用法 2.4Method类及其用法 Java反射超详解✌ 1.反射基础 Java反射机制是在程序的运行过程中,对于任何一个类,都能够知道它的所有属性和方法:对于任意一个对象,都能够知道它的任意属性和方法,这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制. Java反射机制主要提供以下这几个功能: 在运行时判断任意一个对象所属

  • 超详细讲解Java异常

    目录 一.Java异常架构与异常关键字 Java异常简介 Java异常架构 1.Throwable 2.Error(错误) 3.Exception(异常) 4.受检异常与非受检异常 Java异常关键字 二.Java异常处理 声明异常 抛出异常 捕获异常 如何选择异常类型 常见异常处理方式 1.直接抛出异常 2.封装异常再抛出 3.捕获异常 4.自定义异常 5.try-catch-finally 6.try-with-resource 三.Java异常常见面试题 1.Error 和 Excepti

  • PHP之深入学习Yii2缓存Cache组件详细讲解

    什么是缓存组件Cache 缓存是提升 Web 应用性能简便有效的方式. 通过将相对静态的数据存储到缓存并在收到请求时取回缓存, 应用程序便节省了每次重新生成这些数据所需的时间. 定义缓存组件 Yii2的缓存是通过组件Component实现的,在项目的配置文件中,配置components->cache实现对缓存组件的定义. 项目配置文件的路径为config/web.php. 页面缓存PageCache 作为网站来讲,Yii2的页面缓存非常便捷地将已经渲染完全的网页结果保存起来,并在一个缓存周期内不

随机推荐