redis-黑马点评-商户查询缓存,Redis商户查询缓存的黑马点评

马肤

温馨提示:这篇文章已超过414天没有更新,请注意相关的内容是否还可用!

Redis作为内存数据库,在商户查询缓存方面表现出色。黑马点评利用Redis的快速响应和高效数据存储特性,实现商户信息的快速查询和缓存。Redis的存储结构能够支持高效的读写操作,提高查询效率,减少响应时间。Redis的持久化机制保证了数据的可靠性和安全性。黑马点评通过合理应用Redis,优化了商户查询功能,提升了用户体验。

缓存:cache

redis-黑马点评-商户查询缓存,Redis商户查询缓存的黑马点评 第1张

redis-黑马点评-商户查询缓存,Redis商户查询缓存的黑马点评 第2张

public Result queryById(Long id) {
        //根据id在redis中查询数据
        String s = redisTemplate.opsForValue().get(CACHE_SHOP_KEY +id);
        //判断是否存在
        if (!StrUtil.isBlank(s)) {
            //将字符串转为bean
            //存在,直接返回
            Shop shop = JSONUtil.toBean(s, Shop.class);
            return Result.ok(shop);
        }
        //不存在,查询数据库
        Shop shop = getById(id);
        if (shop==null){
            //不存在,返回404
            return Result.fail("店铺不存在");
        }
        //数据库中是否存在,存在则写入缓存,并返回
        redisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(shop));
        return Result.ok(shop);
    }

JSONUtil.toBean(s, Shop.class);

JSONUtil.toJsonStr(shop);

缓存更新策略:

redis-黑马点评-商户查询缓存,Redis商户查询缓存的黑马点评 第3张

redis-黑马点评-商户查询缓存,Redis商户查询缓存的黑马点评 第4张

先删除数据库后删除缓存的线程安全可能性低。

redis-黑马点评-商户查询缓存,Redis商户查询缓存的黑马点评 第5张

redis-黑马点评-商户查询缓存,Redis商户查询缓存的黑马点评 第6张

缓存穿透:

redis-黑马点评-商户查询缓存,Redis商户查询缓存的黑马点评 第7张

1.查询店铺在数据库和redis中都不存在时,写入空值到redis中

2.查询数据为空值时,直接返回不要查询数据库。

public Result queryById(Long id) {
    //根据id在redis中查询数据
    String s = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY +id);
    //判断是否存在
    if (!StrUtil.isBlank(s)) {
        //将字符串转为bean
        //存在,直接返回
        Shop shop = JSONUtil.toBean(s, Shop.class);
        return Result.ok(shop);
    }
    //判断是否店铺是否存在,缓存中的空数据
    if (s!=null){
        //返回空值
        return Result.fail("店铺信息不存在");
    }
    //不存在,查询数据库
    Shop shop = getById(id);
    if (shop==null){
        //不存在,返回404
        //缓存空值到数据库中
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
        return Result.fail("店铺不存在");
    }
    //数据库中是否存在,存在则写入缓存,并返回
    stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
    return Result.ok(shop);
}

redis-黑马点评-商户查询缓存,Redis商户查询缓存的黑马点评 第8张

redis-黑马点评-商户查询缓存,Redis商户查询缓存的黑马点评 第9张

redis-黑马点评-商户查询缓存,Redis商户查询缓存的黑马点评 第10张

redis-黑马点评-商户查询缓存,Redis商户查询缓存的黑马点评 第11张

redis-黑马点评-商户查询缓存,Redis商户查询缓存的黑马点评 第12张

互斥锁解决缓存击穿:

redis-黑马点评-商户查询缓存,Redis商户查询缓存的黑马点评 第13张

使用redis中的setnx实现互斥锁,如果key不存在则创建,存在则无法创建。

//使用redis中的setnx实现互斥锁。
    private boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        //潜在的NullReferenceException,如果装箱的对象是null,那么在拆箱时可能会抛出NullReferenceException
        return BooleanUtil.isTrue(flag);
    }
    private void unLock(String key){
        stringRedisTemplate.delete(key);
    }
拆箱和装箱操作可能会产生的问题:

1.性能开销:装箱和拆箱涉及内存分配和复制,这会增加额外的性能开销。

2.类型安全丢失:装箱操作会将值类型包装在一个对象中,这样原本在栈上的值类型数据现在位于堆中,可能导致类型安全检查丢失。

3.垃圾回收压力:装箱操作会创建新的对象实例,这可能增加垃圾回收器的压力,尤其是在频繁进行装箱和拆箱操作的情况下。

4.潜在的NullReferenceException,如果装箱的对象是null,那么在拆箱时可能会抛出NullReferenceException异常。

/**
     * 互斥锁解决缓存击穿
     * @param id
     * @return
     */
    private Shop queryWithMutex(Long id){
        //根据id在redis中查询数据
        String s = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY +id);
        //判断是否存在
        if (!StrUtil.isBlank(s)) {
            //将字符串转为bean
            //存在,直接返回
            Shop shop = JSONUtil.toBean(s, Shop.class);
            return shop;
        }
        //判断是否店铺是否存在
        if (s!=null){
            //返回空值
            return null;
        }
        //缓存重建
        //尝试获取互斥锁
        String lockKey= LOCK_SHOP_KEY+id;
        Shop shop = null;
        try {
            boolean isLock = tryLock(lockKey);
            //判断是否获取锁成功
            if (!isLock){
                //获取锁失败,休眠一会重试
                Thread.sleep(10);
                return queryWithMutex(id);
            }
            //获取锁成功,再次查询缓存中是否存在数据,如果存在,则不需要缓存重建
            s = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY +id);
            //判断是否存在
            if (!StrUtil.isBlank(s)) {
                //将字符串转为bean
                //存在,直接返回
                shop = JSONUtil.toBean(s, Shop.class);
                unLock(lockKey);
                return shop;
            }
            //判断是否店铺是否存在
            if (s!=null){
                //返回空值
                return null;
            }
            
            //缓存中还是没有数据,查询数据库
            //不存在,查询数据库
            shop = getById(id);
            if (shop==null){
                //不存在,返回404
                //缓存空值到数据库中
                stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
                return null;
            }
            //数据库中是否存在,存在则写入缓存,并返回
            stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            //释放锁
            unLock(lockKey);
        }
        return shop;
    }

逻辑过期解决缓存击穿:

redis-黑马点评-商户查询缓存,Redis商户查询缓存的黑马点评 第14张

创建一个redisData类,维护不同对象的过期时间:

@Data
public class RedisData {
    private LocalDateTime expireTime;
    private Object data;
}
 /**
     * 缓存预热将店铺数据存入redis中并且设置逻辑过期时间
     *
     */
    private void save2Redis(Long id,Long expireSeconds){
        //查询店铺信息
        Shop shop = getById(id);
        //封装RedisData
        RedisData redisData=new RedisData();
        redisData.setData(shop);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
        //保存数据到redis中
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(redisData));
    }
private Shop queryWithLogicExpire(Long id){
        //根据id在redis中查询数据
        String s = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY +id);
        //判断是否存在
        if (StrUtil.isBlank(s)) {
            //缓存未命中不存在,直接返回null
            return null;
        }
        //缓存命中,判断缓存是否过期
        RedisData redisData = JSONUtil.toBean(s, RedisData.class);
        JSONObject data = (JSONObject) redisData.getData();
        Shop shop = JSONUtil.toBean(data, Shop.class);
        LocalDateTime expireTime = redisData.getExpireTime();
        if (expireTime.isAfter(LocalDateTime.now())){
            //未过期
            return shop;
        }
        //过期,尝试获取互斥锁
        String lockKey= LOCK_SHOP_KEY+id;
        boolean isLock = tryLock(lockKey);
        if (isLock){
            //TODO 获取互斥锁成功,重新查询缓存是否过期,如果未过期,不用再缓存重建,如果过期,则重建缓存
            CACHE_REBUILD_EXECUTOR.submit(()->{
                try {
                    save2Redis(id,20L);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    unLock(lockKey);
                }
            });
        }
        //获取互斥锁失败,返回过期店铺信息
        return shop;
    }

封装Redis缓存工具类,包含四个方法:

redis-黑马点评-商户查询缓存,Redis商户查询缓存的黑马点评 第15张

@Component
@Slf4j
public class CacheClient {
    private final StringRedisTemplate stringRedisTemplate;
//    基于构造函数注入,不太使用,不太理解
    public CacheClient(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }
//缓存数据到redis中,并设置过期时间
    public void set(String key, Object value, Long time, TimeUnit unit){
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value),time,unit);
    }
//缓存数据到redis中,设置逻辑过期时间
    public void setLogicExpire(String key, Object value, Long time, TimeUnit unit){
        //设置逻辑过期
        RedisData redisData=new RedisData();
        redisData.setData(value);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
        //写入redis中
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData),time,unit);
    }
//缓存穿透解决方案,
    public   R queryWithPassThrough(String keyPrefix, ID id, Class type, Function dbFallback,Long time, TimeUnit unit){
        String key=keyPrefix+id;
        //根据id在redis中查询数据
        String json = stringRedisTemplate.opsForValue().get(key);
        //判断是否存在
        if (!StrUtil.isBlank(json)) {
            //将字符串转为bean
            //存在,直接返回
            R r= JSONUtil.toBean(json, type);
            return r;
        }
        //判断是否店铺是否存在
        if (json!=null){
            //返回空值
            return null;
        }
        //不存在,查询数据库
        R r = dbFallback.apply(id);
        if (r==null){
            //不存在,返回404
            //缓存空值到数据库中
            stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
            return null;
        }
        //数据库中是否存在,存在则写入缓存,并返回
        this.set(key,r,time,unit);
        return r;
    }
//缓存击穿解决方案:逻辑过期
    private static final ExecutorService CACHE_REBUILD_EXECUTOR= Executors.newFixedThreadPool(10);
    /**
     * 使用逻辑过期解决缓存击穿
     * @param id
     * @return
     */
    public   R queryWithLogicExpire(String keyPrefix, ID id, Class type, Function dbFallback,Long time, TimeUnit unit){
        //根据id在redis中查询数据
        String key=keyPrefix+id;
        String json = stringRedisTemplate.opsForValue().get(key);
        //判断是否存在
        if (StrUtil.isBlank(json)) {
            //缓存未命中不存在,直接返回null
            return null;
        }
        //缓存命中,判断缓存是否过期
        RedisData redisData = JSONUtil.toBean(json, RedisData.class);
        JSONObject data = (JSONObject) redisData.getData();
        R r=JSONUtil.toBean(data, type);
        LocalDateTime expireTime = redisData.getExpireTime();
        if (expireTime.isAfter(LocalDateTime.now())){
            //未过期
            return r;
        }
        //过期,尝试获取互斥锁
        String lockKey= LOCK_SHOP_KEY+id;
        boolean isLock = tryLock(lockKey);
        if (isLock){
            //TODO 获取互斥锁成功,重新查询缓存是否过期,如果未过期,不用再缓存重建,如果过期,则重建缓存
            CACHE_REBUILD_EXECUTOR.submit(()->{
                try {
                    //重建缓存
                    //查询数据库
                    R r1= dbFallback.apply(id);
                    //缓存数据
                    this.setLogicExpire(key,r1,time,unit);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    unLock(lockKey);
                }
            });
        }
        //获取互斥锁失败,返回过期店铺信息
        return r;
    }
    //使用redis中的setnx实现互斥锁。
    private boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        //潜在的NullReferenceException,如果装箱的对象是null,那么在拆箱时可能会抛出NullReferenceException
        return BooleanUtil.isTrue(flag);
    }
    private void unLock(String key){
        stringRedisTemplate.delete(key);
    }
}
总结:

redis-黑马点评-商户查询缓存,Redis商户查询缓存的黑马点评 第16张

redis-黑马点评-商户查询缓存,Redis商户查询缓存的黑马点评 第17张

redis-黑马点评-商户查询缓存,Redis商户查询缓存的黑马点评 第18张

redis-黑马点评-商户查询缓存,Redis商户查询缓存的黑马点评 第19张

Spring中bean的注入方式:

在Spring框架中,注入Bean(对象)的方式有多种,以下是一些常见的方法:

1. 构造器注入(Constructor Injection):

通过构造器注入依赖,可以确保在创建对象时,它所依赖的其他对象也被创建。

```

public class ExampleBean {

private final AnotherBean anotherBean;

@Autowired

public ExampleBean(AnotherBean anotherBean) {

this.anotherBean = anotherBean;

}

}

```

2. Setter方法注入(Setter Injection):

通过setter方法注入依赖,可以在对象创建后,再设置依赖的对象。

```java

public class ExampleBean {

private AnotherBean anotherBean;

@Autowired

public void setAnotherBean(AnotherBean anotherBean) {

this.anotherBean = anotherBean;

}

}

```

3. 字段注入(Field Injection):

通过字段注入依赖,直接在字段上使用`@Autowired`注解。

```java

public class ExampleBean {

@Autowired

private AnotherBean anotherBean;

}

```

4. 方法参数注入(Method Parameter Injection):

在方法参数上使用`@Autowired`注解,Spring会注入对应的依赖。

```java

public class ExampleBean {

public void doSomething(@Autowired AnotherBean anotherBean) {

// ...

}

}

```

5. 接口注入(Interface Injection):

通过定义一个接口来标记需要注入的Bean。

```java

public interface InjectedInterface {

void injectDependency(AnotherBean anotherBean);

}

public class ExampleBean implements InjectedInterface {

private AnotherBean anotherBean;

@Override

public void injectDependency(AnotherBean anotherBean) {

this.anotherBean = anotherBean;

}

}

```

6. 基于注解的注入(Annotation-based Injection):

使用`@Autowired`、`@Resource`、`@Inject`等注解来标记需要注入的依赖。

```java

public class ExampleBean {

@Autowired

private AnotherBean anotherBean;

}

```

7. 自动装配(Autowiring):

Spring可以自动装配依赖,无需显式注入。

```java

public class ExampleBean {

@Autowired

private AnotherBean anotherBean;

}

```

8. 基于Java配置的注入:

使用`@Configuration`和`@Bean`注解来定义和注入Bean。

```java

@Configuration

public class AppConfig {

@Bean

public ExampleBean exampleBean() {

return new ExampleBean(anotherBean());

}

@Bean

public AnotherBean anotherBean() {

return new AnotherBean();

}

}

```

选择哪种注入方式取决于你的具体需求和设计偏好。构造器注入和setter注入是最常用的方式,因为它们可以保证依赖的完整性和初始化的一致性。而字段注入和基于注解的注


0
收藏0
文章版权声明:除非注明,否则均为VPS857原创文章,转载或复制请以超链接形式并注明出处。

相关阅读

  • 【研发日记】Matlab/Simulink自动生成代码(二)——五种选择结构实现方法,Matlab/Simulink自动生成代码的五种选择结构实现方法(二),Matlab/Simulink自动生成代码的五种选择结构实现方法详解(二)
  • 超级好用的C++实用库之跨平台实用方法,跨平台实用方法的C++实用库超好用指南,C++跨平台实用库使用指南,超好用实用方法集合,C++跨平台实用库超好用指南,方法与技巧集合
  • 【动态规划】斐波那契数列模型(C++),斐波那契数列模型(C++实现与动态规划解析),斐波那契数列模型解析与C++实现(动态规划)
  • 【C++】,string类底层的模拟实现,C++中string类的模拟底层实现探究
  • uniapp 小程序实现微信授权登录(前端和后端),Uniapp小程序实现微信授权登录全流程(前端后端全攻略),Uniapp小程序微信授权登录全流程攻略,前端后端全指南
  • Vue脚手架的安装(保姆级教程),Vue脚手架保姆级安装教程,Vue脚手架保姆级安装指南,Vue脚手架保姆级安装指南,从零开始教你如何安装Vue脚手架
  • 如何在树莓派 Raspberry Pi中本地部署一个web站点并实现无公网IP远程访问,树莓派上本地部署Web站点及无公网IP远程访问指南,树莓派部署Web站点及无公网IP远程访问指南,本地部署与远程访问实践,树莓派部署Web站点及无公网IP远程访问实践指南,树莓派部署Web站点及无公网IP远程访问实践指南,本地部署与远程访问详解,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南。
  • vue2技术栈实现AI问答机器人功能(流式与非流式两种接口方法),Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法探究,Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法详解
  • 发表评论

    快捷回复:表情:
    评论列表 (暂无评论,0人围观)

    还没有评论,来说两句吧...

    目录[+]

    取消
    微信二维码
    微信二维码
    支付宝二维码