redis的使用场景以及常见的面试题

1. redis的使用场景

1.1 作为缓存

(1)数据存储在内存中,数据查询速度快。可以分摊数据库压力。

(2)什么样的数据适合放入缓存

查询频率比较高,修改频率比较低。

安全系数低的数据

(3)使用redis作为缓存

package com.lqh.qy151redisspringboot.service;

import com.lqh.qy151redisspringboot.dao.DeptMapper;
import com.lqh.qy151redisspringboot.entity.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.concurrent.TimeUnit;

@Service
public class DeptService {

    @Autowired
    private DeptMapper deptMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    //业务代码
    public Dept findById(Integer id){
        ValueOperations forValue = redisTemplate.opsForValue();
        //查询缓存
        Object o = forValue.get("dept::" + id);
        //缓存命中
        if(o!=null){
            return (Dept) o;
        }
        Dept dept = deptMapper.selectById(id);
        if(dept!=null){
            //存入缓存中
            forValue.set("dept::"+id,dept,2, TimeUnit.HOURS);
        }
        return dept;
    }

    public int deleteById(Integer id){
        redisTemplate.delete("dept::"+id);
        int row = deptMapper.deleteById(id);
        return row;
    }

    public Dept insert(Dept dept){
        int insert = deptMapper.insert(dept);
        return dept;
    }

    public Dept update(Dept dept){
        ValueOperations forValue = redisTemplate.opsForValue();
        forValue.set("dept::"+dept.getId(),dept,2, TimeUnit.HOURS);
        int insert = deptMapper.updateById(dept);
        return dept;
    }
}

查看的缓存: 前部分代码相同@before通知,后部分代码也相同后置通知。 我们可以AOP完成缓存代码和业务代码分离。

spring框架它应该也能想到。--使用注解即可完成。解析该注解。

(1)把缓存的配置类加入

 @Bean
 public CacheManager cacheManager(RedisConnectionFactory factory) {
     RedisSerializer<String> redisSerializer = new StringRedisSerializer();
     Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
     //解决查询缓存转换异常的问题
     ObjectMapper om = new ObjectMapper();
     om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
     om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
     jackson2JsonRedisSerializer.setObjectMapper(om);
     // 配置序列化(解决乱码的问题),过期时间600秒
     RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
             .entryTtl(Duration.ofSeconds(600)) //缓存过期10分钟 ---- 业务需求。
             .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))//设置key的序列化方式
             .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) //设置value的序列化
             .disableCachingNullValues();
     RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
             .cacheDefaults(config)
             .build();
     return cacheManager;
 }

(2)使用开启缓存注解

 (3)使用注解

//业务代码
 //使用查询注解:cacheNames表示缓存的名称 key:唯一标志---dept::key
 //先从缓存中查看key为(cacheNames::key)是否存在,如果存在则不会执行方法体,如果不存在则执行方法体并把方法的返回值存入缓存中
 @Cacheable(cacheNames = {"dept"},key="#id")
 public Dept findById(Integer id){
     Dept dept = deptMapper.selectById(id);
     return dept;
 }
//先删除缓存在执行方法体。
 @CacheEvict(cacheNames = {"dept"},key = "#id")
 public int deleteById(Integer id){
     int row = deptMapper.deleteById(id);
     return row;
 }

 //这个注释可以确保方法被执行,同时方法的返回值也被记录到缓存中,实现缓存与数据库的同步更新。
 @CachePut(cacheNames = "dept",key="#dept.id")
 public Dept update(Dept dept){
     int insert = deptMapper.updateById(dept);
     return dept;
 }

1.2 分布式锁

使用压测工具测试高并发下带来线程安全问题

 我们看到同一个库存被使用了n次。以及数据库中库存为负数。 线程安全问题导致。

1. 解决方案: 使用 synchronized 或者lock锁

package com.lqh.distrinctlock.service.impl;

import com.lqh.distrinctlock.dao.ProductStockDao;
import com.lqh.distrinctlock.service.ProductStockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class ProductStockServiceImpl2 implements ProductStockService {
    @Autowired
    private ProductStockDao productStockDao;

    @Override
    public  String decreaseStock(Integer productId) {
              synchronized (this) {
                  //查看该商品的库存数量
                  Integer stock = productStockDao.findStockByProductId(productId);
                  if (stock > 0) {
                      //修改库存每次-1
                      productStockDao.updateStockByProductId(productId);
                      System.out.println("扣减成功!剩余库存数:" + (stock - 1));
                      return "success";
                  } else {
                      System.out.println("扣减失败!库存不足!");
                      return "fail";
                  }
              }

    }
}

使用synchronized 或者lock锁 如果我们搭建了项目集群,那么该锁无效。

 使用idea开集群项目

 

 发现又出现: 重复数字以及库存为负数。

2. redis的解决分布式锁的bug

 可以使用:redission依赖,redission解决redis超时问题的原理

为持有锁的线程开启一个守护线程,守护线程会每隔10秒检查当前线程是否还持有锁,如果持有则延迟生存时间。

使用:

(1)引入jar包

  <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.13.4</version>
        </dependency>

(2)配置

         //获取redisson对象并交于spring容器管理
    @Bean
    public Redisson redisson(){
        Config config =new Config();
        config.useSingleServer().
                setAddress("redis://localhost:6379").
                //redis默认有16个数据库
                setDatabase(0);
        return (Redisson) Redisson.create(config);
    }

(3)测试

@Autowired
private Redisson redisson;
@Override
    public String decreaseStock(Integer productId) {
        //获取锁对象
        RLock lock = redisson.getLock("aaa::" + productId);
        try {
            lock.lock(30, TimeUnit.SECONDS);
            //查看该商品的库存数量
            Integer stock = productStockDao.findStockByProductId(productId);
            if (stock > 0) {
                //修改库存每次-1
                productStockDao.updateStockByProductId(productId);
                System.out.println("扣减成功!剩余库存数:" + (stock - 1));
                return "success";
            } else {
                System.out.println("扣减失败!库存不足!");
                return "fail";
            }
        } finally {
            lock.unlock();
        }


    }

3. redis中常见的面试题

3.1 什么是缓存穿透?怎么解决?

1. 数据库中没有该记录,缓存中也没有该记录,这时由人恶意大量访问这样的数据。这样就会导致该请求绕过缓存,直接访问数据,从而造成数据库压力过大。

2.解决办法:
   [1]在controller加数据校验。
   [2]我们可以在redis中存入一个空对象,而且要设置过期时间不能太长。超过5分钟
   [3]我们使用布隆过滤器。底层:有一个bitmap数组,里面存储了该表的所有id.

//伪代码
String get(String key) { //布隆过滤器钟存储的是数据库表钟对应的id
    String value = redis.get(key);  //先从缓存获取。  
    if (value  == null) { //缓存没有命中
        if(!bloomfilter.mightContain(key)){//查看布隆过滤器钟是否存在
            return null; 
        }else{
            value = db.get(key); //查询数据库
            redis.set(key, value); 
        }    
    }
    return value;
}

3.2 什么是缓存雪崩?如何解决?

缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是, 缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
1.什么下会发生缓存雪崩:
  [1]项目刚上线,缓存中没有任何数据
  [2]缓存出现大量过期。
  [3]redis宕机
  
2.解决办法: 
   1.上线前预先把一些热点数据放入缓存。
   2.设置过期时间为散列值
   3.搭建redis集群

3.3 什么是缓存击穿?如何解决?

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。

缓存击穿解决方案:
1.设置永久不过期。【这种只适合内存】
2.使用互斥锁(mutex key)业界比较常用的做法。

3.4Redis 淘汰策略有哪些?

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值