热点数据缓存:
为了把一些经常访问的数据,放入缓存中以减少对数据库的访问频率。从而减少数据库的压力,提高程序的性能。【内存中存储】成为缓存;
缓存适合存放的数据:
查询频率高且修改频率低
数据安全性低
作为缓存的组件:
redis组件
memory组件
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;查询
查询缓存中是否存在名称为cacheNames::key的值
如果存在则方法不会执行
如果不存在则执行方法体并把方法的返回结果放入缓存cacheNames::key
//Cacheable:表示查询时使用的注解。 //cacheNames:缓存的名称 //key:缓存的唯一表示值 @Cacheable(cacheNames ={ "clazz"}, key = "#id") @Override public Clazz getById(Integer id) { //查询数据库 Clazz clazz = clazzDao.selectById(id); return clazz; }修改
先执行方法体
把方法的返回结果放入缓存中
//CachePut:表示修改时使用的注解. //cacheNames:缓存的名称 //key:缓存的唯一表示值 @CachePut(cacheNames = "clazz", key = "#clazz.cid") public Clazz update(Clazz clazz) { //修改数据库 int i = clazzDao.updateById(clazz); return clazz; }删除
先执行方法体
把缓存中名称为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(); } } }原理:
加入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 + "的商品库存不足。"; } } }
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); } } }
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集群