性能调优
文章目录
性能调优
那么找到了系统的瓶颈点,我们要如何优化呢?优化方案会随着问题的不同而不同。比方说,如果是数据库访问慢,那么就要看是不是有锁表的情况、是不是有全表扫描、索引加得是否合适、是否有 JOIN 操作、需不需要加缓存,等等;如果是网络的问题,就要看网络的参数是否有优化的空间,抓包来看是否有大量的超时重传,网卡是否有大量丢包等。
2. 系统运维
在系统设计阶段为了保证系统的可用性可以采取上面的几种方法,那在系统运维的层面又能做哪些事情呢?其实,我们可以从灰度发布、故障演练两个方面来考虑如何提升系统的可用性。
你应该知道,在业务平稳运行过程中,系统是很少发生故障的,90% 的故障是发生在上线变更阶段的。比方说,你上了一个新的功能,由于设计方案的问题,数据库的慢请求数翻了一倍,导致系统请求被拖慢而产生故障。
如果没有变更,数据库怎么会无缘无故地产生那么多的慢请求呢?因此,为了提升系统的可用性,重视变更管理尤为重要。而除了提供必要回滚方案,以便在出现问题时快速回滚恢复之外,另一个主要的手段就是灰度发布。
灰度发布指的是系统的变更不是一次性地推到线上的,而是按照一定比例逐步推进的。一般情况下,灰度发布是以机器维度进行的。比方说,我们先在 10% 的机器上进行变更,同时观察 Dashboard 上的系统性能指标以及错误日志。如果运行了一段时间之后系统指标比较平稳并且没有出现大量的错误日志,那么再推动全量变更。
灰度发布给了开发和运维同学绝佳的机会,让他们能在线上流量上观察变更带来的影响,是保证系统高可用的重要关卡。
灰度发布是在系统正常运行条件下,保证系统高可用的运维手段,那么我们如何知道发生故障时系统的表现呢?这里就要依靠另外一个手段:故障演练。
故障演练指的是对系统进行一些破坏性的手段,观察在出现局部故障时,整体的系统表现是怎样的,从而发现系统中存在的,潜在的可用性问题。
一个复杂的高并发系统依赖了太多的组件,比方说磁盘,数据库,网卡等,这些组件随时随地都可能会发生故障,而一旦它们发生故障,会不会如蝴蝶效应一般造成整体服务不可用呢?我们并不知道,因此,故障演练尤为重要。
性能优化原则
“天下武功,唯快不破”。性能是系统设计成功与否的关键,实现高性能也是对程序员个人能力的挑战。不过在了解实现高性能的方法之前,我们先明确一下性能优化的原则。
**首先,性能优化一定不能盲目,一定是问题导向的。**脱离了问题,盲目地提早优化会增加系统的复杂度,浪费开发人员的时间,也因为某些优化可能会对业务上有些折中的考虑,所以也会损伤业务。
**其次,性能优化也遵循“八二原则”,**即你可以用 20% 的精力解决 80% 的性能问题。所以我们在优化过程中一定要抓住主要矛盾,优先优化主要的性能瓶颈点。
**再次,性能优化也要有数据支撑。**在优化过程中,你要时刻了解你的优化让响应时间减少了多少,提升了多少的吞吐量。
**最后,性能优化的过程是持续的。**高并发的系统通常是业务逻辑相对复杂的系统,那么在这类系统中出现的性能问题通常也会有多方面的原因。因此,我们在做性能优化的时候要明确目标,比方说,支撑每秒 1 万次请求的吞吐量下响应时间在 10ms,那么我们就需要持续不断地寻找性能瓶颈,制定优化方案,直到达到目标为止。
在以上四个原则的指引下,掌握常见性能问题的排查方式和优化手段,就一定能让你在设计高并发系统时更加游刃有余。
性能优化的第三点原则中提到,对于性能我们需要有度量的标准,有了数据才能明确目前存在的性能问题,也能够用数据来评估性能优化的效果。所以明确性能的度量指标十分重要。
一般来说,度量性能的指标是系统接口的响应时间,但是单次的响应时间是没有意义的,你需要知道一段时间的性能情况是什么样的。所以,我们需要收集这段时间的响应时间数据,然后依据一些统计方法计算出特征值,这些特征值就能够代表这段时间的性能情况。我们常见的特征值有以下几类。
- 平均值
顾名思义,平均值是把这段时间所有请求的响应时间数据相加,再除以总请求数。平均值可以在一定程度上反应这段时间的性能,但它敏感度比较差,如果这段时间有少量慢请求时,在平均值上并不能如实的反应。
举个例子,假设我们在 30s 内有 10000 次请求,每次请求的响应时间都是 1ms,那么这段时间响应时间平均值也是 1ms。这时,当其中 100 次请求的响应时间变成了 100ms,那么整体的响应时间是 (100 * 100 + 9900 * 1) / 10000 = 1.99ms。你看,虽然从平均值上来看仅仅增加了不到 1ms,但是实际情况是有 1% 的请求(100/10000)的响应时间已经增加了 100 倍。所以,平均值对于度量性能来说只能作为一个参考。
- 最大值
这个更好理解,就是这段时间内所有请求响应时间最长的值,但它的问题又在于过于敏感了。
还拿上面的例子来说,如果 10000 次请求中只有一次请求的响应时间达到 100ms,那么这段时间请求的响应耗时的最大值就是 100ms,性能损耗为原先的百分之一,这种说法明显是不准确的。
- 分位值
分位值有很多种,比如 90 分位、95 分位、75 分位。以 90 分位为例,我们把这段时间请求的响应时间从小到大排序,假如一共有 100 个请求,那么排在第 90 位的响应时间就是 90 分位值。分位值排除了偶发极慢请求对于数据的影响,能够很好地反应这段时间的性能情况,分位值越大,对于慢请求的影响就越敏感。
在我来看,分位值是最适合作为时间段内,响应时间统计值来使用的,在实际工作中也应用最多。除此之外,平均值也可以作为一个参考值来使用。
我在上面提到,脱离了并发来谈性能是没有意义的,我们通常使用吞吐量或者同时在线用户数来度量并发和流量,使用吞吐量的情况会更多一些。但是你要知道,这两个指标是呈倒数关系的。
这很好理解,响应时间 1s 时,吞吐量是每秒 1 次,响应时间缩短到 10ms,那么吞吐量就上升到每秒 100 次。所以,一般我们度量性能时都会同时兼顾吞吐量和响应时间,比如我们设立性能优化的目标时通常会这样表述:在每秒 1 万次的请求量下,响应时间 99 分位值在 10ms 以下。
那么,响应时间究竟控制在多长时间比较合适呢?这个不能一概而论。
从用户使用体验的角度来看,200ms 是第一个分界点:接口的响应时间在 200ms 之内,用户是感觉不到延迟的,就像是瞬时发生的一样。而 1s 是另外一个分界点:接口的响应时间在 1s 之内时,虽然用户可以感受到一些延迟,但却是可以接受的,超过 1s 之后用户就会有明显等待的感觉,等待时间越长,用户的使用体验就越差。所以,健康系统的 99 分位值的响应时间通常需要控制在 200ms 之内,而不超过 1s 的请求占比要在 99.99% 以上。
现在你了解了性能的度量指标,那我们再来看一看,随着并发的增长我们实现高性能的思路是怎样的。
可用性
**高可用性(High Availability,HA)**是你在系统设计时经常会听到的一个名词,它指的是系统具备较高的无故障运行的能力。
我们在很多开源组件的文档中看到的 HA 方案就是提升组件可用性,让系统免于宕机无法服务的方案。比如,你知道 Hadoop 1.0 中的 NameNode 是单点的,一旦发生故障则整个集群就会不可用;而在 Hadoop2 中提出的 NameNode HA 方案就是同时启动两个 NameNode,一个处于 Active 状态,另一个处于 Standby 状态,两者共享存储,一旦 Active NameNode 发生故障,则可以将 Standby NameNode 切换成 Active 状态继续提供服务,这样就增强了 Hadoop 的持续无故障运行的能力,也就是提升了它的可用性。
通常来讲,一个高并发大流量的系统,系统出现故障比系统性能低更损伤用户的使用体验。想象一下,一个日活用户过百万的系统,一分钟的故障可能会影响到上千的用户。而且随着系统日活的增加,一分钟的故障时间影响到的用户数也随之增加,系统对于可用性的要求也会更高。所以今天,我就带你了解一下在高并发下,我们如何来保证系统的高可用性,以便给你的系统设计提供一些思路。
缓存
缓存,是一种存储数据的组件,它的作用是让对数据的请求更快地返回。
我们经常会把缓存放在内存中来存储, 所以有人就把内存和缓存画上了等号,这完全是外行人的见解。作为业内人士,你要知道在某些场景下我们可能还会使用 SSD 作为冷数据的缓存。比如说 360 开源的 Pika 就是使用 SSD 存储数据解决 Redis 的容量瓶颈的。
实际上,凡是位于速度相差较大的两种硬件之间,用于协调两者数据传输速度差异的结构,均可称之为缓存。那么说到这儿我们就需要知道常见硬件组件的延时情况是什么样的了,这样在做方案的时候可以对延迟有更直观的印象。幸运的是,业内已经有人帮我们总结出这些数据了,我将这些数据整理了一下,你可以看一下。
2. 缓存与缓冲区
讲了这么多缓存案例,想必你对缓存已经有了一个直观并且形象的了解了。除了缓存,我们在日常开发过程中还会经常听见一个相似的名词——缓冲区,那么,什么是缓冲区呢?缓冲和缓存只有一字之差,它们有什么区别呢?
我们知道,缓存可以提高低速设备的访问速度,或者减少复杂耗时的计算带来的性能问题。理论上说,我们可以通过缓存解决所有关于“慢”的问题,比如从磁盘随机读取数据慢,从数据库查询数据慢,只是不同的场景消耗的存储成本不同。
**除此之外,我们熟知的 HTTP 协议也是有缓存机制的。**当我们第一次请求静态的资源时,比如一张图片,服务端除了返回图片信息,在响应头里面还有一个“Etag”的字段。浏览器会缓存图片信息以及这个字段的值。当下一次再请求这个图片的时候,浏览器发起的请求头里面会有一个“If-None-Match”的字段,并且把缓存的“Etag”的值写进去发给服务端。服务端比对图片信息是否有变化,如果没有,则返回浏览器一个 304 的状态码,浏览器会继续使用缓存的图片信息。通过这种缓存协商的方式,可以减少网络传输的数据大小,从而提升页面展示的性能。
文章作者 LYR
上次更新 2021-08-17