
做过文件上传,说一下心得体会。
一、前端:
1、通信层,不建议自己去造轮子,Uploadify,webuploader等等,已经做的很好了,比如文件大小这一个小需求现代浏览器可以使用FileApi获取,老旧的浏览器的版本则用flash去支持(你去招一个还会写flash的新前端啊!!)。这些功能如果自己去一一实现,感觉收益效果不会很理想,出于学习是可以的。
就以webuploader为例,文件分片,跨域上传,并发设置这些都有参数支持,可以说是设计很周到,当然使用过程中有踩到过坑,但都有解。
2、业务展现层,伴随这些工具提供的文档,我很方便地实现了自己的业务需求。如文件添加时行为,上传前行为,进度通知,等等,这些钩子事件几乎涵盖了一个上传流程的所有关键点。把工具的对象绑定到自己的dom上即可关联行为。
二、服务端
1、文件分片
如果对于上传的文件是有所预期并且有所限制的,比如图片,小文档,确实可以考虑不做文件分片。但是当你的文件动辄几百兆,一个G,不管是出于容错还是服务器配置的合理性来说,文件分片都是必须的。很不幸当时我的上传内容是游戏安装包,动不动1个G啊。
实现原理很简单,前端把文件切片后,每一片的提交都带上几个参数(共几片、本次是第几片、会话id),服务端当检查到所有分片都到齐后合并文件。
此处出现CASE: 合并后文件错误。
原因:网络原因等会出现数据传输不可靠。
解决办法: 检查文件MD5,但是这需要在合并后才能检查的,更进阶的是每一个分片的md5都要检查,错误的分片立刻要求重传。不过这要注意前端效率问题,js下md5库还是比较慢的,每一片都检查的话会花费较多时间。
2、服务器配置
建议为文件上传的url location单独配置服务器,因为这些参数在常规访问中是很离谱的。
以nginx+php为例子:
php要修改post_max_size和upload_max_filesize
nginx要修改client_max_body_size, client_body_buffer_size,fastcgi_send_timeout 等等
如果是跨域上传,nginx上对option请求的支持也是必须的,详情可以了解下CORS原理。
总体思想就是一些size参数要大于你的单个分片的大小,网络传输上不要造成超时。
3、负载均衡
最早的时候我们只有一台文件上传服务器,但只要是服务器总有意外挂掉的时候,所以2台机器负载均衡是稳定性的基本保障。但是文件分片上传并不是一个无状态的请求,如果每一个分片被打到了不同的机器,合并的复杂度和额外的网络开销又是负担。
所以我们的目标是同一个文件的上传被打到一台固定的后端机器,不同文件的上传可以被转发到不同的后端。
选取一个参数,比如用户id,做一致性hash,即可满足这个需求。这里我采用的方案是nginx+sticky模块
三、其他
文件上传实现起来,主体功能可能就是写一通代码一晚上就搞定的事情,但是后戏比较长,并发数设置,分片大小设置,一致性hash参数选取,超时行为,重试行为等等都需要根据实际业务场景去调整,比如并发数这个参数吧,最终我们就是1,即没有并发,原因有二,公网环境的上行带宽本来就很低,上行带宽很容易就会打满,并发多的意义不大。另一个原因是当并发上传时,每一片的到达是无序的,对于服务端的完整性检查来说会增加复杂度。
临时目录,这也是一个需要被重视起来的问题,并不是所有的分片都被顺利合并了,可能是网络错误,或是用户中途取消,服务器上只接收到了部分分片,而中断后服务端又是无感知的,时间一长,临时目录就会堆满零碎的分片。这里可以根据日期选取临时目录,然后通过定时任务删除2天前的临时目录。当通过日期选取临时目录时,要注意日期分界点的请求,避免被打到不同目录下,所以还需要前端参数配合,当前端参数和本地目录有所关联时,这里就存在接口参数注入的风险,所以这个细节深入就比较多了。
