一.Redis简介
Remote Dictionary Server(远程字典服务)是完全开源的,使用ANSIC语言编写遵守BSD协议,是一个高性能的Key-Value数据库提供了丰富的数据结构,例如String、Hash、List、Set、SortedSet等等。数据是存在内存中的,同时Redis支持事务、持久化、LUA脚本、发布/订阅、缓存淘汰、流技术等多种功能特性提供了主从模式、Redis Sentinel和Redis Cluster集群架构方案
二.应用场景
常见的16种应用场景:
缓存、数据共享分布式、分布式锁、全局 ID、计数器、限流、位统计、购物车、用户消息时间线 timeline、消息队列、抽奖、点赞、签到、打卡、商品标签、商品筛选、用户关注、推荐模型、排行榜、游标分页
三.Redis数据类型
1.string:redis最基本的类型,一个key对应一个value,是二进制安全的,最大支持512MB
2.list:底层是一个双端链表,最大支持2^32-1个元素
3.hash:Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象,最多存储2^32-1键值对
4.set:Set 是 String 类型的无序集合,集合中最大的成员数为 2^32 - 1
5.zset:Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。但是有序zset的成员是唯一的,但分数(score)却可以重复。
6.geo::地理位置信息储存起来, 并对这些信息进行操作
7.HyperLogLog:HyperLogLog 是用来做基数统计的算法
8.bitmap:由0和1状态表现的二进制位的bit数组
9.stream:Redis Stream 是 Redis 5.0 版本新增加的流数据结构。
四.键的常用
1.例如
keys *:查看当前数据库的所有键
exists key:当前key是否存在
type key: 查看当前key的类型
del key: 删除指定的key
unlink key:非阻塞删除,仅仅是将keys从keyspace元数据中删除,真正的删除在后面的异步中操作
ttl key:查看key的过期时间
expire key:为当前key设置过期时间
dbsize:查看当前数据库key的个数
flushdb:清除当前库所有的key,阻塞式删除
flushall:清除所有库的键
五.五大基本数据类型
1.String
String类型是redis中的单key单value类型,是二进制安全的最大支持512M,redis 的 string 可以包含任何数据。如数字,字符串,jpg图片或者序列化的对象。
1.常用的指令
2.set命令
set key value [NX|XX] [GET] [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|KEEPTTL]
set命令使用 EX NX PX 参数时其效果等同于setex、setnx、setpx
3.实战场景
1.缓存:经典使用场景,把常用信息,字符串,图片或者视频等信息放到redis中,redis作为缓存层,mysql做持久化层,降低mysql的读写压力。
2.计数器:redis执行指令的时候是单线程模型,一个命令执行完才会执行下一个,同时数据可以一步落地到其他的数据源。
3.session: 常见方案spring session + redis实现session共享
4.点赞:原理是用到了INCR命令和redis执行指令的时候是单线程模型
2.List 列表
一个双端链表的结构,容量是2的32次方减1个元素,大概40多亿,主要功能有push/pop等,一般用在栈、队列、消息队列等场景。left、right都可以插入添加;它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差。
1. 使用场景
1.公众号订阅消息:我们关注的公众号发布消息后,就会出现我们的list列表当中
3.Hash
Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。
1.应用场景
1.缓存:相比于String,hash更适合存储对象的信息
4.set集合
Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
1.应用场景
1.微信抽奖随机抽奖
2.标签:给用户添加标签,或者用户给消息添加标签,这样有同一标签或者类似标签的可以给推荐关注的事或者关注的人。
3.点赞、收藏:
4.QQ内推
5.微信朋友圈点赞查看同赞的好友
5.zset
Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。
1.压缩列表:ziplist是为了提高效率而设计的一种特殊编码的双向链表。他可以存储字符串和整数,存储整数的时候存储的是二进制它能在O(1)的时间复杂度下完成list两端的push和pop操作。但是因为每次操作都需要重新分配ziplist的内存,所以实际复杂度和ziplist的内存使用量相关
2.跳跃表:跳跃表的性能可以保证在查找,删除,添加等操作的时候在对数期望时间内完成,这个性能是可以和平衡树来相比较的,而且在实现方面比平衡树要优雅,这是采用跳跃表的主要原因。跳跃表的复杂度是O(log(n))。
1.实战场景
1.排行榜:有序集合经典使用场景。例如小说视频等网站需要对用户上传的小说视频做排行榜,榜单可以按照用户关注数,更新时间,字数等打分,做排行。
2.游标分页:对于深翻页的场景,我们可以采用zset实现
六.三大特殊基本类型
1.BitMap
说明:用String类型作为底层数据结构实现的一种统计二值状态的数据类型,Bitmap 即位图数据结构,都是操作二进制位来进行记录,只有0 和 1 两个状态。位图本质是数组,它是基于String数据类型的按位的操作。该数组由多个二进制位组成,每个二进制位都对应一个偏移量(我们称之为一个索引)。Bitmap支持的最大位数是232位,它可以极大的节约存储空间,使用512M内存就可以存储多达42.9亿的字节信息(232 = 4294967296)
1.应用场景
1.签到
2.布隆过滤器
2.HyperLogLog
1.实战场景
1.UV基数统计:一个大型的网站,每天 IP 比如有 100 万,粗算一个 IP 消耗 15 字节,那么 100 万个 IP 就是 15M。而 HyperLogLog 在 Redis 中每个键占用的内容都是 12K,理论存储近似接近 2^64 个值,不管存储的内容是什么,它一个基于基数估算的算法,只能比较准确的估算出基数,可以使用少量固定的内存去存储并识别集合中的唯一元素。而且这个估算的基数并不一定准确,是一个带有 0.81% 标准错误的近似值(对于可以接受一定容错的业务场景,比如IP数统计,UV等,是可以忽略不计的)。
3.Geospatial
Redis 的 Geo 在 Redis 3.2 版本就推出了! 这个功能可以推算地理位置的信息: 两地之间的距离, 方圆几里的人
1.实战场景
1.美团酒店推送
2.高德地图附近的核酸检测站
七.Redis持久化
1.为什么需要持久化
Redis是个基于内存的数据库。那服务一旦宕机,内存中的数据将全部丢失。通常的解决方案是从后端数据库恢复这些数据,但后端数据库有性能瓶颈,如果是大数据量的恢复,1、会对数据库带来巨大的压力,2、数据库的性能不如Redis。导致程序响应慢。所以对Redis来说,实现数据的持久化,避免从后端数据库中恢复数据,是至关重要的。
2.持久化的方式
最关键的是目前官方文档上能够看到的Redis对持久化存储的支持明确的就只有两种方案(https://redis.io/topics/persistence):RDB和AOF。所以本文也只会具体介绍这两种持久化存储方案的工作特定和配置要点。
3.RDB持久化
RDB(Redis 数据库):RDB 持久性以指定的时间间隔执行数据集的时间点快照。
RDB 就是 Redis DataBase 的缩写,中文名为快照/内存快照,RDB持久化是把当前进程数据生成快照保存到磁盘上的过程,由于是某一时刻的快照,那么快照中的值要早于或者等于内存中的值。触发方式有两种:自动触发、手动触发
1.手动触发
1.save:会阻塞当前Redis服务器,直到RDB过程完成为止,对于内存 比较大的实例会造成长时间阻塞,线上环境不建议使用
2.bgsave:Redis进程执行fork操作创建子进程,RDB持久化过程由子 进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短
1.redis客户端执行bgsave命令或者自动触发bgsave命令;
2.主进程判断当前是否已经存在正在执行的子进程,如果存在,那么主进程直接返回;
3.如果不存在正在执行的子进程,那么就fork一个新的子进程进行持久化数据,fork过程是阻塞的,fork操作完成后主进程即可执行其他操作;
4.子进程先将数据写入到临时的rdb文件中,待快照数据写入完成后再原子替换旧的rdb文件;
5.同时发送信号给主进程,通知主进程rdb持久化完成,主进程更新相关的统计信息(info Persitence下的rdb_*相关选项)。# 自动触发
2.自动触发
1.redis.conf中配置save m n,即在m秒内有n次修改时,自动触发bgsave生成rdb文件;
2.主从复制时,从节点要从主节点进行全量复制时也会触发bgsave操作,生成当时的快照发送到从节点;
3.执行debug reload命令重新加载redis时也会触发bgsave操作;
4.默认情况下执行shutdown命令时,如果没有开启aof持久化,那么也会触发bgsave操作;
3.优缺点
1.优点
1.适合大规模的数据恢复
2.按照业务定时备份
3.对数据完整性和一致性不高
4.RDB文件在内存加载,速度比AOF快得多
2.缺点
1.一定时间间隔备份一次,如果redis挂掉的话,就会丢失当前至最近一次快照的数据,快照之间的数据会丢失
2.内存数据的全量同步,如果数据太大会导致I/O严重影响服务器性能
3.RDB依赖于主进程的fork,在更大的数据集中,这可能会导致服务请求的瞬间延迟。fork的时候内存中的数据被克隆了一份,大致2倍的膨胀性,需要考虑
4.实战场景
1.由于生产环境中我们为Redis开辟的内存区域都比较大(例如6GB),那么将内存中的数据同步到硬盘的过程可能就会持续比较长的时间,而实际情况是这段时间Redis服务一般都会收到数据写操作请求。那么如何保证数据一致性呢?
RDB中的核心思路是Copy-on-Write,来保证在进行快照操作的这段时间,需要压缩写入磁盘上的数据在内存中不会发生变化。在正常的快照操作中,一方面Redis主进程会fork一个新的快照进程专门来做这个事情,这样保证了Redis服务不会停止对客户端包括写请求在内的任何响应。另一方面这段时间发生的数据变化会以副本的方式存放在另一个新的内存区域,待快照操作结束后才会同步到原来的内存区域。举个例子:如果主线程对这些数据也都是读操作(例如图中的键值对 A),那么,主线程和 bgsave 子进程相互不影响。但是,如果主线程要修改一块数据(例如图中的键值对 C),那么,这块数据就会被复制一份,生成该数据的副本。然后,bgsave 子进程会把这个副本数据写入 RDB 文件,而在这个过程中,主线程仍然可以直接修改原来的数据。
2.在进行快照操作的这段时间,如果发生服务崩溃怎么办?
很简单,在没有将数据全部写入到磁盘前,这次快照操作都不算成功。如果出现了服务崩溃的情况,将以上一次完整的RDB快照文件作为恢复内存数据的参考。也就是说,在快照操作过程中不能影响上一次的备份数据。Redis服务会在磁盘上创建一个临时文件进行数据操作,待操作成功后才会用这个临时文件替换掉上一次的备份。
八.AOF
1.APF介绍
将Redis执行过的所有写指令记录下来(读操作不记录),以日志的形式来记录每个写操作,只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。AOF日志记录Redis的每个写命令,步骤分为:命令追加(append)、文件写入(write)和文件同步(sync)。
2.三种写入策略
3.工作流程
4.优缺点
1.优点
1.使用AOF Redis 更加持久: 您可以有不同的 fsync 策略: 根本不fsync、每秒fsync、每次查询时fsync。使用每秒fsync的默认策略,写入性能仍然很棒。fsync 是使用后台线程执行的,当没有 fsync 正在进行时,主线程将努力执行写入,因此您只能丢失一秒钟的写入。
2.AOF 日志是一个仅附加日志,因此不会出现寻道问题,也不会在断电时出现损坏问题。即使由于某种原因(磁盘已满或其他原因) 日志以写一半的命令结尾,redis-check-aof 工具也能够轻松修复它
当AOF变得太大时,Redis 能够在后台自动重写 AOF。重写是完全安全的,因为当 Redis 继续附加到旧文件时,会使用创建当前数据集所需的最少操作集生成一个全新的文件,一旦第二个文件准备就绪,Redis 就会切换两者并开始附加到新的那一个
3.AOF 以易于理解和解析的格式依次包含所有操作的日志。您甚至可以轻松导出 AOF 文件。例如,即使您不小心使用该FLUSHALL命令刷新了所有内容,只要在此期间没有执行日志重写,您仍然可以通过停止服务器、删除最新命令并重新启动 Redis 来保存您的数据集
2.缺点
1.AOF 文件通常比相同数据集的等效 RDB 文件大。
2.根据确切的 fsync策略,AOF 可能比 RDB 慢。一般来说,将fsync 设置为每秒性能仍然非常高,并且在禁用 fsync 的情况下,即使在高负载下它也应该与 RDB 一样快。即使在巨大的写入负载的情况下,RDB 仍然能够提供关于最大延迟的更多保证
5.AOF重写机制
AOF会记录每个写命令到AOF文件,随着时间越来越长,AOF文件会变得越来越大。如果不加以控制,会对Redis服务器,甚至对操作系统造成影响,而且AOF文件越大,数据恢复也越慢。为了解决AOF文件体积膨胀的问题,Redis提供AOF文件重写机制来对AOF文件进行“瘦身”。可以手动使用命令 bgrewriteaof 来重新。
Redis通过创建一个新的AOF文件来替换现有的AOF,新旧两个AOF文件保存的数据相同,但新AOF文件没有了冗余命令。
1.触发方式
1.自动触发
满足配置文件中的选项后,Redis会记录上次重写时的AOF大小默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时
2.手动触发
bgrewriteaof
2.Aof重写会阻塞主进程吗
AOF重写过程是由后台进程bgrewriteaof来完成的。主线程fork出后台的bgrewriteaof子进程,fork会把主线程的内存拷贝一份给bgrewriteaof子进程,这里面就包含了数据库的最新数据。然后,bgrewriteaof子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志。所以aof在重写时,在fork进程时是会阻塞住主线程的。
3.AOF日志何时会重写?
有两个配置项控制AOF重写的触发:
auto-aof-rewrite-min-size:表示运行AOF重写时文件的最小大小,默认为64MB。
auto-aof-rewrite-percentage:这个值的计算方式是,当前aof文件大小和上一次重写后aof文件大小的差值,再除以上一次重写后aof文件大小。也就是当前aof文件比上一次重写后aof文件的增量大小,和上一次重写后aof文件大小的比值。
4.重写日志时,有新数据写入咋整?
重写过程总结为:“一个拷贝,两处日志”。在fork出子进程时的拷贝,以及在重写时,如果有新数据写入,主线程就会将命令记录到两个aof日志内存缓冲区中。如果AOF写回策略配置的是always,则直接将命令写回旧的日志文件,并且保存一份命令至AOF重写缓冲区,这些操作对新的日志文件是不存在影响的。(旧的日志文件:主线程使用的日志文件,新的日志文件:bgrewriteaof进程使用的日志文件)而在bgrewriteaof子进程完成会日志文件的重写操作后,会提示主线程已经完成重写操作,主线程会将AOF重写缓冲中的命令追加到新的日志文件后面。这时候在高并发的情况下,AOF重写缓冲区积累可能会很大,这样就会造成阻塞,Redis后来通过Linux管道技术让aof重写期间就能同时进行回放,这样aof重写结束后只需回放少量剩余的数据即可。最后通过修改文件名的方式,保证文件切换的原子性。在AOF重写日志期间发生宕机的话,因为日志文件还没切换,所以恢复数据时,用的还是旧的日志文件。
总结操作:
主线程fork出子进程重写aof日志
子进程重写日志完成后,主线程追加aof日志缓冲
替换日志文件
5.在重写日志整个过程时,主线程有哪些地方会被阻塞?
1.fork子进程时,需要拷贝虚拟页表,会对主线程阻塞。
2.主进程有bigkey写入时,操作系统会创建页面的副本,并拷贝原有的数据,会对主线程阻塞。
3.子进程重写日志完成后,主进程追加aof重写缓冲区时可能会对主线程阻塞。
6.为什么AOF重写不复用原AOF日志?
两方面原因:
父子进程写同一个文件会产生竞争问题,影响父进程的性能。
如果AOF重写过程中失败了,相当于污染了原本的AOF文件,无法做恢复数据使用。
7.重写原理
1:在重写开始前,redis会创建一个“重写子进程”,这个子进程会读取现有的AOF文件,并将其包含的指令进行分析压缩并写入到一个临时文件中。
2:与此同时,主进程会将新接收到的写指令一边累积到内存缓冲区中,一边继续写入到原有的AOF文件中,这样做是保证原有的AOF文件的可用性,避免在重写过程中出现意外。
3:当“重写子进程”完成重写工作后,它会给父进程发一个信号,父进程收到信号后就会将内存中缓存的写指令追加到新AOF文件中
4:当追加结束后,redis就会用新AOF文件来代替旧AOF文件,之后再有新的写指令,就都会追加到新的AOF文件中
5:重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似
九.RDB-AOF混合使用
1.加载流程
1.redis重启时判断是否开启aof,如果开启了aof,那么就优先加载aof文件;
2.如果aof存在,那么就去加载aof文件,加载成功的话redis重启成功,如果aof文件加载失败,那么会打印日志表示启动失败,此3.时可以去修复aof文件后重新启动;
4.若aof文件不存在,那么redis就会转而去加载rdb文件,如果rdb文件不存在,redis直接启动成功;
5.如果rdb文件存在就会去加载rdb文件恢复数据,如加载失败则打印日志提示启动失败,如加载成功,那么redis重启成功,且使用rdb文件恢复数据;
6.那么为什么会优先加载AOF呢?因为AOF保存的数据更完整,通过上面的分析我们知道AOF基本上最多损失1s的数据。