Redis
redis是一个基于内存的key-value数据库,主要可以用于数据库存储数据,数据持久化,缓存,集群。
Redis4.0之前是单线程,所以没有线程安全问题;
Redis4.0之后提供了多线程,仅针对于后台一些功能【主从复制、持久化.......】;
Redis6.0之后提供了多线程,仅针对于网络传输
Redis的多线程默认是关闭的,除非达到瓶颈,否则不用开
1.Redis的五种数据类型及应用场景
值的五种类型:
String:key,value
Hash:key;key1,value1;key2,value2...
list:key,list 有序
set:key,list 无序,不重复
zset:key,score,list 。 Set是无序的集合,Zset是有序的集合。
应用场景:
-
String --- 验证码、分布式session、计数器incr(文章阅读数、点赞数,将值 + 1)、缓存热点数据
-
hash --- 购物车
-
list --- 队列
-
set --- 微信朋友圈
-
zset --- 排行榜
2.Redis持久化机制
redis中的数据全部保存在内存中,如果突然宕机,数据就会全部丢失.为了防止这种事情发生,我们需要一种机制保证在宕机发生之后,我们重启服务,内存中的数据还可以恢复,这就是redis的持久化机制. 对应产生的数据文件为dump.rdb
redis提供了三种持久化机制分别为RDB(Redis DataBase)方式,AOF(Append Only File)方式和混合模式.
持久化机制:
1.RDB(默认持久化模式):
会根据配置的规则进行定时快照备份;优点:
快照数据以二进制存储、文件小、恢复速度快;
缺点:
耗费时间,耗费性能,会丢失一定的数据,不可控
原理:
Redis 会在某一时刻触发并将内存中所有的数据以二进制的方式保存在一个 dump.rdb 文件里,这也是 Redis 默认的持久化方式 。
持久化策略:
redis持久化需要条件触发:可以修改conf文件中配置save的参数 “ save <seconds><changes>” 如果我配置成了 save 60 10000 ,那就是代表如果在 60s 内执行了 10000 条修改命令,就会触发RDB持久化 .
手动触发:客户端执行save命令
besave命令: Redis 是单线程的,因此 save 命令在做持久化的时候时候会阻塞其他命令的执行,属于同步执行 。bgsave 则会从主线程中另外 fork 出一个子线程进行持久化操作,从而不会阻塞其他命令的执行,属于异步执行
上面讲的自动触发持久化也是通过 bgsave 的方式来进行的
2.AOF(默认关闭):
AOF持久化就是Redis在持久化数据的时候会把修改数据的命令通过追加的方式保存到 aof 文件中,在宕机恢复的时候通过执行 aof 文件中的命令来恢复数据以日志的形式记录key发送变化的指令;
优点:
数据以命令的形式存储,数据不容易丢失。文件会重写,去除无用指令,优化数据最终得到的结果即可。
缺点:
AOF文件可能会被篡改,文件大、恢复速度慢
持久化策略:
appendfsync always: 每次有新命令追加到 AOF 文件时就执行一次 fsync,数据持久化非常慢,也非常安全
appendfsync everysec: 每秒 fsync 一次,足够快,并且在故障时也只会丢失 1 秒钟的数据,这个是redis默认的持久化策略
appendfsync no: 从不 fsync ,将数据交给操作系统来处理。更快,也更不安全的选择
如果在持久化的时候要想要在持久化的速度和数据安全之间来权衡的话,可以选择第二种
RDB+AOF:
AOF + aof文件在压缩时以RDB去存储(二进制存储)
不持久化
持久化:把数据保存在磁盘上
数据恢复:先恢复AOF,再补充RDB
3.混合模式(RDB+AOF 推荐使用)
3.Redis淘汰策略
Redis数据存储在内存,如果内存满了会触发淘汰策略。
淘汰策略
-
volatile-lru: 在内存不足时,Redis会在设置过了生存时间**的key**中干掉一个最近最少使用的key.key1:10m 只用1次 key2:10m只用了2次(建议使用这种)
-
allkeys-lru:在内存不足时,Redis会在全部的key中干掉一个最近最少使用的key。
-
volatile-lfu: 在内存不足时,Redis会在设置过了生存时间的key中于掉一个最少频次使用的key。使用10个key,key1用了2次 key2 用了1次
-
allkeys-lfu:在内存不足时,Redis会在全部的key中干掉一个最少频次使用的key
-
volatile-random: 在内存不足时,Redis会在设置过了生存时间的key中随机干掉一个。
-
allkeys-random:在内存不是时,Redis会在全部的key中随机干掉一个。
-
volatile-ttl:在内存不是时,Redis会在设置过了生存时间的Key中干掉一个剩余生存时间最少的key。
-
noeviction:在内存不足时,Reids对写操作进行报错,对读操作可以执行
4.Redis 过期键的删除策略
-
定时删除:在设置键的过期时间的同时,创建一个定时器 timer。让定时器在键的过期时间来临时,立即执行对键的删除操作。
-
惰性删除:放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期,就返回该键。
-
定期删除:每隔一段时间程序就对数据库进行一次检查,删除里面的过期键。至于要删除多少过期键,以及要检查多少个数据库,则由算法决定。
5.Redis的高可用性
主从模式:
从节点设置了主节点后,slave通过master节点建立socket连接,每隔1秒执行一次。连接成功后从节点会创建一个专门处理复制工作的事件处理器,用于命令传播以及接收RDB文件等。
职责:主节点负责写,从节点负责读
数据同步:
-
全量复制
-
增量辅助:通过offset 来定位需要复制后数据的位置
哨兵模式:
职责:
为了提升主从架构的可用性,用来监控主从架构是否可用
选举:
哨兵监控主节点宕机,通知其他哨兵,确认主节点宕机了,通知从节点选举
谁的数据多,谁当主节点;如果一样多,谁的数据新,谁当主节点
集群模式(cluster):
主节点负责写,从节点只负责数据的同步,会根据key进行hash运行,得到对应的槽位,把数据放进去
6.分布式锁
传统锁:
-
Synchronized 是 Jvm 中的关键字,没办法手动释放锁
-
Lock 是 Java 类库中的api,可以手动释放锁
分布式锁:
分布式锁的实际应用 --- 热点数据重建问题:
为什么要加锁?
热点数据因为其高访问量的缘故,为了提升性能,应该尽量减少对数据库的访问,不必每一次都直接查询数据库,可以使用redis作为缓存机制。当redis中没有热点数据时就访问数据库,若redis中存在热点数据,则直接从redis中获取即可。但存在特殊情况,如果在访问数据库之后,将数据存入redis之前,这时另一个进程抢走了cpu的执行权,此时redis中任然没有数据。那么此进程又将直接对数据库进行访问,这时就应该就整个进程加上一个锁。
为什么要加分布式锁?为什么不能用传统锁?
传统锁,synchronized锁只能作用于JVM之中,如果是集群部署,传统锁就失效了,这个时候就需要使用分式锁。
分布式锁使用实例
其中加入uuid,解决释放锁的问题,保证线程只释放自己线程上的锁。
7.Redis使用问题
1、缓存穿透
一般的缓存系统,都是按照key去缓存查询,如果不存在对用的value,就应该去后端系统查找(比如DB数据库)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。
解决:
对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert之后清理缓存。
对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该Bitmap过滤。比如:布隆过滤器
2、击穿
某一个热点数据的key突然过期,造成大量请求直达数据库
解决:
分布式锁、热点数据永不过期、限流
3、缓存雪崩
当缓存服务器重启或者大量缓存集中在某一时间段失效,这样在失效的时候,会给后端系统带来很大的压力,导致系统崩溃。
解决:随机过期时间、热点数据永不过期
8.缓存与数据库数据一致性
-
先更新数据库,再删除缓存,虽然会有短暂的不一致情况,但最终会一直的,最终会保证数据的一致性
-
加锁,把更新数据库,再删除缓存的操作当成一个原子性操作,给这个操作加锁,性能较差
-
延迟双删
-
基于mysql binlog日志进行异步更新缓存 --- canal
9.Redis的读写是单线程的,为什么那么快?
-
单线程,没有线程切换开销
-
纯内存操作
-
IO多路复用技术【NIO】,NIO是面向缓冲的非阻塞IO,多路复用是指同时使用不阻塞的方式处理多个客户端的请求。其中多路是指不同的客户请求,复用则指redis单线程
10.Redis做异步队列
使用List结构作为队列, rpush 生产消息,lpop 消费消息 。 pub/sub 主题订阅者模式,可以实现1:N 的消息队列。
11.Redis做延迟队列
使用 sortedset,拿时间戳作为score,消息内容作为 key 调用 zadd 来生产消息,消费者用 zrangebyscore 指令获取 N 秒之前的数据轮询进行处理。