即:一天中80%的流量集中在20%的时间内发生。
例如:pv=10000000;
则峰值QPS=(10000000*0.8)/(24*3600*0.2)=463.
理论上:如果一台服务器每秒能处理100个请求,那日pv千万的流量也只需要分布式5台服务器就能抗住。
架构演进的过程:单机混沌状态--各自独立--集群化--分布式--多集群部署--异地部署
集群:多个人干活,每个人干的活都一样。例如:三个厨师都需要干洗菜切菜炒菜的活。
分布式:把活拆开一组人去做,大家协同完成,例如:一个人洗菜一个人切菜一个人烧菜。
拆分维度:系统维度,功能维度和读写维度。
系统维度:商品系统,购物车系统,订单系统,优惠券系统。
功能维度:例如优惠券系统,可以拆分为建券服务,领券服务和用券服务。
读写维度:读的量非常大,比写的多的多。
核心:异步,平缓的处理
2.2.1:队列案例1--流量削峰
流量削峰的由来:瞬间流量巨大,例如几万人抢购100件商品。就像洪水涌入。
流量削峰的目的:让服务处理请求更佳平缓,波阿虎服务器资源。
直接调用模式:多个调用者一起请求,流量大的时候产生调用高峰,被调用者压力大,有崩溃风险。
消息队列模式:所有调用请求都去排队,即使高峰期也不会对被调用者直接产生影响。
流量大的情况下核心转变思想:转变为异步的队列处理模式。
2.2.2:队列案例2--分布式事务(可靠消息模式)
案例场景:用户成功下单后,为用户增加相应的积分。
直接调用的问题:如果积分系统故障,订单系统不知道,已经生成的订单不好处理。
分布式事务:需要保证订单系统,积分系统的执行结果一致,都成功,或者都失败。
改进:
使用消息队列,从同步调用改为异步调用,可以不关心积分系统的状态,保证最终一致性。
问题:订单系统应该是先写数据库还是先发消息?
改进:
将写消息和创建订单放在同一个事务中,把消息写入本地数据库的“消息表”中。这样保证了创建订单和创建消息的一致性。
问题:如何把消息发送到消息队列?
改进:
通过一个后台程序“定时任务”去发送消息,并修改“消息表”中的数据状态。
问题:消息会不会重发?消息重发了怎么办?(此时需要积分系统负责保证幂等性)
1:上游(订单)系统要保证信息不丢,是通过本地消息表和后台定时程序来实现的。
2:下游(积分)系统要保证消息不重复处理,也就是保证幂等性,可以通过判重表来实现。
(幂等:多次调用同一请求同一参数的处理的结果一样)
适用场景:
分布式事务的提交或者回滚只取决于事务的发起方,也就是无需回滚。
类型:
客户端缓存:浏览器/app客户端
网络缓存:CDN
接入层缓存:NGINX代理缓存。
应用层缓存:redis。
缓存的读写策略:
Cache Aside策略 and Read/Write Through策略:
先更新缓存,还是先更新数据库?
假设先更新数据库:
现象:A先更新数据库,由于网络等原因在还没有更新缓存成功的同时,B已经更新了数据库和缓存,此时A再更新缓存,就导致数据库和缓存中的数据不一致。
假设先更新缓存:
现象:如果先更新缓存,再更新数据库,可能失败回滚,导致数据库和缓存中的也不一致。
所以,综上所述先更新谁都不妥。
2.3.1:Cache Aside 策略:
更新数据时不更新缓存,删除缓存中的数据。
读取数据时,如果缓存中没有数据,从数据库中读取,更新到缓存中。
cache aside策略的读写流程:
读:读请求-先读缓存--如果命中,直接返回数据---如果没有命中,读取数据库,写入缓存,返回数据。
写:写请求-先写数据库--再删除缓存。
问题:在写的时候可不可以先删除缓存?不可以!(因为先删缓存,再更新数据库,可能更新数据库失败,这样后续的读就增加了访问数据库的压力)
2.3.2:Read/Write Through 策略:
核心原则:用户只与缓存打交道,由缓存和数据库通信,写入或者读取数据。
注意,这里有个关键角色,就是这个”缓存组件“,有点像仓库管理员。
read/write through 策略的读写流程:
读:读请求--读缓存--如果命中,直接返回数据--如果不命中,读数据库,写缓存,返回数据。
写:写请求--读缓存--命中,直接写数据库--未命中,先写缓存,再由缓存组件同步写入数据库。
2.3.3:缓存的雪崩
缓存的雪崩:大量数据同时失效 导致 数据库压力过大。
解决方案:
1.为有效期 增加随机值
2.使用高可用 分布式缓存
3. 热点数据永远 不过期。
4.在缓存失效后,通过加锁或者队列来控制读数据库的线程数量。
2.3.4:缓存的穿透
缓存的穿透:缓存中没有,需要访问数据库,这样是缓存被穿透了。穿透后就需要查询DB,量大的话就会压垮数据库。
解决方案:
1.缓存,空值
缓存“空值”的处理场景(正常访问的场景):
正常请求不存在的数据,可能其他请求也会读取这个数据,为防止多次无效访问数据库,我们可以把这个空值也缓存起来,用户下次请求时,就直接返回空。
2.采用布隆过滤器。
布隆过滤器的处理场景(恶意攻击的场景):
大量恶意请求不存在的数据,即使缓存空值也无效,大量请求读取数据库,需要使用布隆过滤器,帮助我们在不查数据库的情况下就知道此ID是否存在,把恶 意请求直接过滤掉。
2.3.5:布隆过滤器
1970年布隆提出了一种过滤器的算法,用来判断一个元素是否在一个集合中。
这种算法由一个二进制数组和一个Hash算法组成。
优势:省空间,性能高!
不足:有误判,不能删除。
如何使用布隆过滤器来解决缓存穿透的问题呢?
1:写入数据时,更新布隆过滤器
2:查询数据时,先查询布隆过滤器是否存在,如果不存在,直接返回空,如果存在,再去查询数据库。
图:
布隆过滤器的不足:
功能降级:电商平台很普遍的“推荐功能”,可以提升销量,但不是购物核心流程。
在系统压力大时,可以降级,改为默认内容。
写服务降级:不更新数据库,只更新缓存,把要写的数据放到消息队列,流量高峰过了以后,把消息队列里的数据回放到数据库。
降级的定义:
整体负载,流量>阀值
为了保证重要的服务能正常运行,1:拒绝部分请求,2:延迟和暂停一些不重要的服务,任务。
降级的作用:
降级是系统保护的重要手段,保证系统的高可用。简单理解降级就是“丢车保帅”,保证系统核心功能的正常。
降级的方式:
自动降级:
程序调用时发生问题时,自动降级。
超时降级:对于非核心服务,如果长时间响应慢,就可以自动降级。
限流降级:当触发了限流阀值时,可以使用暂时屏蔽的方式进行降级。
统计失败次数降级:在一个时间段内,调用失败率超出阀值,自动降级。
手动降级:
使用开关配置,对系统中可降级的服务都设置好开关项。
配置文件实现开关配置:
适用于系统部署结构简单的场景。
系统自动监控配置文件的变化
配置文件变化后重新载入。
配置中心实现开关配置:
适用于分布式系统,有统一配置中心。
服务开关在配置中心中定义
在配置中心界面修改相应参数,自动通知相应服务。
配置中心实现技术:zookeeper,redis,consul,etcd。
降级后的处理方案:
使用默认值;兜底数据;缓存数据;排队页面;无货通知;错误页面。。。。
对于系统,在应对高性能压力的场景时,限流已经成为了标配技术解决方案,保证了系统的平稳运行。
限流就是对请求进行限制,例如某一个接口的请求限制为100个每秒,对超过限制的请求则不处理。
保护系统的手段:
缓存:提升系统的访问速度,增大系统处理能力。
降级:暂时屏蔽,过后打开。
限流:对稀缺资源限制请求量,限速,拒绝服务。
四个常用的限流算法:
1.固定时间窗口:
对每个时间窗口内的请求进行计数,如果计算器超过了限制数量,则本窗口内后续的请求都被丢弃。当时间达到下一个窗口时,计数器重置。
缺点:由于限流过于粗糙,无法应对两个时间窗口临界时间内的突发流量。例如:例如每秒限流3个请求,但前一秒内3个请求是在后半秒来的和后一秒内3个请求是在前半秒内来的,此时无法应对两个时间窗口临界时间内的突发流量【实际在这后半秒+前半秒的一秒时间内,请求数超过了3个达到了6个。】
2:滑动时间窗口
记录在时间窗口内每个接口请求达到的时间点,判断当前时间窗口内的接口请求数是否小于限流值。
3:令牌桶
令牌以固定速率生成,如果令牌桶满了则多余的令牌直接丢弃。
当请求达到时,会尝试从令牌桶中去令牌,取到了令牌的请求可以执行。
如果桶空了,那么尝试去令牌的请求会被直接丢弃。
4:漏桶算法
漏桶容量固定,按照固定速率流出请求
可以任意速率流入请求
如果流入过快,会溢出对其请求。
限流形式:
1:单机限流,每个应用实例自己本机实现限流。
2:分布式限流,一个应用集群统一做限流。