0620 - 關於 Klib 分享,各種糾結之後,我選瞭如下方案
關於 Klib 分享,各種糾結之後,我選瞭如下方案
技術方案選型是件很有意思的事,各個環節都有各種選擇,可以組合出各種可能。在這些可能性中,挑選出最佳方案,是我很喜歡做的事。
最近剛剛完成 Klib 的標註分享,趁著熱乎勁,小結一下:過程中糾結了哪些方案,以後最後選擇了什麼。
0) 先來看看最終效果
這就是 Klib 分享標註的操作流程:點選分享,立即得到可以全球訪問的網頁。操作不能更簡單,背後的技術邏輯卻很複雜:
實際的開發是混在一起的、思路也是交叉的,不過,為了介紹方便,我大致按照資料流來推演。
1) Klib 與介面伺服器
這部分的功能比較直接:Klib 將標註內容傳送給介面伺服器,伺服器處理完後返回結果。
需要介紹的,倒是功能之外的東西:
- 如何防止介面被攻擊
- 如何做身份識別
這部分內容其實是很複雜的,我最終採用了和 Klib 安全性相稱的方案。
1.0) 防止介面被攻擊
1.0.0) 介面伺服器使用 https
這是最基礎、但非常有效的方式,全程使用 https 加密,已經可以大大提高安全性。
1.0.1) 防止介面被非法使用
如果介面是公開的、所有人都可以任意訪問,就可以隨意地向伺服器丟垃圾資料,迅速將伺服器擠爆。
比如好的做法是 使用非對稱加密,即使用一對私鑰、公鑰,使用 私鑰加密 的資料,只能使用 公鑰解密;反之,使用 公鑰加密 的資料,只能使用 私鑰解密。整體流程大致如下:
- 介面伺服器開放
公鑰 A
- 每個 Klib 客戶端生成新的
私鑰 B
和公鑰 B
- Klib 客戶端使用
公鑰 A
加密公鑰 B
,並將其傳送給介面伺服器 - 介面伺服器使用
私鑰 A
解密後,儲存該客戶端對應的公鑰 B
- 之後,Klib 客戶端傳送資料時,使用
私鑰 B
加密,介面伺服器收到後使用公鑰 B
解密,並用公鑰 B
加密後返回資料
聽起來有點像繞口令?
開發上也有點麻煩,畢竟伺服器還要儲存每個 Klib 客戶端對應的公鑰。如果有多個伺服器,則需要在不同伺服器間同步公鑰,更加麻煩。對於我這個小產品 + 實驗功能來說,暫時不需要這麼高的安全級別。
於是,我採用了更簡單、但夠用的 AES 對稱加密。即 Klib 客戶端和介面伺服器使用相同的 AES 加密方法、同一個密碼,加密請求和響應的資料;如果不能提供正確的加密,就無法使用伺服器介面。
這一方案主要的風險是:黑客可以反編譯 Klib 得到密碼。除了 Klib 本身會編譯並簽名,我還在程式碼里加密儲存密碼。基本上除了跟我有八輩子解不開的愁,99.9999% 的人是不會花精力來破解這個密碼的。
1.0.2) 使用時間戳 + MD5
即使加密過的資料,最終也只是表現為一個 http 請求,而這個請求是可能被本地攔截,進而用於模擬正常使用者請求。
對應的防護是,在 http 請求中加入時間戳,並對 http 頭的內容部分計算 MD5(或 CRC 等),伺服器端進行驗證,就可保證 http 頭不被濫用。
其實,這是 OAuth 的範疇。好在,我在開發 圖床神器 iPic 時,先後從客戶端的角度實現了七牛、又拍、阿里雲、Imgur、Flickr、Amazon S3 的 OAuth,這次實現一個簡單的伺服器端部分,也不算麻煩。
1.1) 如何做身份識別
上面說的是在面對黑客時的防護,聽著有點暈是吧?下面來說說正常情況下的身份識別。
比如:如果使用者嘗試停止一個分享,如何判斷該使用者是否有許可權?
如果有賬戶系統,這點比較容易解決。而 Klib 尚未引用賬戶系統,怎麼辦呢?比較高階的是使用區塊鏈(咳咳),我目前的做法是:使用者使用 Klib 分享一本書的標註時,伺服器會返回一個隨機數。下次使用者在停止分享時,只要能提供這個隨機數,即判定為有效請求。在上述各種防護的前提下,可以有效地防止被惡意停止分享。
2) 介面伺服器
介面伺服器是整個系統中最複雜的部分,它的職責比較多:
- 驗證請求,並接收資料
- 儲存資料
- 根據資料生成靜態網頁
- 將靜態網頁輸送給靜態伺服器
- 更新、刪除分享時,更新資料儲存和靜態伺服器
驗證請求和前面的介紹是對應的,這裡略過不表。
2.0) 使用 Python + Flask 實現功能部分
所謂介面伺服器,首先就是要開放介面(開門接客)具體的,就是 http 請求的路由表。比如,當 Klib 客戶端向 https://api.klib.me/share 傳送資料時,要有相應的程式碼來接收處理這個請求。
在之前的文章 我入門 Python 後總結的基礎教程 中,我已經介紹了使用 Flask 框架,這裡不再重複。
2.1) 使用 Nginx + Gunicorn 搭建伺服器
同上,請參考 我入門 Python 後總結的基礎教程。
另外,使用 Supervisorctl 保證服務可靠執行。
2.2) 使用 MySQL + SQLAlchemy 儲存資料
從資料儲存的角度看,書的標註都是很規整的,無非是書名、作者、筆記內容等等。於是我選擇了最常用的關係型資料庫:MySQL
如果直接使用 SQL 語句運算元據庫,既繁瑣又不安全,這裡我使用可稱為 ORM (Object Relational Mapping) 界事實標準的 SQLAlchemy 構建 Model、運算元據庫。
我本來想說「這沒什麼好介紹的」,但實際上,MySQL 的坑很多。比如,如果要支援 Emoji 表情,就要全程使用 utf8mb4 編碼。還有很多其他的坑,此處略去一萬字…
2.3) 使用 Jinja 模板生成靜態網頁
關於標註部分,Klib 傳送的是 Markdown 格式,如:
# 簡單思考
## 卷首語
- 商業的本質就是“持續提供使用者真正想要的東西”,除此無他。
- 召集具備回應使用者需求的熱情與能力的員工,併為他們營造出無拘無束可最大限度地發揮其才能的環境,除此無他。
## 第一章 經商不是“打仗”
- 重要的是不斷磨鍊對“大眾真實需求”的感知能力和使之實體化的技術。
- 音樂和體育不同,不用與任何人戰鬥。
需要使用 markdown 模式將其轉換成 html 格式,如:
<h1>簡單思考</h1>
<h2>卷首語</h2>
<ul>
<li>
<p>商業的本質就是“持續提供使用者真正想要的東西”,除此無他。</p>
</li>
<li>
<p>召集具備回應使用者需求的熱情與能力的員工,併為他們營造出無拘無束可最大限度地發揮其才能的環境,除此無他。</p>
</li>
</ul>
<h2>第一章 經商不是“打仗”</h2>
<ul>
<li>
<p>重要的是不斷磨鍊對“大眾真實需求”的感知能力和使之實體化的技術。</p>
</li>
<li>
<p>音樂和體育不同,不用與任何人戰鬥。</p>
</li>
</ul>
這裡讚歎一下:Python 輪子就是多。只需輕輕地匯入 markdown 模組,即可優雅地將 Markdown 轉換為 html 格式,舒爽。
import markdown
html_str = markdown.markdown(markdown_str)
對於最終生成的靜態網站,像 css/js 等部分都是一樣的,只是頁面標題、正文等內容性的東西不同。於是,使用 Jiaja 模式表示這些通用部分,並 {{ title }} 這樣的標註符表示各個分享所不同的內容部分;再用 render_template 方法替換模板中的內容,即可生成對應的靜態檔案。
感嘆:這樣簡潔直接的操作、無需各種複雜的配置,就能得到最後想要的東西,真真是程式設計中最可愛的環節。
3) 靜態伺服器與 CDN
有了靜態伺服器,就像是有了寶貝,不能只是自己藏著,得拿出來讓大家瞧瞧,這就是靜態 (Web) 伺服器要乾的事。
當然,靜態伺服器和介面伺服器,在物理上可以是同一臺伺服器,這裡只是從角色上進行區分。
在展示靜態網頁方面,技術選型上主要有 2 方面的需求:
- 網頁內容能實時更新
- 使用者訪問速度快
其中,內容的更新對應使用者分享時的 3 種操作:
- 建立分享
- 對應建立靜態 html 檔案
- 更新分享的內容
- 對應更新 html 檔案內容
- 停止分享
- 對應刪除 html 檔案
好,帶著「實時」建立、更新、刪除 html 檔案這 3 個需求,我們來看看如何提高訪問速度。
3.0) ? 僅使用單一伺服器
首先,如何什麼都不做,意味全球的使用者(Klib 必須是國際性產品,得考慮全球使用者,嗯)都要連線這臺伺服器。
且不說併發數等限制,單從網速上看,如果將伺服器放在國內,國外使用者勢必慢;反之亦然。更何況國內還是電信、網通、以及神奇的長寬,國外也有 N 多國家。
如果確實要這麼做,比較好的方案是使用 阿里雲香港伺服器,可以兼顧國內國外使用者。暫時,不採用這一方案,每月省下 $19…
3.1) ? CDN
進而,通常的做法是使用 CDN.
CDN 確實可以有效提高不同地區、不同網路環境下的訪問速度,且極大地降低對靜態伺服器的壓力。不過,CDN 有個致命的侷限:內容更新慢。尤其在更新、刪除內容時,這種慢會帶來業務上的問題。
比如,使用者在 Klib 中分享標註後又停止,卻發現之前產生的網頁依然可以訪問,使用者會覺得這是 Bug,進而會帶來很大的客服壓力。於是,跳過這一方案。
3.2) ? 國內、國外多臺伺服器
下一方案是:國內、國外各一臺(或多臺)伺服器,通過 DNS 伺服器進行分流,相當於自建 CDN。
不料,卻遇到一個坑:國內伺服器的外網速度普遍較慢。比如我試了阿里雲上海節點,從國外伺服器使用 scp 或 rsync 傳輸一個 10 KB 的檔案需要 4s,跌破了我的眼鏡。並且,阿里雲我也只買了 1 MB 頻寬的小水管,併發時速度會很慢。於是,這一方案也被放棄。
3.3) ? 最終方案
最終採用的方案時:國內使用阿里雲 OSS、國外使用 Amazon S3(注:因為測速顯示,我的國外伺服器在全球的訪問速度尚可,暫未實施,不過原理是一樣的)
- 速度 方面,測試軟體顯示,阿里雲 OSS,在國內的訪問速度是不錯的。
-
可靠性 方面,阿里雲 OSS 本身肯定值得依賴的(至少比我自己搭靜態伺服器靠譜的多)
- 另外,阿里雲 OSS 可以從我的國外靜態伺服器回源,意味著使用者通過阿里雲 OSS 訪問網站時,肯定可以得到內容,這已經滿足了 90% 的場景
- 實時更新 方面,伺服器端使用 pyinotify 監控 html 檔案所在目錄,一旦發現有檔案更新、刪除,立即同步至阿里雲 OSS 上。同步速度也不錯(比阿里雲 ECS 強太多),使用者的響應幾乎是實時的
比如,你訪問這篇分享試試速度如何:
http://s.klib.me/share.html
以及 全國測速結果,開啟時間基本在 1s 內,滿意。
注:我另外也考慮了七牛雲,不過 七牛不支援為儲存空間繫結域名、而只支援 CDN 的方式繫結域名,而 CDN 的方案已經被放棄,所以只能放棄七牛(以及七牛的免費 http 流量…)
3.4) 搜尋引擎優化
這是個全方面的話題,這裡只提幾點:
- 搜尋引擎直接訪問靜態伺服器,因為 CDN 等因素對搜尋引擎是種干擾。這一點,可以通過 DNS 伺服器來解決。
- 向搜尋引擎提供 sitemap 檔案,包含靜態網頁的網址列表。雖然搜尋引擎不一定真的使用這個檔案,但必要的基本功還是要做的,萬一被用了呢?
4) 網頁的適配
好,經過千辛萬苦,使用者終於可以開啟網址、看到分享內容了,是不是大功告成了?
錯,還有很大一個坑:網頁佈局及適配
比如:
- 作為文字類的網頁,字型、字號、配色、行間距、留白得看得過去吧?
- 使用者可能在 24 寸顯示器、iPad、手機等不同裝置上開啟網頁,總得能正確顯示吧?
- 總得方便使用者分享到不同 SNS 吧?
- 使用者分享到微信、朋友圈,總得顯示個縮圖吧?
- 使用者分享到 Facebook、Twitter,總得以卡片形式顯示、摘要總要有吧?
- …
這方面我不是專家,Klib 的分享也只是做個入門。
在 Klib 中閱讀效果類似:
PC 端最終效果類似:
手機端類似:
5) 其他
表一漏萬,除了看得見的、說得出的,還有很多看不見的東西,挑幾個來說。
5.0) 區分開發、測試、線上環境
如何在同一程式碼倉庫下,區分開發、測試、線上環境?
我目前的做法是,建立排除在 Git 外的 .env 檔案,其中儲存了開發、測試、線上環境所對應的配置。程式啟動時,先讀取配置檔案,然後根據配置檔案啟動對應的引數,如資料庫地址、服務埠、日誌級別及檔案位置等等。
5.1) 服務監控
從上面的介紹可以看到,整體的框架涉及多個模式,並且互為備份的環節並不多,有一個模組出問題,就可以導致整個流程出問題。最好有個機器人一直盯著,一旦出問題,立即通知我(總比使用者跑過來報 Bug 強)
如何能有效監控呢?幾個供參考:
- 介面伺服器方面,一旦發現嚴重錯誤,立即發告警郵件(目前還沒做)
- 在介面伺服器建立測試指令碼,包含建立、更新、刪除分享功能,並定期工作。一旦出問題,立即發告警郵件
- 使用 360 監控等服務,監控某分享連結是否可訪問
- 以及,一些基礎的伺服器監控工具
5.2) 一定要有日誌
日誌可以說是線上服務的生命線,一旦出問題,第一件要做的事:查日誌。那首先得有日誌呀?恩,程式碼裡要輸出。
另外,日誌檔案要 定期切割,避免過大。
5.3) 一定要有備份
萬一伺服器被黑了、被人 rm / -rf
了,萬一資料庫被刪了…作為獨立開發者,我總不能申請破產保護吧?
備份一定要有(雖然最好不要用),比如:
- 伺服器級別的定期製作映象,一般雲伺服器都有此功能
- 製作指令碼,定期將資料庫、日誌等關鍵資料,備份至 Dropbox、Amazon S3 等可靠的網路儲存中,當然要加密儲存
- 使用 GitHub/GitLab 等私有 Git 倉庫備份程式碼。什麼都沒了,只要有程式碼,就能東山再起
6) 事務諸葛亮
以上就是 Klib 分享大體的技術選型、以及期間的糾結。雖然已經能幹活,現在回過頭來看,主要的問題是:
涉及模組多,維護成本高
想象一下這個場景:
- 在吃年夜飯時,收到不能建立分享的報警郵件
- 一邊後悔沒有給伺服器上香,一邊放下筷子、開啟 MBP
- 查了半天,發現是 MySQL 的問題
- 又查了半天,發現竟然是因為 VPS 硬碟空間不夠了
- 於是,新增新資料盤,小心翼翼地遷移資料,膽戰心驚地重啟資料庫
- 手動測試,持續觀測日誌一段時間,好在沒有問題
- 剛巧,新年的鐘聲敲響,完美…
相比之下,如果使用 AWS 這種能託管 MySQL 的服務,上述問題都不會發生,還不會錯過微信紅包裡的幾個億。
再比如,使用 Heroku 之類的服務,就可以免去大部分需要自建伺服器部分。沒選 Heroku 主要的原因是:國內訪問速度太慢。不過現在想想,可以通過國內、或香港伺服器回撥的方式加速。於是,Heroku 又成為很有優勢的選擇。
聽完之後,你會怎麼選?
我所說的,都是錯的。
那麼,我的公眾號「自在開發」,你還關注嗎?每週二早 8 點,技術長文、準時推送。
相關文章
- 0620 - 關於 Klib 分享的技術選型,各種糾結之後,我選瞭如下方案
- 乾貨丨RPA 關於各種對賬的分享與總結
- 關於Android中各種尺寸的總結Android
- 關於糾結的recycle pool的設定
- 關於join查詢的那麼點糾結
- 我終於弄懂了各種前端build工具前端UI
- 關於SQL Server tempdb 的各種操作SQLServer
- UML 之 各種檢視簡介 & UML類圖幾種關係的總結
- 各種CSS居中方案CSS
- 關於雲控系統的各種細節
- 關於ERP系統,模切企業別糾結了
- 基於SSM的小程式中後端各結構層作用及關係SSM後端
- 我為什麼勸你不要過度糾結於技術細節?
- 程式猿的年終總結,各種版本各種殘
- Oracle體系結構之-Oracle中各種名稱Oracle
- 各種實用連結
- .NET系列 第一篇 關於跨平臺的種種言論之我見
- 年底跳槽真的很糾結?解決方案在此!
- antd專案各種webpack打包方案Web
- 關於開啟軟體提示各種缺少dll問題
- 關於網上各種技術文章的一點思考
- 我眼中的各種程式語言
- 關於C#委託三種呼叫的分享C#
- 關於react我的理解與總結React
- 各種相關的圖結構-定義及相關研究進展
- 「API分享」整理了各種各樣的免費API介面API
- Photoshop 中各種快捷方式整理分享
- 關於AppDelegate瘦身的多種解決方案APP
- 關於insert /*+ append*/ 各種insert插入速度比較APP
- 關於各種List型別特點以及使用的場景型別
- css各種佈局總結CSS
- Java 各種鎖的小結Java
- js各種驗證總結JS
- asp.net 各種連結ASP.NET
- oracle 各種遷移總結Oracle
- 關於我
- CSS渲染之各種炫技CSS
- 愛奇藝的資料庫選型大法,實用不糾結!資料庫