为什么要用Redis?
因为传统的关系型数据库比如MySQL已经不能适用于所有的场景了。在传统的关系型数据库里,数据存放在磁盘上,IO开销很大。在高并发的情景下,比如秒杀系统里的库存扣减、对访问流量高峰的应对等,很容易把数据库打崩,所以需要引入缓存中间件。目前比较主流的缓存中间件有Redis和Memcached,综合考虑他们的优缺点,最后选择了Redis。
(1)提高对热点数据的访问性能
(2)在高并发场景下,分担数据库压力。
Redis相比Memcached有什么优势呢?
Memcached不支持持久化,数据只能放在内存里。
Redis可以持久化,支持更多数据类型。
Redis原生支持集群。
Redis常见问题
- 缓存和数据库双写一致性问题
- 缓存雪崩、缓存穿透、缓存击穿问题
- 缓存的并发竞争问题
Redis有哪些数据结构?
最基本的数据结构有5种,包括字符串string、哈希hash、链表list、集合set、有序集合sorted set。
此外还有位图bitset、hyperLogLog、Geo、Pub/Sub、BloomFilter等。
这些数据结构各可以用来做什么?Redis可以用来做什么?
Redis是一个开源的内存中的数据结构存储系统,可以用来做数据库、缓存、消息中间件。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fu9z9t3j-1578570129201)(quiver-image-url/4C6EFC9EEDA7EADB30C1F0C0BDF81C5D.jpg =646x372)]
(1)string:缓存功能,计数器,存储用户session
(2)Hash:存储结构化的对象,但是不能表达嵌套对象的对象
(3)List:下拉分页,消息队列
(4)set:去重,交集看共同好友,并集差集
(5)zset:排行榜,热搜榜,带权重的队列
(6)BitMap:位图,以bit位为基本的单位存储信息,记录boolean,省空间
(7)Hyperloglog:不精确的去重技术功能,如统计UV(网站访客量)
(8)Geospatial:保存地理位置,做距离计算。寻找附近的人
(9)pub/sub,订阅发布功能,用作简单的消息队列
(10)pipeline:批量执行一组指令,一次性返回全部结果,减少频繁的请求应答
(11)Lua:保证原子性,秒杀系统可以用
缓存穿透、缓存击穿、缓存雪崩是什么,如何解决 ?
-
缓存穿透:正常的流程是:用户发起请求,先去缓存中找,没找到再去数据库里找。找到了便更新缓存,返回给用户,找不到便返回空。如果大量请求要找的都是根本不存在(缓存里没有,数据库里也没有)的数据,就会对数据库造成很大压力。
解决方法: 在接口层增加校验,判断条件是否有效;布隆过滤器;缓存里增加key:null。 -
缓存击穿:某个热点数据失效,大量请求要找同一条缓存中没有,但是数据库中有的数据(缓存到期),对数据库造成很大压力。
解决方法: 设置热点数据永不过期(首页更新就更新缓存);访问数据库添加互斥锁,没拿到锁的就等待一下再去缓存里取。 -
缓存雪崩:大量数据到达过期时间,大量请求需要访问数据库,压力很大。
解决方法: 过期时间随机化,高可用架构,本地缓存分级缓存,限流+降级。事后rdb快照+aof快速恢复
常见的缓存有哪几种?
缓存的作用是提高对热点数据的访问性能。
本地缓存(性能高,容量小,不便扩展);
分布式缓存(容量大,易扩展,性能稍低);
多级缓存(最热数据放本地+其他作为分布式缓存)
Redis的过期策略和内存淘汰策略?
对于过期的数据,Redis会进行定期删除(每秒10次,随机从过期字典里选择20个key,删除其中已经过期的,如果过期比例超过四分之一,重复,循环上限25ms)。如果同一时间大量key过期,会造成卡顿。所以key的过期时间要随机化。
惰性删除。
此外,还有内存淘汰机制防止过期数据积压,比较常用的是当内存不足时,在所有key或设置了过期时间的key中移除最少访问的数据(近似LRU,随机选若干个,移除上次访问时间最早的那个)。或移除寿命小的,或随机移除。
从库的过期策略:主库会写一个del指令,同步到从库。
如何在成千上万个key里找出特定前缀的key?
keys prefix*,缺点是没有limit,一次返回所有;时间复杂度O(N),如果结果特别特别多,会阻塞线程,造成其他Redis请求阻塞。
scan指令的特点:通过游标分步进行,不会阻塞线程;提供limit参数;也提供模式匹配功能;返回的结果可能会有重复;遍历过程中如果有数据修改,改动后的数据能不能遍历到是不一定的;返回的游标值为0代表遍历结束。
Redis的持久化
持久化机制保证即使宕机,数据也不会丢失。Redis的持久化机制分为快照(一次全量备份,二进制序列化形式,非常紧凑)和AOF日志(连续增量备份,记录内存数据修改的指令,要定期进行重写瘦身)。
-
快照机制:父进程继续处理客户端请求,调用fork产生一个子进程,进行copy on write快照,即持久化的是子进程产生那一瞬间的数据版本,父进程修改数据时会复制一个新版本再修改,内存占用不是立即增长到2倍,而是逐渐增长,小于2倍。
-
AOF日志:存储Redis服务器的顺序指令序列,只存储修改指令。(收到修改指令,先写指令,再执行)bgrewriteaof可以进行AOF日志瘦身。
-
混合持久化
全量日志快照 + 快照时间后的AOF日志重放,效率是最高的
RDB五分钟一次,恢复快但是数据不完整。
AOF一秒一次,通过后台线程fsync操作,追加写数据,异步刷新缓存区,文件太大。
管道Pipeline
管道的作用:客户端一次发送多个请求,一次接受多个请求,提高QPS。不能无限提升,受制于服务端的cpu处理能力。
PubSub
Publisher / Subcriber,支持消息队列的多播(一个生产者,多个消费者队列,每组消费者有各自的消费逻辑)。
Redis5.0新特性:Stream数据结构
强大的支持多播的可持久化的消息队列。
CAP原理
C :Consistent一致性
A : Availability 可用性
P : Partition tolerance 分区容忍性
网络分区(分布式系统中节点之间网络断开)发生时,一致性(断开连接的主机无法继续同步)和可用性(想保持一致性就要停掉服务进行维护)难两全。
Redis如何支持高可用
-
Sentinel哨兵。
自动化主从切换,抵抗节点故障。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-auArZiuE-1578570129202)(quiver-image-url/A75B50D085625552FB7221D6680062E5.png =490x340)]
客户端连接redis时,先询问哨兵主节点ip,询问到以后再去连接该ip。如果连接不上,就再次询问哨兵,得到新的主节点ip。对于挂掉的主节点,哨兵持续监控,重新可用以后就变成从节点。
问题:如果主从延迟大,就会丢失消息。无法保证消息完全不丢,两个参数避免主从延迟过大。min-slaves-to-write 1表示至少有多少从节点在进行正常复制(设置min-slaves-max-lag,如果为=】10,表示超过10s没收到从节点反馈说明复制出现异常)
三个(若干个)哨兵节点做的事:
(1)每隔10秒给主从节点发送info指令获取他们的信息,包括ip
(2)每隔2秒同时跟订阅的sentinel_hello频道进行同步,获取新哨兵节点的信息,交换主节点的状态信息
(3)每隔1秒对主节点、从节点、其他哨兵节点发送ping命令做心跳测试。
如果一个哨兵发现主节点down了,此时是”主观下线“,向其他哨兵咨询,如果半数都同意,变为”客观下线“。
客观下线后,选取一个哨兵领导者,选出新的主节点(不能是不健康节点(比如主观下线了、断线、很久没有回复ping命令、与主节点失联),优先级比较高的,复制偏移量最大数据最完整的) -
Cluster集群架构
主从同步
异步同步,只能保证最终一致性。从节点加入集群时,快照同步+增量同步。先RDB快照发过去,并且主库的操作写到复制buffer里(环状,新的会覆盖旧的),buffer再发给从节点,从节点反馈偏移量。如果网络不好会发生还没发过去就被覆盖了。
wait指令:同步复制,保证严格一致性,但是如果长时间阻塞,丧失了可用性。
2.8之后快照不用全部生成再发,有优化。
双写一致性问题,缓存更新问题
(1)cache aside:取数据在缓存里取,找不到就去数据库里取,并存到缓存里。更新的时候先更新数据库,再让缓存失效。
Redis是单线程还是多线程服务?为什么单线程还这么快,能支持高并发?(待完善)
纯内存操作,优秀的数据结构底层,不需要线程上下文切换,使用了非阻塞IO多路复用机制。
单线程,特指网络请求模块使用了单线程,即一个线程处理所有网络请求,其他模块仍然是多线程。
为什么使用单线程:操作都是基于内存的,cpu不会成为瓶颈。
单线程避免了多线程会带来的上下文切换开销、死锁现象、对锁的抢夺。
之所以能够处理大量并发的客户端连接,是因为使用了事件轮询(多路复用技术) + NIO(非阻塞IO)。
Redis内部使用文件事件处理器 File Event Handler,采用IO多路复用机制同时监听多个socket。
(阻塞IO在读时,读够指定长度再返回,否则阻塞;写时,缓冲区写满了就阻塞,写完再返回。非阻塞IO能读多少读多少,能写多少写多少,不会阻塞。但是会导致一个问题————如果没读完,剩下来的什么时候继续读?如果没写完,剩下来的什么时候继续写?这就用到了事件轮询技术。)
缓存预热,缓存降级,二八定律
缓存预热:提前加载热点数据到缓存里。
Redis为什么这么快
(1)内存数据库,速度快
(2)单线程,没有锁的竞争和上下文切换。
(2)多路IO复用。
(4)数据结构效率高,比如XXX底层实现用的是XXX。。。
懒惰删除
unlink,对删除大key进行懒处理,异步回收
flushall -asyc
AOF也是异步的
如何进行限流(待完善)
简单限流 + 漏斗限流
分布式锁
setnx key value
如果在这个时候宕机,setnx+expire的原子操作
expire key time (防止出现意外没有显式释放)
del key
如何实现延迟任务
使用zset数据结构,score为时间, 取第一个key,时间到了就拿出来执行。多线程抢夺使用zrem,返回1则为抢夺成功。