性能优化:你该如何去做!


### 前言 最近在面试中经常碰到别人讲性能优化问题,最近也终于也沉下心来,来写写关于性能优化的事情。

写性能问题我想从几个方面去写,第一,怎么去发现性能问题,第二,性能优化的手段。

怎么去发现性能问题

先举一个实际例子,前公司有这么个场景,我们需要做一个监控异常数据的场景,输入数据大概是一批特征数据,然后拿输入的数据跟我现有的库中的数据做个1:n的比较,比中后,通过推入到消息中间件推送到前端。

流程图如下:
请添加图片描述

在未优化前所有的步骤都是通过redis建立缓存关系,这个看似没问题的场景却出现了性能瓶颈。
所以当时第一步我们要做的几件事,

  1. 找出每个环节的平均耗时,耗时最长的环节肯定是需要最先优化的环节
  2. 需要寻找流程中会产生热点的环节
  3. 分布式环境中,也可以通过cpu,jvm,硬盘,网络,内存等指标的监控来发现一些问题

第一个,耗时如何打点也是个有讲究的地方,现在比较流行的通过是java agent或者asm动态字节码的方式将每个方法的耗时时间打印出来,但是有些代码写得并不是那么原子化,可能一个方法存在上千行代码,所以最关键的还是对于一些请求外部资源的方法进行打点。还有对于流程要有分析,一些关键流程进行超时的埋点。

比如上图这个例子,我就会在流程中所列的几个查询点前后建立一个埋点

  1. 查询监控任务埋点
  2. 查询任务对应的库埋点。
  3. 1:n算法埋点。
  4. 推送消息埋点。

第二个,热点的分析,一方面可以通过redis命令去做分析,比如slowlog,来看,单位时间哪些键调用比较频繁。通过流程图进行分析,分析流程中可能存在的热点,当然还有一些中间件的统计方式,也能够帮你去实时分析热键,一般都是通过时间窗口的方式去实现统计。

第三个,就属于一些监控工具的建立了,比如普罗米修斯这种来做监控数据的统一汇总。

怎么去解决性能问题

就近原则

比如数据库操作可以比为远程磁盘操作,redis可以比作远程内存操作。 他们之间的耗时关系就是
远程磁盘>本地磁盘>远程内存>本地内存>cpu级的缓存,当然对于javaer来讲,一般最快就是本地内存了。所以一般如果我们出现了redis的性能问题,那么就要考虑本地缓存了,比如现在比较优秀的本地缓存框架有,caffieneguava,这里就有人说了,本地缓存会存在一致性的问题,这里就是另外个话题,但是从优化性能角度就是得这么去做。

消除热点

消除热点其实总的思路就是将key做水平的拆分。或者垂直拆分,什么意思了。
垂直拆分指的,将一个大key拆成若干个小key,比如拿电商的例子来说一个订单可能包含N个属性,比如可以分几类,
第一类订单本身的信息,比如订单号,订单创建时间。
第二类订单详情信息,包括购买商品的详情。
第三类 商品本身的详情。
所以订单本身是一个聚合根,如果每次查询任何以上信息,都需要把整个key找出来,无疑加大了热点的可能性。所以我们可以将一个大的订单信息垂直拆分成三个键,那么根据不同场景下的调用,这个热键的流量就被做了分离。

水平拆分指的是将同一个key通过hash方式存好几个副本,这样在做查询的时候也可以通过hash方式将流量进行打散,具体的像rediscluster场景,水平拆分就会比较有效,因为很可能因为某个热点键打到某个slot,在slot本身不可能水平拆分的情景下,就需要对键进行水平拆分处理了。当然这里面就涉及到如何对写拆分和读拆分,写拆分一般来说用得比较少,写拆分可能会需要去考虑总分的问题,当然不管如何它都能够起到消除热点的作用,核心思路就是复制副本,读分散,写集中(已保证数据一致性)。
当然像本地缓存这种,也是起到了一个解决热点的手段。

使用缓存减少中间商

什么意思了我们看看上面图中我需要的几个步骤,那我是不是可以直接从设备id找到对应的库的信息了。那么上面两个流程就变成了一个流程。
请添加图片描述

这样的话我将这种缓存关系直接放到了修改的时候的维护,而不需要每次去查询的时候进行关联。
这样带来两个好处。

  1. 减少了缓存的get操作。
  2. 因为关联关系带来的o(n)的时间复杂度。
单个处理转为批量处理

批量处理的意思是, 100个请求,我可以一个一个处理,我也可以将100个请求合并成一个去处理,
优点
第一,可以做一些共性的处理,比如数据分类,实现数据共同特征的挖掘。
第二,一些请求类,可以减少请求次数的发起。

针对上面所说的那个场景,本身批量数据的计算和单个数据的计算时间是接近的,那么批量处理后本身的整体的计算能力比之前高了几十倍。所以整体系统的负载能力也大大得到了提升。

请添加图片描述

降低计算复杂度

其实计算复杂度降低就依赖于平时对算法的了解,常见的几个计算复杂度的例子。

  1. 查询的时候使用索引,比如数据库里面b+ tree的结构。
  2. 比如用hashmap,做查询键的操作
  3. 比如冒泡排序,快速排序的时间复杂度差别,通过一定空间换时间的方式来降低计算的复杂度
  4. 比如查重set和数组的区别。
    所以对于不同场景,不同数据结构,不同算法的选择,能够带来整体性能提高。
    这里面强调的一个核心,就是选择对的算法,包括jvm的空间调优,本质也是通过内存的设置,来让算法效果最优。
减少线程的上下文切换。

一些框架常用的零copy,比如一些语言的特性,如go的协程,都是为了减少由于系统空间和用户空间的来回切换,导致的性能问题。其实这里面的一个中心思想就是尽量减少cpu的计算工作。

总结

这篇文章主要是总结如何去做性能优化,这里面主要讲的是一个大的思考思路,让我们的性能优化,有据可行,不会摸不着头脑,当然具体的场景还要做具体分析,使用不同的方式和命令做优化。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

偷懒的程序员-小彭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值