内存读写效率远高于磁盘读写,省去磁盘IO操作
Redis作为K-V键值对型的内存数据库,所有键值都是用字典来存储,即哈希表结构。哈希表的特性就有在O(1)时间复杂度就可以获取对应的值。
支持多种数据结构及编码,针对不通业务场景,都有相对应的数据结构和编码。
根据元素的数量,有一个阈值,小于阈值和大于阈值的编码不同。
核心思想:让单个线程去监视多个连接,某个连接就绪,就触发读写事件。即可以单个线程处理多个客户端连接,无需创建和维护过多的进程和线程。
Redis使用全局哈希表保存键值对。
哈希表相当于一个数组,每个数组元素称为一个哈希桶,每个哈系桶中保存了键值对的数据。
数据增加到一定阈值,数组扩容会导致数据发生移动,此时访问阻塞。
渐进式ReHash:把一次性大量拷贝(数组移动)的开销,分摊到多次请求中去处理。
Redis默认使用两张全局哈希表,开始插入数据时默认使用表1,此时表2并没有被分配空间。随着数据量增多,开始执行ReHash。
业务中,我们一般使用System.currentTimeMillis()或new Date()等方式获取系统的毫秒时间戳,每次获取都是一次系统调用(需要调用操作系统中对应的函数,涉及上下文切换),相对比较耗时。
而Redis由一个定时任务,毫秒更新一次缓存,获取时间都是从缓存中直接拿,不需要进行系统调用。
本质:数据库和Redis都不存在
场景:查询id为-1的数据
坏处:每次都需要查询数据库和redis,增加磁盘IO的压力
解决方案:
本质:数据库和Redis都存在,但redis都过期了
同一时间,缓存大面积失效,大量请求都直接去访问数据库
解决方案:
本质:单个key没有或过期,同一时间查询这同一条数据并发量过多
解决方案:
含义:访问频率较高的key
场景:秒杀、热点新闻等读多写少的场景,请求分片集中,比如固定的key落入同一台服务器。
解决方案:
Redis中同时使用了惰性过期和定期过期两种策略。
每隔100ms就随机抽取一定量key,检查和删除。同时获取key时,会检查一下是否过期,过期则删除。
隐患:同样可能定期删除,漏删了大量过期key,也没有走惰性删除,就会导致大量过期key堆积在内存。
volatile-lru:内存不足以写入新数据时,从设置了过期时间的key中使用LRU(不经常使用的)算法进行淘汰。
allkeys-lru:内存不足以写入新数据时,从所有key中使用LRU(不经常使用的)算法进行淘汰。
volatile-lfu:4.0版本新增,内存不足以写入新数据时,在设置了过期时间的key中使用LFU(最近最少使用)算法淘汰。
allkeys-lfu:4.0版本新增,内存不足以写入新数据时,所有key中使用LFU(最近最少使用)算法淘汰。
volatile-random:内存不足以写入新数据时,从设置了过期时间的key中,随机淘汰数据。
allkeys-random:内存不足以写入新数据时,从所有key中,随机淘汰数据。
volatile-ttl:内存不足以写入新数据时,从设置了过期时间的key中,根据过期时间进行淘汰,越早过期的越被优先提前淘汰。
noeviction:默认淘汰策略,当内存不足以容纳新写入数据时,新写入操作会报错。
名称 | 英文名 | 作用域 |
---|---|---|
字符串 | String | 缓存、计数器、分布式Session |
哈希 | Hash | 存放对象 |
列表 | list | 消息队列、文章列表 |
集合 | set | 标签、随机数、社交图谱 |
有序集合 | ZSET | 排行榜 |
Bitmaps | Bitmaps | 布隆过滤器 |
HyperLogLog | HyperLogLog | UV |
Geo | Geo | Redis3.2推出的,地理位置定位,用于存储地理位置信息,并对存储的信息进行操作 |
热点数据存放缓存,提升网页访问效率,降低DB压力,同时还提供了RDB和AOF等持久化机制。适用String数据类型和hash类型,存放对象使用hash类型。
销量排行榜、礼物排行榜、投票排行榜。Redis提供zset数据类型,能实现这些复杂的排行榜。适用list数据类型。
短视频播放数、网站浏览数等,需要实时,Redis天然支持计数功能。适用String数据类型。
使用Redis将用户Session集中管理,多个服务共享,适用String数据类型。
场景:同一个资源并发访问,如秒杀、下单减库存等场景。适用String数据类型。
点赞、粉丝、共同好友、喜好、推送、下拉刷新等功能。适用String数据类型和hash类型,存放对象使用hash类型。
标签适用于set数据类型,例如一个用户可能对娱乐、体育感兴趣,另一个用户可能对历史、新闻感兴趣,这些兴趣点就是标签。通过这些数据可以得到喜欢同一个标签的人,这些数据对于用户体验以及增强用户粘度比较重要。
排行榜适用于有序集合(ZSET)数据类型。
业务解耦、流量削峰及异步处理实时性低的业务,Redis提供了发布/订阅及阻塞队列功能,能实现一个简单的消息队列系统。适用list数据类型。
用于数据量上亿的场景下,例如几亿用户系统签到,去重登录次数统计,某用户是否在线状态等。此时不可能给每个用户一个key。
这里需要用到位操作,使用setbit、getbit、bitcount命令。
原理:redis内构一个足够长的数组,每个数组只能是0和1两个值,数组下标表示用户id,这个几亿长的大数组就可以通过下标和元素值(0和1)来构建一个记忆系统。
适用于BitMaps数据类型。
将数据写入磁盘,避免因进程退出而造成的数据丢失,下次重启时通过持久化文件恢复数据。
通过快照(内存中数据在某一时刻的状态记录)的方式实现持久化,根据快照的触发条件,将内存的数据快照写入磁盘,以二进制的压缩文件进行存储。
缺点:每隔一段时间触发持久化,数据安全性低。
以独立日志的方式记录每次写的命令,重启时重新执行AOF文件中的命令恢复数据
AOF重写机制:AOF文件的大小达到某个阈值时,会将其中指令进行压缩。(如果有对于某个key多次的变更指令,则仅保留最新的数据指令)。
优化:
缺点:AOF文件可能过大,性能较差
生产环境中一般采用两种持久化机制混合使用。
将内存中数据快照存储在AOF文件中(模拟RDB),后续再以AOF追加方式。
如果仅作为缓存使用,可以承受几分钟数据丢失,可以使用RDB,对主程序性能影响最小。
高可用:数据不能丢失(尽量减少丢失),保证Redis
问题:数据不一致。
原因:主从库网络延迟,从库接收到命令,但它正在执行阻塞性命令。
解决方法:保证网络通畅,监控主从库复制进度。
Redis从2.8开始提供哨兵机制。
由一个或多个哨兵实例组成哨兵系统,监控其他Redis节点的同时,哨兵实例之间也互相监控。
哨兵之间通过发布订阅机制组成集群。一主多从
缺点:无法实现在线扩容,并发压力受限于单个服务器的资源配置。
哨兵模式解决了自动切换主从的问题,但是没有解决在线扩容的问题。
本质:Redis Cluster实现了Redis的分布式存储,每个节点存储不同的数据,实现数据分片。
引入Slot槽实现数据分片,每个节点分配一个Slot区间,当我们存取Key的时候,Redis根据key计算得到Slot值,找到对应的节点进行读写。多主多从。
概念:
注意事项:
看门狗:开源框架Redisson,只要线程一个线程加锁成功,就会启动一个watch dog,每隔10秒检查一下锁是否释放,只要第一个线程还持有锁,就延长锁的失效时间,解决了锁过期但业务还没执行完的问题。
效率较低,代码耦合,不便维护。
先删缓存,在更新数据库,休眠一会儿,再次删除缓存,休眠期间可能出现脏数据。
因为延迟双删可能存在第二次缓存失败的情况,可能删除失败就多删几次包证成功。
异步删除、更新缓存,
6.0之前,Redis处理客户端请求时,读写socket、解析、执行都由一个顺序串行主线程处理,即单线程。
之前几乎不存在CPU成为瓶颈的情况,Redis主要受限于内存和网络。
6.0后,Redis还使用单线程模型处理客户度请求,只用多线程处理数据读写和协议解析,执行命令还使用单线程。
使用多线程主要是提升提升IO读写效率,主要针对大公司大项目。
本质:按照顺序串行化执行队列中的每个命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
顺序性、一次性、排他性执行一个队列中的一系列命令。
命令 | 描述 |
---|---|
EXEC | 执行所有事务块命令 |
DISCARD | 取消事务,放弃执行事务块所有命令 |
MULTI | 标记一个事务块,开启事务 |
UNWATCH | 取消WATCH命令对所有key的监视 |
WATCH | 监视key,如果在事务执行之前,该key被其他命令改动,那么事务将被打断 |
//构建redis连接
Jedis jedis = jedisPool.getResource();
//标记一个事务块,开启事务
Transaction transaction = jedis.multi();
try {
transaction.set("name1","江先进");
// int n = 10/0;
transaction.set("name2","张硕");
//执行事务
transaction.exec();
}catch (Exception e){
//取消事务
transaction.discard();
e.printStackTrace();
}
布隆过滤器可以应对缓存穿透问题
数据结构:一个很长的二进制向量和一组Hash映射函数组成。
作用:检索一个元素是否在一个集合中,空间查询效率比一般的算法要好的多。
缺点:有一定误识别率和删除困难。
原理:集合A中有n个元素,利用K个哈希散列函数,将A中每个元素映射到一个长度为a位的数组B中不同位置上。这些位置上的二进制数均设置为1。经过这个K个哈希散列函数映射后,发现其k个位置上的二进制数全部为1,这个元素很可能属于集合A。
本质:基于Key-Value存储结构的非关系型数据库
数据类型:提供5种基本数据类型,String、set、zset、list、Map。
性能:基于内存存储,并且在数据结构上做了大量优化,IO性能较好。
作用:作为应用与数据库之间的分布式缓存组件。
高可用:提供了主从复制、哨兵、以及集群方式实现高可用,集群中通过hash槽的方式实现了数据分片,进一步提升了性能。
redis将某一时刻的数据持久化到磁盘,是一种快照式的持久化方式,是以二进制压缩文件的形式存储的
持久化触发方式:
手动触发
save命令: 在客户端中执行 save 命令,就会触发 Redis 的持久化 ,但是会阻塞主线程命令的执行
bgsave命令:bgsave会 fork() 一个子进程来执行持久化,整个过程只有 fork() 子进程的时候有短暂的阻塞,子进程创建之后,redis的主进程就可以响应客户端的请求了
fork() 作用:复制一个与当前进程一样的进程,新进程所有的数据数值与原进程一致,但是是一个全新的进程,并作为原进程的子进程
一般情况父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。
自动触发
save m n:在m秒内,如果有n个键位发生变化,则自动触发持久化,参数 m 和 n 可以在 Redis 的配置文件中找到
eg:save 60 1 则表明在 60 秒内,至少有一个键发生改变,就会触发 RDB 持久化
当设置多个save m n的时候, 满足任意一个条件都会触发持久化
eg:save 60 10 save 600 1
60s 内如果有 10 次 Redis 键值发生改变,就会触发持久化;如果 60s 内 Redis 的键值改变次数少于 10 次,那么 Redis 就会判断 600s 内,Redis 的键值是否至少被修改了一次,如果满足则会触发持久化。
redis在进行数据持久化的时候,会将数据先写入一个临时文件中,等持久化完成后,会用这个临时文件替换上次持久化好的文件,因为这种特性,我们可以随时进行备份,因为快照文件总是完整的
如果需要进行大规模的数据恢复,并且对数据恢复的完整性要求不高,使用RDB比AOF更高效
RDB优点
RDB缺点
AOF,Append Only File,即只允许追加不允许改写的文件。
AOF方式是将执行过的指令记录下来,在数据恢复的时候按照从前到后的顺序将指令都执行一遍
通过配置redis.conf中appendonly yes 就可以开启AOF功能,如果有写的操作,redis就会把指令追加到AOF文件的末尾
默认的AOF持久化策略是每秒钟 fsync 一次(fsync是指把缓存中的写指令记录到磁盘),在这种情况下redis可以保持良好的性能,即使redis故障,也只会丢失最近一秒的数据
在追加日志时,如果遇到磁盘空间满等情况导致日志写入不完整,redis提供了 redis-check-aof 工具,可以进行日志修复
AOF这种追加指令的方式,会造成AOF文件越来越大,因此redis提供了AOF文件重写机制,当AOF文件的大小超过所设定的阈值的时候,redis就会对AOF的文件进行压缩,只保留可以恢复数据的最小指令集
eg:我们执行了100条指令,AOF文件就要存储100条指令,低效的,我们可以把这100条合并成少量的命令,这就是重写机制的原理
直接执行 BGREWRITEAOF 命令,redis就会生成一个全新的 AOF 文件,包括了可以恢复现有数据的最少的命令集
AOF优点
AOF缺点
同样数据规模的情况下,AOF文件比RDB文件大,而且AOF方式的恢复速度大于RDB的方式
修改被写坏的AOF文件
重启redis的时候,一般使用 AOF 来恢复数据,因为数据完整性更高,但是在大数据量的情况下,花费时间很长,redis4.0为了解决这个问题,采用了混合持久化方式
原理:将RDB的文件内容和增量的AOF文件存在一起,这里的AOF不再是全量的日志,而是自持久化开始到结束的这段时间发生的增量 AOF 日志,这部分AOF日志很小
在redis重启的时候,先加载 RDB 的内容,再执行 AOF 增量日志就可以代替之前的 AOF全量文件执行,效率提高
混合持久化执行流程:
redis配置文件中的 aof-use-rdb-preamble 参数开启混合持久化,默认开启是 yes ,混合持久化结合了 RDB 和AOF 的优点,在redis 5.0 之后默认开启的
混合持久化优点
混合持久化缺点
高可用:数据不丢失/ 尽量减少丢失(AOF和RDB),保证redis的可用性(不能单点部署)
为了实现高可用,通常是将redis数据库复制多个部署在不同的服务器上,即使其中一台挂了其他的也可以继续提供服务。redis实现高可用有三种模式:主从模式,哨兵模式,集群模式
主从概念
redis主从同步过程
第一阶段:主从库建立连接,协商同步
第二阶段:主库把数据发送到从库,从库收到数据后,完成本地加载
第三阶段:主库把新的写入操作发送到从库
redis主从可能产生的问题
主从数据不一致
如何解决数据不一致问题
全量同步,增量同步,指令同步:
全量同步:一般发生在第一次建立主从关系的时候,会把主库的所有数据都进行同步
增量同步:从库会定时发起同步,如果每次都把所有数据进行同步,性能消耗大且耗时,因此每次同步的时候只需要同步和上一次不同的数据就可以
指令同步:主库输入的命令会异步同步给从库
主从模式中,一旦主节点故障,需要人为更换主节点,同时还要通知客户端更换主节点地址,多数场景不能接受这种故障处理方式,因此redis2.8 开始正是提供了 Redis哨兵机制来处理这个问题
过滤和打分:在多个库中,先按照一定的筛选条件,把不符合条件的从库过滤掉,再按照一定规则,给剩下的从库逐个打分,将得分最高的选为新的从库
先判断从库状态,已下线的,直接过滤掉
如果从库网络不好,也会被过滤掉,参数 down-after- milliseconds 表示我们认定的主从库断连最大超时时间
过滤掉不合适的,给剩下的从库打分,按照三个规则来:
从库优先级越高,打分越高,优先级可以通过 slave-priority 配置,如果优先级一样,就选与旧主库复制最快的从库,如果优先级和复制进度一样,选从库ID号小的
一个哨兵标记主库为主观下线后,它会征求其他哨兵的意见,确认主库是否进去主观下线状态,如果足够多数量的数量的哨兵认为主库已经主观下线,主库就会被标记为客观下线。
主库被标记为客观下线后,标记的这个哨兵就会发起投票(这个过程成为Leader选举),在进行投票的这段时间,任何一个判断主库客观下线的哨兵都可以进行投票,最终票数最多的哨兵(也被称为Leader)执行主从切换。
想要称为Leader要满足两个条件
如果没有产生符合条件的Leader,等待一段时间(一般是哨兵故障转移超时时间的 2 倍),会重新进行选举
假设有三个哨兵,一个主库M1,两个从库 S1 和 S2
当哨兵检测到M1出现故障,就需要进行故障转移
假设选出哨兵3作为Leader,流程如下:
哨兵模式基于主从模式,可以自动切换主从,可用性高,但是每个节点存储的数据是一样的,浪费内存,还难以扩容,因此redis 3.0 之后加入了 Redis Cluster 集群(切片集群),也就是说每台redis服务器上存储的内容都不同,解决了扩容问题,它还可以保存大量数据,还提供了复制和故障转移功能