当前位置:   article > 正文

redis的使用场景以及常见的面试题_redis分布式锁使用场景 面试题

redis分布式锁使用场景 面试题

1. redis的使用场景

1.1 作为缓存

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

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

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

安全系数低的数据

(3)使用redis作为缓存

  1. package com.lqh.qy151redisspringboot.service;
  2. import com.lqh.qy151redisspringboot.dao.DeptMapper;
  3. import com.lqh.qy151redisspringboot.entity.Dept;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.data.redis.core.RedisTemplate;
  6. import org.springframework.data.redis.core.ValueOperations;
  7. import org.springframework.stereotype.Service;
  8. import org.springframework.transaction.annotation.Transactional;
  9. import java.util.concurrent.TimeUnit;
  10. @Service
  11. public class DeptService {
  12. @Autowired
  13. private DeptMapper deptMapper;
  14. @Autowired
  15. private RedisTemplate redisTemplate;
  16. //业务代码
  17. public Dept findById(Integer id){
  18. ValueOperations forValue = redisTemplate.opsForValue();
  19. //查询缓存
  20. Object o = forValue.get("dept::" + id);
  21. //缓存命中
  22. if(o!=null){
  23. return (Dept) o;
  24. }
  25. Dept dept = deptMapper.selectById(id);
  26. if(dept!=null){
  27. //存入缓存中
  28. forValue.set("dept::"+id,dept,2, TimeUnit.HOURS);
  29. }
  30. return dept;
  31. }
  32. public int deleteById(Integer id){
  33. redisTemplate.delete("dept::"+id);
  34. int row = deptMapper.deleteById(id);
  35. return row;
  36. }
  37. public Dept insert(Dept dept){
  38. int insert = deptMapper.insert(dept);
  39. return dept;
  40. }
  41. public Dept update(Dept dept){
  42. ValueOperations forValue = redisTemplate.opsForValue();
  43. forValue.set("dept::"+dept.getId(),dept,2, TimeUnit.HOURS);
  44. int insert = deptMapper.updateById(dept);
  45. return dept;
  46. }
  47. }

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

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

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

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

(2)使用开启缓存注解

 (3)使用注解

  1. //业务代码
  2. //使用查询注解:cacheNames表示缓存的名称 key:唯一标志---dept::key
  3. //先从缓存中查看key为(cacheNames::key)是否存在,如果存在则不会执行方法体,如果不存在则执行方法体并把方法的返回值存入缓存中
  4. @Cacheable(cacheNames = {"dept"},key="#id")
  5. public Dept findById(Integer id){
  6. Dept dept = deptMapper.selectById(id);
  7. return dept;
  8. }
  9. //先删除缓存在执行方法体。
  10. @CacheEvict(cacheNames = {"dept"},key = "#id")
  11. public int deleteById(Integer id){
  12. int row = deptMapper.deleteById(id);
  13. return row;
  14. }
  15. //这个注释可以确保方法被执行,同时方法的返回值也被记录到缓存中,实现缓存与数据库的同步更新。
  16. @CachePut(cacheNames = "dept",key="#dept.id")
  17. public Dept update(Dept dept){
  18. int insert = deptMapper.updateById(dept);
  19. return dept;
  20. }

1.2 分布式锁

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

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

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

  1. package com.lqh.distrinctlock.service.impl;
  2. import com.lqh.distrinctlock.dao.ProductStockDao;
  3. import com.lqh.distrinctlock.service.ProductStockService;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.stereotype.Service;
  6. @Service
  7. public class ProductStockServiceImpl2 implements ProductStockService {
  8. @Autowired
  9. private ProductStockDao productStockDao;
  10. @Override
  11. public String decreaseStock(Integer productId) {
  12. synchronized (this) {
  13. //查看该商品的库存数量
  14. Integer stock = productStockDao.findStockByProductId(productId);
  15. if (stock > 0) {
  16. //修改库存每次-1
  17. productStockDao.updateStockByProductId(productId);
  18. System.out.println("扣减成功!剩余库存数:" + (stock - 1));
  19. return "success";
  20. } else {
  21. System.out.println("扣减失败!库存不足!");
  22. return "fail";
  23. }
  24. }
  25. }
  26. }

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

 使用idea开集群项目

 

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

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

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

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

使用:

(1)引入jar包

  1. <dependency>
  2. <groupId>org.redisson</groupId>
  3. <artifactId>redisson</artifactId>
  4. <version>3.13.4</version>
  5. </dependency>

(2)配置

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

(3)测试

  1. @Autowired
  2. private Redisson redisson;
  3. @Override
  4. public String decreaseStock(Integer productId) {
  5. //获取锁对象
  6. RLock lock = redisson.getLock("aaa::" + productId);
  7. try {
  8. lock.lock(30, TimeUnit.SECONDS);
  9. //查看该商品的库存数量
  10. Integer stock = productStockDao.findStockByProductId(productId);
  11. if (stock > 0) {
  12. //修改库存每次-1
  13. productStockDao.updateStockByProductId(productId);
  14. System.out.println("扣减成功!剩余库存数:" + (stock - 1));
  15. return "success";
  16. } else {
  17. System.out.println("扣减失败!库存不足!");
  18. return "fail";
  19. }
  20. } finally {
  21. lock.unlock();
  22. }
  23. }

3. redis中常见的面试题

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

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

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

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

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

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

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

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

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

3.4Redis 淘汰策略有哪些?

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/732615
推荐阅读
相关标签
  

闽ICP备14008679号