2 months ago

在開始前建議先去安裝 htop 這個 linux 即時監控軟體。主要因應不同的 # of process core 與 memory size,可以調整 php5-fpm 與 nginx 中兩個部分的設定。

php-fpm-with-nginx-htop.png

Nginx

在 /etc/nginx/nginx.conf 下約莫第二行有個 worker_processes 參數。

worker_processes 4;

根據運行機器的 cpu 核心數填入即可,可用 htop 或輸入以下 shell command 取得

cat /proc/cpuinfo| grep processor 

PHP5-FPM

位置:/etc/php5/fpm/pool.d/www.conf
在約莫 88 行有個 pm 變數可設定 Process Manager 要以什麼型態呈現,有 static, dynamic, ondemand。

  • static: 根據 pm.max_children 所設定的值,一直固定運行這個數量的 php-fpm child process 來處理 php script 的執行需求。如一般的 process 一樣,所耗用的 memory 會隨著運行而有一定程度的增加。
  • dynamic: 根據 pm.max_children, pm.start_servers, pm.min_spare_servers, pm.max_spare_servers 這四個變數值來發揮作用。即一開始運作 php5-fpm 時就運行 pm.start_servers 數量的 child process,若沒事的 child process 就會被殺掉以維持在 pm.min_spare_servers ~ pm.max_spare_servers 的數量,因應需求最多不會產生超過 pm.max_children 的 child process 數量。通常,pm.start_servers, pm.min_spare_servers, pm.max_spare_servers 這三個參數值會是一樣。
  • ondemand: 根據 pm.max_children, pm.process_idle_timeout 這兩個參數來發揮作用。在這個模式下,一開始啟動 php5-fpm 時不會產生任何 child process,只有當有需求時才會去產生。若產生的 child process 超過 pm.process_idle_timeout 秒都沒事做的話就殺了它!

基本上 ondemand 與 dynamic 模式差不多的感覺,都是能有效 release 出 memory 供其他程式使用,但也因此在每次重新喚起新的 child process 時都會花額外的時間,不像 static 就是一直保持 child process 待命的狀態。

如何決定 pm.max_children

那至於 child process 數量要怎麼決定呢?通常一安裝 php5-fpm 時預設就是 static 搭配 pm.max_children = 32 的設定,最土法煉鋼的方式,就~運行網站個一兩天觀察一下每個 php-fpm child process 運作一段時間後需花多少 memory(如上方的 htop 截圖),即可以抓到每個 child process 所需的 memory 最大值。如此案例,就可以抓每個 child process 大概是 1% of total memory,即約 75 MB。保險起見就抓個 100 MB 來做計算,以這台機器約 8G 的 RAM,就可以設定 pm.max_children 為 70(記得保留 1~2 G 的 quota 給其他的程式哟),或大膽一點就設定 100 XDDD

最後

那~根據最近兩三個禮拜的測試,dynamic, ondemand 固然能一直保持 memory 一直有著一定量的 free memory 供其他程式使用,維持機器處於一個較高效能的狀態,但一旦有超大需求(request)衝進來,那它是不管你三七二十一,直接就衝上所設定最高的 # of child process,很容易導致 memory 超需,機器就會爆惹.....(最近兩個禮拜就因為這樣 G 掉兩次QQ)。因為大量需求一進來,當然你跟機器說我最大能做到那程度,它當然也就盡它所能發揮到極致,畢竟目前看起來是還沒能設定每個 php5-fpm child process 能使用的最大 memory 上限的功能,OS 似乎也沒看過有這樣的支援(窘~

所以已決定放棄追求高效能,把 pm 改回 static,追求伺服器穩定。畢竟伺服器穩穩乖乖的,我們才有好日子可以過~

BTW, 希望以後還能有這系列的續集

Reference

 
3 months ago

PHP 中建立 XML 的寫法大概是這樣

$domtree = new DOMDocument('1.0', 'UTF-8');
/* create the root element of the xml tree */
$xmlRoot = $domtree->createElement("xml");
/* append it to the document created */
$xmlRoot = $domtree->appendChild($xmlRoot);

$currentTrack = $domtree->createElement("Root");
$currentTrack = $xmlRoot->appendChild($currentTrack);
$currentTrack->appendChild($domtree->createElement('key','value'));
echo $domtree->saveXML();

Parse XML 字串成 Object 是

$xml = simplexml_load_string("<?xml version='1.0' encoding='utf-8' ?>
<Root><key>value</key></Root>");
echo $xml->key;  // 這行可能會出錯

有些情況下 Parse XML 的 Object Element 在存取時會出現「Serialization of 'SimpleXMLElement' is not allowed」,這時只要強制轉行成 String 即可

echo (string)$xml->key;
 
3 months ago

AES (Advanced Encryption Standard) 進階加密標準,目前已成為流行的對稱式加密法之一,也是美國國家安全局 NSA 批准可用在最高機密資訊上的加密方式(聽起來敲厲害的啊~~),當然隨著密鑰的長度越長(128, 192, 256 bits),安全性就越高。更多詳細介紹可參考 wiki

這裡分享的 Snippets 是固定的 128 bits密鑰,並且針對要加密的資料做 base64 encode。

public function aesDecrypt() {
        $encryptedData = "6lbH0qa6J1Sl5/I5C2YeGg==";
        $cipher = MCRYPT_RIJNDAEL_128;
        $mode = MCRYPT_MODE_CBC;
        $key = "A123456789012345";  // 回合金鑰
        $iv = "B123456789012345";  // 密鑰
        $initializationVectorSize = mcrypt_get_iv_size($cipher, $mode);

        $data =  mcrypt_decrypt(
            $cipher,
            $key,
            base64_decode($encryptedData),
            $mode,
            $iv
        );
    $pad = ord($data[strlen($data) - 1]);
        return substr($data, 0, -$pad);
    }
  
public function getAesEncrypt() {
        $xml_data = "AllPayTest";
        $cipher = MCRYPT_RIJNDAEL_128;
        $mode = MCRYPT_MODE_CBC;
        $key = "A123456789012345";   // 回合金鑰
        $iv = "B123456789012345";   // 密鑰
        $blockSize = mcrypt_get_block_size($cipher, $mode);
        $pad = $blockSize - (strlen($xml_data) % $blockSize);

        return base64_encode(mcrypt_encrypt(
            $cipher,
            $key,
            $xml_data . str_repeat(chr($pad), $pad),
            $mode,
            $iv
        ));
    }

如果密鑰希望每次加密時都可以不一樣,可以參考此 gist

 
4 months ago

假設需求:從 Project 資料表抓取 id 為 2652, 2468, 2534, 2308, 2019 的五筆專案資料

SQL:select * from Project where id in (2652, 2468, 2534, 2308, 2019)

query 後的 result 會自動按照 id 大小排序變為 2019, 2308, 2468, 2534, 2652,但就是想要丟進去的順序阿阿阿~

SQL(修改後):select * from Project where id in (2652, 2468, 2534, 2308, 2019) order by FIELD(id, 2652, 2468, 2534, 2308, 2019)

用 order by 搭配 FIELD 解決!

Laravel

$hotIDs = [2652, 2468, 2534, 2308, 2019];
Project::whereIn('id', $hotIDs)->orderBy(DB::raw("FIELD(id,".implode(",", $hotIDs).")"))->get();
 
4 months ago

此篇目的為將 Heroku 上的 DB dump 下來後,轉成 mysql 可以吃的 .sql 檔格式後匯入 MySQL Server,因為 Heroku 把他們的 DB 從 sqlite 改成 postgre SQL惹...

Environment: Mac OSX

Dump

因為有安裝 Heroku 的 Add-on 「PG Backups」,所以 dump 的動作相對單純(這個 Add-on 是免費的,推薦大家加減裝一下作個備份)

heroku pgbackups:capture
curl -o latest.dump `heroku pgbackups:url`   # latest.dump 可以改成自己想要的檔名

安裝 Postgre SQL 並回復

brew update # A package management tool for osx: Homebrew
brew install postgres
postgres -D /usr/local/var/postgres # 指定 postgres 的設定檔位置
pg_restore --verbose --clean --no-acl --no-owner -h localhost -d database latest.dump # 把 dump 的資料庫回復

使用 Razor SQL 將 Postgre SQL 資料庫轉成 MySQL 格式的 .sql 檔案

請至 下載頁面 下載安裝

步驟一:新增資料庫連線

razorSQL1.png

步驟二:輸出資料庫

「DB Tools」->「Database Conversion」->「Convert Multiple Tables」
Screen Shot 2014-03-26 at 下午3.27.01.png

步驟三:選擇 Schema (通常為 public)
步驟四:選擇所有(Select All)資料表
步驟五:以 MySQL Format 輸出

Screen Shot 2014-03-26 at 下午3.29.22.png

BTW, 看似簡單的幾個步驟蒸騰了一整天...希望能幫到有這需求的人

 
4 months ago

最近在用 npm 安裝 socket.io 時發生了錯誤「Error: SELF_SIGNED_CERT_IN_CHAIN
npm_install_error.png

如官網發佈的訊息表示:不再支援「SELF_SIGNED_CERT_IN_CHAIN」,建議升級 npm 的版本:

npm install npm -g --ca=null

或告知 npm 不要使用 SSL

npm config set strict-ssl false
 
5 months ago

此篇目的在於使用 Amazon SQS (Simple Queue Service) 作為 Queue Server,以另一台機器作為 Queue Worker 來寄送 Email,使 Production Server 能更專注在服務網站,使用者體驗也會更好(若沒使用 Queue Server 使用 sync 模式寄送 Email,會讓使用者操作停住等待發送完成才能繼續動作)。

第一步:設定 app/config/queue.php

  1. 將 default 改成 sqs('default' => 'sqs')
  2. 設定 sqs
    'sqs' => array(
            'driver' => 'sqs',
            'key'    => 'your sqs key',
            'secret' => 'your secret',
            'queue'  => 'your queue url',
            'region' => 'us-west-2',
        ),
    

註:同理可設定為其他第三方平台(如:ironmq

第二步:解決 Mail:queue 的 Closure 問題

此處以發送重設密碼信件為例,Closure 概念可參考此篇文章

static public function noticeResetPassword($user)
    {
        $data['user_name'] = $user->nickname;
        $data['user_email'] = $user->email;

        Mail::queue('emails.noticeResetPassword', $data, function($message) use ($data)
        {
            $message->from('[email protected]', 'your service name');
            $message->to($data['user_email'], $data['user_nickname'])->subject('[提醒] 您的密碼已重新設定');
        });
    }

function ... use ... 是 PHP Closure 的用法,讓 callback 函式需要額外變數資料(原本不在函式內的變數)的時候,在定義時一起帶進去,事先將所需的變數記憶體位置 Reference 起來。所以若是要用不同機器處理 callback 函式那就不能這樣使用。

這裡使用「 Mail::queue('emails.noticeResetPassword', $data, function($message) use ($data) 」將 Closure 帶的變數與傳給 Mail::queue 的變數一樣就可以解決了。不然會發生錯誤「 ErrorException: Insufficient data for unserializing 」。

 
5 months ago

AWS 的 Reserved Instance 白話說就是一個下單長期打折的概念,如果是高使用量的最多一次買三年可省上65%,詳情可參考官網

看似很棒,不知道為什麼的網路上幾乎沒文章教說怎使用 Reserved Instance...或許是因為太簡單惹~
這是一堆 RIs
aws-ri.png

關鍵是 Instance TypeZone 在創建 Instance 時跟 RIs 一樣即可,從帳單上就可以看到沒有 charge 的 Instance (因為帳在 RIs 上 XDDD
Screen Shot 2014-03-07 at 上午12.56.36.png

 
5 months ago

環境狀況:

  • Ubuntu
  • Nginx + php5-fpm
  • Laravel

-- 這一個錯誤是在做 composer update 時所遇到的問題 ---

Fatal error: Uncaught exception 'ErrorException' with message 'proc_open(): fork failed - Cannot allocate memory' in phar:///usr/bin/composer.phar/vendor/symfony/console/Symfony/Component/Console/Application.php on line 990

兩種方式:

  1. 重新啟動 php5-fpm
  2. 跟 composer update 說我需要多一點的 memory。(composer update -dmemory_limit=750M)

--- 這是在執行網站時發生的錯誤 ---

[RuntimeException] Error Output: PHP Fatal error: Class 'Monolog\Logger' not found in /home/Sites/production/flyingv-mall/releases/20140219161020/vendor/laravel/framework/src/Illuminate/Log/LogServiceProvider.php on line 23

把 composer 更新後就正常了(composer self-update)

 
12 months ago

本文是採用pure-ftpd作為我們的ftp server,有試過常見的vftpd,但權限等問題難以釐清,故捨棄。

安裝步驟

  • 於terminal鍵入"yast2"
  • 選擇"軟體" -> "軟體管理"
  • 輸入"pure-ftpd"做搜尋 -> 按下enter選擇 -> 按下F10接受並開始安裝

設定開機啟動

chkconfig pure-ftpd 35 # 設定開機時啟動pure-ftpd服務

FTP設定

1. 修改設定檔案

vi /etc/pure-ftpd/pure-ftpd.conf

找到以下屬性設定值做修改(可使用/做搜尋)

AnonymousOnly    yes>
NoAnonymous     no
AutoRename    no
NoRename    no

上述設定主要是將匿名登入功能關閉,以local user(在/etc/passwd內的帳戶)認證授權為主,並給予使用者做修正檔名的權限
rcpure-ftpd start  # others: start, restart, stop

2. 設定虛擬帳號

設定pure-ftpd.conf

vi /etc/pure-ftpd/pure-ftpd.conf

找到以下屬性設定值做修改(可使用/做搜尋)

PureDB     /etc/pure-ftpd/pureftpd.pdb
CreateHomeDir      yes

重啟服務
rcpure-ftpd start

建立虛擬帳號所共用的ftp專屬帳號

groupadd virtualgrp    # 專屬帳號的主要群組
useradd -g virtualgrp -m virtualuser    # ftp專屬帳號

建立虛擬帳號

pure-pw useradd tina -u virtualuser -d /home/virtualuser/tina
mkdir /home/virtualuser/tina
pure-pw mkdb    # 更新認證資料庫,只要虛擬帳戶的密碼(/etc/pure-ftpd/pureftpd.passwd)有更動都需執行此指令
pure-pw useradd tina -u virtualuser -d /home/virtualuser/tina -m    # 或在建立虛擬帳號同時加入-m參數便可順便建立認證資料

3. 虛擬帳戶內容詳細設定(Optional)

pure-pw usermod tina -t 100 -T 50 -q 1 -Q 10 -n 30 -N 2 -z 1400-2230 -r 192.168.5.0/24,192.168.1.0/24 -m
pure-pw show tina   # 檢驗設定後的結果
pure-pw passwd tina -m  # 修改虛擬帳戶密碼,並更新認證資料庫
pure-pw userdel tina -m # 刪除虛擬帳戶

參數說明:
-t、-T:設定下載及上傳頻寬。
-q、-Q:設定上傳/下載比率。
-n:限定可使用的檔案數。
-N:限制可使用的空間大小(MB)。
-z:限定連線時間。
-r:允許存取的來源端。

## 4. 修改Port(Optional)
vi /etc/pure-ftpd/pure-ftpd.conf

約在218行處,本來是註解成"#Bind 127.0.0.1,21"

若需要改port為5566,並且接受所有IP位址對ftp server的requests,則為:
Bind 0.0.0.0,5566

0.0.0.0代表接受所有ip

pure-pw小結

pure-pw就像是在虛擬帳號環境內的CLI,舉凡在正常shell下有關user的指令,像是usermod, useradd, userdel等等,同理可針對虛擬帳號做相對應動作,只需要在指令前加上pure-pw即可

參考資料