在高并发系统中,业务往往想提高系统的性能,降低接口的延迟,提高用户体验。本文介绍如何提供系统服务的一些参考方法。
读服务在实现流程上,基本上是纯粹地从存储中一次或者多次获取原始数据,进行简单的逻辑加工,或者直接返回给用户/前端业务系统。它是无状态的或者是无副作用的,也就是说每一次执行都不会在存储中记录数据或者修改数据,每一次读请求都和上一次无关。
比如在电商 App 里,首页展示的商品和促销等信息,是运营根据营销策略配置的,业务后台接收到读请求,然后直接去存储获取数据并进行加工后返回给业务前台系统
通过上述可以知道,读服务在实现上需要满足的功能要求,那性能需求也需要满足以下几点:
通常情况下为了降低系统架构复杂度,通过水平拆分将一些共性的、不易变的代码逻辑单独封装成一个模块对外服务。这样能够减少重复,提升效率。
这个时候,我们的系统读取数据流程会是这样:读模块->数据访问模块->存储层。
在实际的应用中,通过监控图可以发现此种架构下,读服务性能的平均值离 TP99 或 TP999 有较大的差距,通常在一倍以上。另外,性能的毛刺也比较多。产生这种情况有以下两个原因。
为了提升性能,实战中的架构通常选用基于内存的、性能更好的 Redis 作为主存储,MySQL 作为兜底来构建
在初始的时候,所有数据都存储在数据库中。当读服务接受请求时,会先去缓存中查询数据,如果没有查询到数据,就会降级到数据库中查询,并将查询结果保存在 Redis 中。
但是这样会带来很多问题,可以阅读我之前的文章:缓存数据一致性,雪崩,大Key,热Key问题解决方案。
方法 | 描述 |
---|---|
精排缓存 | 缓存精排计算结果,下次请求时直接使用,减少耗时计算 |
一致性hash | 按用户维度做hash,对用户信息进行缓存高热结果cache使用LRU,LFU,ARC等策略 |
高热内容缓存 | 尽量减少耗时计算的次数 |
多级缓存 | 本地,磁盘,远程 多级缓存逐步扩大存储空间,延迟也越来越高 |
数据压缩 | 为增加缓存容量,可以压缩 |
如果你的接口需要获取的数据太多,我们也可以将串行调用改为并发调用,可以加快数据获取。
如果一次读请求和存储需要交互三次,假设每次交互时间为 10ms,采用串行的方式总耗时为 30ms,而采用了异步并行的方式后,三次交互为并行执行,总耗时仍为 10ms。整体的性能提升了很多。
这也带来了一些问题和局限:
方法 | 描述 |
---|---|
大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是utilization、saturation、erros(利用率、饱和度、错误)三个词的缩写,应用于性能研究,用来识别系统瓶颈,一言以蔽之,就是:对于所有的资源,查看它的使用率、饱和度和错误。
RED 方法定义了应为体系结构中的每个微服务度量的三个关键指标。这些指标是:
*(请求)Rate - 您服务每秒提供的请求数
*(请求)Errors - 每秒失败的请求数
*(请求)Duration - 每个请求所花时间的分布
一般来说,RED方法只适用于请求驱动的服务,它不适用于面向批处理或流式服务。 它也不是包罗万象的。 而 USE 方法应用于主机 CPU 和内存或缓存等资源时就是一个很好的例子。