• 性能优化究竟应该怎么做


    在高并发系统中,业务往往想提高系统的性能,降低接口的延迟,提高用户体验。本文介绍如何提供系统服务的一些参考方法。

    读服务的功能

    读服务在实现流程上,基本上是纯粹地从存储中一次或者多次获取原始数据,进行简单的逻辑加工,或者直接返回给用户/前端业务系统。它是无状态的或者是无副作用的,也就是说每一次执行都不会在存储中记录数据或者修改数据,每一次读请求都和上一次无关。
    比如在电商 App 里,首页展示的商品和促销等信息,是运营根据营销策略配置的,业务后台接收到读请求,然后直接去存储获取数据并进行加工后返回给业务前台系统
    通过上述可以知道,读服务在实现上需要满足的功能要求,那性能需求也需要满足以下几点:

    • 保证高可用;其实不管是不是读服务,都需要满足高可用。
    • 保证高性能。我先定义一个较大的指标,TP 999 要在 100ms 以内。比如你去浏览新闻和电商 App,如果首页打开得非常慢,体验一定非常差。
    • 支持的 QPS 非常高(如上万~百万的峰值 QPS)。因为大部分业务场景都是“读多写少”。
      针对这些技术功能性指标,下面将讲解如何实现。

    如何实现高可用,高性能读服务

    减少分层

    通常情况下为了降低系统架构复杂度,通过水平拆分将一些共性的、不易变的代码逻辑单独封装成一个模块对外服务。这样能够减少重复,提升效率。
    这个时候,我们的系统读取数据流程会是这样:读模块->数据访问模块->存储层。
    在实际的应用中,通过监控图可以发现此种架构下,读服务性能的平均值离 TP99 或 TP999 有较大的差距,通常在一倍以上。另外,性能的毛刺也比较多。产生这种情况有以下两个原因。

    • 因为采用分层架构之后,网络传输相比不分层的架构多了一倍。
    • 另一方面,读服务的业务逻辑都比较简单,性能主要消耗在网络传输上。因此,请求查询的数据越少,性能越好,假设为 10ms;数据多时,性能则较差,假设为 50ms。当叠加上分层架构,性能就会翻倍。比如数据少时,从 10ms 变成 20ms;数据多时,从 50ms 变成 100ms。分层后,数据的多与少带来的性能差距达到了 80ms,这也是产生毛刺差的原因。

    增加缓存

    为了提升性能,实战中的架构通常选用基于内存的、性能更好的 Redis 作为主存储,MySQL 作为兜底来构建
    在这里插入图片描述

    在初始的时候,所有数据都存储在数据库中。当读服务接受请求时,会先去缓存中查询数据,如果没有查询到数据,就会降级到数据库中查询,并将查询结果保存在 Redis 中。
    但是这样会带来很多问题,可以阅读我之前的文章:缓存数据一致性,雪崩,大Key,热Key问题解决方案

    缓存优化

    方法描述
    精排缓存缓存精排计算结果,下次请求时直接使用,减少耗时计算
    一致性hash按用户维度做hash,对用户信息进行缓存高热结果cache使用LRU,LFU,ARC等策略
    高热内容缓存尽量减少耗时计算的次数
    多级缓存本地,磁盘,远程 多级缓存逐步扩大存储空间,延迟也越来越高
    数据压缩为增加缓存容量,可以压缩

    并发调用

    如果你的接口需要获取的数据太多,我们也可以将串行调用改为并发调用,可以加快数据获取。
    在这里插入图片描述

    如果一次读请求和存储需要交互三次,假设每次交互时间为 10ms,采用串行的方式总耗时为 30ms,而采用了异步并行的方式后,三次交互为并行执行,总耗时仍为 10ms。整体的性能提升了很多。
    这也带来了一些问题和局限:

    • 并行增加了线程的消耗,每一个异步并行都对应一个线程,进而带来 CPU 的消耗;
    • 并行的多线程开发也带来的编程复杂度和维护难度;
    • 并行化只能应用在每一次和存储交互都是独立的、无先后关系的场景里。

    代码尽可能优化

    方法描述
    大json解析选择更高效的JSON库,例如golang第三方库:https://github.com/json-iterator/go
    删除无效逻辑清理历史遗留问题,删除无效逻辑
    精简关键路径对关键路径pipline拆解,然后逐一论证每一个步骤的必要性
    指针传递避免大对象的值传递拷贝
    零拷贝技术网络传输时,减少用户态到内核态的数据拷贝
    减少系统调用协程技术减少了上下文切换带来的资源消耗
    优化耗时操作基于业务逻辑,减少耗时操作的次数

    性能指标

    通过性能指标可以度量目前存在的性能问题,同时作为性能优化的评估依据。一般来说,会采用一段时间内的接口响应时间作为指标。

    • 平均响应时间:最常用,但是缺陷很明显,对于慢请求不敏感。比如1万次请求,其中9900次是1ms,100次是100ms,则平均响应时间为1.99ms,虽然平均耗时仅增加了0.99ms,但是1%请求的响应时间已经增加了100倍。它会反映每个用户真正的耗时情况,因此直接影响用户的体验。

    • TP90、TP99等分位值:将响应时间按照从小到大排序,TP90表示排在第90分位的响应时间,分位值越大,对慢请求越敏感。它会反映每个用户真正的耗时情况,因此直接影响用户的体验。
      在这里插入图片描述

    • 吞吐量:和响应时间呈反比,比如响应时间是1ms,则吞吐量为每秒1000次。通常有QPS和TPS等表述方式,它能够衡量系统真正的处理效率,因此直接刻画系统的性能。

    USE 方法

    USE是utilization、saturation、erros(利用率、饱和度、错误)三个词的缩写,应用于性能研究,用来识别系统瓶颈,一言以蔽之,就是:对于所有的资源,查看它的使用率、饱和度和错误。

    • 识别系统中有哪些资源(CPU/内存/磁盘/IO带宽等)
    • 分别查看这些资源的三个指标(使用率、饱和度和错误)跟进现象,分析并缩小性能瓶颈的范围(数据中心,服务集群,单机节点,进程,线程,函数,指令)
    • 定位瓶颈,使用适当的优化策略进行优化
    • 观测系统,验证优化收益
    • Utilization:整个系统的平均 CPU 利用率
    • Saturation:一个近似值是第 99 个之间的差异延迟百分位和平均延迟(假设第 99 个是饱和驱动的)
    • Errors:每秒失败的请求数

    RED 方法

    RED 方法定义了应为体系结构中的每个微服务度量的三个关键指标。这些指标是:
    *(请求)Rate - 您服务每秒提供的请求数
    *(请求)Errors - 每秒失败的请求数
    *(请求)Duration - 每个请求所花时间的分布

    一般来说,RED方法只适用于请求驱动的服务,它不适用于面向批处理或流式服务。 它也不是包罗万象的。 而 USE 方法应用于主机 CPU 和内存或缓存等资源时就是一个很好的例子。

    如何发现系统性能瓶颈

    在这里插入图片描述

    文档参考

    • https://talkgo.org/uploads/short-url/jm6M523c03LRtyQzLJepouas1Is.pdf
    • https://nxwz51a5wp.feishu.cn/docs/doccnKV6gYLVLrMz5MLBTfXCKFd#GEO5L8
    • https://kaiwu.lagou.com/course/courseInfo.htm?courseId=595#/detail/pc?id=6128
    • https://mp.weixin.qq.com/s/QtHEUzSdRBZpB0Yw3FtTiw
    • https://nxwz51a5wp.feishu.cn/docs/doccnKV6gYLVLrMz5MLBTfXCKFd#0b2Nx7
  • 相关阅读:
    【数据结构前置知识】初识集合框架和时间,空间复杂度
    视频领域 CLIP4clip:An Empirical Study of CLIP for End to End Video Clip Retrieval
    TCP/IP 测试题(三)
    oracle中使用rownum作为条件的失效问题的原因和解决方法
    ffmpeg ts 关于av_seek_frame
    【Golang开发面经】蔚来(两轮技术面)
    3.3.OpenCV技能树--二值图像处理--图像形态学操作
    自然语言处理(NLP)技术
    ESP8266-Arduino编程实例-AHT20温湿度传感器驱动
    磁盘相关概述
  • 原文地址:https://blog.csdn.net/baidu_32452525/article/details/126567708