限流算法就3种:、1.计数器方法:
固定时间窗口,比如1min/1h,设置一个计数器统计单位时间内一个请求的访问量,超过计数器最大值,可以让请求放入等待队列 or 直接拒接访问,这种方法简单粗暴,但是易造成突刺现象。
2.漏斗算法:
可以理解成一个固定容量的漏桶,不管流量多还是少,我都按照常量固定速率流出水滴,如果流入水滴超出了桶的容量,就让水溢出。这种算法优点是稳定速率,缺点是无法面对突发流量。
3.令牌桶算法:
让每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择等待可用的令牌,或者直接拒绝。优点是系统发放令牌的速率是可变的,能够面对突发流量,缺点是有点复杂。
具体使用哪种算法,要根据具体业务场景,比如系统时候会有突发流量,调用方重要程度等,如果调用方不是很重要,为了顾全大局,直接放调用方稍后重试。
那具体这值该如何评估,说到现在我还是不知道限流到底要怎么设置,可以给我一点经验方法吗?
(我继续…)
对于核心服务限流的值可以通过以下方法来设置合理的值:
- 观察评估法:系统总该有QPS监控系统吧,看看流量环比最大值,最小值,平均值,这就是很好的参考,总不能我一拍脑袋设置一个250上去。
- 压力测试法:找QA,半夜业务低峰期,看看系统能承受的最大QPS。
同时,限流还有和重试、降级、熔断等作为组合方法一起使用。
2. 面试官:服务启动的时候服务基本信息被注册到注册中心,如果服务提供者挂了,注册中心如何知道服务不可用了呢?
3. 服务掉线分为主动下线和心跳检测,比如服务由于发版时,在重启之前先主动通知注册中心:我要重启了,有流量进来先不要分给我,让别的机器服务,等我重启成功后在放流量进来,或者是在管理后台手动直接摘掉机器,这个是主动下线。
心跳检测是处理服务非正常下线(如断电断网)的情况,这个时候如果注册中心不知道该服务已经掉线,一旦被其调用就会带来问题。为了避免出现这样的情况,注册中心增加一个心跳检测功能,它会对服务提供者(Provider)进行心跳检测,比如每隔 30s 发送一个心跳,如果三次心跳结果都没有返回值,就认为该服务已下线,赶紧更新 Consumer 的服务列表,告诉 Consumer 调用别的机器。
负载均衡算法?
轮询算法 加权轮询算法: 随机算法:加权随机算法:最小连接数算法 Hash 算法:一致性Hash:
那你刚刚说的Redis数据结构都有哪几种,如何选择使用哪种?
问题分析: 常用的5种,重点学会这5种数据结构的使用足够了。
我:在高并发请求时,为何我们频繁提到缓存技术?最直接的原因是,磁盘IO及网络开销是直接请求内存IO千百上千倍,做个简单计算,如果我们需要某个数据,该数据从数据库磁盘读出来需要0.0045S,经过网络请求传输需要0.0005S,那么每个请求完成最少需要0.005S,该数据服务器每秒最多只能响应200个请求,而如果该数据存于本机内存里,读出来只需要100us,那么每秒能够响应10000个请求。通过将数据存储到离CPU更近的未位置,减少数据传输时间,提高处理效率,这就是缓存的意义。
比较常用的有5种
- 字符串 String: 字符串是 Redis 中最为基础的数据存储类型,数据结构简单,可存储文本,Json,图片数据等任何二进制文件。如姓名,订单号等,对于一些特殊的数据结构,比如List、Set等,建议采用相应的下面介绍的List和Set数据结构进行存储,这样不仅可以节省存储空间还可以提高操作效率。
- 列表 List: 类似 Java 中的 List ,按照插入顺序排序的字符串链表,在插入时,如果该键并不存在,Redis将为该键创建一个新的链表。与此相反,如果链表中所有的元素均被移除,那么该键也将会被从数据库中删除。
- 集合 Set: 类似 Java 中的set,但它是一个无序集合,用于存储无序(存入和取出的顺序不一定相同)元素,值不能重复。可以使用Redis的Set数据类型跟踪一些唯一性数据,比如访问系统的唯一IP地址,唯一用户ID等信息,再比如在微博应用中,每个人的好友存在一个集合(set)中,这样求两个人的共同好友的操作,可能就只需要用求交集命令即可。
- 有序集合 Sorted Set: 类似 Java 中的 TreeSet,支持从小到大排序的 set,适用于排行榜结构的数据存储。
- Hash: 类型相当于Java中的HashMap,所以该类型非常适合于存储值对象的信息,比如用户基本信息对象含有昵称、性别和Age等属性,可以使用Hash来存储User对象,Key可以为用户的唯一ID属性。
除此之外,新版本的Redis还提供了位图,地理坐标,流几种结构。
1.简单动态字符串结构,Redis字符串的实现方式
简单动态字符串(simple dynamic string)简称SDS。Redis使用C语言编写,但是传统的C字符串使用长度为 N+1 的字符串数组来表示长度为N的字符串,所以为了获取一个长度为C字符串的长度,必须遍历整个字符串。和C字符串不同,动态字符串的数据结构中,有专门用于保存字符串长度的变量,我们可以通过获取len属性的值,直接知道字符串长度,从一定程度上提高了读取效率。
Redis源码中,动态字符串的定义:
/*
* 保存字符串对象的结构
*/
struct sdshdr {
// buf 中已占用空间的长度
int len;
// buf 中剩余可用空间的长度
int free;
// 数据空间
char buf[];
};
- len 变量,用于记录buf 中已经使用的空间长度。
- free 变量,用于记录buf 中还空余的空间,初次分配空间,一般没有空余,在对字符串修改的时候,会有剩余空间出现,这样做是为了杜绝C语言中缓冲区溢出的可能性,当我们需要对一个SDS进行修改的时候,Redis 会在执行拼接操作之前,预先检查给定SDS空间是否足够,如果不够,会先拓展SDS的空间,然后再执行拼接操作。
- buf 字符数组,用于记录我们的字符串(记录Redis)。
2.链表数据结构,List 底层结构
链表还是常规的普通双端链表,可以支持反向查找和遍历,更方便操作,通过增删节点来灵活地调整链表的长度,双端链表在Redis内部也是被多次使用:
事务模块使用双端链表依序保存输入的命令。
服务器模块使用双端链表来保存多个客户端。
订阅/发送模块使用双端链表来保存订阅模式的多个客户端。
事件模块使用双端链表来保存时间事件(time event)。
3.跳跃表,sorted set底层结构
Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,(如果你还不了解红黑树,需要先额外补补功课),HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。
那为什么Redis的作者使用 SkipList 结构而不是红黑树?
红黑树:红黑树的查找效率很高,但是在进行重新平衡时,会涉及到大量节点的变化,因此实现和操作起来都比较复杂。
跳跃表:通过简单的多层索引结构,实现简单,且能达到近似于红黑树的查找效率,插入节点(多层插入)不需要像红黑树那样有额外操作。而且跳跃表还能实现范围查找及输出,而红黑树只支持单个元素查找,对于范围查找效率低。
关于缓存的一些算法
(偷偷告诉你,这几个关于Reids的算法很大概率也会被问到,需要多少知道几种)
常用缓存数据淘汰策略
缓存是非常宝贵的资源,不能把所有数据都放入缓存,只能把最重要的或者要求查询速度最快的数据缓存起来,比如微博热门话题排行榜功能,通常使用缓存查询,而不是数据库。
- FIFO(First In First Out): 先进先出算法,即先放入缓存的先被移除。
- LRU(Least Recently Used): 最近最少使用算法,使用时间距离现在最久的那个被移除。
- LFU(Least Frequently Used): 最不常用算法,一定时间段内使用次数(频率)最少的那个被移除。
缓存数据更新策略
- 定时任务从数据库直接更新缓存:适用于对时间不敏感的数据。
- 查询时写缓存,即查询优先查询缓存,若缓存未命中,查询数据库,将返回结果写入缓存,数据更新时先 delete缓存,再更新缓存。
- MQ 消息异步更新缓存,后文中会针对MQ的应用做单独讲解。
RDB(Redis DataBase)详解:
RDB 是 Redis 默认的持久化方案。在指定的时间间隔内,执行指定次数的写操作,则会将内存中的数据写入到磁盘中。即在指定目录下生成一个 dump.rdb 文件。Redis 重启会通过加载 dump.rdb 文件恢复数据。
Redis 提供了 SAVE 和 BGSAVE 两个命令来生成 RDB 文件,区别是前者是阻塞的,后者是后台 fork 子进程进行,不会阻塞主进程处理命令请求。载入 RDB 文件不需要手工运行,而是 server 端自动进行,只要启动时检测到 RDB 文件存在 server 端便会载入 RDB 文件重建数据集。当然上面简介中已经提到,如果同时存在 AOF 的话会优先使用 AOF 重建数据集,因为其保存的数据更完整。
你在项目中有没有遇到 Redis 热点数据问题,一般都是什么原因引起的?
问题分析:上次听群里大佬面试阿里 p7 就被问到这个问题,难度指数五颗星,对我等小白着实是加分项。
我:
关于热点数据问题我有话要说,这个问题我早在刚刚学习使用 Redis 时就从已经意识到了,所以在使用时会刻意避免,坚决不会给自己挖坑,热点数据最大的问题会造成 Reids 集群负载不均衡(也就是数据倾斜)导致的故障,这些问题对于 Redis 集群都是致命打击。
先说说造成 Reids 集群负载不均衡故障的主要原因:
1. 高访问量的 Key,也就是热 key,根据过去的维护经验一个 key 访问的 QPS 超过 1000 就要高度关注了,比如热门商品,热门话题等。
2. 大 Value,有些 key 访问 QPS 虽然不高,但是由于 value 很大,造成网卡负载较大,网卡流量被打满,单台机器可能出现千兆 / 秒,IO 故障。
3. 热点 Key + 大 Value 同时存在,服务器杀手。
那么热点 key 或大 Value 会造成哪些故障呢:
1. 数据倾斜问题:大 Value 会导致集群不同节点数据分布不均匀,造成数据倾斜问题,大量读写比例非常高的请求都会落到同一个 redis server 上,该 redis 的负载就会严重升高,容易打挂。
2. QPS 倾斜:分片上的 QPS 不均。
3. 大 Value 会导致 Redis 服务器缓冲区不足,造成 get 超时。
4. 由于 Value 过大,导致机房网卡流量不足。
5. Redis 缓存失效导致数据库层被击穿的连锁反应。
真实项目中,那热点数据问题你是如何准确定位的呢?
我:
这个问题的解决办法比较宽泛,要具体看不同业务场景,比如公司组织促销活动,那参加促销的商品肯定是有办法提前统计的,这种场景就可以通过预估法。对于突发事件,不确定因素,Redis 会自己监控热点数据。大概归纳下:
1. 提前获知法:
根据业务,人肉统计 or 系统统计可能会成为热点的数据,如,促销活动商品,热门话题,节假日话题,纪念日活动等。
2.Redis 客户端收集法:
调用端通过计数的方式统计 key 的请求次数,但是无法预知 key 的个数,代码侵入性强。
3.Redis 集群代理层统计:
像 Twemproxy,codis 这些基于代理的 Redis 分布式架构,统一的入口,可以在 Proxy 层做收集上报,但是缺点很明显,并非所有的 Redis 集群架构都有 proxy。
4.Redis 服务端收集:
监控 Redis 单个分片的 QPS,发现 QPS 倾斜到一定程度的节点进行 monitor,获取热点 key, Redis 提供了 monitor 命令,可以统计出一段时间内的某 Redis 节点上的所有命令,分析热点 key,在高并发条件下,会存在内存暴涨和 Redis 性能的隐患,所以此种方法适合在短时间内使用;同样只能统计一个 Redis 节点的热点 key,对于集群需要汇总统计,业务角度讲稍微麻烦一点。
以上为说的这 4 个方法都是现在业界比较常用的,方法,我通过学习 Redis 源码还有一个新的想法。第 5 种:修改 Redis 源码。
5. 修改 Redis 源代码:(从读源码中想到的思路)
我发现 Redis4.0 为我们带来了许多新特性,其中便包括基于 LFU 的热点 key 发现机制,有了这个新特性,我们就可以在此基础上实现热点 key 的统计,这个只是我的个人思路。
面试官心理:小伙子还挺有想法,思路挺开阔,还打起了修改源码的注意,我都没这个野心。团队里就需要这样的人。
(发现问题,分析问题,解决问题,不等面试官发问,直接讲述如何解决热点数据问题,这才是核心内容)
如何解决热点数据问题
我:
关于如何治理热点数据问题,解决这个问题主要从两个方面考虑,第一是数据分片,让压力均摊到集群的多个分片上,防止单个机器打挂,第二是迁移隔离。
概括总结:
1.key 拆分:
如果当前 key 的类型是一个二级数据结构,例如哈希类型。如果该哈希元素个数较多,可以考虑将当前 hash 进行拆分,这样该热点 key 可以拆分为若干个新的 key 分布到不同 Redis 节点上,从而减轻压力
2. 迁移热点 key:
以 Redis Cluster 为例,可以将热点 key 所在的 slot 单独迁移到一个新的 Redis 节点上,这样这个热点 key 即使 QPS 很高,也不会影响到整个集群的其他业务,还可以定制化开发,热点 key 自动迁移到独立节点上,这种方案也较多副本。
3. 热点 key 限流:
对于读命令我们可以通过迁移热点 key 然后添加从节点来解决,对于写命令我们可以通过单独针对这个热点 key 来限流。
4. 增加本地缓存:
对于数据一致性不是那么高的业务,可以将热点 key 缓存到业务机器的本地缓存中,因为是业务端的本地内存中,省去了一次远程的 IO 调用。但是当数据更新时,可能会造成业务和 Redis 数据不一致。