Redis
Redis是什么?
Redis 是一个使用 C 语言开发的高速缓存数据库。
Redis进行数据库切换命令
SELECT index 数据库索引号index用数字值指定,以0作为起始索引值。
Redis使用场景?
热点数据
经常会被查询,但是不经常被修改或者删除的数据,首选是使用redis缓存
记录帖子点赞数、点击数、评论数;缓存近期热帖;缓存文章详情信息;记录用户会话信息。
Redis 有哪些功能?
数据缓存功能、分布式锁的功能、支持数据持久化、支持事务、支持消息队列。
Redis 为什么是单线程的?
因为 cpu 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器内存或者网络带宽。既然单线程容易实现,而且 cpu 又不会成为瓶颈,那就顺理成章地采用单线程的方案了。
关于 Redis 的性能,官方网站也有,普通笔记本轻松处理每秒几十万的请求。
而且单线程并不代表就慢 nginx 和 nodejs 也都是高性能单线程的代表。
为什么这么快
1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
2、数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
4、使用多路I/O复用模型,非阻塞IO;
5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
多路I/O复用模型介绍
多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。
这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗),且 Redis 在内存中操作数据的速度非常快,也就是说内存内的操作不会成为影响Redis性能的瓶颈,主要由以上几点造就了 Redis 具有很高的吞吐量。
击穿现象及解决方式
当前key是一个热点key(例如一个秒杀活动),并发量非常大。重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的SQL、多次IO、多个依赖等。在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。
解决方案:
1. 分布式互斥锁
只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。set(key,value,timeout)
2. 永不过期
从缓存层面来看,确实没有设置过期时间,所以不会出现热点key过期后产生的问题,也就是“物理”不过期。
从功能层面来看,为每个value设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去更新缓存
雪崩现象及解决方式
由于缓存层承载着大量请求,有效地保护了存储层,但是如果缓存层由于某些原因不可用(宕机)或者大量缓存由于超时时间相同在同一时间段失效(大批key失效/热点数据失效),大量请求直接到达存储层,存储层压力过大导致系统雪崩。
解决方案
可以把缓存层设计成高可用的,即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务。利用sentinel或cluster实现。
采用多级缓存,本地进程作为一级缓存,redis作为二级缓存,不同级别的缓存设置的超时时间不同,即使某级缓存过期了,也有其他级别缓存兜底
缓存的过期时间用随机值,尽量让不同的key的过期时间不同(例如:定时任务新建大批量key,设置的过期时间相同)
缓存穿透现象及解决方式
缓存穿透是指查询一个根本不存在的数据,缓存层和持久层都不会命中。在日常工作中出于容错的考虑,如果从持久层查不到数据则不写入缓存层,缓存穿透将导致不存在的数据每次请求都要到持久层去查询,失去了缓存保护后端持久的意义 .
缓存穿透问题可能会使后端存储负载加大,由于很多后端持久层不具备高并发性,甚至可能造成后端存储宕机。通常可以在程序中统计总调用数、缓存层命中数、如果同一个Key的缓存命中率很低,可能就是出现了缓存穿透问题。
造成缓存穿透的基本原因有两个。第一,自身业务代码或者数据出现问题(例如:set 和 get 的key不一致),第二,一些恶意攻击、爬虫等造成大量空命中(爬取线上商城商品数据,超大循环递增商品的ID)
解决方案
1.缓存空对象:是指在持久层没有命中的情况下,对key进行set (key,null)
2.布隆过滤器拦截
在访问缓存层和存储层之前,将存在的key用布隆过滤器提前保存起来,做第一层拦截,当收到一个对key请求时先用布隆过滤器验证key是否存在,如果存在在进入缓存层、存储层。可以使用bitmap做布隆过滤器。这种方法适用于数据命中不高、数据相对固定、实时性低的应用场景,代码维护较为复杂,但是缓存空间占用少。
布隆过滤器实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
布隆过滤器
类似于redis中的set可以去重, 是一个很长的二进制向量和一系列随机映射函数 ,用于检索一个元素是否在一个集合中(存在的时候可能存在,不存在的时候一定不存在),效率比较高 解决redis的缓存穿透问题,缺点是删除困难(要计数删除)
防止缓存穿透,先用布隆过滤器判断key的hash值是否存在bitmap集合中,0 是不存在.1是可能存在;
Redis的超时处理机制
缓存过期处理
超时淘汰机制
Redis 支持的数据类型
Redis 支持的数据类型:string(字符串)、list(列表)、hash(字典)、set(集合)、zset(有序集合)。
zset的底层原理?
zset为有序(有限score排序,score相同则元素字典序),自动去重的集合数据类型,其底层实现为字典(dict)+跳表(skiplist),当数据比较少的时候用ziplist编码结构存储。
同时满足以下两个条件采用ziplist存储:
1、有序集合保存的元素数量小于默认值128个
2、有序集合保存的所有元素的长度小于默认值64字节
ziplist存储方式
当ziplist作为zset的底层存储结构时候,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个元素保存元素的分值
Redis 持久化方式
Redis 的持久化有两种方式,或者说有两种策略:
RDB(Redis Database):是记录在磁盘中的Redis中的所有数据快照的文件, 快照文件也称RDB文件
AOF(Append Only File):是追加文件 记录的是redis处理的每一个写命令;可以查看日志命令文件;
RDB持久化在四种情况下会执行:
执行save命令
执行bgsave命令
Redis停机时
触发RDB条件时
RDB原理
bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据。完成fork后读取内存数据并写入 RDB 文件。
fork采用的是copy-on-write技术:
当主进程执行读操作时,访问共享内存;
当主进程执行写操作时,则会拷贝一份数据,执行写操作。
AOF原理
AOF全称为Append Only File(追加文件)。Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件。
分布式锁
Redis分布式锁原理:
1.Redission框架继承后,可以指定一个节点(key,value),通过Lua脚本向Redis加锁
2.加锁后将会启动一个"看门狗"的后台线程,定时检测是否仍持有锁,如果持有锁,将延长锁持续时间
3.其他需要获取该锁的服务,将不断循环获取该锁,直到前面的线程释放了锁为止
优点: redis性能很高,适合高并发下的加锁机制
缺点:如果加锁的redis,master 故障,刚好数据也还没同步到slave,那其他加锁的客户端则会在此加锁成功,则相当于有两个客户端都拿到了锁
Redis怎么实现分布式锁?
Redis分布式锁其实就是在系统里面占一个“坑”,其他程序也要占“坑”的时候,占用成功了就可以继续执行,失败了就只能放弃或稍后重试。
占坑一般使用 setnx(set if not exists)指令,只允许被一个程序占有,使用完调用 del 释放锁。
Redis 分布式锁有什么缺陷?
Redis 分布式锁不能解决超时的问题,分布式锁有一个超时时间,程序的执行如果超出了锁的超时时间就会出现问题。
Redis如何做内存优化?
尽量使用 Redis 的散列表(哈希表),把相关的信息放到散列表里面存储,而不是把每个字段单独存储,这样可以有效的减少内存使用。比如将 Web 系统的用户对象,应该放到散列表里面再整体存储到 Redis,而不是把用户的姓名、年龄、密码、邮箱等字段分别设置 key 进行存储。
Redis常见的性能问题有哪些?该如何解决?
主服务器写内存快照,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以主服务器最好不要写内存快照。
Redis主从复制的性能问题,为了主从复制的速度和连接的稳定性,主从库最好在同一个局域网内。
Redis和数据库中数据如何保证一致?
两种方案: 1.先删除缓存再更新数据库 高并发可能会出现问题
2.先更新数据库再删除缓存 应对高并发比较好,但容易造成数据不一致 因为删除缓存时候可能会 失败
集群
redis集群:
主从集群和分片集群; 主从集群实现读写分离 在从节点上执行slaveof <masterip> <masterport>
主从集群数据同步的原理: 第一次同步是全量同步 以后是增量同步;
全量同步:主库执行bgsave生成RDB文件()
增量同步: 就是指更新salve与master存在差异的部分数据
分片集群: 利用插槽机制实现动态扩容,16384,节点平分,根据key计算槽值,在计算节点;
搭建
Redis集群至少需要3个节点,因为投票容错机制要求超过半数节点认为某个节点挂了该节点才是挂了,所以2个节点无法构成集群。
redis 集群作用:
1. 分散单台服务器的访问压力,实现负载均衡;
2. 分散单台服务器的存储压力,实现可扩展性
3. 降低单台服务器宕机带来的业务灾难,实现高可用性
Redis的主从复制怎么实现的?
主库 master 和从库 slave 之间通过复制 id 进行匹配,避免 slave 挂到错误的 master。Redis 的复制分为全量同步和增量同步。
Redis 在进行全量同步时,master 会将内存数据通过 bgsave 落地到 rdb,同时,将构建 内存快照期间 的写指令,存放到复制缓冲中,当 rdb 快照构建完毕后,master 将 rdb 和复制缓冲队列中的数据全部发送给 slave,slave 完全重新创建一份数据。
这个过程,对 master 的性能损耗较大,slave 构建数据的时间也比较长,而且传递 rdb 时还会占用大量带宽,对整个系统的性能和资源的访问影响都比较大。
而增量复制,master 只发送 slave 上次复制位置之后的写指令,不用构建 rdb,而且传输内容非常有限,对 master、slave 的负荷影响很小,对带宽的影响可以忽略,整个系统受影响非常小。
在 Redis 2.8 之前,Redis 基本只支持全量复制。在 slave 与 master 断开连接,或 slave 重启后,都需要进行全量复制。在 2.8 版本之后,Redis 引入 psync,增加了一个复制积压缓冲,在将写指令同步给 slave 时,会同时在复制积压缓冲中也写一份。
在 slave 短时断开重连后,上报master runid 及复制偏移量。如果 runid 与 master 一致,且偏移量仍然在 master 的复制缓冲积压中,则 master 进行增量同步。
但如果 slave 重启后,master runid 会丢失,或者切换 master 后,runid 会变化,仍然需要全量同步。
因此 Redis 自 4.0 强化了 psync,引入了 psync2。在 pysnc2 中,主从复制不再使用 runid,而使用 replid(即复制id) 来作为复制判断依据。同时 Redis 实例在构建 rdb 时,会将 replid 作为 aux 辅助信息存入 rbd。重启时,加载 rdb 时即可得到 master 的复制 id。从而在 slave 重启后仍然可以增量同步。
在 psync2 中,Redis 每个实例除了会有一个复制 id 即 replid 外,还有一个 replid2。Redis 启动后,会创建一个长度为 40 的随机字符串,作为 replid 的初值,在建立主从连接后,会用 master的 replid 替换自己的 replid。同时会用 replid2 存储上次 master 主库的 replid。这样切主时,即便 slave 汇报的复制 id 与新 master 的 replid 不同,但和新 master 的 replid2 相同,同时复制偏移仍然在复制积压缓冲区内,仍然可以实现增量复制。
哨兵
Redis哨兵Sentinel): Redis利用哨兵机制来实现主从集群的自动故障转移;
哨兵的三个作用:
- 监控Redis主从节点是否正常工作 基于心跳检测机制,每1秒向集群每个实例发送ping命令 如果超过指定数量的sentinel认为实例主观下线,实例就客观下线
- 集群自动故障恢复: 当主节点故障后,会自动选出一个从节点提升为master,主故障恢复后也以新的主节点为主;(选主节点会看从节点的优先级 id 偏移量)
- 通知: 通知redis客户端主节点换了,推送最新的主节点信息;
故障转移步骤:
1.首先会选一个salve执行slaveof no one(永不为奴) 把它变为主节点
2.然后在所有的从节点上都执行slaveof 新节点信息(就是ip+端口)
3.老的主节点恢复后,也执行slaveof 新节点信息
Redis的bitmap: 就是操作String数据结构的key锁存储的字符串指定漂移量上的位,返回原位置的值
优点:节省空间 效率高
Redis集群投票选举机制
当redis集群的主节点故障时,Sentinel集群将从剩余的从节点中选举一个新的主节点,有以下步骤:
选举过程
1、主观下线
Sentinel集群的每一个Sentinel节点会定时对redis集群的所有节点发心跳包检测节点是否正常。如果一个节点在down-after-milliseconds时间内没有回复Sentinel节点的心跳包,则该redis节点被该Sentinel节点主观下线。
2、客观下线
当节点被一个Sentinel节点记为主观下线时,并不意味着该节点肯定故障了,还需要Sentinel集群的其他Sentinel节点共同判断为主观下线才行。
该Sentinel节点会询问其他Sentinel节点,如果Sentinel集群中超过quorum数量的Sentinel节点认为该redis节点主观下线,则该redis客观下线。
如果客观下线的redis节点是从节点或者是Sentinel节点,则操作到此为止,没有后续的操作了;如果客观下线的redis节点为主节点,则开始故障转移,从从节点中选举一个节点升级为主节点。
3、Sentinel集群选举Leader
如果需要从redis集群选举一个节点为主节点,首先需要从Sentinel集群中选举一个Sentinel节点作为Leader。
每一个Sentinel节点都可以成为Leader,当一个Sentinel节点确认redis集群的主节点主观下线后,会请求其他Sentinel节点要求将自己选举为Leader。被请求的Sentinel节点如果没有同意过其他Sentinel节点的选举请求,则同意该请求(选举票数+1),否则不同意。
如果一个Sentinel节点获得的选举票数达到Leader最低票数(quorum和Sentinel节点数/2+1的最大值),则该Sentinel节点选举为Leader;否则重新进行选举。
4、Sentinel Leader决定新主节点
当Sentinel集群选举出Sentinel Leader后,由Sentinel Leader从redis从节点中选择一个redis节点作为主节点:
过滤故障的节点
选择优先级slave-priority最大的从节点作为主节点,如不存在则继续
选择复制偏移量(数据写入量的字节,记录写了多少数据。主服务器会把偏移量同步给从服务器,当主从的偏移量一致,则数据是完全同步)最大的从节点作为主节点,如不存在则继续
选择runid(redis每次启动的时候生成随机的runid作为redis的标识)最小的从节点作为主节点
可用性
缓存
Redis的六种淘汰策略
1、volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰。
2、volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰。
3、volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。
4、allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰。
5、allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰。
6、no-enviction(驱逐):禁止驱逐数据。
JWT
redis里的token值怎么防止暴力破解?
可以让登录接口登录3次后不让其再次去数据库中查询数据,直接返回友好提示,待 5 分钟后再试
JWT 令牌的结构:
JWT令牌由 Header、Payload、Signature 三部分组成每部分中间使用点(.)分隔
1)Header
头部包括令牌的类型(即 JWT)及使用的哈希算法(如 HMAC SHA256 或 RSA)。
2) Payload
第二部分是负载,内容也是一个 json 对象,它是存放有效信息的地方,它可以存放 jwt 提供的现成字段,比如:iss(签发者),exp(过期时间戳), sub(面向的用户)等,也可自定义字段。此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。
3)Signature
第三部分是签名,此部分用于防止 jwt 内容被篡改。这个部分使用base64url将前两部分进行编码,编码后使用点(.)连接组成字符串,最后使用 header 中声明签名算法进行签名。
什么是签名?
签名是数字签名,发送方将消息原文使用摘要算法生成摘要,再用私钥对摘要进行加密,生成数字签名。
传输数据时为了保证数据的完整性可以使用数字签名技术:
1、发送方使用私钥对内容进行数字签名
2、将内容附带数字签名发送给对方
3、对方收到内容和数字签名,使用公钥进行验签(相当于解密的过程),如果发现内容不一致则说明传输过程被篡改。