找到你系统中的瓶颈

引言:提到服务端编程,很多时候写的就是IF/ELSE,一些简单的CURD操作。作为服务端编程的同学,每天面对的很多是很简单的逻辑,简单的逻辑里面又有着不同寻常的事情。
海量请求的情况下,就会发生量变引起质变的过程。如何优化你的系统,优化你的接口,优化你的服务。让你的服务跑的更欢快。下面一起来讨论一下。

ROUND 1  最简单的系统

能想到的最最简单的系统,可能就是简单的静态网站了,只要有一个web服务器,就能够实现服务。
举个例子: 简单的静态宣传页面。大网站的静态页面。

静态页面的整个服务流程中,从请求的发起到正常的响应,整体流程很简单。

用户通过URL发起一个域名相关的请求; 请求到达DNS服务器,找到相关的ip地址。通过ip访问主机,获取数据,得到数据后访问设备(浏览器)通过数据去渲染。用户就看到了最终的呈现。

我们来假设一个系统,有一个某公司的宣传页面。是一个单纯的静态页面,部署于单机的一台静态服务器上面。页面上面设计比较多的静态资源(图片,js文件等。)
如果你发现你的静态单页面访问速度不是很理想,访问速度比较慢,加载比较慢等等问题。 能出问题的方向无非下面几个:

DNS解析时间过长: dns的解析时间。优化dns服务器,dns的解析是通过访问dns服务器来实现的,如果用户首次访问,会根据用户设置的dns服务器去查找记录,如果找到使用,如果找不到会逐层的递归查找,直到找到相关的结果,
一般的客户端会缓存dns的解析记录,再下一次访问的时候直接使用。这个跟域名的ttl有关系。
可以通过百度百科了解一下DNS的相关知识: https://baike.baidu.com/item/dns/427444

并发数量不够: 现在的网站多数都是基于http1.1进行访问,http1.1底层是使用TCP协议,由于http协议设计之初没有考虑太多的连接复用问题,会出现连接数不够用的情况。
虽然使用keep-alive可以解决一部分问题,但是tcp又有慢启动等问题。所以说对于访问来说。如果有很多资源需要下载,会出现不断下载资源的过程中拖慢整体网站的打开速度的情况。虽然这些属于前端的范畴,但是我们需要知道的事情的原因。
解决办法也很简单。一般的客户端对单独域名的访问时会有并发的请求限制。如果我们能够把资源分别放在多个域名上面,就能够解决一个域名下面的并发到极限的情况。更好的解决办法是使用HTTP2.0协议,因为2.0的协议支持连接的复用。 能够大大加快单独域名下面的资源访问速度。

流量与带宽问题: 试想一下,如果一个网站的整个页面资源有2M,而我们的带宽只有500K。那么最快也要4秒才能下载完整个网站。可能对于用户来说这样的情况并不存在,现在轻轻松松就实现了百兆入户了,但是对于服务端来讲。我们的出口带宽很大程度上是受到限制的,如果有多个用户在同一时间访问,那么就会出现带宽竞争的问题。假设一个用户占用了100K的带宽,那么有20个用户并发的情况下,2M 的带宽就会出现拥堵。解决这个问题,可以考虑增加带宽。

地域问题: 这个就不多说了,得益于我们中国的地大物博。如果服务部署于南部沿海地带。在中国北方来访问。多多少少都会有一些延迟,光速是300KM/ms  在加上路由的时间等 ,怎么也要个几毫秒。解决这个问题,还是通过CDN,或者就近部署。

COOKIE-FREE :

http协议里面规定,在客户端向服务端发送请求的过程中需要携带当前域名的cookie等一系列信息。放在请求头中。用于session保持等工作,cookie-free就是服务端用单独的域名来部署一些服务,这些服务不使用cookie,这样在每次客户端发起请求的时候请求头就能够得到有效的缩减。

很多大型一点的网站会使用图片单独域名的配置,这个也是能够很大程度上节约带宽的,因为图片可以控制客户端针对资源在客户端(浏览器)做缓存,在缓存生效期间,下次客户端在发送请求的时候就会使用304请求,可以大大减少传输的流量。同时实现cookie-free。对于量不大的访问。这样的流量可能不可观。但是如果图片等讲台资源很多,并且访问流量很大,这个流量就很可观了。

前端缓存和HTTP304

HTTP状态码304是一个协商请求,如果客户端在上次请求中已经缓存了相关的资源,在发现当前请求的资源没有过期的情况下,客户端发起请求的时候会携带当前资源的最后更新时间(IF MODIFY SINCE ),如果服务端发现当前资源没有过期,会直接返回状态码(304),同事携带当前资源没有过期(not modify)状态。 这样只有一些请求头的流量,请求体的资源流量就被节省掉了。

ROUND 2 简单的系统

简单的系统,有一定的动态逻辑,比如说我们在一个请求到来的时候会去读取数据库,最简单的。去数据库读取用户的一些相关信息,然后在前端进行展示。或者写入一些数据到数据库,然后返回处理结果。
比较典型的,博客系统。这里我们假设我们使用的是统一的机器模型,单核4G的实例。

这里假设我们的系统nginx作为web服务器,使用PHP语言 ,使用单机mysql来部署。每个服务部署于单核的一台机器上面。
在这个系统中,我们需要知道每个环节的极限性能。
作为一个服务端开发的同学需要知道的是在linux系统中的一些常用软件的性能。

nginx

得益于nginx的事件循环模型。nginx的整体效率很高,在单核心的情况下,nginx能够提供的可靠QPS 数量级在万级别上下。

动态语言:

以我常用的PHP为例,单核4g的场景下,基本上能够达到150QPS。这个时候虚拟内核的CPU使用量基本上在70%左右,内存稳定。

对于编译型的语言来讲,如GO ,java等  单机的性能极限能有1-3倍的增长。这里我们按照500QPS来衡量。

数据库

单机mysql的极限性能取决于磁盘的读取写入速度,也取决于数据库表的设计和索引的结构。影响很大。如果索引合理,分库分表合理,主键查询和写入的情况下,单核读取极限在3K左右 ,写入速度上千 基本就是极限了。

如果是SSD  可能效率会高一点,能够提升1.5-2倍,但是提升有限。

网络传输

要记得我们的数据库和动态语言还有前端服务器不一定是部署在同一台机器上面的,理想的情况我们需要把这几个服务都部署在相同的机房里面,这样就不会有跨地域的流量了。而且能够保证访问速度。所以在这个简单的模型里面,我们把网络的事情忽略掉,就认为都是内网流量,基本不会产生延迟。

这样我们获得了这个系统的各个环节的性能极限(单核情况下)。基本如下:

  1. 数据库 :读 2000qps/写 1000qps    典型  mysql
  2. 动态语言 :  处理速度100-200 qps  典型语言PHP
  3. 静态语言 : 处理速度500-1000qps    典型语言 go java
  4. web服务器  10000QPS             典型  nginx

如果按照一个请求只有一次数据库交互,那么整体服务器的瓶颈在数据库这里。  可以通过加入缓存来增加系统的整体io 。可用的系统过包括memceche。redis这些 。

ROUND 3 复杂的系统

如果在大公司工作,除了内部部署的相关系统是上面的拓扑结构外,不可能有公司会采用数据库,缓存,业务服务和web服务全混部的方案。
因为这样整个系统的可用性将得不到保证。比较合理的方案是。去细化各个服务,将每个服务都用一组服务器来承载。这样能够根据服务的特征来优化整体的性能。

拿数据库举例。如果我们的数据库系统有很高的读取流量,我们可以采用主从读写分离的架构来实现增加读请求的承载量。
如果写请求也很高。可以采用上层使用数据库proxy。下层使用多数据库分片的方式来实现。

web服务器来说,可以使用nginx来实现七层的负载均衡。vip的方案也是业内比较通用的解决方案之一。通过四层来做负载均衡。

业务服务器也能够进行横向扩展。

Show Comments
备案信息: 京ICP备20002019号