文章目录
1. 非关系型Nosql数据库-
-
单线程
-
使用多路i/o,非阻塞式
2. 设置过期时间
EXPIRE 将key的生存时间设置为ttl秒
PEXPIRE 将key的生成时间设置为ttl毫秒
3. 优点-缺点
-
优点
- 读写性能强
- 支持持久化
- 支持事物
- 数据结构多
- 支持主从复制
-
缺点
- 容量受内存影响
- 不会自动恢复,需要重启
- 不支持在线扩容
4. 为什么使用
- 高性能 直接从内存中获取数据
- 高并发 能承受的请求数远远大于数据库的
5. 数据类型
-
字符串 string k-v 一个k对应一个value
- 场景: 存储验证码, 存储序列化后的对象,存储json
-
Hash 散列表 是一个键值对 k-filed-v 适合存储对象一个k对饮一个map
- 购物车数据,每个用户一个组, 每个商品一个key
- 无法存储大量对象
-
list 列表 一个k对应多个value,可重复,有序
- 秒杀中使用, 有数据限制
-
set 集合 一个k对应多个value,不可重复,无序
- 共同好友, 粉丝等
-
SortedSet 有序集合 有序,不能重复
- 搜索引擎搜索的数据
6. 持久化
-
rdb 默认的,按照一定时间或者操作后才能持久
- 二进制快照, 保存当前内存数据本身
- 新的快照覆盖之前的快照
-
aof 将每次写命令都记录到日志文件,aof要单独开启
- 保存的写命令
- 一直写会导致文件越来越大, aof拥有压缩功能, 即文件过大会合并命令, 只保存最终信息
两个都配置,优先加载aof
7. 架构模式
-
单机,启用及调用
-
主从复制: 将读库的压力转交给从库
-
哨兵模式:
- 监控: sentinel会一直监控你的主服务器和从服务器是否正常运行
- 提醒: 当某个服务器出问题, sentinel会通过api发送通知
- 自故障迁移: 当一个主不能工作,sentinel会将一个从库投票为一个主库
-
集群proxy
- redis-cluster
8. redis除了缓存还可以做什么
8.1 redis作为队列-异步
rpush 把数据插入列表list尾部,----发送消息-生产者
rpop 移除第一个元素,并返回----接收消息-消费者
这样就形成了一个队列
同步,发一个接一个
异步,一直发,然后慢慢接收
8.2 缓存淘汰策略
-
淘汰访问量最少的
-
淘汰过期时间短的
-
随机淘汰
-
先进先出淘汰
8.3 缓存预热
-
知道那些是热点数据, 提前将数据塞入缓存中
-
开发避免差集(在某个时间段某些数据访问频率高)
- 防止击穿, 配合加锁的方式
9. 缓存穿透
查询一个不存在的数据,缓存不命中,将去查询数据库,数据库也没有,那么每次查询不存在的数据都会去数据库中查询,导致缓存失去意义
解决:将null结果也进行缓存,并加入一个短暂的过期时间
10. 缓存雪崩
缓存中非常多的数据,在同一时间失效,那么所有请求都会转发到数据库,数据库压力过大,导致雪崩
解决:在原有的失效时间上加上一个随机的过期时间1-5分钟,这样就不会发生集体失效的情况
11. 缓存击穿
如有有个热点的数据,高并发的访问,如果大量的请求过来,他刚好失效,那么所有的查询,都会落到数据库
解决:给当前数据加锁,有一个请求查询到数据后,才让其他的请求查询这个数据
加锁:
-
在查询数据库的时候进行加锁,如果第一个请求查询到数据后,需要在这里在进行查询缓存一次-
-
在微服务,每一个业务不止一个服务,本地锁只能锁住当前服务的请求,需要添加分布式锁,锁住所有的服务
-
保证查数据库和放入缓存在同一把锁下,不然后导致第一个请求查到数据,还没有放入缓存的时候,第二个请求就开始查询缓存中的数据,导致数据库查询次数增加
12. 主从原理master-salve
- 全量同步,刚开启主从的时候,将master的所有数据同步到Slave
- 增量同步,主服务器发生的写操作,同步到从服务器
- 主要是读写分离, 主库写, 从库读
- 如果redis从节点断开, 那么同步机制会判断数据差值, 如果差值过大则全量同步一次, 如果差值小则使用增量同步
- 缺点: 如果主节点挂了, 则redis就不能使用了
13. 哨兵sentinel
当有一个主服务器宕机后, 哨兵会投票出一个从服务器当做主服务器,
如果主服务器恢复了,那么他就会变成一个从服务器
- 保证了主节点挂掉, redis还可以继续使用
- 缺点: 还是有写入瓶颈
14. 集群机制redis cluster
每个服务器都存储一部分的数据,降低了单服务器的压力
(1)自动将数据进行分片,每个master上放一部分数据
(2)提供内置的高可用支持,部分master不可用时,还是可以继续工作的
- 支持1684个哈希槽
- 缺点: 资源占用高, 成本高
能解决什么问题
- 大key: 拆分数据,防止资源倾斜
- 热key: 多级缓存
- 大热key: 通过jd-het工具发现它, 或者提前预判, (数据打散,多级缓存)
15. 脑裂问题1主3从
-
当主服务器1和从服务器的通讯断开,
-
哨兵与slave通讯正常,那么哨兵就会投票一个从服务器当做主服务器2
-
那么salve1连接master1,salve2连接master2 那么数据就不一致了
-
连接成功
16. 分布式锁 set k v nx ex (避免重复, 过期时间)
-
所有分布式服务去占一把锁
-
所有服务,用一个key在redis中存放一个值,要求没有这个值才能存放
-
redis实现:set key value nx
-
java实现:ops.setIfAbsent(key,value)
如果返回值是true锁门占锁成功,如果是false说明占锁不成功
占锁成功才执行查询数据库操作,执行完业务后就释放锁(删除redis中的key)
占锁失败,重试----休眠100ms后再次调用这个方法
-
-
注意:
-
如果在删除锁之前服务器宕机,其他服务就不能占锁,那么就会形成死锁,所以需要设置过期时间,
-
而且需要设置锁和设置过期时间需要在使用原子命令执行,防止设置锁后就宕机,还没有设置过期时间,一样会形成死锁:
-
*redis实现:set key value ex 300 nx
-
**java实现:ops.setIfAbsent(key,value,timeout过期时间,timeunit过期时间单位) **
-
-
业务超时,那么锁过期了,当前请求操作还没有完成,那么其他请求就会再次占到锁
-
在占锁的时候,指定value是唯一的uuid,然后在删锁的时候获取锁的值,如果和你的uuid一致,就可以删锁,防止删除了别人的锁
-
获取值和删除需要是原子操作,防止你获取到值,还没有删锁的时候,锁过期了
需要使用redis脚本解锁 (加锁保证原子性,解锁也要保证原子性)
//保证加锁【占位+过期时间】和删除锁【判断+删除】的原子性。 String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; /*redis执行脚本,第一个参数:脚本和返回值类型 第二个参数:key的数组,将key存入数组,传入参数 第三个参数:value值 效果:先查询到数据,然后判断数据和你的uuid是否一致,一致则删除数据 */ redisTemplate.execute(new DefaultRedisScript<Longr>(script,Long.class),Arrays.asList("lock"),uuid)
-
锁的自动续期
-
删锁,使用try-finally,不论业务执行怎么样,最终都执行解锁
-
-
17. 分布式锁框架Redisson
-
redisson
引入依赖
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.16.0</version> </dependency>
-
配置reidsson
https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95
-
可重入锁
方法A调用方法B,B方法要和A方法持有同一把锁,B看到A加了锁,直接拿来用就可以了
设置所有锁都是可重入锁,避免死锁问题
-
自旋锁
没有获取到锁的线程就一直循环等待判断该资源是否已经释放锁,这种锁叫做自旋锁,它不用将线程阻塞起来(NON-BLOCKING)
finally,再次调用当前方法,实现自旋
-
redisson实现juc中的锁,可以直接使用本地锁,实现redisson的锁功能,不需要重新学习
17.1 使用lock方法
//1.获取一把锁,只要锁的名字一样就是同一把锁
Rlock lock = redisson.getLock("lock");
//2.加锁,
lock.lock();//阻塞式等待,如果拿到锁,就执行下面的程序
//锁的时间会自动续期,如果业务超长,运行期间会自动上新30s,不用担心业务时间长,锁会自动过期
//加锁的业务只要运行完成,就不会给当前锁自动续期,即使不手动解锁,锁也会在30s后自动删除
try{
//执行业务代码
}finally{
//解锁,不论业务代码执行怎么样,都进行解锁
lock.unlock();
}
//只要占锁成功,就会启动一个定时任务,重新给锁设置过期时间,新的过期时间就是看门狗的默认时间
//internallLockLeaseTime【看门狗时间】/3
//每隔10秒就给锁上新30秒的过期时间
//最佳使用,不自动续期 设置30秒就可以,如果30秒都没有执行完任务,那么就等用户重新发送请求吧
lock.lock(30,TimeUnit.SECONDS);
//省掉了续期的过程,与上面lock.lock()不同,
## 使用tryLock
boolean flag=lock.tryLock(最多等待时间,上锁后多少时间解锁,时间单位);
//判断这个值是什么就可以了
17.2 公平锁 fair lock
Rlock fairLock = redisson.getFairLock("lock");
//使用方法和lock一致
fairLock.lock();
17.3 读写锁**
读写锁还是同一把锁,使用位置不同
-
改数据加写锁
//获取一把锁,名字相同就是同一把锁 RReadWriteLock lock == redisson.getReadWriteLock("lock"); //获取写锁 RLock wLock = lock.writeLock(); //加锁 wLock.lock(); //解锁 wLock.unlock();
-
读数据加读锁
//获取一把锁,名字相同就是同一把锁 RReadWriteLock lock == redisson.getReadWriteLock("lock"); //获取写锁 RLock rLock = lock.readLock(); //加锁 rLock.lock(); //解锁 rLock.unlock();
写完成写锁释放,才能进行读–保证一定能读到最新数据
写锁是一个排他锁(互斥锁,独享),写锁是一个共享锁
读+读:相当于无锁–并发锁,只会在redis中记录,所有的读都会加锁成功
写+读:等待写锁释放
写+写:阻塞方式
读+写:有读锁为释放,写也需要等待
只要有写的存在,都必须等待
17.4 闭锁
其他的锁都完成了,当前锁才释放
RContDownLatch door = redisson.getContDownLatch("door");//获取闭锁door.trySetCount(5);//闭锁有5个,都需要完成door.await();//等待闭锁都完成
RContDownLatch door = redisson.getContDownLatch("door");//获取闭锁door.countDown();//闭锁计数减一//当前方法执行5次,上面的锁才释放
17.5 信号量 Semaphore
模拟:车库停车3个车位,在redis中park存入数字3
RSemaphore park = redisson.getSemaphore("park");//获取锁//park.acquire();//获取一个信号量,获取一个值,占一个车位,会一直等待boolean b =park.tryAcquire();//获取到值,返回true,否则返回false
RSemaphore park = redisson.getSemaphore("park");//获取锁park.release();//释放一个信号量,释放一个车位
可以做限流,设置100万访问量, 当超出就必须要等待,等待其他的请求释放
18.缓存一致性
- 经常读的数据,不加入缓存,直接读数据库
- redis强一致性
- 加锁
- 事物
- MQ操作
19. redis单线程,多线程
- 单线程, 多线程6.0版本以后
- 多线程使用在命令的前置处理, 最终执行还是
20.redis扩容机制
- 类似hashMap的扩容机制
- 底层有两层链表, 重写进行hash运算, 数据重拍序