星期一, 10月 01, 2007

PHP 內的 extract()

這陣子在 trace 程式碼的時候發現公司裡面的 Programmer 對於 extract() 的用法似乎不是很熟悉。

以往我們會用 foreach$_POST 的變數複製到全域變數上:
foreach ($_POST as $k => $v)
${"pvar_$k"} = $v;

但實際上 有現成的函數可以用:
extract($_POST, EXTR_PREFIX_ALL, "pvar");
extract($_GET, EXTR_PREFIX_ALL, "gvar");
extract($_COOKIE, EXTR_PREFIX_ALL, "cvar");

這樣就可以用 $pvar_variable 取得 $_POST["variable"] 的值。

星期六, 7月 14, 2007

在 DBIx::Class 內的兩項功能

兩個功能,第一個是比較複雜的 SQL query,第二個是自訂函數。

第一個功能是透過 scalar reference 做的,像是想要下:
UPDATE mytable SET viewnum = viewnum + 1 WHERE id = 123;

這樣的指令,那麼就用:
my $element = $c->model('MyDB')->find(123);
$element->update({viewnum => \'viewnum + 1'})

這個問題還蠻常在 的 mailing list 上被問,原因在於這是 的範疇,所以沒仔細看完文件不太會知道這個用法。

第二個功能其實還蠻重要的,因為有些 complex operation 沒辦法放到 Trigger 時得在 Database Library 這層做,其實是個蠻常見的情況,但我找了不少說明文件都沒有提到解法。後來發現其實在 就有提供了,不過因為這個功能還是 EXPERIMENTAL,所以大多數的文件都沒有講。

我建議用自訂函數後要把 的 mailing list 訂起來看,之後的版本可能是以其他方式解決這個問題。

星期五, 3月 30, 2007

C/PHP 的單一出口

PHP 裡面看到類似這樣的程式碼:
if (isset($_GET['var1'])) {
$var1 = $_GET['var1'];
if (isset($_GET['var2'])) {
$var2 = $_GET['var2'];
if (isset($_GET['var3'])) {
$var3 = $_GET['var3'];
// do something...
}
}
}

如果條件式再多一點的話就會更爆炸,我看到的 code 是七層... -_-

其實早在 C 語言時這個問題就有一個堪用的解法,拿到 PHP 上用也可以:
do {
if (isset($_GET['var1']))
break;
$var1 = $_GET['var1'];

// 以下類推
} while (0);

星期三, 3月 28, 2007

IE 裡 XMLHttpRequest 的 cache

在 IE 裡使用 XMLHttpRequest 時會被 cache (沒有遵守 Cache-Control 的 no-cache 參數),這個問題不會太大 (因為很好解,解法也不會破壞其他遵守規定的 Browser),解法是在 Cache-Control 外多送出幾組 header:
Cache-Control: no-cache, must-revalidate
Pragma: no-cache
Expires: Mon, 1 Mon 1990 00:00:00 GMT
Last-Modified: 現在的時間

這樣在 IE 上就會正常了。

星期日, 2月 25, 2007

Session 的瓶頸與解決方法

Session 的使用 這篇最後面提到了當 PHP server 超過一台時會有問題,這有蠻多的解法,不同的解法偏重在解決不同的問題。

第一種解法是修改 PHP Session 存放目錄,指到 NFS 路徑上,這樣就不會有在 web1 登入後 web2 讀不到的問題。這個在 交大資工 的 PHP server 就是這樣做,這樣做很明顯的優點是「簡單」,缺點就是 NFS 效率與穩定性的問題,不過對於小量的網站來說其實還不錯,因為第一次擴充時的瓶頸通常都是先出現在前端的 PHP server 上,NFS 的效率反而不是重點。

第二種解法則是透過 memcached 來放,這種方法通常都是因為 NFS 效率影響太大,或是覺得 NFS 的穩定性不夠,把 NFS 換成 memcached 希望能解決掉這個問題。這個作法需要利用 session_set_handle() 設定自己寫 (或是網路上抓來的) 程式碼將 Session 資料丟到 memcached 裡。另外一個方案是 Mohawk Software Session Handler Functions,沒有實際用過,不過看起來也是這一類的方案。

第三種解法是透過密碼學的方式將資料放在使用者端。如果資料不敏感 (像是只有使用者的 username),就透過 HMAC 簽,然後在使用者的瀏覽器上設 Cookie 紀錄,其他的頁面就可以利用 HMAC 檢查是不是自己簽的。如果資料比較敏感,不方便給使用者看到,那麼先透過 Cipher 加密後再用 HMAC 簽,然後再塞到使用者端瀏覽器的 Cookie。這種方式的好處是 Server 端是處於 Stateless 的狀態,這種方式等於完全不需要在 Server 端存資訊。但缺點就是 HMACCipher 用的 key 如果外洩就糟糕了。

第四種解法是透過硬體或是某些特定的 Reverse Proxy 解決 Load Balance 時 Session 所產生的問題,簡單來說就是某個 Cookie 一樣就會導到後端一樣的機器 (在這個例子裡是 PHPSESSID),於是後端就可以用最原始的 /tmp 來放 Session 資料。

這四種解法都有優缺點,並沒有哪個絕對比較好或是比較差,好壞要看使用的環境而定。

Session 的使用

Session 一直是網站設計裡面很重要的一個重點,有時候是因為資料不能放在使用者端的 Cookie,怕被使用者修改,另外一種原因是資料根本就不能給使用者知道。

像是 PHP 提供的 Session 的基本運作原理是透過 Cookie 設定一串十六進位的數字給 PHPSESSID,然後在 /tmp 下放對應的 sess_[PHPSESSID] 檔案,檔案裡就放 Session Variable。

舉個例子來說,User Login 後我們會通常會這樣寫:

// 假設前面已經確認身份了
session_start();
$_SESSION['user'] = $username; // 設定 $username 到 Session 裡

然後在其他檔案這樣檢查:

session_start();
if (isset($_SESSION['user'])) {
// 如果有 user 這個變數的話 blah blah...
}

對於小規模的 Web server 這樣做相當完美,因為使用者的 PHPSESSID 只是一個亂數對應到 /tmp 下的某個真正存放 Session 的檔案,使用者沒有辦法隨便亂改。同時使用者也沒有辦法猜出其他的人使用的 PHPSESSID。(當然這裡的前提是 PHP 的 Sessoion 模組寫的夠好,不會讓人用其他方法「猜」出來)

但當網站不斷的成長後,就會遭遇到一個問題:假設我有兩台 PHP server,這兩台存放 PHP Session 資料的 /tmp 當然是分開的。如果使用者在 web1 登入後,下一個連線是到 web2 上面,在 web2 上的 PHP 就沒辦法認出使用者登入過了。

這些問題留在下一篇提出一些解法。

星期三, 2月 14, 2007

HyperEstraier 大小限制

剛剛在轉資料的時候發現 HyperEstraier 的 node 沒辦法處理超過 4GB 的 database,所以必須建立多個 node 來放,再透過 setlink 將各節點串起來。

不是什麼大問題,不過 trace 了老半天才發現...

HyperEstraier 的速度問題

這是拿 strace 仔細追 estmaster 的時候發現的問題,用的參數是 strace -o output.txt -p PID -ttt。(沒有用 ktrace 是因為 kernel 沒編進去)

主要的問題有兩個,影響比較大的是在 estmaster.c 裡面有一堆 est_usleep() 造成不必要的 nanosleep(),這個部份可以簡單的 comment 掉加速。

另外一個問題是在 est_sock_recv_line() 這個 function 裡面,讀取一行的方法是一次讀 1 byte,這會使得速度被拖慢 (雖然沒有上一個多),不過這個部份得改寫所有透過 socket 處理的 code,並不是那麼容易生出 patch...

總而言之, 頗有 woju 的味道...

星期二, 12月 05, 2006

HyperEstraier 與 qdbm 的一些雜記

的使用了 的 Database 的部份以外,還使用了自己寫的輪子,cabin(3)。

這套輪子功能相當多,提供了許多 Data Structure Function 與 Algorithm 使用,不過如果真的要用的話,我也許會選擇其他 的 library (),像是 libisc (從 裡抽出來的)。

這次遇到 看起來有 memory-leak 的問題是 cabin(3) 的 cbmapout() 產生的。cbmap* 是處理 Hash 的 Data Structure Function,cbmapout() 會將元素從 Hash 中移出,不過我測試的結果發現移出後並不會釋放記憶體,於是就會造成 memory-leak 的「假象」,不過實際上這些記憶體會重複被使用,並沒有 memory-leak 的情況發生。這可能是因為 resize 的 overhead 考量而故意設計的。

所以塞資料進 要注意在塞一定的數量後對每個 node 下一次 sync 指令,強迫將快取的內容寫回硬碟後清掉,另外一個方法是把上一篇對 estmaster.c 的 patch 拿掉,這樣理論上在固定一段時間沒有動作後就會寫回去。(不過就不能一次大量寫進去,快取的大小會超過 process size...)

星期日, 12月 03, 2006

HyperEstraier

Open Source 的 Fulltext Search Engine 其實還蠻多套的,不過對於 UTF-8 環境來說,Fulltext Search 真正的問題在於非拉丁語系的切詞方式,而非底層的技術。

拉丁語系的切詞只要對標點符號及空白的地方切開,但非拉丁語系的語言 (像是 CJK) 並沒有辦法這樣做,解決的方法有幾種:最簡單的就是直接去找各國語言的詞庫 (以及頻率表),然後切詞,但這個方法對於新產生的詞沒有辦法處理,而且對於我們,不知道要如何取得非中文的詞庫。另外一種方法是完全透過 Algorithm 處理切詞,這樣我們就不用想辦法取得其他語言的詞庫,也不用管新的名詞出現。

我最近幫的忙是一個中文站台要對文章做全文搜尋 (UTF-8 環境,所以還是要考慮有少量的外國語言),目前站上的數量大約有 1 million 的文章要處理,文章數量會不斷成長。

當時在找的時候找到幾套軟體,最後因為切詞問題決定使用 ,一方面是作者相當活躍 (與 QDBM 同一個作者),另外一方面作者是日本人,我們認為日文與中文跑出來的效果應該不會差太多... (純粹是希望 :p)

FreeBSD 上安裝 沒什麼問題,直接到 裡安裝就可以了,不過我們測試的結果發現有些地方需要改掉,以免影響 performance 太多。

我把 patch 檔放成 FreeBSD ports patch 的格式,所以只要把這兩個檔案放到 /usr/ports/textprocs/hyperestraier/files/ 裡,再透過 ports 安裝就可以了。第一個檔案是 patch-estmaster.c
--- estmaster.c.orig    Sat Dec  2 19:42:59 2006
+++ estmaster.c Sat Dec 2 19:44:45 2006
@@ -1258,6 +1258,7 @@
}
est_usleep(1000 * 100);
}
+#if 0
if(isec >= g_idleflush * (1.0 - g_cacheratio) &&
(g_cacheratio > 0.0 || isec % (g_idleflush / 2 + 1) == 0)){
if(g_stmode){
@@ -1301,6 +1302,7 @@
}
est_usleep(1000 * 100);
}
+#endif
}
free(stfile);
log_print(LL_INFO, "shutting down");

第二個檔案是 patch-estraier.c
--- estraier.c.orig     Sat Nov 11 16:26:03 2006
+++ estraier.c Thu Nov 30 22:53:10 2006
@@ -2310,7 +2310,9 @@
}
}
db->dnum++;
+#if 0
if(est_db_used_cache_size(db) > db->icmax && !est_db_flush(db, INT_MAX)) err = TRUE;
+#endif
return err ? FALSE : TRUE;
}

其他的就照官方網站上所說的用法就可以了。另外一個很重要的資源是 存取 HyperEstraier 的方式,我自己是透過 處理,測試起來沒什麼問題... :p