这是我参与「第三届青训营 -后端场」笔记创作活动的的第5篇笔记.
高并发专题
透过现象看本质
应对高并发其实是个复杂的系统性的任务,需要考虑的因素很多。我们将以一种自上而下的思考方式来梳理一下什么是高并发、高并发的分类、如何应对等,并在梳理的过程中,进行一些相关知识点的串联。
通产意义上讲,我们每天上网(比如刷抖音、购物、看新闻等)其实是一次次的网络访问。简单来讲,网络访问由网络请求(Request)和网络响应(Response)。
经典面试题:浏览器中输入www.baidu.com,展示了网页,都经历了什么?
-
在浏览器地址栏中输入网址
-
浏览器获取这个网址之后,会先去缓存中看看有没有要访问的资源,从浏览器缓存-系统缓存-路由缓存中查看,如果有就不再进行http请求,直接从缓存中加载资源。
-
浏览器拿到域名自动去向DNS(域名系统)服务器发起请求,查询用户输入的域名对应的ip地址。
- 这一步很多幺蛾子:
- DNS优化 1.DNS缓存 DNS存在着多级缓存,从离浏览器的距离排序的话,有以下几种: 浏览器缓存,系统缓存,路由器缓存,IPS服务器缓存,根域名服务器缓存,顶级域名服务器缓存,主域名服务器缓存。 2.DNS负载均衡 DNS可以返回一个合适的机器的IP给用户,例如可以根据每台机器的负载量,该机器离用户地理位置的距离等等,DNS可以返回一个合适的机器的IP给用户,例如可以根据每台机器的负载量,该机器离用户地理位置的距离等等,
-
浏览器拿到ip地址后,通过Ip地址和端口号和服务器建立tcp连接(三次握手)
-
建立连接成功之后,浏览器开始向服务器发起http请求,并通过http协议将请求信息包装成请求报文(包含请求行、请求头、空行、请求体),然后通过socket发送到服务器。
-
注:url与uri的区别:
-
uri:统一资源标志符(Uniform Resource Identifier, URI),指示每一个资源,由三部分组成:资源的命名机制、存放资源的主机名、资源自身的名称
-
url:URL是URI的一个子集。它是Uniform Resource Locator的缩写,译为“统一资源定位符”。格式:protocol :// hostname[:port] / path / ;parameters#fragment
-
url格式由三部分组成:
①第一部分是协议(或称为服务方式)。
②第二部分是存有该资源的主机IP地址(有时也包括端口号)。
③第三部分是主机资源的具体地址,如目录和文件名等。
-
两者区别:URI和URL都定义了资源是什么,但URL还定义了该如何访问资源。URL是一种具体的URI,它是URI的一个子集,它不仅唯一标识资源,而且还提供了定位该资源的信息。URI 是一种语义上的抽象概念,可以是绝对的,也可以是相对的,而URL则必须提供足够的信息来定位,是绝对的。
-
-
后端处理完后发送报文给浏览器
-
浏览器按照HTTP协议将报文解析出来
-
浏览器拿到响应报文中响应体的数据开始渲染html、css,执行JS
-
如果在解析过程中(从上到下)中,发现有外链的标签(link、css、img),浏览器会自动对该标签的路径地址发起新的请求,同上。
什么是高并发?
定性:单位时间内,非常密集的网络请求。
定量:并发到底有多密集,一般用QPS来表示。
结果:如果没有好的应对措施或者架构建设,常见的结果是:用户发出网络请求,但网络响应延迟严重,甚至得不到网络响应。
应对高并发的难点在于,高并发来到的时候,高可用无法保证。
极端的例子:恶意的高并发:DDOS(Distributed Denial of Service)攻击。找很多ip抢占流量、耗费服务器资源。
高并发的分类
网络请求分两种:读请求、写请求
对应的高并发场景也可分两种:高并发读场景、高并发写场景(往往伴随有高并发读)。
应对和讨论高并发场景的时候,我们首先要分清我们是应对高并发读场景还是应对高并发写场景。
高并发读
现象:单位时间内,读请求的流量突然变大了;可能会导致服务响应变慢,甚至无法提供服务。

通常的应对思路
应对高并发读,通常的思路有分布式和缓存。
其他思路,还有类似限流、降级、熔断这种偏防御型策略。
分布式
关键词:More
思路:靠量取胜,一台不行就一百台,一百台不行就一千台。
分布式是一种思想或者架构。支持高并发的同时,带来高可用性。
常见名词:分布式系统、分布式集群、分布式计算、分布式存储、分布式锁、分布式事务。
分布式的存在,解决问题的同时,也极大地带来了服务端技术的复杂性。
分布式相关问题:
1.负载均衡:
a.常见的负载均衡算法:轮询(Round Robin)、随机、加权随机、最小连接数等。
b.动态的负载均衡算法:一致性哈希
2.分布式协议算法:Paxos、Raft、ZAB、Gossip
3.CAP理论、BASE理论
注:分布式和集群的区别
- 集群:很多人干一个活,所有人干一样的事
- 分布式:很多人干一个大活,每个人负责一部分小活
缓存
关键词:Better
思路:如果用铁锹不行,那用挖土机呢?如果DB撑不住,用缓存呢?
缓存的使用,利用了计算机存储介质访问速度的金字塔原理。
缓存也可以看作是一种思想,让更快的缓存来提供访问的结果。
缓存的使用在各个维度都有,到处都是。例如Nginx、浏览器、CDN、MySQL、操作系统等。
常见的缓存,内存(Local Cache)、分布式缓存(Redis Memcached)
缓存的使用,也会带来相关的复杂性。比如:
-
缓存的容量相对不大,需要提高缓存的命中率,让高频的热的数据存在于缓存中。
- 缓存的预加载和过期策略;
- 缓存的替换算法:LRU、LFU等;
-
缓存或者多级缓存的使用,当有数据写操作时,会涉及到数据如何同步。
- 缓存一致性问题
-
缓存失效情况的考虑和处理
-
缓存穿透:访问一个缓存和DB都不存在的key
-
接口鉴权:调之前先看你有没有权利调
-
缓存空值:缓存你攻击的key,value为空值
-
布隆过滤器:判断不存在的,则一定不存在;判断存在的,大概率存在;比HashMap节省空间==》
- 用三个哈希函数算三个值,输入的key如果没有重合则一定不存在,如果重合则可能存在(因为可能组合不同)
-
-
缓存击穿:热key过期,导致大量请求打到DB上=》热key不过期,关注更新策略
-
缓存雪崩:大量key或者热key同时或者密集过期,导致系统压力骤增,引起雪崩==》过期时间打散
-
-
缓存数据结构的选择、关注大KEY(会影响redis的响应速度)
以下是防御型措施:
限流
关键词:Limit
思路:既然处理不了那么多量,那就排个队~
限流通常是用消息队列来实现的,几种常见的限流算法:漏桶、令牌桶
漏桶算法
通过漏桶算法来进行限流,比如每10毫秒处理一次请求。因为处理的速度是固定的,请求进来的速度是未知的,可能突然进来很多请求,没来得及处理的请求就先放在桶里,既然是个桶,肯定是有容量上限,如果桶满了,那么新进来的请求就丢弃。
(连桶都放不下的请求就直接拒绝)
特点:没有流量红峰,而且对于突发流量一点变法都无,无法一定程度上应对突然增加的流量。
令牌桶算法
在令牌桶算法中,存在一个桶,用来存放固定数量的令牌。算法中存在一种机制,以一定的速率往桶中放令牌。每次调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择等待可用的令牌、或者直接拒绝。
令牌桶算法,除了能限制数据的平均传输速率外,还能允许某种程度的突发流量。==》之前有一段空闲累积了很多令牌的话,下一次请求可以拿所有空闲令牌。
降级、熔断
降级:在有限的资源情况下,为了能抗住大量的请求,就需要对系统的分支功能做出一些牺牲,有点“弃卒保帅”的意思。放弃一些功能,保证整个系统能平稳运行。
熔断:系统中,由于某些原因使得服务出现了过载现象,为了防止造成整个系统故障,从而采用一种保护措施,暂时“熔断”对下游的访问。所以很多地方把熔断亦称为过载保护,熔断一般还可以自动检测修复。
降级和熔断在严格意义上说不是应对高并发的,而是尽量保证在高并发下的高可用程度和万一出现问题的恢复速度。
高并发写
大部分高并发写场景,也会同时伴随高并发读,并且可能读的并发量比写请求的并发量还大。
高并发写需要注意的问题
-
幂等问题(其任意多次执行所产生的影响均与一次执行的影响相同)
-
数据一致性问题
- 写覆盖或写乱序
- 写与读之间的延迟
- 原子性(或者全部执行、或者全部不执行)
通常的应对思路
对于幂等问题,一般的思路是使用分布式锁(有一个共有的地方能够控制,只有一份有效);
对于数据一致性问题
- 写覆盖或写乱序:使用锁
- 写与读之间的延迟:保证最终一致性
- 原子性:使用分布式事务