日常工作中的性能优化实践

1,142 阅读8分钟

前言

性能一直是软件系统要考虑的一大非功能性需求,也是非常影响用户的使用体验因素之一。如果一个系统功能强大,但是任何一个操作都要加载几分钟,那么这个系统最终使用的人会越来越少。

一般一个网站的响应速度在1秒以内是比较不影响用户使用体验的,如果在3~5秒,就已经算是慢的了,大于8秒很多用户都已经没有耐心去等待了。这就是很多人说的互联网8秒原则。

所以性能高真的非常重要,以下是一些关于如何提高性能的实践,会持续更新。

发现性能问题

在日常工作中,性能问题一般是用户上报或者测试发现的。

  1. 用户上报
  2. 压测,观察一些数据得到,例如fps等

找出性能瓶颈

知道有性能问题后,一般通过追踪的工具来找到性能瓶颈:

  1. zipkin 查看服务的链路,找到最耗时的服务
  2. 听云
  3. Chrome DevTools 分析运行时性能

性能优化方案

性能优化方案分为接口层面和前端层面来处理,但是要注意最有效的是解决瓶颈问题,其他的只是锦上添花。

一、从接口层提高性能

在性能中,接口的性能是不能忽略的很大一方面。数据没有返回,前端也展示不出数据,所以提高接口的响应速度很重要。

1 解决耗时长的接口

耗时长的接口一般都是性能瓶颈,找到它,看看里面具体是什么原因导致耗时长再针对性的解决。

例如:

  • 查找数据库慢,如果是数据库导致的,可以考虑加索引,优化数据库结构,考虑是否要分库分表、读写分离(一个数据库又读又写可能会有锁,分了读写数据库的请求)

  • 调用第三方接口慢,查看调用第三方的接口,看第三方系统的文档或者咨询相关人员,找到性能更好的方式。 例如加一些查询参数,可以缩减查询的内容; 同样的一个接口,用search in方式拿到的数据,可能没有直接调用明确的=来的快;

  • 如果性能瓶颈还是在第三方接口上,考虑缓存机制。

2 HTTP缓存

如果有HTTP缓存,并且缓存没有失效,浏览器会直接使用缓存;反之,则向服务器请求数据。

HTTP请求的缓存分为2个方面,一个是服务端的缓存(常说的协商缓存,ctrl+f5可以刷新),一个是浏览器的缓存(常说的强制缓存,要改路径才可以清除缓存)。

对于服务端缓存来说,经常使用NGINX来反向代理前端服务,可以在NGINX中配置缓存,在缓存在有效时间内,会直接使用缓存。

对于浏览器缓存来说,例如对于像前端css、JS、图片等文件资源,变更不会太过频繁,一般只有发版的时候才会进行变更,所以在这些文件的HTTP中加上cache-control(HTTP1.1)和expires(HTTP1.0,对比浏览器和服务器的时间)能够让浏览器进行缓存。这里就又涉及到了发版的时候如何更新,现在大部分的做法都是,每次发版的css、JS文件会带上hash值,因为hash不一样所以每次发版后请求的都是最新的。

什么时候清缓存

  • 设置缓存过期时间
  • 数据更新的时候更新缓存
  • 没有缓存的时候去第三方接口获取,然后缓存起来

3 前端浏览器本地存储

cookie:一般只用来存储用户信息,例如登录的token等,很小的大小限制(4KB),发送请求的时候会一起给后端。

localstorage :保存在浏览器中的,永久存储(5M)。只有用户清空浏览器缓存才会失效。

sessionStorage:限制为5M,和当前标签页生命周期一样,关闭标签页就失效了。

indexDB:前端大容量存储(50M)非关系型数据库。

3 服务器CDN缓存

现在的大型企业应用一般都有自己的CDN,用来做负载均衡等,所以CDN缓存就是在这个缓存服务器中,保存了用户信息和分发的服务器信息。当用户发起请求的时候,优先把缓存的内容回传给用户。

4 服务器redis缓存

后端服务可以通过Redis来缓存数据。

5 异步

有一些连带操作是不影响用户使用体验的,可以考虑把这些连带操作设计成异步的,不去阻塞主进程,让用户无感知。

6 减少HTTP请求

每次发送HTTP请求都需要建立网络通信链接,并且后端服务需要开启新的线程来响应,这些通信和服务都需要开销,可以通过减少HTTP请求来优化一部分的性能。

操作:

(1)可以一起请求的后端服务API可以在BFF组装好,一次请求返回。

(2)合并CSS、JS的文件,尽可能一次请求完毕。

(3)Css sprites(雪碧图):合并图片,通过CSS偏移来处理。

二、从前端页面提高性能

在前端开发过程中,发现应用会有很多方面的性能需要注意,例如:

  • APP卡顿
  • 网络慢
  • APP加载慢 以下是一些具体的实践经验。 性能优化分为两个方面:一个是加快静态资源的加载,一个是提高页面渲染的速度。

1 页面渲染性能优化

  • 减少DOM层级,在写DOM的时候没必要的层级可以不要加。
  • 不需要阴影的地方,不要加阴影。
  • 使用chrome的performance分析,分析原因。

2 静态资源加载优化

静态资源加载优化涵盖了非常多的处理方案,例如压缩、减少资源大小、预加载和懒加载等。

2.1 资源压缩

页面的资源包括js、css、images等等,这些文件尽可能压缩。可以使用webpack添加plugin来压缩。

2.2 使用 tree-shaking、scope hoisting、code-splitting

以下也可以通过webpack处理:

  • tree-shaking 移除无用代码,只加载实际使用了的代码。
  • scope hoisting 作用域提升,允许工具检测哪些 import 可以被提升或者可以转换成一个内联函数。
  • Code-splitting 将代码分为按需加载的“块”。

2.3 使用lazyload&preload

为了提高页面的加载速度,我们期望的是按需加载,也就是说只加载当前的页面,别的页面不会一起被加载进来,免得阻碍了当前页面的加载。这就是懒加载,也被称为延迟加载,延迟相关资源的加载。 当首页加载完毕后,预判用户行为,提前加载之后的页面,也会提高用户的体验。这就是预加载。

3 APP卡顿的时候,多次点击触发事件的处理

当APP卡顿时,如果多次点击一个button,刚好这个button会打开下一页,那么就会看到打开了很多页的动画效果。为了防止这种情况,考虑了很多做法。

  1. debounce 加上一个Directive,监听click事件,在某个时间段内,多次点击,只触发一次操作。
import { Directive, EventEmitter, HostListener, OnInit, Output } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { debounceTime } from 'rxjs/operators';

@Directive({
  selector: '[appDebounceClick]'
})
export class DebounceClickDirective implements OnInit {
  @Output() debounceClick = new EventEmitter();
  private clicks = new Subject();

  constructor() {}

  ngOnInit() {
    this.clicks.pipe(debounceTime(4000)).subscribe(e => this.debounceClick.emit(e));
  }

  @HostListener('click', ['$event'])
  clickEvent(event) {
    event.preventDefault();
    event.stopPropagation();
    this.clicks.next(event);
  }
}

component的html:

 <button appDebounceClick (debounceClick)="log()">Debounced Click</button>

component的ts文件

public log() {
// do some thing
  }

Angular具体做法可参考: coryrylan.com/blog/creati…

  1. disable button 其实就是在button触发事件后,通过disable属性来控制接下来的事件是否生效,这个需要注意button disabled样式可能会发生变化,并且要在合适的时机来控制该button的disable。如果对于APP中全部button都做这个控制的话,可以抽一个Button component,并加上disable控制。

  2. disable event 目前的应用中,主要遇上的问题是多次点击button跳转页面时,会打开多个页面,又不想改变button的样式,所以在button触发的页面跳转时,加上一个flag,判断是否跳转,在跳转后的callback方法里重置该flag。

结尾

性能解决的方案有很多方面,在日常工作中做好性能优化的各种实践,以免累积到后面造成明显的性能问题。 而遇到已经存在的性能问题通过分析针对瓶颈去解决。

参考

前端性能优化清单

谈谈关于前端的缓存的问题

浏览器缓存