千万级流量架构优化策略
一、导读
问题:
朋友自己做的项目稳步发展,但是受市场行情影响,访问量突然增多且不断地在进行增加,在这个情况下来咨询我应该怎么做。以下是我的一个简单思路,大家不妨一块来看一下。
做法:
1、了解现状
架构、业务特点;
2、给出一个最简单有效的应急方案
目标:改造时间短、可以短期内支撑住访问压力;
现状:
架构、业务都比较简单,只有一个热点业务。
技术架构:nginx+web集群(webserver)+主从数据库(MySQL)+Redis
业务:主要是一个热点业务。
方案:
1、CDN
减少静态资源的访问压力;(自动选择离用户最近的cdn故此能够减轻服务压力并提高用户体验度)
2、业务拆分
隔离热点,独立扩容;(将并发较高的业务独立拆分,使之即便挂掉了也不影响其他服务)
3、读写分离
分散数据库压力。(提高读写效率,对于读数据库进行负载均衡)
以上只是简单的处理,后期想要长期发展自然还需要进一步的做系统改造,咱们第一步先解决现有问题。
峰值QPS=(日总PV数*80%)/(日总秒数*20%)
这个公式的概念是:一般情况下每天80%的并发量集中在20%的时间内。
(PV*0.8)/(3600*24*0.2)
PV*0.8/17280
PV=1000,0000
1000,0000*0.8/17280=463
峰值为463
假设每台服务器能够承受的并发量是100,那么我们所需的服务器是
463/100=5
所以千万级服务器我们所需的服务器大概是5台服务器就足够了,当然这只是一个大概的值。
并且峰值QPS为100还有很大的优化空间,所以千万级的流量并不像大家所想的那么可怕!
二、策略
而我们所应考虑的是什么呢?
数据访问达到千万级后,访问量的快速增加所带来的架构变化。所以我们要在战略上藐视敌人,战术上重视敌人。
架构的设计以及演进是非常系统的,我们今天就以下几个点来重点聊一聊。
核心策略:拆分、队列、缓存、降级、限流等几个核心策略。
拆分:网站架构的演进过程,怎么从单机结构演进到异地多集群的分布式架构。方便大家更好的了解拆分的思想。
队列:咱们主要看一下,如何使用队列解决分布式事务的问题。
缓存:在更新数据时,你是应该先更新数据库呢还是先更新缓存呢?
降级:降级的场景以及如何做好降级。
限流:限流算法。
2.1、拆分策略
架构演进历程:混沌状态--->各自独立--->集群化--->分布式--->多集群部署--->异地部署
混沌状态:所有东西都放在一起,目标是快速起步。
各自独立:各司其职,资源独占。
集群化:增加人手,和工作分派角色。
分布式改造:业务分布式拆分,服务组件采用分布式体系。
集群和分布式的概念:
集群:多个人干活,每个人干的活都一样。每个人都负责全流程。
分布式:任务拆分来做,一个人负责一项,流程化协同。
多集群部署:使用硬件负载均衡器做集群间的请求分派。
异地部署:在多个地区部署,使用DNS根据用户地理位置就近分派。
拆分的维度 :系统维度、功能维度、读写维度等。
系统维度:例如,做电商项目中,就拆分了商品系统、购物车系统、订单系统、日志系统等。
功能维度:例如,电商平台中的优惠券系统,还可以拆分为建券系统、领券系统、用券系统。
读写维度:例如,商品系统,读的量非常大,比写多得多,那么可以拆分为商品读服务、商品写服务。
2.2、队列策略
队列的核心:异步、平缓的处理。
案例:流量削峰
什么是流量削峰:瞬间流量巨大,比如秒杀场景下,几万人购买几百件商品。
削峰的目的:让服务器请求更加平缓,保护服务器资源。
直接调用模式:多个调用者一起请求,流量大的时刻产生调用高峰,被调用者压力大,有崩溃风险。
消息队列模式:所有调用请求都去排队,即使高峰期也不会对被调用者直接产生影响。(将同步请求转化为异步请求)相当于一个水库。
案例:分布式事务解决方案
案例场景:用户成功下单后,为用户增加相应的积分。
直接调用的问题:如果积分系统故障,订单系统不知道,已经生成的订单不好处理。(积分未正常累计或订单撤销后积分也未进行扣除)
分布式事务:需要保证订单系统、积分系统的执行结果一致,都成功,或者都失败。
改进:使用消息队列,从同步调用改为异步调用,可以不关心积分系统的状态,保证最终一致性。
问题:订单系统应该先写数据库,还是先发消息?
改进:把消息写入本地数据库的消息表,与订单操作放在一个事务里面。
问题:如何把消息发到消息队列?
改进:一个定时执行的后台程序,从消息表中获取发送状态未“未发送”的消息,发送给消息队列,消息的状态为“已发送”。
问题:消息重复发送怎么办?
改进:积分系统负责保证幂等性(多次同样的调用结果一致)。
分布式事务总结:
1、上游系统要保证消息不丢,是通过本地消息表和后台定时程序来实现的;
2、下游系统要保证消息不重复处理,也就是保证幂等性,可以通过判重表来实现。
适用场景:分布式事务的提交或回滚只取决于事务发起方,也就是无需回滚。
消息队列中还有很多模式来处理问题,之后我们再慢慢了解哦!
2.3、缓存策略
缓存类型:
客户端(浏览器、APP客户端):适用于对实时性不敏感的数据,商品详情、评价、广告等。
网络(CDN):网站静态资源的缓存,提高对用户的响应。(按流量收费,比较耗钱)。
接入层(nginx代理缓存)、应用层(Redis)。
Cache Aside与Read Write Through策略
Cache Aside策略:
先更新缓存还是数据库?
假设先更新数据库:
假设先更新缓存:
都会产生数据不一致。
Cache Aside策略:更新数据时不更新缓存,删除缓存中的数据。读取数据时,如果缓存中没有数据,从数据库中读取,更新到缓存中。
Cache Aside读写流程:
在上面的场景下是否可以先删除缓存?不可以!!!——读写不一致。
Read Write Through策略
核心:用户只与缓存打交道,由缓存和数据库通信,写入或读取数据。注意,这里有一个关键角色,就是“缓存组件,感觉有点像仓库管理员。
Read Write Through读写流程:
各自都有自己的弊端,所以我们要根据项目中的实际情况来进行。
缓存雪崩与缓存穿透解决方案
雪崩:大量数据同时失效,数据库压力过大。
解决方案:
为有效期增加随机值;
使用高可用分布式缓存;
热点数据永远不过期;
在缓存失效后,通过加锁或者队列来控制读数据库的线程数量。
缓存穿透:缓存中没有,需要访问数据库,这就是缓存穿透了。穿透后需要查询DB,量大的话就会压垮数据库。
解决方案:
缓存空值;
布隆过滤器。
缓存空值处理场景:正常请求不存在的数据,可能其他请求也会读取这个数据,为防止多次无效访问数据库,我们可以把这个空值也缓存will,用户下次再请求时,就直接返回空。
布隆过滤器处理场景:大量恶意请求不存在的数据,即使缓存空值也无效,大量请求读取数据库,需要使用布隆过滤器,帮助我们在不查数据库的情况下就知道此ID是否存在,把恶意请求直接过滤掉。
什么是布隆过滤器?
是由一个二进制数组和一个hash算法组成。建议大家具体了解一下,咱们这里就不赘述了。
如何使用布隆过滤器来解决缓存穿透的问题?
1、写入数据时,更新布隆过滤器;
2、查数据时,先查询布隆过滤器是否存在,如果不存在就直接返回空,如果存在,再去查询数据库。
布隆过滤器优点:除了能够解决以上问题外,还比较节省空间。
布隆过滤器缺点:
1、由于hash冲突,存在误判。例如hash(张三)=4,hash(李四)=4。那么可以使用多个hash函数一起计算提高精度。
2、不能删除。可以使用int数组计算。
2.4降级策略
降级的应用场景
功能降级
电商平台很普遍的推荐功能,可以提升销量,但不是购物的核心流程。
在系统压力大的时候,可以降级改为默认内容。
写服务降级
在较大数据量时,不更新数据库只更新缓存,把要写的数据放到消息队列,流量高峰过了以后,把消息队列里的数据回放到数据库。
降级的定义:整体负载、流量>阈值,为了保证重要的服务能正常运行:
1、拒绝部分请求;
2、延迟或暂停一些不重要的服务、任务。
降级的作用:
降级是系统保护的重要手段,保证系统的高可用。
简单理解,降级就是丢车保帅,保证系统核心功能的正常。
降级的实现方式
手动降级:使用开关配置,对系统中可降级的服务都设置好开关项。
就像一个总闸,可以预先把非核心的功能关闭,也可以在监控系统发现问题时人工介入。
配置文件实现开关配置:适用于系统部署结构简单的场景。系统自动监控配置文件的变化,配置文件变化后自动重新载入。
配置中心实现开关配置:适用于分布式系统,有统一配置中心,服务开关在配置中心定义。在配置中心界面修改相应参数,自动通知相应服务。实现技术有:zookeeper、Redis、consul、etcd等。
自动降级:超时降级(对于非核心服务,如果长时间响应慢,就可以自动降级)、限流降级(当触发了限流阈值时,可以使用暂时屏蔽的方式来进行降级)、统计失败次数降级(在一个时间段内,调用失败率超出阈值,自动降级)。当程序遇到这些情况的时候自动进行降级处理。
降级后的处理方案:使用默认值、兜底数据、缓存数据、排队页面、无货通知、错误页面等。
2.5限流策略
限流的应用场景
为什么要限流?
热门的旅游景点、地铁都会限制人流量,防止超出其接待能力。
对于系统,在应对高性能压力的场景时,限流已经成为了标配技术解决方案,保证了系统的平稳运行。
限流就是对请求进行限制,例如某一接口的请求限制为100个每秒,对超过限制的请求则不处理。
保护系统的手段:
缓存:提升系统的访问速度,增大系统处理能力;
降级:暂时屏蔽,过后打开;
限流:对稀缺资源限制请求量,限速、拒绝服务。
限流的常用算法
固定时间窗口算法:对每个时间窗口内的请求进行计数,如果计数器超过了限制数量,则本窗口内后续的请求都会被丢弃。当时间到达下一个窗口时,计数器重置。(但是无法应对两个窗口间的突发流量)
滑动时间窗口算法:记录在时间窗口内每个接口请求到达的时间点,判断当前时间窗口内的接口请求数是否小于限流值。
令牌桶算法:令牌以固定速率生成,如果令牌桶满了则多余的令牌会直接丢弃。当请求到达时,会尝试从令牌桶中提取令牌,取到令牌的请求可以执行。如果桶空了,那么尝试取令牌的请求会被直接丢弃。
漏桶算法:漏桶容量固定,按照固定的速率流出请求,可以任意速率流入请求。如果流入过快,会溢出,对其请求
限流形式
1、单机限流,每个应用实例自己本机实现限流;
2、分布式限流,一个应用集群统一做限流。
以上,大致是千万级流量的优化措施,当然方法还有很多,之后根据大家需要咱们慢慢来看。