有組織就有協(xié)作劫瞳,有協(xié)作就有共享呕童,想把共享作為資源保留就要屯盤(pán)建庫(kù)崔赌。你產(chǎn)品的用戶(hù)接下來(lái)會(huì)問(wèn):“我能否把選好的資源打包下載呢意蛀?”,衣食父母的吩咐這得接著峰鄙。如何設(shè)計(jì)一個(gè)靠譜的打包下載服務(wù)呢?
摘要如下
要求
- 即刻下載
- 瀏覽器支持
- 獨(dú)立可復(fù)用
- 并發(fā)訪問(wèn)
- 資源占用小
- 安全
- 高可用太雨、高并發(fā)
設(shè)計(jì)
- 性能考慮:放棄壓縮
- 資源考慮:按流處理
- 并發(fā)考慮:Golang實(shí)現(xiàn)web服務(wù)
- 安全考慮:secure-token auth
- 獨(dú)立可復(fù)用考慮:獨(dú)立無(wú)狀態(tài)服務(wù)
- 瀏覽器支持考慮:小心處理
- 高可用吟榴、高并發(fā)考慮:集群方案
實(shí)現(xiàn)
要求
注定是產(chǎn)品經(jīng)理和技術(shù)總監(jiān)都必須滿意的,架構(gòu)師心里暗自打鼓囊扳。
即刻下載
產(chǎn)品經(jīng)理說(shuō)了:點(diǎn)擊打包下載按鈕之后還提示讓用戶(hù)等 “ZIP文件準(zhǔn)備就緒吩翻,請(qǐng)點(diǎn)擊鏈接” 的通知再開(kāi)始下載,這是不能接受的锥咸,說(shuō)好的無(wú)需等待呢狭瞎?
技術(shù)總監(jiān)說(shuō)了:點(diǎn)擊打包下載按鈕之后就 Loading 十幾秒甚至幾十秒直到下載開(kāi)始,這是不能接受的搏予。性能要好熊锭,否則大文件的話到底等到什么時(shí)候去?
瀏覽器支持
產(chǎn)品經(jīng)理說(shuō)了:產(chǎn)品的用戶(hù)群挺廣,用什么瀏覽器的用戶(hù)都有碗殷,都能支持的吧精绎?特別是從IE下載,文件名可不能是亂碼的锌妻。
獨(dú)立可復(fù)用
技術(shù)總監(jiān)說(shuō)了:做一個(gè)服務(wù)代乃,就要能獨(dú)立部署,要能被幾個(gè)產(chǎn)品復(fù)用仿粹。每個(gè)產(chǎn)品都來(lái)一套的話搁吓,可接受不了。
產(chǎn)品經(jīng)理說(shuō)了:每個(gè)產(chǎn)品要下載的資源可能來(lái)自不同地方吭历,要能支持任意源地址堕仔。
并發(fā)訪問(wèn)
產(chǎn)品經(jīng)理說(shuō)了:我們估計(jì)了初期同時(shí)使用打包下載的用戶(hù)數(shù),支持千人并發(fā)訪問(wèn)就可以了毒涧,圖片文檔下載嘛文件都不大贮预,我們會(huì)限制讓用戶(hù)一次不能下載很多文件的。
資源占用小
技術(shù)總監(jiān)說(shuō)了:經(jīng)費(fèi)有限契讲,初期用戶(hù)用得也不頻繁仿吞,這個(gè)服務(wù)只能給你們劃一臺(tái)低配置的VM,2核CPU捡偏、2G內(nèi)存唤冈、20GB硬盤(pán)夠了吧,日志不能丟哦银伟,好好干......
安全
產(chǎn)品經(jīng)理說(shuō)了:網(wǎng)上很多資源都存在被盜鏈或竊取的情況你虹,用戶(hù)A打包下載zip文件之后,這個(gè)zip文件不會(huì)被其他人再下載或盜鏈吧彤避?要保證安全哦傅物。
高可用、高并發(fā)
技術(shù)總監(jiān)說(shuō)了:用戶(hù)會(huì)積少成多琉预,架構(gòu)設(shè)計(jì)要走在前面董饰。高可用性如何保證?更高的并發(fā)訪問(wèn)如何應(yīng)對(duì)圆米?
不斷點(diǎn)頭稱(chēng)是的架構(gòu)師此刻腦子飛快的運(yùn)轉(zhuǎn)著卒暂,一面在心里再次確認(rèn)這些要求不是空想,一面如玩魔方一般拼湊思路進(jìn)行設(shè)計(jì)娄帖。
設(shè)計(jì)
傳統(tǒng)的文件打壓縮包方案
步驟如下也祠,缺點(diǎn)在括號(hào)內(nèi)。這些資源消耗在并發(fā)量增大時(shí)會(huì)急劇上升近速,VM資源成為瓶頸诈嘿。
- 從各源地址下載源文件到服務(wù)器(有等待時(shí)間堪旧,源文件臨時(shí)占硬盤(pán))
- 打包壓縮(有等待時(shí)間,壓縮很耗CPU)
- 提供壓縮包文件可對(duì)外下載(壓縮包占硬盤(pán))
性能考慮:放棄壓縮
在前文列出的《要求》里永淌,并沒(méi)有著重提到打包的壓縮比崎场,其原因
- 用戶(hù)最需要的是打包,即免去一個(gè)一個(gè)下載文件的繁瑣遂蛀。
- 用戶(hù)關(guān)心的是能否立刻開(kāi)始下載谭跨,而不苛求下載的文件大小與時(shí)間。
- 用戶(hù)打包的文件中李滴,除了文檔之外螃宙,如照片、視頻所坯、PDF文件可壓縮尺寸的余地太小谆扎,壓縮反倒徒勞。
放棄壓縮之后芹助,拿到源文件的第一塊內(nèi)容之后就立刻可以生成打包文件了堂湖,省下CPU資源,省下等待時(shí)間状土。假設(shè)要求輸出是ZIP包无蜂,無(wú)壓縮的包仍是ZIP格式。
對(duì)糾結(jié)用戶(hù)下載流量大小的人蒙谓,告訴你打包下載的內(nèi)容是經(jīng)過(guò) gzip 再由瀏覽器解開(kāi)的斥季,你可以安心了。
資源考慮:按流處理
既然放棄壓縮累驮,省下的工作都是I/O酣倾,即搬運(yùn)源文件內(nèi)容到服務(wù)器,再以一個(gè)文件的形式搬運(yùn)到用戶(hù)手里谤专,為什么中間要存一次文件呢躁锡?我們大可以把前后兩個(gè)管道接上,拿到的每一塊源文件內(nèi)容都立即倒手給用戶(hù)置侍。
我們只要明白并處理好
- 無(wú)壓縮的打包只是把源文件內(nèi)容一個(gè)一個(gè)接起來(lái)映之,形成一個(gè)文件而已。
- 文件下載起始需要一個(gè)預(yù)估大小墅垮,只能多惕医,不能少耕漱。
按流處理之后算色,無(wú)論源文件或打包產(chǎn)出文件都無(wú)需存在硬盤(pán)上了,剩下硬盤(pán)資源螟够,剩下I/O時(shí)間灾梦。
并發(fā)考慮:Golang實(shí)現(xiàn)web服務(wù)
打包下載服務(wù)仍是請(qǐng)求響應(yīng)模型的web服務(wù)峡钓。如何提高并發(fā)下載數(shù)?在資源緊張的情況下
- 多進(jìn)程較為消耗資源若河,考慮使用多線程或協(xié)程
- I/O 為主能岩,考慮使用貼近底層的語(yǔ)言
- web 服務(wù)選簡(jiǎn)單些的框架,不需要 view萧福、不需要 session拉鹃、不需要關(guān)系數(shù)據(jù)模型
結(jié)果 Golang 語(yǔ)言勝出,采用 Golang 內(nèi)置的 http 庫(kù)由 goroutine 支持并發(fā)鲫忍。
安全考慮:secure-token auth
打包下載服務(wù)授予 key pair 給服務(wù)使用者膏燕,并約定共同的hash算法,由服務(wù)使用者發(fā)出請(qǐng)求前做簽名生成 token悟民,之后由打包下載服務(wù)驗(yàn)證簽名坝辫,并進(jìn)一步驗(yàn)證是否過(guò)期以反盜鏈。
在打包下載服務(wù)這里實(shí)際只做了驗(yàn)證(authentication)射亏,而沒(méi)有做授權(quán)(authorization)近忙。沒(méi)錯(cuò),任何簽名正確且未過(guò)期的請(qǐng)求都可以被放行智润。你應(yīng)該注意到了及舍,簽名請(qǐng)求的是服務(wù)使用者,那是你的某個(gè)使用此服務(wù)的產(chǎn)品做鹰,不是最終用戶(hù)击纬,對(duì)最終用戶(hù)做授權(quán)是各產(chǎn)品自己的事,這也是為獨(dú)立性的考慮钾麸。
獨(dú)立可復(fù)用考慮:獨(dú)立無(wú)狀態(tài)服務(wù)
服務(wù)中必須把獨(dú)立做到極致更振,才易復(fù)用
- 不耦合任何產(chǎn)品里的業(yè)務(wù)和代碼
- 不存儲(chǔ)任何產(chǎn)品的數(shù)據(jù)
- 容器化,把代碼打成docker image饭尝,按需要的配置啟動(dòng)container
- 只接受可直接訪問(wèn)的源文件地址肯腕,并在打包前發(fā)option請(qǐng)求嘗試訪問(wèn)
- auth算法是公開(kāi)的,只有key pairs是服務(wù)自己存儲(chǔ)的钥平,前一節(jié)提到過(guò)
瀏覽器支持考慮:小心處理
這是些十分大眾又出名的坑实撒,小心處理就是。
小心文件名
- IE和現(xiàn)代瀏覽器對(duì)UTF8文件名的對(duì)待是有區(qū)分的涉瘾,我們?cè)诜?wù)端都滿足它們知态。
- 不同瀏覽器對(duì)特殊字符的轉(zhuǎn)換也是有差異的,我們?cè)诜?wù)里直接統(tǒng)一改好名立叛,同時(shí)做到區(qū)別重名负敏。
別拼接源文件地址進(jìn) URL
提出打包下載請(qǐng)求肯定要帶著若干源文件的地址,發(fā)一個(gè) POST ajax 請(qǐng)求比較理想秘蛇。因?yàn)槿暨x GET 請(qǐng)求只能拼 URL其做,源文件地址的URL長(zhǎng)度和數(shù)目未知顶考,存在超過(guò) URL 長(zhǎng)度限制的可能(IE有2084個(gè)字節(jié)的限制)。
POST 請(qǐng)求帶回一個(gè)固定長(zhǎng)度的存根妖泄,在回調(diào)方法里再組裝一個(gè)下載打包文件的 URL驹沿,直接打開(kāi)它就可以啟動(dòng)下載了。這兩個(gè)請(qǐng)求的銜接是一個(gè)用戶(hù)無(wú)法干預(yù)的連貫動(dòng)作蹈胡,所以時(shí)間很快渊季,所以把存根存放在內(nèi)存數(shù)據(jù)庫(kù)如 Redis 中可以設(shè)一個(gè)很快的過(guò)期時(shí)間。
高可用罚渐、高并發(fā):集群方案
看了前文梭域,你該知道這個(gè)服務(wù)被設(shè)計(jì)成獨(dú)立無(wú)狀態(tài)的。和其他集群一樣搅轿,一個(gè)負(fù)載均衡器就可實(shí)現(xiàn)病涨。
但是前文我說(shuō)過(guò)是用 docker 部署的,我推薦在幾個(gè)VM之間建 docker swarm璧坟,直接利用 docker 的 ingress 網(wǎng)絡(luò)特性來(lái)做負(fù)載均衡既穆,通過(guò)橫向擴(kuò)展 docker swarm 中節(jié)點(diǎn)數(shù)目和container數(shù)目來(lái)支持更高并發(fā)。
存根依賴(lài)的內(nèi)存數(shù)據(jù)庫(kù)如 Redis 自然也需要做集群雀鹃,負(fù)載很小幻工,只為高可用。
實(shí)現(xiàn)
你很走運(yùn)黎茎,我已實(shí)現(xiàn)好了囊颅,拿去吧~
demo: http://zipper.demo.wushaobo.info
github: https://github.com/wushaobo/zipper
docker: https://hub.docker.com/r/wushaobo/zipper