在前三篇我们几乎说完了Redis的所有的基础知识以及Redis怎么实现高可用性,那么在这一篇文章中的话我们主要就是说明如果我们使用Redis出现什么问题以及解决方案是什么,这个如果在未来的工作中也有可能会遇到,希望对看这篇博客的人有帮助,话不多说直接开干
一.Hotkey以及BigKey
Hotkey:
HotKey就是说在一段时间范围之内的访问频率特别高,所以我们常说缓存击穿的问题(不清楚的话可以看Redis持久化策略以及三大缓存问题-CSDN博客)的话几乎就是因为Hotkey所导致的问题常见的场景: 热点新闻 判断是否为Hotkey就是根据访问频率 发现的途径:1.会有第三方监控平台进行监控 2.就是使用命令 3.就是使用二级缓存也就是说JVM的缓存(Caffine) 如果实在不行的话那么就会搭建集群
//这个就能展现出所有的key的访问的次数
redis-cli -p 6379 --hotkeys
Bigkey:
BigKey就是说以string为数据类型的话那么就是说value的值占用的空间超过512M那么或者如果是Zset数据类型的话那么就是说value的数目过多,所以的话Bigkey占用的内存空间就是特别大,那么我们都知道Redis是基于内存那么如果出现BigKey肯定对其中的一些方面产生一些影响:如AOF文件的重写因为AOF重写的话是需要进行写时复制的那么占用内存更大以及生成的RDB数据快照的话生成的文件也是比较大等等如果不知道AOF以及RDB的话那么可以看我的Hotkey的那个博客 解决方案: 1.可以将Bigkey进行拆分若干个smallKey 2.如果想要删除的话那么要进行异步以及批量进行删除
二.怎么保证数据库和缓存数据的一致性问题
对于数据一致性问题的话这个是一个非常常见的问题除了这个场景的话还有就是Mysql进行读写分离的时候肯定也会遇到怎么保证主数据库和从数据库之间的数据一致性的问题,所以这个问题也是比较头疼的但是这个有一个特别的说明: 就是没有系统可以保证数据的实时的一致性,所以有一段时间的数据不一致的话那么是可以接受的
解决方案: cannel+旁路缓存: cannel中间件的话其实就是为了监听Mysql的binlog日志,因为如果数据库里面的数据发生变化的话那么binnlog日志里面的数据肯定也会发生变化那么这个时候我们的cannel中间件监听到之后这个时候就会告诉我们客户端然后就会对缓存中的数据进行一个删除的操作 旁路缓存: 这个就是说要从读以及写的方面进行说明:
读角度: 读的话那么客户端请求的时候肯定是从缓存中进行数据的读取如果缓存中没有的话那么肯定是从数据库中进行数据的读取那么除了读之外的话就会将读取的数据加载到缓存中 注意: 从缓存中读取数据的时候要先获得分布式锁
写角度: 当进行数据修改的时候我们是先修改数据库当中的数据然后再删除缓存中的数据,那么这个时候就是又会有问题: 1.为什么不是修改缓存中的数据而是删除 2.为什么不是先改变缓存中的数据而是先改变数据库当中的数据 对于第一个问题:因为对于修改操作的话那么会有一定的时序性(有可能就是因为网络的问题导致修改和预期的结果不一致)而且删除更加轻量级
对于第二个问题:因为先修改(删除)缓存数据的话如果在修改数据库的时候出现网络问题导致修改数据库失败那么后面的请求就会读到旧的数据而且缓存中的数据就是以数据库的数据为根本所以肯定是先修改数据库中的数据
三.Redis分布式锁的应用以及实现原理
对于分布式锁的话其实一点都不陌生如:我们常听的秒杀或者防止超卖的场景,但是对于分布锁很多人只是停留在理论的基础之上以及只知道这个概念但是不知道分布式锁的底层原理如怎么实现互斥性以及实现可重入性的下面的就会进行说明
首先我们先说Redis分布式锁的实现方式: 首先我们肯定就是想到的就是原生锁
// NX 表示只有当键不存在时才设置键,EX 设置键的过期时间 这个主要就是为了解决死锁问题
SET RedislockName NX EX second
但是我们都知道如果设置过期时间的话那么如果到了过期时间的话但是我的线程执行的业务逻辑还没有执行完成这个时候如果释放锁的话那么就会产生巨大的问题,所以后来的话我们就使用Redission分布式锁
Redisson分布式锁:
优点:这个相对于原生锁的话就是添加了一个看门狗(watchDog)机制也就是说会自动续约,这个看门狗机制就是说默认的条件下就是每隔10s就会检查key是否要过期了如果要过期的话那么就会进行续约,但是这个的话就是会有一个疑问: 假如我续约完成之后由于一些原因导致线程暂停那么这个时候不是会一直进行续约吗那么这个不就会产生一定的问题 回答: 会有一个心跳的检测功能,如果发生心跳在一段时间范围之内没有接收到回应的话那么就会自动释放分布式锁
实现步骤以及应用:
1.引入相关的依赖
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>2.7.0</version>
</dependency>
2.进行配置redisclient:
@Configuration
@Slf4j
public class redisConfig {private static final Logger logger = LoggerFactory.getLogger(redisConfig.class);@Beanpublic RedissonClient createClient() {Config config = new Config();config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(4);return Redisson.create(config);}
3.获得以及释放锁实现相关的业务逻辑
线程实现的业务逻辑代码:
3.1. 在获得锁的时候添加持有锁的的参数(方案1)
public void run() {RLock lock = redissonClient.getLock("lockName");boolean isLock = false;try {//3就是代表最多等待获得锁的时间//2就是代表的就是持有锁的时间isLock = lock.tryLock(3,2,TimeUnit.SECONDS); if (isLock) {System.out.println("线程 " + threadId + " 获取到了锁");// 模拟业务逻辑System.out.println("线程 " + threadId + " 执行业务逻辑...");Thread.sleep(2000); // 模拟长时间的业务处理} else {System.out.println("线程 " + threadId + " 未能获取到锁");}} catch (InterruptedException e) {System.err.println("线程 " + threadId + " 被中断");} finally {// 释放锁if (isLock) {lock.unlock();System.out.println("线程 " + threadId + " 释放了锁");}}}
3.1.2.线程进行trylock的时候不添加持有锁的时间(方案2)
isLock = lock.tryLock(3,TimeUnit.SECONDS);
3.2.测试代码
3.3.测试结果
方案1:
不难看出来这个是会报错的这个错误的意思就是说 当一个线程试图解锁它没有锁住的对象时抛出的异常 那么就说明锁已经释放了并且被别的线程获得了那么看门狗机制不生效了
Exception in thread "Thread-13" java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: eec734cc-03c5-4879-adf6-f981e58c0809 thread-id: 89
方案2:
这个和方案1不一样那么就说明看门狗机制,因为线程4获得锁之后业务逻辑没有执行完成那么这个时候不会自动释放锁的因为其他获得线程等待时间到了之后就获得不了锁了
特别说明: 使用分布式锁的中trylock方法的时候不要添加持有锁的时间