2017年4月1日 星期六

NAS CPU 風扇

群暉 (Synology) 的 NAS 很好用,深受大家肯定。但是它用的零件也超爛的,也同樣的頗受大家批評,每隔幾個月,就通報一次 「CPU fan 1 on xxxx has stopped working.」。換新的風扇後,幾個月後又壞了。一顆風扇賣 600 元,說貴不貴,但幾個月就壞掉,實在讓人不爽之外,還是不爽吧。

拿出主機板模組,看它風扇的貼紙是,「 EVERCOOL,EC4010M12ER,EL BEARING,DC 12V」。嗯,你想找個便宜的 CPU 風扇換上去,就解決問題了嗎? 沒這回事,請看下面的討論
https://forum.synology.com/enu/viewtopic.php?t=59568





2017年3月2日 星期四

開啟 Mozilla Firefox 的相關設定

electrolysis (e10s) 開啟

Electrolysis (縮寫為 e10s) 是 Firefox 的多程序架構的專案名稱,目標是將 Firefox 程式的核心,與 Legacy NPAPI plugin (如 java 外掛、或 flash player)、media playback (網站的影音檔案、播放)、web content (網頁內容) 後面三者分離,內容的溝通只通過規範的通道(ipdl protocols),而不直接影響瀏覽器核心作用。

因此,「內容」都是用新的子程序在跑,而不論它跑的順不順利,主程式依然可以不受干擾的運作,如此一來好處是顯而易見的:網頁開啟速度更快、也有更安全的防護 (透過沙箱的方式),也不會因為單一頁面崩潰而導致整個瀏覽器當掉。

在 Firefox 的瀏覽器列輸入 about:support,可看到
多程序視窗 : 0/1 (已關閉)

表示未開啟「多程序視窗」。

經由下述的步驟,可以開啟 Firefox 的「多程序視窗」(e10s) 功能。
  1. 在 Firefox 的瀏覽器列輸入 about:config,再按下 Enter 鍵。
  2. 搜尋 browser.tabs.remote.autostart,並且將其值修改為 true browser.tabs.remote.autostart=true
  3. 在表格的任意處,按滑鼠右鍵,新增 boolean 值,將其命名為 browser.tabs.remote.force-enable,即  browser.tabs.remote.force-enable=true
  4. 重新啟動瀏覽器。
在 Firefox 的瀏覽器列輸入 about:support,可看到
多程序視窗 : 1/1 (由使用者開啟)
Multiprocess Windows : 1/1 (Enabled by user)

另外,Firebug 不能再用了,console.log 的訊息無法出現在主控台,必須使用內附的開發工具。

放大 console 的字型 *2018-03-26)

參考 Increase code font size in firefox developer tool

Open Firefox and type about:support. In Application Basics section chose Profile Directory - Open Directory. It will fire your file manager. If there is no chrome folder than create it. After that go to this chrome folder and create an userChrome.css file, open it in a text editor and add : .devtools-monospace {font-size: 12px!important;} Save. Be sure to restart Firefox.
.devtools-monospace {
    font-family: "Noto Sans Mono CJK TC" !important;
    font-size: 16px !important;
}



2017年2月26日 星期日

下載 RTMP 影音檔

RTMP 的影片連結

在網頁上,使用 flash 播放的影片,有許多都採用 Adobe 的 rtmp 協定傳送影片。在網頁中,可以看到像下面這樣的連結
file: "rtmp://jzks.streamguys1.com/vod/mp4:advret/video1.mp4"
或是
file: '/mp4:advret/video1.mp4'
streamer: 'rtmp://jzks.streamguys1.com/vod'

可以使用 vlc 或 mpv 的播放程式來播放該連結
mpv  rtmp://jzks.streamguys1.com/vod/mp4:advret/video1.mp4

假如要下載的話 (請確認授權是否允許),有下列幾種方式

1. 使用 ffmpeg 

ffmpeg -i 'rtmp://jzks.streamguys1.com/vod/mp4:advret/video1.mp4' -c copy video1.mp4

2. 使用 rtmpdump

rtmpdump -r "rtmp://jzks.streamguys1.com/vod/" -y "mp4:advret/video1.mp4" -o video1.mp4

3. 使用 curl

curl "rtmp://jzks.streamguys1.com/vod playpath=mp4:advret/video1.mp4" -o video1.mp4

2017年2月17日 星期五

Apache 的 override 對效能的影響

最近,有一台伺服器,因為效能太差,CPU 的效能一直被吃光。有同事想要把它換成號稱無敵的 Nginx。 但是在網路上,看到一些討論,在動態網頁上,效能取決於 PHP,Nginx 未必較好。而 Apache 的效能,則受到 override 的影響很大,若將其關閉,則動態網頁可能完勝 Nginx。

測試條件

硬體:CPU *2、4GB 記憶體
軟體:Apache 2.4.25 / PHP 7.0.15
64824 條 rewrite rule,大概長得像這樣
Redirect /981ccp https://ceiba.net/course/6a06f1/index.htm

指令:ab -c 100 -n 1000 http://10.161.91.212/tmp/phpinfo_x.php

Document Length:        78741 bytes
Concurrency Level:      100

AllowOverride none

Time taken for tests:   0.804 seconds
Complete requests:      1000
Failed requests:        97
   (Connect: 0, Receive: 0, Length: 97, Exceptions: 0)
Total transferred:      78925892 bytes
Requests per second:    1243.75 [#/sec] (mean)
Time per request:       80.402 [ms] (mean)
Time per request:       0.804 [ms] (mean, across all concurrent requests)
Transfer rate:          95863.61 [Kbytes/sec] received

CPU 使用率:2 個 CPU 大約 50%
記憶體使用率:523MB

AllowOverride  All

Time taken for tests:   49.415 seconds
Complete requests:      1000
Failed requests:        80
   (Connect: 0, Receive: 0, Length: 80, Exceptions: 0)
Total transferred:      78925914 bytes
Requests per second:    20.24 [#/sec] (mean)
Time per request:       4941.538 [ms] (mean)
Time per request:       49.415 [ms] (mean, across all concurrent requests)
Transfer rate:          1559.76 [Kbytes/sec] received

CPU 使用率:2 個 CPU 皆 100%
記憶體使用率:最高 2GB,大約在 1GB 至 1.5GB 之間

討論

從上面的結果來看,6萬多條的 rewrite rule,在此測試中,並沒有派上用場,但為了處理那些 rule,要多耗 1GB 的記憶體,並且拖垮 CPU。

2017年2月14日 星期二

PHP 透過 xsendfile 下載檔案

使用步驟說明

原本使用 PHP 來做檔案下載控制,包括限制下載速率,同一IP的連線數目。可是,CPU 使用率會飆高,記憶體的使用率也不低。試過 Nginx,Apache 的 event 模式,結果都差不多。

 後來,看到可以設定 X-SendFile header,交由 Apache 控制下載,今天花了一天的時間,終於搞定。

需要安裝的模組,包括
  • mod_limitipconn: 限制個別IP的連線數
  • mod_ratelimit: 限制下載的速度,如 150 為 150KB/s
  • mod_xsendfile: X-Sendfile 模組
單純針對直接的檔案下載,設定如下,其中將 mp4 強制下載存檔,否則支援 mp4 的瀏覽器會直接播放。
<Directory "/var/www/html/vod_dl">
    <Files *.mp4>
        ForceType application/octet-stream
        Header set Content-Disposition attachment
    </Files>

    <IfModule mod_limitipconn.c>
        # limit concurrent connection for 2
        MaxConnPerIP 2
    </IfModule>

    <IfModule mod_ratelimit.c>
       SetOutputFilter RATE_LIMIT
        SetEnv rate-limit 850
    </IfModule>

</Directory>

有可能需要設定 XSendFilePath,例如 "XSendFilePath /home/video_data",不然會出現 "The given path was above the root path .... " 的錯誤。

因為我的下載程式的 url 是長的這樣,
http://10.161.81.118/dl_video.php?fn=105S101.mp4

設定時,需針對 dl_video.php 做設定,因此下面的設定也會套用到像 dl_videx_xxx.php 的程式。
<Files dl_video*>
    <IfModule mod_xsendfile.c>
        # 啟用 xsendfile
        XSendFile On
        SetEnv MOD_X_SENDFILE_ENABLED 1
    </IfModule>

    <IfModule mod_limitipconn.c>
        # 限制每一IP同時連線的數目
        MaxConnPerIP 8
    </IfModule>

    <IfModule mod_ratelimit.c>
       # 限制下載速率 850KB/s
       SetOutputFilter RATE_LIMIT
       SetEnv rate-limit 850
    </IfModule>
</Files>

PHP 的程式長得像下面這樣,只要設定 header,就會轉由 Apache 控制下載。

// 權限檢查 .....
// 檔案檢查 .....
//  .....
header("X-Sendfile: {$fullname}");
header("Content-type: application/octet-stream");
header('Content-Disposition: attachment; filename="' . basename($fname) . '"');

注意,$fullname 是檔案系統中的實際目錄名稱,例如

/var/www/html/mp4_files/105S101.mp4


這樣子,就算開到 1千多個檔,CPU 的使用率不高,記憶體大概也只在 500MB左右。

Nginx 也有類似功能,不過它是接受URI,正如一般的 request,而非目錄檔名,我個人覺得比較不盡理想。

使用 docker 

原先的環境是使用 CentOS,但其已於數年前被宣告死亡,只能再找其他途徑。使用 docker.hub 的 PHP 官方 image -- php:7.4.33-apache-bullseye,bullseye 為 Debian 當前的舊的穩定(oldstable)版。

Debian 官方沒有 mod_limitipconn,必須自己下載編譯安裝。變通的方式,是在 docker 中編譯好,把用到的檔案 copy 出來。

apt install apache2-dev wget
wget https://dominia.org/djao/limit/mod_limitipconn-0.24.tar.bz2
tar -xvf mod_limitipconn-0.24.tar.bz2
cd mod_limitipconn-0.24

make
make install
--
## 需要的檔案是下面兩個
/usr/lib/apache2/modules/mod_limitipconn.so
/etc/apache2/mods-available/limitipconn.load

Dockerfile 如下。

# Debian 當前的舊的穩定(oldstable)版
FROM php:7.4.33-apache-bullseye

# Install required extensions
# libicu-dev -- required by php ext: intl
# libpq-dev -- required by php ext: pgsql pdo_pgsql
# libonig-dev -- required by php ext: mbstring
RUN apt-get update && \
    apt-get -y install libapache2-mod-xsendfile \
        libicu-dev libpq-dev libonig-dev \
    && docker-php-ext-install intl pdo \
		mysqli pdo_mysql  pgsql pdo_pgsql \
        mbstring

COPY conf/ocw_vod_dl.conf /etc/apache2/conf-enabled
COPY conf/mod_limitipconn.so /usr/lib/apache2/modules
COPY conf/limitipconn.load /etc/apache2/mods-available

RUN a2enmod headers limitipconn ratelimit xsendfile

EXPOSE 80
CMD ["apache2-foreground"]

docker-compose_tst.yml 如下。

# version: '2'

services:
  ocw-dl:
    container_name: ocw-dl-server
    build: .
    domainname: ocw.aca
    hostname: ocw-dl-server
    working_dir: /var/www/html
    volumes:
        - ../web:/var/www/html
        - /ext_hd8t/ajax/wk_backup/ocw_bak/vod:/var/www/html/vod_dl
    ports:
        - 8380:80

以上為 2024-01-23 更新,陽明山下雪之日。







2017年2月12日 星期日

Apache + php-fpm in CentOS 7

參考 1: Apache 2.4 mpm_event + php-fpm in CentOS 7
參考 2: Install PHP-FPM to make PHP scripts be fast
參考 3: High-performance PHP on apache httpd 2.4.x using mod_proxy_fcgi and php-fpm

prefork+module_php

依照網上找到的說明,裝好 Apache、PHP、PHP-FPM。
預設的安裝,是使用 prefork+module_php。
phpinfo() 送回的資訊為 "Server API: Apache 2.0 Handler"

prefork+php-fpm

修改 /etc/httpd/conf.d/php.conf,取消原來的 AddHandler,新增 SetHandler
# AddHandler php7-script .php
SetHandler "proxy:fcgi://127.0.0.1:9000"

即可在 prefork 模式下,透過 proxy:fcgi 來呼叫 PHP-FPM 處理 PHP 程式
phpinfo() 送回的資訊為 "Server API:  FPM/FastCGI"

event+php-fpm

更進一步,修改 /etc/httpd/conf.modules.d/00-mpm.conf,取消 mod_mpm_prefork.so,改用 mod_mpm_event.so
# LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
LoadModule mpm_event_module modules/mod_mpm_event.so

以及在 /etc/httpd/conf/httpd.conf 中,加上
<IfModule mpm_event_module>
     ProxyPassMatch ~.php$ fcgi://127.0.0.1:9000
</IfModule>

則可使用 event 模式+PHP-FPM。

測試說明

以下,稍微用 ab 跑一下測試,瞭解一下差異。
$ ab -c 60 -n 2000 http://10.161.91.37:8080/laravel51-test/

取回資料的長度:        1197 bytes

計算 httpd process 的數量,注意,以下的指令會多算一個
# ps ax | grep httpd | wc -l 

另外,使用 htop 觀察系統各項資源的情形。

1. 預設的 prefork+module_php

重新啟動 (systemctl restart httpd) 之後
process: 6
memory: 238M, 其中,1.8% *1 、0.5 * 5
測試時,大約的最大值
process: 132
memory: 950M,其中 1.8% *1、1.2%*1、其餘 1.1%
效能
Requests per second:    150.58 [#/sec] (mean)
Time per request:       398.449 [ms] (mean)

2. prefork+php-fpm

重新啟動之後
process: 7
memory: 245M,1.8% *1、0.5% *6
php-fpm 的 memory:  master: 1.6% *1、其餘: 0.5%
測試時,大約的最大值
process: 72
memory: 609M
效能
Requests per second:    171.73 [#/sec] (mean)
Time per request:       349.376 [ms] (mean)

3. event+php-fpm

重新啟動之後
process: 5
memory: 243M,0.9% *1、其餘 0.6%。
假如 php-fpm 也重新啟動,memory: 214M
測試時,大約的最大值
process: 8,外加一堆 thread
memory: 535M
 效能
Requests per second:    147.16 [#/sec] (mean)
Time per request:       407.722 [ms] (mean)


小結

使用 event 模式,就是省記憶體,效能則一樣是取決於 PHP。70

大家都在傳 Nginx 很快,但也有測試比較的結果,假如 Apache 把 .htaccess 的 override 關掉,效能立刻變好。而且,在 PHP 的網頁,效能取決於 PHP。雖然,有人聲稱 .htaccess是萬惡之源,但想偷懶,就繼續用囉。

下載伺服器測試

prefork+mod_php: 139 processes, 600MB
prefork+php-fpm: 130 processes, 700MB
event+php-fpm: 120 threads, 800MB/ 150 threads, 920MB

先前聽說 Nginx 多強大,決定將原來運作的下載伺服器,改用 Nginx。但在換成 Nginx 之後,發現CPU使用率飆高,外部很難連上來,然後,就開始不穩定,PHP-FPM會當掉。

改回 Apache 之後, 發現不論那一種組合,運作都很穩定,CPU和記憶體的使用率,都沒有太大的差異。所以,真的有點懷疑網路上,對 Nginx 的吹捧。除非,我的設定,真的那裡有問題,而我能力有限,無法進一步改善。

嗯,我的下載伺服器,就只是跑一堆像下面的程式,把下載的速度控制在 800KB/s 左右,避免頻寬被吃光。改用 Nginx,伺服器就一付快掛的樣子,而用 Apache,硬就是游刃有餘。

哦,對了,順帶一提,這程式支援續傳 (resume),有興趣的人,可以參考參考。請多指教!

<?php

$fullname = $doc_root.'/mp4/'.$fname;

if (!is_file($fullname)) {
    // file does not exist
    ob_clean();
   
    header("HTTP/1.0 404 Not Found");
    // header("HTTP/1.0 404 Not Found", true, 404);
    die ('Sorry, file not found!!');
    exit;
}

$file_size = filesize($fullname);
$fp = fopen($fullname, 'rb');


if (!$fp) {
    // file couldn't be opened
    ob_clean();
    flush();
    header("HTTP/1.0 500 Internal Server Error");
    exit;
}

if(isset($_SERVER['HTTP_RANGE'])) {
    list($size_unit, $range_orig) = explode('=', $_SERVER['HTTP_RANGE'], 2);
    if ($size_unit == 'bytes')
    {
        //multiple ranges could be specified at the same time, but for simplicity only serve the first range
        //http://tools.ietf.org/id/draft-ietf-http-range-retrieval-00.txt
        list($range, $extra_ranges) = explode(',', $range_orig, 2);
    } else {
        $range = '';
        header('HTTP/1.1 416 Requested Range Not Satisfiable');
        exit;
    }
} else {
    $range = '';
}

//figure out download piece from range (if set)
list($seek_start, $seek_end) = explode('-', $range, 2);

//set start and end based on range (if set), else set defaults
//also check for invalid ranges.
$seek_end   = (empty($seek_end)) ? ($file_size - 1) : min(abs(intval($seek_end)),($file_size - 1));
$seek_start = (empty($seek_start) || $seek_end < abs(intval($seek_start))) ? 0 : max(abs(intval($seek_start)),0);

ob_clean();

header('Content-type: application/mp4'); //告訴瀏覽器 為下載
header('Content-Transfer-Encoding: Binary'); //編碼方式
header("Content-Disposition:attachment; filename=\"".$fname."\""); //檔名.

//Only send partial content header if downloading a piece of the file (IE workaround)
if ($seek_start > 0 || $seek_end < ($file_size - 1)) {
    header('HTTP/1.1 206 Partial Content');
    header('Content-Range: bytes '.$seek_start.'-'.$seek_end.'/'.$file_size);
    header('Content-Length: '.($seek_end - $seek_start + 1));
} else {
    header("Content-Length: $file_size");
}

header('Accept-Ranges: bytes');

set_time_limit(0);
fseek($fp, $seek_start);

while(!feof($fp)) {
    // limited rate to about 0.5 MB/Sec
    // 10KB * 50 = 500KB
    // 1000,000/50 = 20,000
    print(@fread($fp, 1024*100));
    ob_flush();
    flush();
    // wait for 20,000 micro-seconds
    usleep(1.2*pow(10,5));
    if (connection_status()!=0) {
        @fclose($fp);
        exit;
    }
}

ob_end_flush();
exit;
 




2017年2月7日 星期二

難以理解的 GoLang Closure

GoLang Closures Running as Go routines
http://golang-basic.blogspot.tw/2014/06/golang-closures-running-as-go-routines.html

程式 1,輸出 : c, c, c
package main

import (
    "fmt"
)

func main() {
    done := make(chan bool)

    values := []string{"a", "b", "c"}
    for _, v := range values {
        go func() {
            fmt.Println(v)
            done <- true
        }()
    }

    // wait for all goroutines to complete before exiting
    for _ = range values {
        <-done
    }
}

上面這個程式的輸出是 c, c, c,因為由 go 啟動的 closure,共享外層的變數 v,內層的程式還沒完成啟動,外層的 for 迴圈已經跑完,變數 v 的值為 'c',因此印出的值都是一樣的。

程式2 的 for迴圈,輸出 c, a, b
   values := []string{"a", "b", "c"}
    for _, v := range values {
        go func(u string) {
            fmt.Println(u)
            done <- true
        }(v)
    }

程式3 的 for迴圈,輸出 c, a, b
   values := []string{"a", "b", "c"}
     for _, v := range values {

       v := v // create a new 'v'.
        go func() {
            fmt.Println(v)
            done <- true
        }()
    }

網誌存檔