赞
踩
(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; }
使用压测工具测试高并发下带来线程安全问题
我们看到同一个库存被使用了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开集群项目
发现又出现: 重复数字以及库存为负数。
可以使用: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();
- }
-
-
- }

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;
- }
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是, 缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
1.什么下会发生缓存雪崩:
[1]项目刚上线,缓存中没有任何数据
[2]缓存出现大量过期。
[3]redis宕机
2.解决办法:
1.上线前预先把一些热点数据放入缓存。
2.设置过期时间为散列值
3.搭建redis集群
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
缓存击穿解决方案:
1.设置永久不过期。【这种只适合内存】
2.使用互斥锁(mutex key)业界比较常用的做法。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。