第一次网站的搭建和优化经历
第一次网站的搭建和优化经历。
前言
上个月接到个任务,需要开发一个代码提交网站,以在这个月的比赛中使用,届时会有将近千名考生提交代码。嗯当时的需求就是如此简洁明了。
设计
因为之前重构过一次网站,采用的是React+golang
,于是这次也采用这样的前后端分离的技术。
于是花了一个星期,设计了前端样式,前后端数据传输的格式,数据库表结构,并且去github
搜索了可行的技术框架,参考别人的一些代码。
虽然当时预想的是在比赛结束前30分钟或者比赛结束后10分钟开放提交。但考虑到考生应该对系统有熟悉的时间,于是设计时将比赛的active
时间和open
时间分开,也即考生可以登录的时间和可以提交的时间分开。这样利于考生提前登录熟悉环境,也有利于分散流量。
编码
在感觉一切确定好后,在某个周末开工,由于先前重构过网站,这两个网站有一定的相似性,和一些类似的demo
,通过简单CV
和更改,花了一天写完了前端,一天写完了后端。
- 在前端方面,
不得不说react router
的升级真的激进,v5
跟v6
的差别好大,好多函数组建都被改名或弃用了,导致网上参考的别人的代码(采用v5
的)都失效了,只能去看官方文档。其余的,react
引入的hook
是真的香,不用再写一些类,定义一些有的无的东西,简化了代码,感觉非常舒心。不过目前也就只会用useState
和useEffect
,对于表单form
之类的,在数据处理方面写的有些繁琐,表单的每一项都要一个单独的handle
函数,提交也是单独一个提交函数,感觉完全割裂了表单数据的整体性,不像之前看到的php
写法。听说可以用useForm
来解决,下次去看看。
前端UI
库还是沿用了老一辈学长所使用的semantic UI
。但这个库感觉过于古老了,以至于在使用上,尤其是错误消息提醒方面的效果和动画渐变效果比较难以实现,不像antd
的错误效果有专门的函数,能够实现右上角、中间弹窗消失的效果。不过semantic
唯一让我中意的就是table
的样式,尤其是单元格的颜色对错时的绿色和红色。
- 在后端方面,
由于要维持用户的登录状态,众所周知这需要cookie
,但这个cookie
存哪里呢我一直没找到什么解释。故我就采用最原始的存在数据库里并存着失效时间,但这样每次访问都要验证cookie
的有效性而查询数据库,感觉效率会大大降低。网上搜到的一些方法如gin
也就采用session
的方式,但具体原理未能理解。
其次,在最初的设计中没有考虑后端代码的结构,虽然这次没有像第一次重构时所有代码写到一个文件里导致长达1000行,而是分了不同的文件,但在写的时候还是碰到了冗余代码的问题:在验证用户的权限、提交题目的权限等地方都有多次获取用户所在组等信息。原本设想的是不同地方获取的信息不同,如果统一写的话在不同地方会有冗余信息出来,但像这样,在提交题目时需要获取用户所在比赛的信息,在验证用户是否有权限访问某网站需要用户的身份的信息,特化在特定行为需要特定的信息,就为了那微不足道的性能优化降低了代码的可维护性,大大增加后续代码维护的成本。因此下次,设计时包括后端代码的结构组织,重复代码的提取,不要过早优化性能。
上周无意间发现了个不错的golang
的脚手架singo。采用的MVC
组织方式,将api
的监听层和事务逻辑层分隔开,层次清晰,同时也学习到了GORM
库,将与数据库交互的sql
语句进行封装。也学习到了更优美的中间件的使用方式。忽然意识到去年10月用python
的django
也是采用这样的方式组织代码,有视图层,管理层,序列化层,看样子是一个很好的组织方式。
优化
写完后,忽然多了个需求想增加个通知界面,能够发布通知和试题压缩包链接之类的,于是很快啊,考虑到通知的增加需求,加了个简单页面,数据库加了个表。
然后啊,忽然说想让选手可以在整场比赛都可以提交,并且想提前一天开放登录,但不能提交,让他们熟悉下系统。还好之前考虑到这点,区分了这两种行为的时间,就不需要做改动了hhh。
就绪后,整了个服务器,然后把网站部署了下,测试测试,感觉一切功能良好,然后按了下F12
,网络一览忽然发现传输的main.js
文件多大1.8MB
。
一个人传输1.8MB
,那1000个人就是2G
了。这对于区区只有10M
带宽的服务器怎么抵得住。
为了减少这个文件的大小,了解了webpack
的作用,通过询问别人得知可以通过source-map-explorer
插件查看这个js
的内部结构。于是看到了为什么文件会这么大。
原来是代码高亮插件highlight
如此之大,webpack
把该插件中适用所有语言的高亮脚本都整进去了,而事实上我们允许提交的语言只有C++
,因此只要保留这个语言的高亮脚本就好了。
明白了第一步的优化方向,剩下的就是如何做到这个。通过网上的搜索,由于我是用create-react-app
构建的应用,修改webpack
我不想eject
内部配置,于是用react-app-rewired
和customize-cra
(实际这个没用到,因为没有写成模块化的形式)修改。通过如下代码:
config.plugins.push( |
但这个代码是在理解了webpack
的配置形式才写出来的,相关的参考资料有:非常棒的,了解的,优化方向的。
可以看见highlight
插件的大小陡然减少了。
然后又对一些常用库比如reach, reach-router, axio, ace
等库采用cdn
引入,同时将原本用的关于时间的插件moment
插件换成了更加轻量级的dateformat
插件,也减少了体积。最终效果如下。
而关于cdn
的,一开始采用的是https://www.bootcdn.cn
,但在实际测试时网站有时加载比较慢,由于从这个网站获取js
慢导致的,因此最后还是把这些js
库放到了自己的服务器里,用腾讯云的cdn
来缓存这些网页文件。新用户能有几十G的流量应该够用的。
杂项
本地开发时数据转发的问题,是解决cookies跨域(?)的问题,在src
文件夹下创立setupProxy.js
,内容如下
const {createProxyMiddleware} = require("http-proxy-middleware"); |
关于用react-app-rewired
进行webpack
的配置的config-overrides.js
文件,创立于与package.json
同级目录,内容如下:
var webpack = require('webpack') |
其中引用cdn
的写法,也即externals
里面编写的形式,在上面提到的非常棒的的介绍里有提到。非常感谢这位作者。
简单来说,冒号前面是包名,后面是该脚本js
最终赋值给window的全局变量名称。而这个名称通过查阅该js
脚本就可以找到。一般就位于js
脚本的开头或结尾。
结果
最终,网页访问压力都由腾讯的cdn
承担了,后端api
的访问由服务器承担。
在实际上线时,全程监测着服务器的cpu
占用率和流量情况。由于前一天发现轻量级服务器不支持短时提升带宽,对当前服务器只有10M带宽感到担忧,但从最终结果来看,带宽高峰期也就出现在比赛开始前和比赛即将结束时,cdn
的带宽高峰期有11M
,只承担了后端api
的服务器带宽高峰期也有9M
(但感觉有点异常),可见cdn
很好的缓解了流量高峰时服务器的压力,将前端后端访问分离开,才保证了本次比赛的顺利,因为两者加起来所需要的带宽超过了服务器本身的带宽。
总结
最后的最后,感谢你能看到最后。这里只是一些个人的碎碎念。
总而言之,在最初阶段,应该有充足的时间进行设计,规划好前端页面的组织,前后端数据格式,后端代码的结构。一个良好的设计能够大大降低后续代码的编写复杂度和维护难度,毕竟磨刀不误砍柴工。
在优化方面,前端涉及的领域是webpack
,优化除了上述提到的js
,实际还有css
方面的优化。该网站虽然主要的js
只有40kb
,但css
文件有500kb
。可能是因为包括了semantic
库的缘故。但由于后来在服务器的nginx
开启了gzip
压缩,传输的css
只有100kb
,因此也就没有太大的优化了。后端的由于不太熟悉,除了用高性能的框架之外,还暂时不知该如何优化。