redis相关知识总结
redis的简介
-
读写速度快的内存数据库,是k-v类型的数据库
-
多用于缓存,分布式锁服务
-
多种数据类型。还有高级功能(redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案)。
简单来说 redis 就是一个数据库,不过与传统数据库不同的是 redis 的数据是存在内存中的,所以读写速度非常快,因此 redis 被广泛应用于缓存方向。另外,redis 也经常用来做分布式锁。redis 提供了多种数据类型来支持不同的业务场景。除此之外,redis 支持事务、持久化、LUA脚本、LRU驱动事件、多种集群方案。
redis的性能为什么这么好
1.完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。
2.数据结构简单,对数据操作也简单。
3.采用单线程,避免了锁,避免了不必要的上下文切换和竞争条件
也不存在多进程或者多线程 导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因 为可能出现死锁而导致的性能消耗。
4.使用多路I/O复用模型,非阻塞IO。
为什么用redis作为缓存服务
高性能和高并发。
高性能:第一次访问数据库比较慢,第二次直接走缓存快。 缓存在内存中。同步数据库和缓存即可。
高并发:缓存并发量远大于数据库。部分数据转移到缓存中。 数据可以不经过数据库。
redis常见的数据类型
1.String-还可以包含数字
常用命令: set,get,decr,incr,mget 等。
String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字。 常规key-value缓存应用; 常规计数:微博数,粉丝数等。
2.Hash-映射表,存储对象
常用命令: hget,hset,hgetall 等。
hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以 hash 数据结构来存储用户信息,商品信息等等。比如下面我就用 hash 类型存放了我本人的一些信息:
3.List-双向链表,微博关注列表
- 双向链表,双向查询
- 基于lrange,基于list做分页查询
常用命令: lpush,rpush,lpop,rpop,lrange等
list 就是链表,Redis list 的应用场景非常多,也是Redis最重要的数据结构之一,比如微博的关注列表,粉丝列表,消息列表等功能都可以用Redis的 list 结构来实现。
Redis list 的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。
另外可以通过 lrange 命令,就是从某个元素开始读取多少个元素,可以基于 list 实现分页查询,这个很棒的一个功能,基于 redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西(一页一页的往下走),性能高。
4.Set-排重的list.实现并集和差集
- 自动排重
- 实现并集和差集。-共同关注。
常用命令: sadd,spop,smembers,sunion 等
set 对外提供的功能与list类似是一个列表的功能,特殊之处在于 set 是可以自动排重的。
当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。
比如:在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程,具体命令如下:
sinterstore key1 key2 key3 将交集存在key1内
5.Sorted Set-排序的set,可以赋其序号或者权重
常用命令: zadd,zrange,zrem,zcard等
- score 排序
和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。
举例: 在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用 Redis 中的 Sorted Set 结构进行存储。
redis数据类型的底层实现
1.string底层实现——SDS (动态字符串)
SDS包含以下几个属性:(最重要有最大容量和当前长度)
-
len : 字符串的长度
-
alloc : 表示字符串的最大容量,不包含 header 和空的终止符
-
flags : header的类型
-
buf: 存放字符串的数组
同时SDS分为三种:int,raw,embstr
- int编码
- 保存的是可以用long类型表示的整数值
- raw编码
- 保存长度大于44字节的字符串
- embstr编码
- 保存长度小于44字节的字符串
int用来保存整数值,raw用来保存长字符串,embstr用来保存短字符串。embstr编码是用来专门保存短字符串的一种优化编码。
2.List底层实现——双端链表
列表对象 列表对象的编码可以是ziplist或者quicklist
- ziplist(压缩列表),利用数组来模拟链表
- linkedlist(已弃用)改为quicklist。其实为双向链表。每一个节点是一个 ziplist。
- 1、列表保存元素个数小于512个 。2、每个元素长度小于64字节。 使用ziplist
ziplist的特点:
- 查询速度快。(数组)
- 删除麻烦。但是删除需要移动数组。
quicklist的特点:
- 双向链表,节点是ziplist。
- 结合数组和链表的优点。数组所占空间小,连续,查询速度快。
- 如果使用的是链表,那么每个节点所占用的空间大。(首尾指针和内存)其次,双向链表的各个节点是单独的内存块,地址不连续,节点多了容易产生内存碎片。
- ziplist由于是一整块连续内存,所以存储效率很高。但是,它不利于修改操作,每次数据变动都会引发一次内存的realloc。特别是当ziplist长度很长的时候,一次realloc可能会导致大批量的数据拷贝,进一步降低性能。
- 可以手动设置 节点 ziplist的大小。
- 如果太大,则为ziplist分配大块连续内存空间的难度就越大。有可能出现内存里有很多小块的空闲空间(它们加起来很多),但却找不到一块足够大的空闲空间分配给ziplist的情况。
- 如果太小,则内存碎片越多。内存碎片多了,有可能在内存中产生很多无法被利用的小碎片,从而降低存储效率。
3.Hash底层实现——ziplist或者hashtable
hash对象的编码可以是ziplist或者hashtable
- 当使用ziplist,也就是压缩列表作为底层实现时,新增的键值是保存到压缩列表的表尾。
- hashtable 编码的hash表对象底层使用字典数据结构,哈希对象中的每个键值对都使用一个字典键值对。Redis中的字典相当于Java里面的HashMap,内部实现也差不多类似,都是通过“数组+链表”的链地址法来解决哈希冲突的,这样的结构吸收了两种不同数据结构的优点。
数据量小的时候,用ziplist:
1、列表保存元素个数小于512个
2、每个元素长度小于64字节
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aDYbGUpz-1593343792739)(redis%E7%9B%B8%E5%85%B3%E7%9F%A5%E8%AF%86%E6%80%BB%E7%BB%93/image-20200628151303147.png)](https://img-blog.csdnimg.cn/20200628193140106.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1MzU2ODQw,size_16,color_FFFFFF,t_70)
4.set的底层实现——inset和hashtable
集合对象 set 是 string 类型(整数也会转换成string类型进行存储)的无序集合。
- 元素是数字类型。使用intset
- 不全是数字,且数量大。使用hashtable
hashtable 实现 set。类比 java中hashmap实现 hashset。k为值,v为null.
5.zset的底层实现
zset 有序集合的编码可以是ziplist和skiplist之一。
- ziplist实现。第一个保存元素的成员,第二个保存元素的分值,而且分值小的靠近表头,大的靠近表尾。
- skiplist实现。最底层是跳跃表和字典结合实现的。同时使用。冗余存储
- 字典能够快速获得元素的权重值
- 跳跃表的有点是有序。
总览如下:
redis 设置过期时间
功能-cookie有效期,短信验证码
redis的删除机制:
- 定期删除:每100ms随机删除.不全删,是担心key太多,cpu负担大.
- 惰性删除:系统查询过期的key值,才会删除.
如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期key堆积在内存里,导致redis内存块耗尽了。怎么解决这个问题呢? redis 内存淘汰机制。
redis 内存淘汰机制(MySQL里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据?)
机制如下:
- lru:最近最少使用。经常使用。
- random:随机删除
- ttl:即将过期删除
- lfu:最不经常使用
- 不删除(应该没人用吧)
redis 持久化机制(怎么保证 redis 挂掉之后再重启数据可以进行恢复)
有两种方式:
- 快照-储存某个时间点的上副本,可以备份给其他机器,主从集群 ——RDB
- 追加文件-把每一条命令写入文件——AOF
特点:
- 快照优点:全量数据快照,文件小,恢复快
- 快照缺点:无法保存最近一次快照之后的数据
- 日志优点:可读性高,适合保存增量数据,数据不易丟失
- 日志缺点:文件体积大,恢复时间长
持久化机制选项
追加到文件的选项:(同步性能更号)
appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘
appendfsync no #让操作系统决定何时进行同步
快照选项:
save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
Redis 4.0 持久化优化-混合持久化(日志和快照综合)
- AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。
- 重写期间-加载效率更高,同步性更好
- 格式可读性差
Redis 事务
- 打包的批量执行脚本
- 非原子性,不回滚滚
单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。
事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。
- 开始事务。
- 命令入队。
- 执行事务。
缓存雪崩
redis热点数据,集体失效,请求打在mysql上.
简介:缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
有哪些解决办法?
- 事前:尽量保证整个 redis 集群的高可用性,发现机器宕机尽快补上。选择合适的内存淘汰策略。
- 选择合适的内存淘汰策略。对于数据的失效,不要设置同一时间。设置固定值 + 一个随机值。
- 事中:本地ehcache缓存 + hystrix限流&降级,避免MySQL崩掉
- 事后:利用 redis 持久化机制保存的数据尽快恢复缓存
缓存穿透问题
缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库.
缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。举个例子:某个黑客故意制造我们缓存中不存在的 key 发起大量请求,导致大量请求落到数据库。
一般 3000 个并发请求就能打死大部分数据库了。
有哪些解决办法?
- 参数校验
一些不合法的参数请求直接抛出异常信息
- 缓存无效 key.让访问打再redis上.设置短的时间.
缓存无效 key : 如果缓存和数据库都查不到某个 key 的数据就写一个到 redis 中去并设置过期时间,具体命令如下:
SET key value EX 10086
。这种方式可以解决请求的
key 变化不频繁的情况,如果黑客恶意攻击,每次构建不同的请求key,会导致 redis 中缓存大量无效的 key
。很明显,这种方案并不能从根本上解决此问题。如果非要用这种方式来解决穿透问题的话,尽量将无效的 key 的过期时间设置短一点比如 1 分钟。
- 布隆过滤器,在redis前面过滤
布隆过滤器是一个非常神奇的数据结构,通过它我们可以非常方便地判断一个给定数据是否存在与海量数据中。我们需要的就是判断 key 是否合法
引伸知识——布隆过滤器
- 肯定告诉某个值一定不在
- 通过bit数组实现
本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”。
布隆过滤器 实现原理
bit数组中,映射一个值的三次不同hash值.
查找过程,先三次hash,bit数组中该三位其中有一位,为0,说明该值一定不存在.
如何保证缓存与数据库双写时的数据一致性?
严格要求缓存+数据库必须一致性
缓存和数据库策略是:
- 读:先读缓存,没有去读mysql.并将mysql内容加载到redis中
- 写:先写mysql,然后把redis中数据删掉.
不一致的原因其实都是缓存没及时更新
将不一致分为三种情况:
- 数据库有数据,缓存没有数据; - 读的时候,写入缓存失败
- 数据库有数据,缓存也有数据,数据不相等;- 更新缓存失败
- 数据库没有数据,缓存有数据。 - 数据库删除了,但是缓存删除失败
解决方案大概有以下几种:
- 对删除缓存进行重试,数据的一致性要求越高,我越是重试得快。
- 定期全量更新,简单地说,就是我定期把缓存全部清掉,然后再全量加载。
- 给所有的缓存一个失效期。失效期越短,数据一致性越高
Redis集群
redis的集群模式有三种模式
- 主从:主节点负责读写,从节点负责同步主节点,读数据。读写分离。
- 无法解决热切换。master死掉,必须手动切换
- Redis的容量受限于master容量。
- 哨兵:有哨兵负责监视master状态,负责主从自动切换。通知从节点的主从切换的信息。
- 解决了高可用的问题
- Redis的容量受限于master容量。
- cluster:自动将数据进行分片,每个master上放一部分数据。根据key的hash值%16384得到余数,找到插槽上离其最近的节点。
- 无中心结构,数据分片存放到不同节点
- 每个在solt上节点都有自己的备份节点。
- (针对海量数据+高并发+高可用的场景)
主从模式
主节点负责读写,从节点负责同步主节点,读数据。读写分离。
redis主从复制的大致过程:
1)当一个从数据库启动时,会向主数据库发送sync命令,
2)主数据库接收到sync命令后会开始在后台保存快照(执行rdb操作),并将保存期间接收到的命令缓存起来。
3)当快照完成后,redis会将快照文件和所有缓存的命令发送给从数据库。
4)从数据库收到后,会载入快照文件并执行收到的缓存的命令。
主从节点之间的同步机制
全量同步
Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份。具体步骤如下:
1)从服务器连接主服务器,发送SYNC命令;
2)主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录 此后执行的所有写命令;
3)主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
4)从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
5)主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
6)从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;
完成上面几个步骤后就完成了从服务器数据初始化的所有操作,从服务器此时可以接收来自用户的读请求。
增量同步
Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。
增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。
Redis主从同步策略
主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。
注意点
如果多个Slave断线了,需要重启的时候,因为只要Slave启动,就会发送sync请求和主机全量同步,当多个同时出现的时候,可能会导致Master IO剧增宕机。
哨兵模式
Redis的哨兵(sentinel) 系统用于管理多个 Redis 服务器,该系统执行:
监控(Monitoring): 哨兵(sentinel) 会不断地检查你的Master和Slave是否运作正常。
提醒(Notification):当被监控的某个 Redis出现问题时, 哨兵(sentinel) 可以通过 API 向管理员或者其他应用程序发送通知。
- 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
- 当哨兵监测到master宕机,会自动将slave切换成master(投票机制),然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
cluster模式
分布式的关键就是一致性hash。
- 0-2^32的空间的hash环
- 节点hashcode放在环上
- 数据hashcode后放在环上离其最近的节点。顺时针。
redis会有数据倾斜及解决(数据节点少的情况)
解放方法:引入虚拟节点。等距离,映射道实际的节点上。