比较杂乱的笔记

Scroll Down

最大QPS推算及验证

RT

公式一:RT = Thread CPU Time + Thread Wait Time
RT(Response Time,响应时间)可以简单地理解为系统从输入到输出的时间间隔,系统是指一个网站或者一个其他类型的软件应用,或者指某个设备。所以RT是一个比较广泛的概念。服务器端RT的含义是指从服务器接收请求到该请求响应的全部数据被发往客户端的时间间隔。客户端RT的含义是指从客户端发起请求到客户端接收该请求响应的全部数据的时间间隔。需要注意的是,服务器端RT + 网络开销 ≈ 客户端RT。也就是说,一个差的网络环境会导致两个RT差距悬殊。
客户端的RT直接影响用户体验,要想降低客户端RT,提升用户体验,必须考虑两点,一个是服务端的RT,另一个是网络。对于网络来讲,常见的优化方式有CDN、AND和专线,分别适用于不同的场景。
对于服务器端的RT来说,主要看服务器端的做法。从公式一中可以看到,要想降低RT,就要降低Thread CPU Time或者Thread Wait Time。

单线程的QPS

简单地定义了RT由两部分组成,一个是CPU Time,另一个是Wait Time,如果系统里只有一个线程或者另一个进程,那么最大的QPS为: 单线程QPS = 1000ms / RT

最佳线程数

最佳线程数公式如下:
最佳线程数 = (RT/CPU Time) * CPU核数 * CPU利用率

最大QPS

QPS = 1000ms/CPU Time * CPU核数 * CPU利用率
决定QPS的是CPU Time和CPU利用率

  1. CPU Time CPUTime不只是业务逻辑所消耗的CPU时间,而是一次请求中所有环节上消耗的CPU时间之和。比如在Web应用中,一个请求过来的HTTP的解析所消耗的CPU时间,是CPU Time的一部分,另外,这个请求中请求RPC的encode和decode所消耗的CPU时间也是CPU Time的一部分。CPU Time是由下面这些因素决定:均摊问题,Hash问题,排序和查找问题,状态机问题,序列化问题。
  2. CPU利用率 以下因素会决定CPU的利用率:I/O能力,包括磁盘IO和网络IO。数据库连接池,并发能力 = PoolWaitTime / RT * PoolSize。内存不足,GC占用大量的CPU,导致给业务逻辑使用的CPU利用率下降。共享资源的竞争,比如各种锁策略,各种阻塞队列,等等。所依赖的其他后端服务QPS低造成瓶颈。线程数或者进程数,乃至编程模型。

压力测试最佳线程数和QPS的临界点

当线程数不断增加的时候,到达某个临界点之后对系统就开始产生负面影响了。
(1)、大量线程上下文切换的开销,引起CPU Time的增加及QPS的减少。所以,有时候还没有达到最佳线程数,而QPS已经开始略微下降了。因为CPUTime发生了变化,线程多了之后,调度引起的CPU Time提升的百分比和QPS下降的百分比成正比,上下文切换带来的开销如下:上下文切换。JVM本身的开销。CPU Cache加载。
(2)、线程的栈空间会占用大量内存,假设每个线程的栈空间是1MB,这么多线程就要占据数GB的内存。
(3)、在CPU Time不变的情况下,因为线程上下文转换和操作系统想尽力为线程在宏观平均分配时间片的行为,导致每个线程的Wait Time都增加了,于是每个请求的RT也增加了,最终导致用户体验下降。

TCP优化

TCP传输原理

TCP通过发送-应答(ACK确认)-重传机制来确保传输的可靠性,它是端到端进行传输的,对于大型网站,响应报文从服务器端给客户端浏览器进行报文的传输,所以在服务器端,可以通过优化来降低响应给客户端的网络延时。

TCP传输的简要说明

TCP传输是分段的,一个HTTP响应报文会被操作系统切成多个MSS大小(一般为1460B)的段,发送端每次只会发送若干段,能够发送多少个数据包,由拥塞窗口和接收端窗口共同决定,直到接收端接收到完整的报文为止。在此过程中,报文分段按照顺序进行发送,每个报文段在发送时,会做顺序编号,以便能够完整地组装,所以当HTTP的请求响应模型将请求发送给服务器时,服务器端都需要多个RTT的传输,物理距离越远,总体网络耗时越长。

滑动窗口-接收端流量控制

滑动窗口本质上是描述接收方的TCP数据报缓冲区大小的数据的,发送方根据这个数据来计算自己最多能发送多长的数据。如果发送方收到接收方的窗口大小为0的TCP数据报,那么发送方将停止发送数据,等到接收方发送窗口大小不为0的数据报的到来。
TCP就是用这个窗口,慢慢地从数据的左边缘移动到右边缘,把处于窗口范围内的数据发送出去(但不用发送所有数据,只是处于窗口内的数据),这就是窗口的意义。

拥塞窗口-发送端流量控制

接收端流量控制在局域网内做流量控制是可行的,但是在公网上就会出现问题。网络数据包在传输过程中,要经过很多路由器的转发,而这些路由器的带宽和缓冲区大小是不确定的。路由器的缓冲区太小,会造成数据包大量堆积甚至拥塞,而接收端的缓冲区一般很大,此时会造成大量的网络拥塞,从而加剧整个网络的拥塞,甚至造成整个互联网不可用。为了解决这个问题,TCP发送方需要确认连接双方线路的数据最大QPS是多少,这就是所谓的拥塞窗口。
拥塞窗口的原理:TCP发送方首先发送一个(或者数个)数据报文段,然后等待对方的回应,ACK返回后就把这个窗口的大小加倍,然后连续发送两个数据报,对方回应后,再把这个窗口加倍,这个机制就是发送端拥塞控制的慢启动算法。
对慢启动算法的简单理解:先发送少量的数据报文段,得到确认后,再将发送报文段的个数增加,直到出现超时错误或者丢包;发送端因此了解到网络的承载能力,也就确定了拥塞窗口的大小,发送方就根据这个拥塞窗口的大小发送数据。在下载文件时,一般开始比较慢,后来会慢慢加快,直到达到一个稳定的速度。
拥塞避免:当发生超时没有得到对方的确认时,发送的报文段个数会线性增长,这就是拥塞避免阶段。

TIME_WAIT 问题案例分析

通过netstat统计命令查看TCP连接的使用情况,TIME_WAIT一般是由主动关闭段产生,可以确定是短连接。
简单描述一下TCP连接关闭的4次挥手。TCP运用了可靠连接关闭,即通过双方的确认后再关闭连接,避免双方因不知道连接关闭造成业务问题。如果客户端主动关闭连接,客户端先发送FIN包给服务端,服务端接收到FIN包,回应ACK给客户端,表示服务器端已经准备关闭连接了,此时服务器端将连接状态设置为CLOSED_WAIT,为了确认回应给客户端的ACK已经收到,服务器端再发送一个FIN包,服务器端的连接此时处于LAST_ACK状态,客户端接收到服务器端发送的FIN包之后,将连接状态设置为TIME_WAIT,TIME_WAIT状态存在的主要目的是防止迷途报文重现,影响新连接,所以要等待足够长的时间。特别是在公网传输过程中,有时发送报文后,如果接收端一直没有收到,那么可能会出现ACK不能回应给发送端的情况。客户端将FIN ACK回应给服务器端,服务器端收到后,将连接设置为CLOSED状态,此时将不再接收客户端发送的任何报文。客户端经过2个MSL大约60s将连接也设置为CLOSED状态。
问题解决方案:
开启TIME_WAIT状态的TCP连接重用,可以看到TIME_WAIT状态的连接数量大幅下降,TIME_WAIT状态的连接数量取决于连接的使用情况:

vi /etc/sysctl.conf

编辑:

net.ipv4.TCP_tw_reuse = 1
net.ipv4.TCP_tw_recycle = 1

运行生效;

/sbin/sysctl -p

DNS优化

DNS基本原理

  1. 根域名服务器(简称根域):根域名服务器是互联网域名解析系统(DNS)中最高级别的域名服务器,负责返回顶级域名的权威域名服务器的地址。全球共有13个根域名服务器,共504个域名服务器(NameServer,NS),分散再世界各地,大部分采用Anycast技术,对外宣告同一个IP地址。在DNS查询过程中,由路由层面寻找到具体承担解析任务的NS进行就近解析,以便提高解析效率。根域名服务器存放了顶级域的NS列表。
  2. 顶级域名服务器(简称顶级域):通常顶级域名指的是com(商业机构)、net(网络提供商)、edu(教育机构)、cn(中国域名),顶级域名服务器是互联网域名解析系统中次高级别的域名服务器,负责返回权威域名服务器的地址。同样,全球共有13个.com顶级域名服务器。
  3. 权威域名服务器(简称权威域):一般是指能够提供最终IP地址解析能力(最终IP地址是由这个NS来解析的)的一组服务器,或者能够控制最终IP地址解析结果的域名服务器(由权威CNAME给其他的NS来解析)。
  4. Local NDS:用户上网需要通过LSP的网络接入互联网,ISP会分配给用户一个DNS服务器,该服务器被称为Local DNS,这个DNS代理域名解析请求给最终的权威DNS,它具有Cache的能力,如果Local DNS在TTL时间内有Cache,不会迭代向根域、顶级域和权威域发送DNS查询请求。
  5. DNS no glue:在顶级域名授权中心注册一个新的域名时,需要提供(权威)NS列表,通常是一组域名。如果只是提供NS的域名,而没有NS的IP地址,那么在DNS解析过程中,需要解析NS的域名,并最终得到NS的IP地址,这样UDP查询才能知道把域名解析请求发送给谁,这种只是提供域名、没有提供IP地址的方式称为no glue。
  6. DNS TTL:简单地说,TTL(Time-To-Live)表示一条域名解析记录在DNS服务器上缓存的时间。当各地的DNS服务器接收到解析请求时,就会向域名指定的DNS服务器发出解析请求从而获得解析记录。在获得这个记录后,记录会DNS服务器中保存一段时间,这段时间内如果再接到这个域名请求,DNS服务器将不再向域名指定的DNS服务器发出请求,而是直接返回刚才获得的记录,这个记录在DNS服务去上保留的时间就是TTL值。

DNS查询的过程

(1)、终端用户的浏览器发起DNS请求,发送到ISP提供给用户的DNS服务器(相当于由代理的DNS解析服务器迭代权威服务器返回的应答,然后将最终的IP地址返回给客户端浏览器)。
(2)、ISP的DNS服务器收到域名解析请求,将域名解析请求发送给其选定的根域名服务器(总共13个,由于根域名的13个地址几乎不发生变化,所以在Local DNS的软件配置文件里内置了13个根域名)。
(3)、根域名服务器收到域名解析请求,发现是来源于.com顶级域名的请求,于是将.com顶级域名服务器列表和IP地址返回给Local DNS。
(4)、Local DNS根据一定的策略选定.com的一个NS,并且将www.xxx.com的域名查询请求发给这个NS。
(5)、选定的.com的NS接收到www.xxx.com的域名查询请求,把在它这里注册和登记的aaa.com的NS列表和IP地址返回给Local DNS。
(6)、Local DNS根据一定的策略选定aaa.com的一个NS,并且将www.aaa.com的域名查询请求发送给这个NS。
(7)、选定的aaa.com的NS接收到www.aaa.com的域名查询请求后,将www.aaa.com的别名www.aaa.lxdns.com返回给Local DNS。
(8)、要解析www.aaa.comlxdns.com仍然要有完整的迭代过程。首先还是要把解析请求发送给根域名服务器(根域名服务器的TTL通常为几天),然后Local DNS在本地的服务器列表缓存中选择一个顶级域名服务器,并且将请求发送给该服务器。
(9)、选定的.com的NS接收到www.aaa.com.lxdns.com的域名查询请求后,将NS列表和IP地址返回给lxdns.com。
(10)、将www.163.com.lxdns.com的域名解析请求发送给Local DNS选定的NS。
(11)、lxdns.com的NS接收到www.aaa.com.lxdns.com的域名解析请求后,将别名aaa.xdwcache.glb0.lxdns.com返回给Local.DNS CNAME。
(12)、要解析aaa.xdwscache.glb0.lxdns.com这个域名,首先要知道lxdns.com的NS地址。因为之前访问过,所以Local DNS有缓存,那么就将这个解析请求发送给选定的其中一个lxdns.com的域名解析服务器。
(13)、选定的lxdns.com的域名解析服务器接收到域名解析请求后,将A地址返回给Local DNS。
(14)、Local DNS再将收到A地址返回给客户端。

CDN优化

CDN优化概述

CDN的中文全名是内容分发网络,其功能是将内容发布到离用户最近的服务器上,有效地避免网络拥塞(越远的距离越容易遇到拥塞)。大型网站一般分布较广,用户地域跨度大,而网站机房的位置离用户的距离有远有近,CDN提供就近访问能力,消除了由于用户离机房的距离不一样带来的体验差异,是大型网站不可缺少的基础组件,CDN的优化对于提供高性能的用户体验起到了关键的作用。
CDN对于一个大型网站而言,主要提供了6种能力:

  1. 静态加速能力:通过本地化缓存加速能力给用户提供一个尽力而为的就近访问的高性能访问架构,将用户访问的内容缓存在边缘节点上,消除由用户地域差异而导致的用户体验不一致,提供不同地区用户的相对一致的高性能访问体验。
  2. 卸载源站能力:CDN将资源缓存在它的服务器上,访问是在用户和CDN之间进行的,原来用户的直接请求都发送到网站服务器上,移交到CDN之后,源站的访问量和带宽占用都会大幅度减小。特别是对大型网站而言,图片等静态资源占了网站所有请求的90%以上。图片访问量对于大型网站来说是巨大的,服务器要提供具备相应吞吐能力的服务,其架构设计,运维规划、监控和预警十分完善,否则很容易出现稳定性问题。
  3. 防攻击能力:一般比较成熟的CDN提供商至少有数百个CDN节点,甚至数千个,而把资源放在CDN上,对网站的恶意攻击大部分都会将目标放到CDN节点上,CDN是一个天然的跨地区甚至跨洲的大型分布式系统。大量CDN节点的存在,可以有效地将攻击由中心化分散到CDN的边缘上,从而有效地阻止或者减小攻击造成的危害。
  4. 动态加速能力:CDN提供静态加速能力的原理是通过将资源缓存在CDN边缘节点上,让用户访问资源的网络距离变短,从而实现性能的优化。CDN不仅适用于可缓存资源的静态加速,而且可以用于动态请求的加速,其原理是通过7层路由路径的优选,克服BGP选路的缺点,实现动态加速能力。
  5. 用户访问序列优化能力
top -p 15577 -H

(1)、页面内容资源预取:获取网页的HTML后,CDN服务器会将HTML中的图片,CSS和JS标签的资源提前预取到CDN的边缘节点上,等浏览器下载序列到相对的资源时,边缘节点上已经存在该资源从而避免回源站处理并获取资源,减少性能损耗。
(2)、页面间资源预取:页面间资源预取指的时在用户访问当前网页时,将要访问页面的资源提前预取到CDN边缘节点上,等浏览器下载序列到相对的资源时,避免回源或者从CDN二级缓存节点上获取资源,以减少网络延迟。
6. 定制化模块开发能力:CDN不仅提供各种标准功能,而且提供定制化的功能开发,这些功能模块中有不少已经标准化,例如边缘化的图片压缩,边缘化图片格式转换,自适应图片下载等功能。

CDN的相关术语

  1. 边缘服务器:对于边缘服务器,CDN提供了就近访问能力,边缘服务器节点就是实际提供给用户就近连接,访问的服务器。
  2. CDN命中率:CDN一般提供的是静态加速能力,静态加速能力通常通过缓存架构来实现,CDN命中指的是CDN服务器有该资源缓存存在,请求到达CDN节点时,CDN服务器可以在本地缓存获取资源直接返回给客户端,如果没有命中,则需要通过CDN节点到源站获取资源。CDN命中的概率即CDN命中率。
  3. 回源:当CDN没有命中缓存时,需要到源站去获取资源,这个过程称为回源,回源需要从CDN节点层层代理访问,最终到源站获取资源。
  4. 中间层服务器:边缘节点比较分散,因此存在缓存穿透的问题。为了避免回源引起的性能大幅下降,在CDN的中间层服务器中将多个CDN节点的访问进行收敛,从而大幅提高命中率。
  5. L2 Cache:L2 Cache也是CDN的中间层服务器,通常也称Midgress Server为二级缓存,L2 Cache通常空间更大,是多个CDN边缘节点的收敛层。Edge Server被称为L1 Cache,L2 Cache层是CDN架构避免回源的常用组成部分之一。
  6. 卸载率:CDN命中率通常也称为写在率,表示卸载源站访问的程度。

从应用看CDN的基本原理

CDN基本架构

用户访问图片的一般路径如下:
(1)、在DNS Lookup时,由全局负载均衡器调度将离用户近的节点发给用户的浏览器。
(2)、浏览器与CDN的边缘节点建立TCP连接,并将请求发送给边缘服务器。
(3)、如果边缘服务器没有命中缓存,会将请求代理给CDN的L1 Cache节点。
(4)、如果CDN的L1 Cache服务器也没有命中,会将请求继续发送给源站。
注意:CDN L1Cache节点通常将相邻的几个CDN节点的请求进行收敛,当同一资源在一个节点预热过,又从另外一个节点访问时,可以在L1 Cache层命中缓存,避免回到源站去获取资源,从而避免性能大幅下降。
(5)、源站接收请求,并通过源服务器的处理,给CDN L1 Cache服务器响应,直到最终将响应逐层返回给用户端浏览器。
简单地说,用户浏览器获取图片或者静态资源的一般过程是,先通过CDN的全局负载均衡器的调度获取离用户最近的CDN节点的VIP(虚拟IP),并建立TCP连接,发送请求给CDN的边缘节点,如果在CDN节点上没有命中缓存,请求会逐步传递给最终的源服务器,并最终由源服务器回应给用户浏览器,并在各个CDN节点上建立各自的本地缓存。

CDN全局调度

CDN全局调度的基本结构和全局负载均衡器的实现原理比较简单,它能够接收在域名查询请求时,根据用户IP地址的(Local DNS)来源,返回给Local DNS和用户IP地址区域匹配的CDN边缘节点的VIP。通常一个地区有两个CDN集群节点,两个CDN节点同时给不同的用户提供服务(容灾和负载均衡)。为了防止DNS Cache过长,导致一个节点过载,通常CND节点的TTL控制在一分钟以内,当一个节点发生问题时,可以在比较短的时间内切换到另外一个节点。
全局调度器从本质上说是一个智能的DNS解析工具,无非是做得更加智能,功能更加强大。全局调度器主要有3个元素:Regin(区域)、POOL(CDN节点池)、Member(CDN节点 VIP)。当域名查询请求发送给全局调度器时,全局调度器能够匹配用户的IP地址(一般是Local DNS的IP地址)和Regin的对应关系,找到对应的POOL,根据Score(分数)的大小,找到合适的POOL,再通过POOL找到对应的Member,Member里面的VIP根据配置的权重,最终向用户返回CDN节点VIP。

CDN的基本调度方式

CDN提供的是尽力而为就近调度的架构,并不一定是就近调度。

  1. 基于Local DNS的静态调度:静态调度是CDN调度的基本方法,全局调度器根据Local DNS 的IP地址(或者终端机器的IP地址,EDNS协议),在其配置里面找到对应IP地址所在的Regin,并最终找到合适的CDN节点。
  2. 基于RTT的调度:基于RTT(Round Trip Time)的调度,是全局调度器根据Local DNS的IP地址,将预调度、多个候选的CDN节点和Local DNS之间的RTT进行比较,全局调度器会将RTT短的CDN节点调度给用户。
  3. 基于成本和带宽的调度:基于成本和带宽的调度是指全局调度器根据CDN节点出口带宽的大小来决定使用哪个CDN节点进行访问。基于成本的调度是指CDN厂商从全局出发,在某些业务少的地区,调度器将访问调度给CDN全局分布点较多的地方。因为往往不会考虑在业务少的地区投入过多的硬件成本。
  4. 基于服务等级的调度:基于服务等级的调度是指,CDN服务提供商为了保障服务等级更好的网站客户的访问,通常会将网络延迟更小(用户访问延迟小)、运行更加稳定的节点给这批用户。

CDN优化常见策略

静态化缓存优化

CDN提供的最基本功能是静态缓存功能,所以Cache服务器是CDN软件系统的标配。从软件架构上来说,一般分为3层。
第一层,高效的4层负载均衡:众所周知,LVS和F5提供了高效的4层负载均衡,4层的效率高主要在于解包的速度快,在OSI 7层网络模型中,TCP在第四层,当HTTP请求包发送到服务器时,基于4层的负载均衡器能够快速将包转发,并且起到均衡负载的作用。
第二层,基于命中率而设计的7层负载均衡。这设计可以针对HTTP相关的属性进行负载均衡,如Cookie、URL、Method,甚至Parameter,而CDN一般针对的是HTTP访问网站的加速,所以7层负载均衡能够根据HTTP保温里面的内容进行负载分担,分担LVS通过NAT转发过来的数据包。如果直接到缓存服务器,由于LVS一般只提供有限的负载均衡方法。不能保证高命中率,同一个HTTPURL,不能保证一台缓存服务器的命中率特别高,特别是针对一些近似长尾的访问。为了解决这个问题,中间架设了7层转发服务,它能够根据URL运用哈希算法,从而保证同一个URL基本上都在同一个服务器上访问,进而保证了最终的命中率。
第三层,本地化Cache服务器。

动态内容静态边缘化

通常CDN静态加速的对象是图片、JS、CSS等静态资源,静态资源具有可以被缓存的特性,在一定时间内不会更改,对实时性要求也比较低,同时也比较容易通过修改URL来获取页面的最新内容。由于动态页面本身的特别要求等,一般都需要从源站获取内容,但是在某些场景下,页面大部分的HTML内容可以缓存,而实时的部分可以用CSI或者ESI来实现。动态HTML因为涉及首屏,白屏等用户体验相关的内容,所以动态内容如果能放在离用户比较近的地方,可以大幅提升用户的体验。

  1. CSI:所谓的CSI其实就是通过浏览器端发起的Ajax请求,从源站获取实时性和一致性要求高的数据,例如价格信息,网页的其他部分全部可以缓存在CDN的边缘服务器上。CSI是通过浏览器客户端获取实时性要求高的数据的。
  2. ESI:ESI通过使用简单的标记语言来区分可以缓存和不可以缓存的片段并进行描述,不可以缓存的部分通过边缘服务器实时地从源站获取,可以缓存的部分从边缘服务器本地缓存中获取,并在返回给用户端浏览器之前,组装成完整的HTML给浏览器。通过这种控制,可以有效地减少从服务器抓取整个页面的次数,只从源站中提取少量不能缓存的片段,因此可以有效地降低从源站的负载,同时缩短用户访问的响应时间。ESI是一个简单的标签。第一次CDN缓存没有命中时,从源站获取HTML内容(源站不渲染ESI标签的内容),获取如上的HTML内容后,将内容缓存到本地,再将ESI标签SRC属性中的URL解析出来,并发起对这个URL的访问,源站响应的内容和本地的内容进行拼接和组装后返回给浏览器。第二次访问时,CDN边缘服务器只要将ESI标签的内容解析出来,并发起请求将动态内容和本地缓存中的内容组装后返回给浏览器。ESI实现的HTML内容的缓存由CDN的边缘服务器负责动态内容和静态缓存中的内容的组装。当页面需要依赖SEO引流时,必须使用ESI技术,因为搜索引擎的爬出无法获取异步的Ajax的内容。
  3. ESI和CSI的使用场景:CSI为了满足实时性要求高的业务要求,通常将实时性的内容通过浏览器端发起请求到源站获取内容,相比ESI来说,用户能更快地看到网页内容。ESI实际上有一部分内容需要回源,缓存的内容在CDN的边缘服务器,响应给浏览器的内容是在CDN边缘节点或者二级节点进行拼接的。在容错方面,ESI更强,如果回源部分的请求出现404错误或者500错误,CDN上可以处理成相应的错误码和内容给浏览器。例如,在发生404错误时,CDN需要忽略缓存内容,直接响应给浏览器源站响应的404错误内容。CSI是CDN先将缓存的内容响应给浏览器,然后浏览器发起实时的部分请求,再通过修改DOM节点的内容进行更新。但是一旦Ajax请求发生问题,当前页面不会发生变化,如果是管家内容,如折扣信息或者价格信息,将无法弥补。另外,如果实时性要求很高的内容通过CSI来获取,网页会出现闪烁的效果,特别是源站返回慢的时候,这种问题将更加明显。
    综合来说,ESI更适合于对重要性内容需要实时化和强一致性的内容静态化的解决方案,CSI的实时处理部分通常适合对网页重要内容不做更改的情况,如业务打点请求,使用ESI还是CSI需要考虑对用户体验的影响,实时部分的重要性,以及容错处理能力,但是ESI的处理过程相对复杂,改造成本相对较高,源站需要自己实现HTML的拼接逻辑,并做各种容错处理。
    综上所述,两者比较如下:
  • ESI实现成本较高,容错能力强,页面加载性能较差。
  • CSI实现成本较低,容错能力弱,页面加载性能较好。
动态加速优化

动态加速的概念是相对静态加速而提出的,传统的静态加速一般指的是,通过CDN的边缘服务器缓存资源,让用户访问时可以直接到离用户最近的服务器上获取缓存的资源。所以在静态加速过程中,资源具有可以被缓存的特性,也就是说,在缓存的时间内,只需要回源站获得资源一次,然后就可以到边缘服务器上获取资源,从而起到加速的作用。
使用动态加速进行优化时,用户的请求仍然会发送到源站,它将通过某种方式减少源站端到用户端的网络延迟。在网站访问过程中,大家都知道从请求发送给服务器、服务器响应,到浏览器接收,大部分时间都消耗在网络上,服务器端的耗时一般比较短,在机房内部的处理,无论是CPU的处理时间还是机房内部的网络转发,丢包率都比较低,耗时都比较短。这和报文传输机制相关,无论是发送报文还是接收报文,都需要经过网络层、TCP传输层、网络转发需要经过BGP路由选路的过程,实际的地理距离和网络距离存在很大差距,这回造成网络转发的延迟时间变得比想象中要长很多。TCP传输带来的网络延迟和TCP处理机制相关,特别是TCP的拥塞处理机制,以及TCP为了保持可靠性而设置的TCP重传机制。TCP一般都是在TCP重传定时器溢出时重新发送丢失的报文,而TCP重传定时器的超时时间如果很长,同样会造成很大的网络延时。
从本质上来说,无论是静态加速还是动态加速,大部分都通过减小网络延迟来进行加速。静态加速是通过把资源缓存在距离用户最近的地方来实现优化的目的,动态加速是通过优化传输,优化网络转发等方式来实现优化的目的。
静态加速一般都是静态文件,如图片、JS、CSS、HTML,这些类型的资源是允许有缓存的,在缓存时间内,内容很少或者几乎不发生变化,而一些动态请求,如强一致性的读/写操作等,都是不能缓存的。这个加速术语动态加速范畴,所以动态加速的请求具有每次请求回源的特性每次请求都会到达源站,不会改变用户到机房的地理距离,但是会减少网络上的距离。

CDN优化实战

一个静态资源耗时长达数秒以上,先要对静态资源的访问路径进行分析,找到链路上的瓶颈,另外可以根据相似性原则进行问题排查。所谓相似性就是问题同时有多个现象,但是这些现象后面的原因有可能是一样的,所以从一个现象入手同样可以发现问题的瓶颈。
304请求问题的基本原理:浏览器在访问同一资源时,会向服务器发送请求,让服务器判断是否用浏览器本地缓存的资源,服务器如果发现客户端本地缓存的资源是最新的,那么会响应304请求给浏览器,告诉浏览器可以使用本地缓存的资源,这样可以减少网络的消耗。
浏览器第一次访问某个资源时,会在响应头里面加入Last-Modified字段,表示此资源在服务器上最后的更新时间。当浏览器第二次访问同样的资源时,在请求头里面加入If-Modifyed-since字段,这个字段的值是第一次请求服务器给浏览器的Last-Modified,服务器以这个时间和文件的最后更新时间做比较,如果浏览器记录的时间比服务器记录文件的更新时间早,说明文件内容发生了变更,服务器会给浏览器最近的资源,如果文件内容没有更新,则给浏览器304的响应,不带文件内容。所以从304类型的请求来看,由于没有文件内容需要下载,因此耗时长的原因可能是跟CDN的特殊处理过程有关系。

系统监控

CPU资源监控

CPU是操作系统的调度中枢,是影响服务器吞吐量的最重要因素。服务器峰值的处理能力取决于CPU的利用率和CPU的处理时间。所以监控CPU的使用情况,能够获知系统瓶颈的变化,以及在发现问题时是否可以通过水平扩展机器数来解决问题。如果一个系统或者应用过载,CPU的利用率很高,CPU处理时间很长,说明系统的瓶颈在CPU上,这时可以通过一些方式来优化CPU的使用来提升系统的吞吐量。

  • CPU利用率:指的是进程中CPU消耗的时间。在Linux内核的操作系统中,进程是根据虚拟运行时间(由进程优先级、nice值加上实际占用CPU时间进行动态计算得出)进行调度的。在执行进程时,需要从用户态转换到内核态,用户空间不能直接操作内核空间的函数。通常需要利用系统调用来完成进程调度,而用户空间到内核空间的转换通常是通过软中断来完成的。例如要进行硬盘操作,用户态需要通过系统调用内核的磁盘操作指令,所以CPU消耗的时间被分割切分成用户态CPU消耗,系统CPU消耗,以及磁盘操作CPU消耗。执行进程时,需要经过一系列的操作,进程首先在用户态执行,在执行过程中会进行进程优先级的调整,通过系统调用到内核,再通过内核调用,硬中断、软中断,让硬件执行任务。执行完成之后,再从内核态返回给系统调用,最后系统调用将结果返回给用户态的进程。
  • CPU us 利用率:进程在用户空间消耗的CPU占比,通常指的是一个监控周期内应用进程的CPU消耗占比。
  • CPU sy 利用率:进程在内核空间消耗的CPU占比,称为PRI。PRI是比较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高。
  • NI:就是我们所说的nice值,它表示进程可被执行的优先级的修正值。PRI值越小越快被执行,加入nice值后,将会使得PRI变为PRI(new) = PRI(old) + nice。由此看出,PRI是根据nice排序的,nice越小PRI越靠前(小,优先权更大)则越快被执行。若nice相同,则进程UID的root的优先权更大。在Linux系统中,nice值的范围从-20到+19(不同系统的值范围是不一样的),正值表示低优先级,负值表示高优先级,值为零则表示不会调整该进程的优先级。具有最高优先级的程序,nice值最低,所以在Linux系统中,nice值为-20表示一项任务非常重要;与之相反,如果任务的nice值为+19,则表示它是无私的任务,允许所有其他任务比自己享有更大使用份额的CPU时间,这也就是nice名称的由来。
  • ID:进程在计算CPU消耗时,如果CPU处于闲置状态,那么记入闲置CPU,即ID。
  • WA:进程在等待磁盘时的CPU消耗,这个值越大,说明磁盘处于越繁忙的状态。
  • HI:硬中断,指的是和系统相连的外部设备自动产生的,主要用于通知操作系统外设状态的变化,如网卡收到一个数据包,就会发出一个中断。硬中断CPU使用占比指的是处于处理硬件中断的CPU消耗的占比。
  • SI:软中断,在CPU获取硬中断时,CPU把结果放在寄存器中,操作系统内核会启动ksoftrip进程来获取操作结果,这个过程叫作软中断。软中断值高通常是网络设备引起的。
  • ST:一般服务器部署都会使用虚拟技术,一个物理机会虚拟出多个虚拟机,当消耗CPU的应用和其他应用部署在同一个物理机上时,会发生窃取现象。如果发生这种问题,可以调整物理部署,尽量将同一个类型的应用部署在一个物理机上,以确保流量相对均衡。
  • CPU Load:包括Load1、Load5、Load15,指在特定的时间间隔内运行队列中的平均进程数。

在CPU监控中需要特别注意下列问题。

  • Runnable状态不等于Running状态。
    处于Running状态的进程数量始终小于等于CPU的核数,但是CPU Load指的是Runnable状态的进程数,这就是我们可以看到CPU Load的数量远大于CPU核数的原因。所有不被调用sleep、wait、stop、yield方法的进程都是Runnable状态的,这些任务都在基于红黑树数据结构的可执行队列上。
  • UNINTERRUPTIBLE任务被计入CPU Load
    UNINTERRUPTIBLE任务在Linux内核实现过程中仍然放置在红黑树里面,不会被调度到等待队列,把它计入CPU Load。TASK_UNINTERRUPTIBLE状态对进程进行保护,以避免进程于设备交互的过程被打断,造成设备陷入不可控的状态。这种情况下的TASK_UNINTERRUPTIBLE状态是非常短暂的,通过ps命令基本上不可能捕获到。TASK_UNINTERRUPTIBLE在大量读取磁盘的时候又总能够轻易被捕获到,这类进程kill -9杀不掉,而且可以肉眼轻易看出它被累加到Load的值里面。
  • 网络等待的进程(或者任务)在等待期间不消耗CPU资源
    代码调用wait或者sleep方法,CPU资源将被释放,远程调用等待服务器响应期间,客户端的CPU资源不会被消耗,所以在优化远程调用时间时,如果遇到了CPU是瓶颈,则优化RT对提升峰值QPS并没有帮助。

CPU监控的实现内核中有个系统定时器周期性地更新系统运行时间,更新实际时间,检查当前进程是否用尽了自己的时间片,更新资源消耗和处理器时间的统计值。系统定时器以固定频率产生中断,越高频率的时钟中断意味着越精确的进程抢占,越精确地执行依赖定时值执行的系统调用。系统定时器的每次中断称为tick,如果内核始终频率是100HZ,那么每秒的tick为100,每10ms时钟中断一次。每次tick,如果CPU正在执行用户态的进程,那么用户态的CPU消耗加1,如果CPU正在执行内核态的进程,那么system的CPU消耗加1,如果CPU在等待磁盘响应,那么IOWait的CPU消耗加1,如果CPU处于闲置状态,那么Idle的消耗加1。

大型网站容量评估

容量评估概述

容量评估是指通过一定的测量、推理方法来确定网站的容量现状,为运维预算、运维设备采购和解决系统瓶颈问题进行预警,并最终作为系统的伸缩性和水平扩展性的依据。简单来说,容量评估解决的是机器扩容、什么时候扩容的问题。
容量评估是对网站目前就能提供的容量上限的清晰了解,是对机器的容量进行相对精确有根据的评估的过程。
在大型网站的运营过程中,会经常进行各种平台组织的促销活动,一旦进行活动,一般都需要经过比较合理的评估以确定需要扩容多少台机器,才能满足容量的需求。一般的容量评估需要经过两个过程:

  • 单机峰值QPS的评估(单机峰值吞吐量)。
  • 评估每个集群需要的容量。包括每个应用、中间件需要提供的峰值QPS(吞吐能力),通过订单、PV和UV等关键数据推导出各个应用、中间件、数据库和存储所需要的容量。

单机峰值QPS的测算

单机峰值QPS是计算机器数的一个重要因子,其大小体现了单机吞吐能力。总体单机吞吐能力的测算过程是通过逐步压力测试来完成的,当出现资源瓶颈时,压力测试停止,峰值QPS就是出现资源瓶颈时的QPS。

  1. 线下压力测试:压力测试到对应的资源瓶颈,通常使用Jmeter工具对应用访问的URL逐步加压进行测试,当超过阈值时可以停止,可以根据经验提高测试效率。在线下压力测试时,需要注意JIT是否启用,-XX:CompileThreshold的默认值是10000次,也就是超过10000次JIT才会启动,直接将URL编译成机器码,否则由于JVM是解释执行的,所以在压力测试时需要经过一定时间进行预热,以便获取比较精确的QPS测算值。在线下压力测试时,可以选择对线上的访问日志(access_log)进行压力测试,这样可以保证正确的配比,因为不同的URL对资源的消耗不同,所以线下模拟的环境要和线上环境保持一致。
  2. 线下脚本压力测试:线下脚本压力测试一般适合预发布机器,预发布机器一般没有用户的真实流量,用线下的jmeter压力测试脚本进行压力测试同样要注意JIT是否启用的问题,确保最真实的测试结果。
  3. 线上引流测试:线上引流测试是主要的测试方式,它通过修改负载均衡器的权重因子,逐步将流量引入某台指定的线上机器。

两种常用的引流压力测试方法

  1. 4层负载均衡器权重比引流:线上引流测试单机吞吐能力,首先将负载均衡器的负载均衡算法修改成round-robin weight,接着在lvs所在的物理机上并发一个能够修改lvs配置的脚本,然后公开一个(HTTP)接口。让线上压力测试系统进行调用,线上压力测试系统会根据经验值逐步发起HTTP接口的调用,将引流的权重逐步加大。线上压力测试系统提交给接口的参数包含权重和机器名,告诉lvs要多少权重,到哪台机器,可以手动停止也可以自动收集机器上的资源情况,如CPU、CPU Load、内存、异常、GC次数、RT等。
  2. 7层负载均衡器代理地址变更引流:在大型网站架构中一般4层负载均衡器主要用于均衡负载,高效地转发数据包。7层负载均衡器代理主要用于日志采集,线上压力测试系统会调用Nginx所在物理机上的能够修改代理转发的服务器地址(默认是本机)的接口(修改nginx.conf中的proxy_pass配置),线上引流压力测试系统会根据一定的时间周期,将应用集群中Nginx的配置地址逐步修改到目标机器上。修改完成后,要重新加载Nginx配置,直到流量足够为止(此时流量资源瓶颈开始出现,QPS监控曲线从最高点逐步衰减)。对于7层负载均衡的引流测试方法而言,机器数量越多,引流的效果越好。引流可以从小流量开始逐步加大,直到峰值QPS出现。

大型网站常用的容量评估方法

二八原则评估法

其实在业务高速发展的时候,经常会遇到一个问题,如何在第一次做容量评估的时候,根据业务量就可以进行呢?第一次没有更好的方案,通常是根据二八原则进行评估。对于一个普通的营销场景可以用二八原则评估法进行容量评估,当然,秒杀、限时抢购、限时活动除外。对于秒杀、限时抢购活动,可以根据业务预计的时间进行调整。

评估QPS = (业务量/24*3600)*80%/20%

高性能系统架构模式

无状态架构

Session集中式存储

无论是Session复制还是Session sticky都带来了很大的伸缩性问题,Session集中式存储是将Session信息缓冲到分布式集中存储中,应用服务器即使出现故障,也不会影响用户的登录,实现无缝的故障转移。同时Session集中式存储如Tair、Redis、本身具有过期时间的自动管理功能,Session过期的问题自然得到解决。
Session集中式存储通常有两种实现方式。
第一种,通过Web容器插件(如Tomcat、Jetty)来实现,Tomcat有tomcat-redis-session-manager,Jetty有jetty-session-redis。有点是对开发人员透明,开发人员还是跟以前一样使用Session,不需要增加,修改任何代码;缺点是过于依赖容器,容器升级问题比较麻烦,好在容器升级是非常大的事情,不需要经常升级。
第二种,流行框架的会话管理工具,例如spring-session等,可以理解为替换了Servlet那一套会话管理,不依赖容器,不用改动代码。如果采用spring-session,则使用spring-data-redis连接池。
集中式存储需要考虑高可用的问题,一旦某个节点不可用,会出现严重的问题,如果是Redis,可以采用主从复制架构,Redis有强大的Mater和多Slave复制的能力。

基于负载均衡器的水平扩展架构

一个应用集群如果采用了无状态架构,应用集群就可以水平扩展了,但是7层负载均衡器如何水平扩展就成了下一个问题。4层负载均衡器的吞吐量和转发速度大大优于7层负载均衡器,4层负载均衡器的高效率转发基于4层转发,包封装层数较少,HTTP7层封装转发效率较低,但是功能也较为强大,7层转发可以做大基于Cookie的负载均衡。
7层负载均衡器在大型网站中的作用通常有两个,一个作用是统计QPS,将HTTP的所有访问记录在日志里面,QPS可以基于日志进行,另外一个作用是通过URL的rewrite来实现静态化链接到动态化的重写。考虑效率问题,为了实现7层负载均衡器的水平扩展,通常在7层负载均衡器上假设4层负载均衡设备或者软负载服务器。

基于DNS的负载均衡

在某些场景下,为了降低部署的复杂度,同时在容量需求还不够大的时候,在技术储备还不是很好的情况下,可以采用DNS多A地址轮询来实现4层负载均衡的水平扩展,DNS的负载均衡有如下的问题。
首先,故障迁移相对而言较为困难,可维护性差。DNS的负载均衡基于配置多A地址进行轮询,如果一台服务器失效,会导致域名解析到到服务器的用户看到服务中断,即使用户按Reload按钮也无济于事。系统管理员也不能随时将一台服务器切出服务进行维护,例如进行操作系统和应用软件升级,需要修改RR-DNS服务器中的IP地址列表,把该服务器的IP地址从中划掉,然后等待一段时间,直到所有域名服务器将该域名到这台服务器的映射淘汰,所有映射到这台服务器的客户机不再使用该站点为止。
其次,负载倾斜可能会非常严重,由于DNS是从Local DNS,一直到权威DNS进行递归解析的,每层都有缓存,一旦有缓存,DNS解析请求不会发送给权威DNS进行解析,从而造成RR轮询不均衡。Local DNS根据TTL进行缓存,而Local DNS存在很强的地域性,例如一个大型运营商在某个地区的Local DNS是相同的,由于运营商大小差异,会造成负载严重倾斜,服务器负载严重不均衡。

读写分离架构

读写分离架构是从小型系统过渡到大型系统的过程中常用的架构,其根本目的是减少写的压力,让读的性能更好,同时读和写互不影响,也消除了读和写之间的锁等待,减少延迟,从理论上能够将应用访问数据库的性能提升一倍。此架构适合在读一致性要求不高的场景中使用,Master主库服务器负责包括读在内的事务型数据库操作,这是为了有强一致性的场景,Slave辅库服务器负责无事务需求的读操作。
在数据库分布式存储-基于分库分表水平切分的架构中,也可以用读写分离的架构进行性能改善,短期内能够产生较为直观的效果。需要特别注意的是,在读写分离时要考虑程序本身是否做了事务控制,如果没有,会造成读写分离在不同的库中数据同步不及时,造成逻辑错误。例如程序的逻辑是根据读的结果来判断是否可写,当在备库读时,由于主库的写未及时同步到备库,因此读不到数据,判断为控,后续的写操作也无法继续。

基于数据水平切分的水平扩展架构

数据水平切分主要针对写进行水平扩展,关系型数据库的写存在明显的上限,一般来说单库写数据时QPS不超过3000,单表最好不要超过4000万行,否则查询、写的性能将急剧衰减。在强一致性的场景下,通常必须使用数据库进行数据的水平切分,将请求分散到不同的物理库中。对大型电子商务系统来说,有时商品数会达到数十亿,订单数据也会达到数十亿,这种规模的数据,任何一个库都存放不下。将数据进行分库、分表存储,可以降低在查询时需要的读的数据和索引的页数,同时也降低了索引的层数,从而提高查询速度。通常不建议将表进行垂直切分,垂直切分将字段进行分离,数据库的维护成本大大增加,同时对于开发使用极不方便。基本都是利用水平切分的方式进行数据的可扩展性的原因。
基于分库的水平切割:按记录行进行分割,不同的记录通过分库分表路由规则计算分别保存到不同的库中,将数据分离在不同的库中进行存储,每个子表的列数相同。
基于分表的水平切割:将表进行别名分割,通过路由规则进行计算,分到不同的表中,通常表名在数据库中将以别名的方式存在。
在分库分表的情况下,会产生跨库查询性能等问题,在这种情况下通常使用搜索引擎构造一个宽表,搜索引擎利用索引倒排的技术,基于行进行负载均衡,请求平均分发到某一行,行中的每个列服务存储不同的索引,一行组成的服务器是索引的全量,最后由merge Server进行合并。

缓存架构

缓存架构是典型的以空间换时间的优化方法。

缓存的基本属性
  • 命中率:表示缓存命中次数占总请求数的百分比,这是缓存设计的重要质量指标之一。
  • 容量:即缓存介质的容量最大值。
  • 成本:即开发成本,部署成本,软硬件成本。
  • 过期时间:缓存一般都有过期时间属性,也可以设置成本不过期。
缓存的分类
  1. 按照存储位置分类
  • 本地缓存:将数据缓存在本地,常见的本地缓存开源框架有EhCache、OSCache、iBatis和Hibernate本地缓存数据已经和这些缓存框架做了很好的集成,通过配置化的方式得到无侵入式的使用。
  • 分布式缓存:常见的分布式缓存框架包括业界流行的Memcached,Tair的MDB解决方案对Memcached进行了很多借鉴。
  1. 按照存储介质分类
  • 内存缓存:将数据缓存在内存里。
  • 磁盘缓存:Tair的LDB解决方案是将数据缓存在磁盘(SSD)上,磁盘缓存的主要优势在于数据不容易丢失,即使在掉电的情况下也不会丢失,数据安全性比内存缓存要高。
  1. 按照缓存的对象分类
  • 页面缓存
  • 对象缓存

缓存命中率是检验缓存效果的重要指标,命中率越高效果越好。命中率的高低通常和业务的特点有关,本身不易变的业务数据命中率较高。缓存的命中率和其他因素也有关,例如过短的缓存时间,过少的缓存空间,导致被LRU交换出去,同时如果缓存对象设计不合理,对象极其易变,缓存频繁失败,最终也会导致命中率过低。

缓存使用场景

(1)、使用缓存的条件

  • 在容量无法突破的情况下,考虑使用缓存来提升容量,否则尽量不要使用缓存。
  • 对用户访问延迟有明显改善的情况,同时对优化RT有明显的效果,例如如果不缓存需要2s,缓存之后只需要100ms,可以考虑使用缓存。
  • 使用缓存之前一定要了解缓存带来的问题,缓存不能保障数据一致性,要能接受这种不一致带来的各种问题,甚至资源损耗的问题。
    (2)、使用内存型缓存还是磁盘型缓存。
    MDB是内存型缓存的代表。
    LDB是磁盘缓存的代表。

缓存使用规范和原则

本地缓存需要确保缓存对象不会被更改,或者尽量少使用本地缓存,除非特殊场景。

  • 内存缓存一定要考虑双防护,后端一定要在DB层进行兜底。
  • 内存缓存一定要加过期时间,一般以业务上数据的变更的时长为准。
  • 缓存的使用要实现主动失效方案,否则要特别说明。
  • 所有为突破性能容量上限而设计的缓存架构方案,必须要考虑兜底方案。
  • 在设计缓存时,需要明确预估缓存命中率,过低的命中率导致的性能下降,比缓存数据不一致带来的风险更大。
  • 避免Tair访问热点,Tair的单服务器设计上限大约在20万QPS,超过容量时Tair将提供服务。
  • 设计缓存架构时,需要充分预估缓存不一致带来的风险,需要考虑快速发现方案,自动修复方案及兜底方案。
  • 快速发现:监控上需要设计如何快速发现Tair的标记是否丢失。
  • 自动修复:在出单时,校验数据库和Tair的标记是否一致,如果不一致,自动补充标记或者去除标记,将影响范围减小到最小。
  • 超预估兜底

字符串操作

  1. 问题描述

在处理字符串的过程中有很多情况下会遇到需要截取字符串的情况,这个时候使用Java中提供的substring方法来截取就非常方便了

  1. 其中比较经常使用到的方法有两个:

① public String substring(int beginIndex)

这个方法截取的字符串是从索引beginIndex开始的,到整个字符串的末尾,例如:字符串String s = "abcdef";

调用s.substring(2)表示从字符串的索引2开始截取到整个字符串结束,截取的字符串为cdef

② public String substring(int beginIndex, int endIndex)

这个方法截取的字符串从beginIndex开始,到字符串索引的endIndex - 1结束,即截取的字符串不包括endIndex这个索引对应的字符,所以endIndex的最大值为整个字符串的长度,所以使用这个方法的时候需要特别注意容易发生字符串截取越界的问题