# server-framework 研究 contributed by <`judy66jo`>, <`JonSyuGithub`> ## 案例探討: 強化 server-framework 效能 ![](https://i.imgur.com/TNsG45q.png) >>中英文字間請以空白區隔 >>[color=red][name=課程助教] ### 運作流程 1. Listen()執行後將會啟用Reactor處理 * Accept connect * Printing connector * Dealing request 2. wait(): thread將會進入thread_join,等待worker thread將工作完成 * 或者透過TERM (ctrl+c)進入close() * close(): 將結束Reactor內的Async裡的所有Worker thread 3. Client的request將由Server的Reactor先送入Work queue並在pipe寫入 one byte * work queue為linked list 4. worker thread將會取得pipe的one byte負責工作 (i.e. 處理某個client的request) ### async, reactor, protocol-server 彼此之間的關聯 #### Reactor Pattern * Resources: client 的 request * Synchronous Event Demultiplexer: 多對一, Server處理多個Client的要求 i.e. Epoll * Dispatcher: 將client待處理的request丟入Work queue,並喚醒thread處理這些request * Request Handler: 也就是Worker thread去處理工作(client request) #### async * 該架構管理thread pool ```clike // 處理存入work queue的運行,透過pipe喚醒work thread處理 // 寫入one byte到pipe static int async_run(async_p async, void (*task)(void *), void *arg) ``` ```clike // 會讀取pipe的one byte /* listen pipe and release thread */ static void *worker_thread_cycle(void *_async) ``` >> 思考這樣的 API 設計有沒有不足?或者值得做 code refactoring 之處 [name=jserv] #### reactor * 回應 requset、使用 epoll 監控整個系統 ```clike // 將一個工作存入epoll int set_fd_polling(int queue, int fd, int action, long milliseconds) ``` ```clike // 透過 reactor_review( ) 將 epoll 中的工作抓出來執行 int reactor_review(struct Reactor *reactor) ``` #### protocol-server * 定義 server、連線收發、連線後工作狀態 ```clike // 用來監控整個系統工作狀態,決定工作是否中斷連線/執行 static void srv_cycle_core(server_pt server) ``` ### 探討 epoll, pipe, signals 等系統呼叫 #### Epoll * 非同步I/O,解決因大量連線造成process和thread無法handle的狀況 * 能handle比select更多連線,且非線性掃描,但只能在linux系統上運作 * ET(edge-triggered):只有在fd變成ready-state時有反應,直到事件結束後又發生下一次,如此一來就不用一直詢問一些不活躍的connect ```clike // 生成epoll int epoll_create(int size) int epoll_create1(int flags) // 新增或移除監聽的fd或更改fd的監聽選項 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) // 回傳ready的fd的數量,把fd存在events array中 int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout) ``` #### PIPE * 單向傳輸的資料流,有兩個端口fd,一個是read_only,一個是write_only * 當嘗試read一個空的pipe時,會被block住,當嘗試寫入full的pipe時,write會被block ```clike // 生成pipe,將read,write端fd存入array int pipe(int pipefd[2]) // 將count byte的資料寫入pipe_write_end ssize_t read(int fd, void *buf, size_t count); // 從pipe_read_end讀count byte的資料 ssize_t write(int fd, const void *buf, size_t count); ``` #### SIGNAL * 接收訊號後改變原本動作,確保server正確執行與結束 ```clike // 改變動作 int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); ``` ```clike // The sigaction structure struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void); }; ``` * signum 輸入參考 ```clike Signal Value Action Comment ────────────────────────────────────────────────────────────────────── SIGINT 2 Term Interrupt from keyboard SIGKILL 9 Term Kill signal SIGPIPE 13 Term Broken pipe: write to pipe with no readers SIGTERM 15 Term Termination signal ``` ## apache ab 效能測試 對 Web Server 送出100個 request ,且一次送出32次 request ```shell $ ./httpd // new terminal $ ab -c 32 -n 100 http://localhost:8080/ ``` Benchmark ``` This is ApacheBench, Version 2.3 <$Revision: 1706008 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking localhost (be patient).....done Server Software: Server Hostname: localhost Server Port: 8080 Document Path: / Document Length: 13 bytes Concurrency Level: 32 Time taken for tests: 15.691 seconds Complete requests: 100 Failed requests: 11 (Connect: 0, Receive: 0, Length: 11, Exceptions: 0) Total transferred: 9900 bytes HTML transferred: 1300 bytes Requests per second: 6.37 [#/sec] (mean) Time per request: 5021.135 [ms] (mean) Time per request: 156.910 [ms] (mean, across all concurrent requests) Transfer rate: 0.62 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 1 0.5 1 1 Processing: 3691 3901 144.2 3999 4000 Waiting: 0 0 0.2 0 1 Total: 3691 3901 144.5 4000 4000 Percentage of the requests served within a certain time (ms) 50% 4000 66% 4000 75% 4000 80% 4000 90% 4000 95% 4000 98% 4000 99% 4000 100% 4000 (longest request) ``` ## 實做與分析 lock-less thread pool * 在使用多執行緒的程式中,為了避免多個執行緒同時對一個變數進行讀寫的動作( race condition),會使用 mutex_lock 結合 condition variable。然而在 mutex_lock 的取得及釋出上,有不小的成本。 ![](https://i.imgur.com/bhsp5jC.png) * 執行緒從一個 work queue 中,去搶(競爭)工作來做,為了避免多個執行緒重複執行同一個工作,在取得工作時都必須以 mutex_lock 將執行緒的執行堵塞住。 * 為了降低 mutex_lock 的使用次數,意味著要降低資源競爭的情況。而為了降低資源競爭的情況,可以將單一個 work queue,改成每一個執行緒都有自己的 work queue,這樣一來每一個執行緒只要從自己的 queue 中拿工作來做,以避免執行緒間搶工作時必須使用 mutex_lock。 ![](https://i.imgur.com/vNpPQ2O.png) ## References * [案例探討: 強化 server-framework 效能](https://www.youtube.com/watch?v=YnfQVvL7m0g) * [說明 async, reactor, protocol-server 彼此之間的關聯](https://embedded2016.hackpad.com/-async-reactor-protocol-server--5uDCriIegs8) * [實做與分析 lock-less thread pool](https://embedded2016.hackpad.com/-lock-less-thread-pool-g7nhI7Mrmq2) ###### tags: `sysprog21` `Team23`