# 網頁後端開發觀念筆記
q公司雲端服務工程師面試的問題,因為被問爆所以做個筆記,也幫自己做個觀念上的澄清。
## 作業系統複習
會使用到的作業系統概念以及接下來的I/O模型都可以參考[這篇](https://segmentfault.com/a/1190000003063859#articleHeader14)
### user space與kernel space
作業系統中的記憶體分為兩個部分,一個是負責運行OS kernel的kernel space,剩下的是user space,這樣切割是為了保證作業系統不會因為user端亂搞而crash,user space要與kernel space要溝通只能透過system call。
### Context switch
Context switch是指電腦在多工情況下,切換process的動作,在切換不同process時,OS必須要先將目前運行的process的運行狀態儲存到register中,再切換成另一個process。
### blocking/non-blocking/multiplexing
當user space要向kernel space要求資料的時候,kernel space準備相對應的資料是需要時間的。而Linux中主要有5種不同的I/O方案,這邊主要介紹四種較常用的I/O模式
#### blocking I/O
blocking I/O在user space向kernel發出system call後就會進入等待狀態(也就是被block住了),直到kernel準備好資料,user space去複製kernel space的資料後才有辦法開始繼續運行user space的process。
這邊可以注意到block並不是kernel給user下的某個指令,而是自然發生等待的一種行為。
#### non-blokcing I/O
non-blocking I/O在user space發出system call後kernel space會馬上給一個回覆,若準備好了user space則可以把資料複製過去,若還沒準備好,kernel會回傳error,而不是讓user space在那邊空等,此時user space就會繼續發system call,不斷詢問直到kernel準備好為止。
#### multiplexing
multiplexing又稱為多路復用,特性其實就是單個process可以同時監聽多個socket,當有某個socket資料已經就緒時就通知user space。
#### asynchronous
非同步I/O的實現方法如下,user space向kernel發起system call後,kernel會馬上回傳給user space,如此user space就不回被block住,直到kernel space準備完成後,kernel space會發一個signal給user space來通知其可以拿資料了。
### 什麼是fd(file descriptor)
在Linux的檔案系統中,當一個process開啟一個檔案,就會在kernel建立一個file entry去指向你開啟的那個檔案,這個entry就是所謂的fd(有可能是一個整數,例如開啟了10個檔案,fd有可能就是0~9),而每個process所擁有的fd都是彼此不相干的(舉例來說不同process內相同的fd0所代表的file是不同的)。
[參考資料](https://stackoverflow.com/questions/5256599/what-are-file-descriptors-explained-in-simple-terms)
## Linux環境下的網路I/O
這邊我們主要探討的是兩種不同的I/O模型,分別是select與epoll,而poll基本上是select的進階版,解決了連線上限的問題。
### selcet
select是屬於blocking I/O,當user space呼叫`select`的system call後,在user space的process會被block,此時kernel space會檢查select負責的所有socket,當有任何socket就緒,就會return,此時user space可以再呼叫`read`來讀取需要的資料。
這邊是`select`的system call API:
```clike
int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
```
呼叫`select`這個system call後會傳入一組fd,讓select來監控這些socket,user space可以透過檢查`fd_set`來確認那些socket已經準備就緒。
select方法最大的優點是跨平台支援,幾乎所有OS都有支援,但是這個方法有其缺點,第一就是一個process能開啟的檔案有其數量上限(預設1024個),再來就是每次對`fd_set`進行掃描會造成時間浪費。
### epoll
epoll透過創造一個事件表來記錄所有監聽的socket,所以他需要額外1個fd來指向這個事件表,epoll的操作會用到以下三個API:
```clike
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
```
`epoll_create`來創造一個大小為size的事件表,注意這個size只是建議大小,並沒有像select一樣有大小限制,`epoll_ctl`將監聽的fd註冊進事件表中,`epoll_wait`把就緒的fd輸出到user_space。如此既可以同時監控更多的socket(取決於epoll_create中的size參數,並不限於1024個),也不再需要對所有監控的fd做掃描。
epoll的缺點就是目前只有Linux2.6以上的作業系統有支援,跨平台支援度遠比select差。
## Web Server的比較
所謂web server有很多的功能,最主要的功能當然是將靜態網頁資料回傳給客戶端(如html)以及做動態資料的轉發(如用Flask寫的application),其他幾個常聽到的功能有負載平衡(load balancing),反向代理(reverse proxy)等等。而其中我們較常使用的web server有**Apache**與**Nginx**兩種,這兩種最主要的差異就在於網路I/O模型的差異,在Linux kernel中,Nginx使用了epoll模型,而Apache則是使用了select模型,由上面網路I/O的探討就可以知道兩者之間的差異。
### Nginx
Nginx透過epoll實現I/O模型,可以同時處理更高的連接數(由於epoll對於多數連接的處理比select更具優勢),當然缺點就跟epoll一樣,跨平台支援較差。
### Apache
Apache透過select實現I/O模型,在Apache中透過multi-thread來增加同時監聽的socket數量,缺點就是就算使用了multi-thread支援,process會被block住並且做context switch的缺點依然存在,故效能沒有顯著提升。
Apache相對於Nginx的優點是較為穩定,各種module開發完整,對動態配置支援較好。
### 總結
若要求較高效能,選Nginx,若要求穩定,選Apache,但高效能且輕量的Nginx遲早會穩定下來,此時Apache可能就會被Nginx取代。
參考資料:
[Nginx+uWSGI v.s. Apache](https://blog.pythonanywhere.com/36/)
## WSGI/uwsgi/uWSGI是甚麼?
### uWSGI
首先介紹uWSGI,這是一個web server,他實現了WSGI, uwsgi, http等等協定,可以用於接收前端的動態請求,並傳給web application。
uWSGI的主要的特色是
### WSGI/uwsgi
WSGI與uwsgi都是是描述web server與web application之間的一種協定,雖然名字裡都有"wsgi"這四個單字但其實是兩種不同的協定。WSGI是用於Python application與web server之間的規範,這讓所有使用WSGI協定寫的web server都可以運行符合協定的web server(例如Flask以及Django都符合此規範),這其實比較像是一種API,讓server side跟application side遵守相同的規範。
而uwsgi則是uWSGI獨有的協定,實際上應用的方法可以參考[這篇](https://www.zhihu.com/question/46945479)。
### 透過gevent使uWSGI效能提高
一般在uWSGI server設定的時候通常都透過process與thread參數的設定來規劃同步處理的請求數量,但是從OS的概念可以知道,多個thread與process做context switch是很耗費時間的,我們不能無限制的加thread上去,所以我們可以使用gevent來實現asynchronous I/O,如此可以提高同時存取的請求數量。
參考資料
[使用gevent提高高I/O的uWSGI server併發量](https://www.cnblogs.com/ajianbeyourself/p/3970603.html)
### 應該使用uWSGI還是gunicorn?
為何我們應該要uWSGI?而不選用Flask官網推薦的另一款gunicorn呢?結論是uWSGI的效能比較高,而gunicorn較穩定,uWSGI可以配置的設定也比gunicorn更多。
參考資料
[uWSGI與gunicorn解說](http://xiaorui.cc/2017/02/16/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3uwsgi%E5%92%8Cgunicorn%E7%BD%91%E7%BB%9C%E6%A8%A1%E5%9E%8B%E4%B8%8A/)
## 資料庫分析與比較
在面試時也被問到了為何之前做的專案要使用Redis作為資料庫?Redis與SQLite差在哪?好處是甚麼?等等問題。
### Redis
Redis是一種in memory的nosql資料庫,支援5種資料儲存型態,並且有針對這些資料型態對應的操作。
由於資料是儲存在記憶體中,所以隨機寫入的速度非常快,但由於記憶體的價格較高,所以通常做為second database來輔助使用(例如Redis+MySQL)。
常見的Redis使用場景如下:
* Session Cache/Full Page Cache:基本上可以拿來做Cache
* Queue:由於Redis支援list, zset等等的資料型態,用於隊列使用非常的好
* 排行榜功能:Redis對隨機寫入的處理效率很高,所以資料的遞增遞減很快速,再加上前述的Queue功能,可以拿來做排行榜。
參考資料:
[解釋隨機寫入](https://stackoverflow.com/questions/27180409/what-is-a-sequential-write-and-what-is-random-write)
### SQLite
參考SQLite的[官網](https://www.sqlite.org/about.html)我們可以了解SQLite的特色,SQLite的主要特色就是:
* self-contained
* serverless
* zero-configuration
self-contained也就是說相關的套件很少,只要C語言的stdlib就能運行,所以相容於所有OS。
serverless的意思是SQLite並不像傳統資料庫系統是client server的架構,SQLite做資料讀取時可以直接對硬碟存取,而不像傳統client server架構的資料讀取需要經過TCP/IP來多繞一層,也因為不需要做任何client server的設定,這就是zero-configuration。
總而言之,做為一款輕量的嵌入式資料庫,SQLite是表現得很好,但也由於這樣的特性,他擁有以下幾種缺點:
* 無法負荷太大量的資料
* 一次只允許一個process寫入導致同時處理大量請求時速度很慢
* 分散資料庫透過網路文檔共享實現導致效能低落以及容易出錯
最後,SQLite也支援in memory的操作,所以如果沒有要做分散式的DB,資料量的需求又不高,SQLite基本上可以海放Redis,詳見參考資料。
參考資料:
[SQLite缺點參考](https://hk.saowen.com/a/592c6a8d931c0866c8ce48f5489a425eb6d29163932467caed4f0ff0af3fae00)
[Redis vs in memory SQLite](https://goo.gl/cC3yi2)