当前位置: 首页 > 后端技术 > PHP

关于七牛云正确使用姿势探索

时间:2023-03-29 14:30:17 PHP

业务场景需求我们的项目有一个文件上传需求,需要从客户端上传到七牛云的对象存储和自己的应用服务器。这里使用七牛云主要是实现下载和分发。应用服务器需要保留一份,因为后面需要做文件分析(而且需要分析结果上传后立即显示给客户端)。另外,由于是初期项目,暂不考虑使用独立服务器进行分析。使用的技术栈服务器:Centos7开发语言:PHP框架:Laravel前端上传组件:百度的WebUploader方案准确的说,我经历了三个阶段,才真正完美的实现了需求(主要是解决上传速度)。第一阶段的解决方案和细节前期面对需求很容易想到的思路:客户端先把文件上传到应用服务器(因为上传可以及时分析),然后再上传到七牛云。所以我的解决方案是:前端使用webuploader,后端使用一个Laravel插件进行七牛云文件处理:overtrue/flysystem-qiniu(https://github.com/overtrue/f...,该插件的接口非常简单易用(但是有坑,后面会提到)。然后为了解决性能问题,我还做了以下工作:1.使用片段上传2.后续上传到七牛云采用异步方式(因为文件上传到其他应用去下载这个文件,中间有很多时间来完成上传任务)关于分片上传,这里介绍分片上传的实现思路。client主要是把大文件按照一定的大小分成块,然后上传到Server,所以会有多次请求,每个请求还需要带上关键信息:当前chunk(从0开始)和chunks(总数片段)。因为我使用WebUploadercomponent,客户端不需要做什么,只需要配置简单的信息(是否分片和分片的大小)。服务端的处理逻辑是:来自客户端的请求有两种情况:1.文件总大小小于待分片大小。这时候直接处理文件。2.流程碎片化。具体逻辑就是判断chunk和chunk。如果相等则为第一种情况,直接处理上传,否则处理分片逻辑。处理分片的逻辑是:将当前分片保存到临时目录(根据分片命名),然后判断所有分片完成后,合并文件。具体逻辑是判断chunk+1是否等于chunks。合并逻辑是循环读取临时文件,然后写入新文件(合并后),这里可以顺便删除临时文件。遇到的坑:这里处理碎片文件的时候,用Laravel的文件处理接口Storage::append很方便,但是这个接口有一个坑,就是号称在文件末尾加一个换行符。因此,合并后的文件无法恢复为原始文件。解决办法就是老老实实用php的fopen,fwrite,fclose的那一套。关于PHP异步处理PHP的异步实现可以参考鸟哥写的文章:http://www.laruence.com/2008/...主要方法有:客户端AJAX,popen函数,curl,fsocketopen等但是这篇文章比较老,有很大的局限性。现在有协程等处理方案(现在swoole也提供协程方案,client-server任务分发也可以用swoole),在架构上可以用queues等(queues就是那种感觉靠谱的).PS:前期用的是简单粗暴的popen,后来用的是Laravel提供的queue。一期方案的问题通过上述方案,很容易实现一个版本。但高兴不了多久。.,在后续测试中遇到了一个奇怪的bug。文件过大时,任务脚本上传到七牛云失败。这里的脚本是用Laravel的artisan写的。当我直接在终端调试脚本命令时,没有任何异常(准确的说是看不到任何异常)。前面说过七牛的SDK使用的是overtrue/flysystem-qiniu,其writeStream接口是出于性能考虑。$disk=Storage::disk('七牛');$stream=fopen($localFileName,'r');$disk->writeStream($fileName,$stream);如果(is_resource($stream)){fclose($stream);代码表面看起来很理想,使用文件流上传(怕吃内存)。但事实证明,一切都只是表面。.遇到大文件无法上传到七牛云,断点调试到$disk->writeStream,发现返回false。然后调试到overtrue/flysystem-qiniu扩展的源码。然后发现了一个大坑。.主要有两个问题:1.writeStream只是一个假流。具体源码写在扩展的QiniuAdapter.php文件中。这是一段代码:publicfunctionwriteStream($path,$resource,Config$config){$contents='';while(!feof($resource)){$contents.=fread($resource,1024);}$response=$this->write($path,$contents,$config);如果(false===$response){return$response;}returncompact('path');}注意这里的$contents变量最终相当于一个大文件内容的大小(服务器为这个变量分配的内存)。稍后将在方法之间传递。所以这是假流!2.界面调试不友好。write方法中屏蔽了$error,只返回false,不方便我们排查问题。最后断点打印这个$error才知道报错是:“invalidmultipartformat:multipart:messagetoolarge”,这应该是七牛实际返回的,但是这么重要的信息被这个扩展屏蔽掉了。二期解决方案知道一期解决方案的具体问题后,我一直在思考(我不会提到扩展..我现在怀疑它的存在..),甚至认为也许整个想法是错误的一开始(通过SDK上传文件的方案)。后来,我真的找到了。七牛云官方提供了一个脚本工具:Qshell(https://github.com/qiniu/qshell)。这是一个在命令行上运行的脚本。具体操作请参考文档。放到我的项目中,也是集成到七牛的任务脚本中。后来测试就OK了,整个过程就可以跑通了。但是无意中发现了第二阶段的重要问题,这个上传是服务器的上行带宽!而我们平时支付的带宽就是我们购买的上行带宽!(下行一般是免费的)。如何才能做到这一点!由于我们的上传业务是商家使用的,使用频率不会太低,上传时会影响前端网站的访问速度。这里具体说一下服务器带宽问题(网上查询后整理的):首先,服务器带宽方向的描述一般用上行和下行,上传和下载指的是动作。上行链路是指从服务器流出的带宽。如果在其他机器上下载服务器上的文件,主要是使用服务器的上行带宽(这里说的是我们平时浏览网页,其实不同的客户端从服务器下载数据,html文件,css等,然后渲染,因此网页浏览也会占用上行带宽)。下行链路是指流入服务器的带宽。如果在其他机器上向服务器上传文件,比如通过FTP上传文件,则主要使用服务器的下行带宽(下行带宽也用于在服务器上下载文件)。目前阿里云等云提供商不限制下行带宽。大多数服务器使用更多的上行链路带宽和更少的下行链路带宽。通过对带宽的理解,再回到我们项目的上传实现思路,可以看出一开始是错误的(应用服务器应该不能作为中转)!为了节省时间,第三阶段(最终)计划跳过了官方文档并使用了第三方扩展。现在看来,又要回到官方文档了。翻了七牛的文档,发现有解决方案可以避免占用服务器上行带宽的问题。主要思路是避免使用应用服务器的上行带宽,因为上行带宽非常宝贵,尽量使用下行带宽(免费而且速度快!阿里的大概是每秒60M)。具体实现是通过七牛的表单上传方案直接将客户端的文件上传到七牛(这一步与应用服务器无关,所以避免了,直接上传到七牛的速度很快,基本只看on根据客户端的网速,一般的需求,七牛提供了回调方法给我们的应用服务器)。那么,因为我们的应用服务器也需要文件,所以解决办法是直接在我们的应用服务器上下载七牛的文件(这个可以同步屏蔽,可以使用前端等待效果,解决用户体验问题)。因为前面提到到服务器的流量占用了下行带宽。所以这里也会非常快(而且是免费的^_^)。这个方案基本上是完美的。总结首先是个人的反思。前期调研还不够,项目初期又有点紧,这也说明了投入时间的重要性。其次,关于项目经验:上传第三方云存储,不要使用应用服务器进行传输!可以直接上传到第三方云服务器,如果有后续处理逻辑可以使用他们的回调接口。