浅谈高并发-前端优化

前言

最近接到个任务,业务场景是须要处理高并发。html

原谅我第一时间想到的竟然是前段时间阮一峰的博客系统遭到了DDoS攻击,由于在个人理解中,它们的原理是想通的,都是服务器在必定时间内没法处理全部的并行任务,致使部分请求异常,甚至会像阮一峰的博客同样崩溃。前端

以前不太有接触太高并发的机会,因此并无什么实际经验,却是以前作的项目中有秒杀功能的实现作过必定的处理,当时的处理就是多利用缓存进行优化和减小一些不必的后端请求,可是由于是创业公司,因此并无多少过多的流量,即使是秒杀,因此也没有进行更进一步的优化了,业务需求不须要,本身也没有过多去思考这个问题了。node

其实刚开始我仍是有些想法,利用HTTP头部,强缓存(cache-control)、协商缓存(last-modified和Etag)、开启HTTP2,尤为是HTTP2应该能将性能提高很多吧,可是这些方案大多都须要后端支持,那么前端能作什么呢,却是还真没好好思考和总结一下。webpack

理解

架构搭建以前首先要把需求理解透彻,因此去谷歌搜索了一波,首先看几个名词:web

  • QPS:每秒钟请求或者查询的数量,在互联网领域,指每秒响应请求数(指HTTP请求)
  • 吞吐量:单位时间内处理的请求数量(一般由QPS与并发数决定)
  • 响应时间:从请求发出到收到响应花费的时间,例如系统处理一个HTTP请求须要100ms,这个100ms就是系统的响应时间
  • PV:综合浏览量(Page View),即页面浏览量或者点击量,一个访客在24小时内访问的页面数量,同一我的浏览你的网站同一页面,只记做一次PV
  • UV:独立访问(UniQue Visitor),即必定时间范围内相同访客屡次访问网站,只计算为1个独立访客
  • 带宽:计算带宽大小需关注两个指标,峰值流量和页面的平均大小

再看几张图:express

正常访问:npm

高并发:segmentfault

客户端精简与拦截:后端

那么怎么浅显的解释下高并发呢?把服务器比做水箱,水箱与外界链接换水有三根水管,正常状况下都能正常进行换水,可是忽然一段时间大量的水须要流通,水管的压力就承受不了了。再简单点:洪涝灾害、迟早高峰、中午12点的大学食堂,大概都是这个原理吧。这些现实问题怎么解决的呢,高并发是否是也能够借鉴一下呢?api

  1. 洪涝灾害:修固堤岸(加强服务器性能)
  2. 迟早高峰:多选择其余路线(分流,和分配服务器线路),不是必定须要就避开迟早高峰(减小客户端请求)
  3. 中午12点的大学食堂:学校多开几个食堂(静态资源与后端api分到不一样服务器)

回到高并发的问题上,我认为解决方案主要有这些:

  1. 静态资源合并压缩
  2. 减小或合并HTTP请求(需权衡,不能为了减小而减小)
  3. 使用CDN,分散服务器压力
  4. 利用缓存过滤请求

后来发现若是要把优化作到很好,雅虎35条军规中不少条对解决高并发也都是有效的。

回到业务

回到业务上,本次业务是助力免单。设计图没有几张,担忧涉及商业信息就不放图了,由于要求是多页面,我将业务分红三个页面:

  1. 首页,查看活动信息页
  2. 查看本身活动进程页,包括活动结束,开始活动,活动进行中和助力失败几个状态
  3. 帮助他人助力页,包括帮他助力和本身也要助力两个状态

解决方案

利用缓存存放数据

简单分析了一下,须要的数据有:

{
	// 这个活动的id,防止多个助力活动同时发起,本地存储混乱的问题
	id:'xxxxxxxxxx',
	// 结束时间,这个时间通常是固定的,也能够放到本地存储,不须要屡次请求,过了时间能够clear这个
	endTime:'xxxxxxxx',
	// 须要助力的人数
	needFriendsNumber:3,
	// 直接购买的价格
	directBuyPrice: 9.9,
	// 本身的信息,在帮助别人和发起助力时须要本身的信息
	userInfo:{
		id:'xxxxxxxxx',
		avatar:'xxxxxxxxx'
	},
	// 帮助过个人人列表,显示帮助个人页面须要用,根据需求看,这个列表人数不会太多,也能够放到本地存储
	helpMeList:[{
		id:'xxxxxxxxx',
		avatar:'xxxxxxx'
	},{
		id:'xxxxxxxxx',
		avatar:'xxxxxxx'
	}
	...
	],
	// 帮助别人的列表,能够放到本地存储中,在进入给别人助力时不用再发起请求,帮助过别人后加到数组中
	helpOtherList:[{
		id:'xxxxxxxxx',
		avatar:'xxxxxxx'
	},{
		id:'xxxxxxxxx',
		avatar:'xxxxxxx'
	}
	...
	]
}
复制代码

嗯,貌似均可以借助本地存储实现减小请求的目的,5M的localStrong应该也够用。这样算来除了助力他人和第一次获取基本信息还有获取助力名单,貌似也不须要其余的额外的请求了。精简请求这个方面目前就是这样了,由于尚未彻底写完,因此还有没考虑到的就要到写实际业务的时候碰到再处理了。

资源压缩

压缩资源的话webpack在build的时候已经作过了。

静态资源上传cdn

而后就是静态资源上传到七牛cdn,具体实现思路是在npm run build以后,执行额外的upload.js,服务器部署的时候只须要部署三个html文件就能够了。 package中:

"build": "node build/build.js && npm run upload",
复制代码
const qiniu = require('qiniu')
const fs = require('fs')
const path = require('path')
var rm = require('rimraf')
var config = require('../config')
const cdnConfig = require('../config/app.config').cdn

const {
  ak, sk, bucket
} = cdnConfig

const mac = new qiniu.auth.digest.Mac(ak, sk)

const qiniuConfig = new qiniu.conf.Config()
qiniuConfig.zone = qiniu.zone.Zone_z2

const doUpload = (key, file) => {
  const options = {
    scope: bucket + ':' + key
  }
  const formUploader = new qiniu.form_up.FormUploader(qiniuConfig)
  const putExtra = new qiniu.form_up.PutExtra()
  const putPolicy = new qiniu.rs.PutPolicy(options)
  const uploadToken = putPolicy.uploadToken(mac)
  return new Promise((resolve, reject) => {
    formUploader.putFile(uploadToken, key, file, putExtra, (err, body, info) => {
      if (err) {
        return reject(err)
      }
      if (info.statusCode === 200) {
        resolve(body)
      } else {
        reject(body)
      }
    })
  })
}

const publicPath = path.join(__dirname, '../dist')

// publicPath/resource/client/...
const uploadAll = (dir, prefix) => {
  const files = fs.readdirSync(dir)
  files.forEach(file => {
    const filePath = path.join(dir, file)
    const key = prefix ? `${prefix}/${file}` : file
    if (fs.lstatSync(filePath).isDirectory()) {
      return uploadAll(filePath, key)
    }
    doUpload(key, filePath)
      .then(resp => {
        rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
          if (err) throw err
        })
        console.log(resp)
      })
      .catch(err => console.error(err))
  })
}

uploadAll(publicPath)
复制代码

抛开与网站服务器的Http请求,第一次打开首页:

以后:

原理大概是这样,效果也仍是不错,本身的服务器只须要执行必要的接口任务就好了,不须要负责静态资源的传输

避免高频刷新页面

作了一个限定,5秒内刷新页面只获取一次列表数据,避免高频刷新带给服务器的压力

async init() {
      try {
        const store = JSON.parse(util.getStore('hopoActiveInfo'))
        // 避免高频刷新增长服务器压力
        if (store && (new Date() - new Date(store.getTime)) < 5000) {
          this.basicInfo = store
        } else {
          this.basicInfo = await getActiveInfo()
          this.basicInfo.getTime = new Date()
        }

        util.setStore(this.basicInfo, 'hopoActiveInfo')
        this.btn.noPeopleAndStart.detail[0].text = `${ this.basicInfo.directBuyPrice } 元直接购买`
        this.computedStatus()
      } catch (error) {
        console.log(error)
      }
    },
复制代码

设置响应头cache-control和last-modified

对于全部的数据和接口设置响应头,利用express模拟,若是两次请求间隔小于5秒,直接返回304,不须要服务器进行处理

app.all('*', function(req, res, next){
  res.set('Cache-Control','public,max-age=5')
  if ((new Date().getTime() - req.headers['if-modified-since'] )< 5000) {
    // 检查时间戳
    res.statusCode = 304
    res.end()
  }
  else {
    var time =(new Date()).getTime().toString()
    res.set('Last-Modified', time)
  }
  next()
})
复制代码

最后总结一下,采起了的措施有:

  1. 利用缓存,精简请求
  2. 合并压缩
  3. 静态资源上传cdn
  4. 避免高频刷新页面获取数据
  5. 设置响应头cache-control和last-modified

最主要的措施大概也只有这几个,作到的优化不多,差的也还很远,任重而道远,继续努力吧。

参考:

相关文章
相关标签/搜索