关于Redis(热点数据缓存,分布式锁,缓存安全(穿透,击穿,雪崩));

热点数据缓存:

为了把一些经常访问的数据,放入缓存中以减少对数据库的访问频率。从而减少数据库的压力,提高程序的性能。【内存中存储】成为缓存;

缓存适合存放的数据:
  1. 查询频率高且修改频率低

  2. 数据安全性低

作为缓存的组件:
  1. redis组件

  2. memory组件

  3. ehcache组件

Redis实现缓存功能
涉及的内容:

spring缓存组件 Redis数据库 AOP面向切面编程

实现:
Spirng缓存组件
//开启缓存注解
@EnableCaching
public class CalassNameApplication {
 public static void main (String[] arge) {
     SpringApplication.run(CalssNameApplication.class,args);
 }
}
添加注解
@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;
}
实现缓存功能
//所在的类和注入
@Service
public class ClazzServiceImpl implements ClazzService {
​
    @Autowired
    private ClazzDao clazzDao;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
查询
  1. 查询缓存中是否存在名称为cacheNames::key的值

  2. 如果存在则方法不会执行

  3. 如果不存在则执行方法体并把方法的返回结果放入缓存cacheNames::key

//Cacheable:表示查询时使用的注解。
//cacheNames:缓存的名称
//key:缓存的唯一表示值
@Cacheable(cacheNames ={ "clazz"}, key = "#id")
@Override
public Clazz getById(Integer id) {
    //查询数据库
    Clazz clazz = clazzDao.selectById(id);
    return clazz;
}
修改
  1. 先执行方法体

  2. 把方法的返回结果放入缓存中

//CachePut:表示修改时使用的注解.
//cacheNames:缓存的名称
//key:缓存的唯一表示值
@CachePut(cacheNames = "clazz", key = "#clazz.cid")
public Clazz update(Clazz clazz) {
    //修改数据库
    int i = clazzDao.updateById(clazz);
    return clazz;
}
删除
  1. 先执行方法体

  2. 把缓存中名称为cacheNames::key的值删除

//CacheEvict:表示删除时使用的注解
//cacheNames:缓存的名称
//key:缓存的唯一表示值
@CacheEvict(cacheNames = "clazz", key = "#cid")
@Override
public int delete(Integer cid) {
    int i = clazzDao.deleteById(cid);
    return i;
}
原理:
@Service
public class ClazzServiceImpl implements ClazzService {
​
    @Autowired
    private ClazzDao clazzDao;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Override
    public Clazz getById(Integer id) {
        //1.查询redis缓存是否命中
        ValueOperations<String, Object> forValue = redisTemplate.opsForValue();
        Object o = forValue.get("clazz::" + id);
        //表示缓存命中
        if(o!=null){
            return (Clazz) o;
        }
        //查询数据库
        Clazz clazz = clazzDao.selectById(id);
        if(clazz!=null){
            forValue.set("clazz::" + id,clazz);
        }
        return clazz;
    }
​
    @Override
    public Clazz save(Clazz clazz) {
        int insert = clazzDao.insert(clazz);
        return clazz;
    }
​
    @Override
    public Clazz update(Clazz clazz) {
        //修改数据库
        int i = clazzDao.updateById(clazz);
        if(i>0){
            //修改缓存
            redisTemplate.opsForValue().set("clazz::"+clazz.getCid(),clazz);
        }
        return clazz;
    }
​
    @Override
    public int delete(Integer cid) {
        int i = clazzDao.deleteById(cid);
        if(i>0){
            //删除缓存
            redisTemplate.delete("clazz::"+cid);
        }
        return i;
    }
}

分布式锁:

自理解:

分布式:一个网站部署多台服务器

锁:保证线程安全(将并行改为串行)只允许同时只有一个可以访问;

分布式锁:通过共享的锁保证每台服务器都能共享到同一个实时数据(进行操作);

涉及内容:

syn和lock锁 nginx代理群 Redis数据库 看门狗redisson

使用工具:

Jmeter压测工具 nginx代理

Jmeter:

模拟高并发工具

使用:
分布式锁实现:
@Service
public class StockService {
​
 @Autowired
 private StockDao stockDao;
 @Autowired
 private RedissonClient redisson;
​
 //
 public String decrement(Integer productid) {
     RLock lock = redisson.getLock("product::" + productid);
     lock.lock();
     try {
         //根据id查询商品的库存: 提前预热到redis缓存中
         int num = stockDao.findById(productid);
         if (num > 0) {
             //修改库存---incr---定时器[redis  数据库同步]
             stockDao.update(productid);
             System.out.println("商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个");
             return "商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个";
         } else {
             System.out.println("商品编号为:" + productid + "的商品库存不足。");
             return "商品编号为:" + productid + "的商品库存不足。";
         }
     }finally {
         lock.unlock();
     }
 }
}
原理:
  1. 加入syn和lock锁;

    syn和lock虽然解决了并发问题,但是项目部署时可能要部署集群模式。

     public String decrement(Integer productid) {
            //根据id查询商品的库存
            int num = stockDao.findById(productid);
            synchronized (this) {
    ​
                if (num > 0) {
                    //修改库存
                    stockDao.update(productid);
                    System.out.println("商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个");
                    return "商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个";
                } else {
                    System.out.println("商品编号为:" + productid + "的商品库存不足。");
                    return "商品编号为:" + productid + "的商品库存不足。";
                }
            }
        }

  1. nginx集群部署并使用redis解决分布式锁文件;

    打开并使用nginx代理集群exe文件;

    nginx(详细操作见linux笔记内):
    代理集群使用
    配置文件:nginx/conf/nginx.conf
    @Service
    public class StockService {
    ​
        @Autowired
        private StockDao stockDao;
        @Autowired
        private StringRedisTemplate redisTemplate;
        //
        public String decrement(Integer productid) {
            ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
            //1.获取共享锁资源
            Boolean flag = opsForValue.setIfAbsent("product::" + productid, "1111", 30, TimeUnit.SECONDS);
            //表示获取锁成功
            if(flag) {
                try {
                    //根据id查询商品的库存
                    int num = stockDao.findById(productid);
                    if (num > 0) {
                        //修改库存
                        stockDao.update(productid);
                        System.out.println("商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个");
                        return "商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个";
                    } else {
                        System.out.println("商品编号为:" + productid + "的商品库存不足。");
                        return "商品编号为:" + productid + "的商品库存不足。";
                    }
                }finally {
                    //释放锁资源
                    redisTemplate.delete("product::"+productid);
                }
            }else{
                //休眠100毫秒 在继续抢锁
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                return decrement(productid);
            }
        }
    }

  1. redis超时问题[业务代码执行时间超过了上锁时间]

    使用第三方redisson插件
    <!--引入redisson依赖,看门狗-->
    <dependency>
     <groupId>org.redisson</groupId>
     <artifactId>redisson</artifactId>
     <version>3.24.3</version>
    </dependency>
    //配置文件
    @Configuration
    public class RedissonConfig {
    ​
     @Bean
     public RedissonClient redisson(){
         Config config = new Config();
    //        //连接的为redis集群
    //        config.useClusterServers()
    //                // use "rediss://" for SSL connection
    //                .addNodeAddress("redis://127.0.0.1:7181","","","")
    //        ;
         //连接单机
         config.useSingleServer().setAddress("redis://172.16.7.18:6379");
         RedissonClient redisson = Redisson.create(config);
         return redisson;
     }
    }
    代码实现:
    @Service
    public class StockService {
    ​
        @Autowired
        private StockDao stockDao;
        @Autowired
        private RedissonClient redisson;
    ​
        //
        public String decrement(Integer productid) {
            RLock lock = redisson.getLock("product::" + productid);
            lock.lock();
            try {
                //根据id查询商品的库存: 提前预热到redis缓存中
                int num = stockDao.findById(productid);
                if (num > 0) {
                    //修改库存---incr---定时器[redis  数据库同步]
                    stockDao.update(productid);
                    System.out.println("商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个");
                    return "商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个";
                } else {
                    System.out.println("商品编号为:" + productid + "的商品库存不足。");
                    return "商品编号为:" + productid + "的商品库存不足。";
                }
            }finally {
                lock.unlock();
            }
        }
    }

问题集:

使用StringRedisTemplate调用get方法获取map数据,使用String接出现

org.springframework.data.redis.RedisSystemException:Error in execution; nested exception is io.lettuce.core.RedisCommandExecutionException: WRONGTYPE Operation against a key holding the wrong kind of value

DefaultSerializer需要一个可序列化的有效负载,但接收到一个类型的对象

java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [com.day51.entity.MyEntity]

缓存安全:

什么是缓存穿透以及如何解决?

缓存穿透: 查询的数据在数据库中不存在缓存中也不存在,这时有人恶意访问这种数据,请求到达数据库。

解决方案 :

第一步:在controller层校验数据。对一些不合法的数据过滤掉.

第二步: 使用bloom布隆过滤器。

第三步: 存放一个空对象,并且设置过期时间不能超过5分钟。

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

缓存击穿: 数据库中存在,但是缓存中该数据过期了。这是有大量的请求访问该过期的数据。压力顶到数据库。

解决方案: [1]使用互斥锁 [2]设置永不过期。

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

缓存雪崩: 当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。

解决方案: [1] 设置散列的过期时间。 [2]预热数据 [3]搭建redis集群

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值