3 months ago

許多時候一個團隊會 hosting 許多平台,且往往各平台間都是有所關聯,像是 flyingv 還有 VShop, VEvent。其會員資料庫都是共用,且同樣的登入方式。若在平台間瀏覽操作需要一直重新登入,是否也太令人不悅了~
因此可以設定這些平台的 cookie (通常登入判斷的資訊都會存於此)為同樣的 Domain,不同的 Sub Domain。例如 domain name 為 example.com,其他平台是 shop.example.com, event.example.com, ...

此處在 Laravel 的設定主要相關三個檔案 app/config/app.php, app/config/cache.php, app/config/session.php,環境為下:

  • Nginx
  • php5-fpm
  • Laravel 4.1
  • PHP 5.5

關鍵1: app/config/app.php

各平台 project 的此檔案 "Encryption Key" 記得相同,可從其中一個 project 複製到其他專案

'key' => 'a random 32 character string',

關鍵2: app/config/session.php

Session Driver 此處假設都為 memcached,Cookie Name 都為 ex_session,Cookie Domain 都為 .example.com

'driver' => 'memcached',
'cookie' => 'ex_session',
'domain' => '.example.com',

若 Session Drive 為 Native,記得設定 files 為同樣的位置

'files' => '/srv/shared/sessions',

關鍵3: app/config/cache.php

因 session driver 指定為 memcached,故此處須設定 Cache Drive 與 memcached server 的位址

'driver' => 'memcached',
'memcached' => array(

        array('host' => '127.0.0.1', 'port' => 11211, 'weight' => 100),

    ),

以上完成後便可做到 Cross-Domain 登入 Cookie 的判斷,若無法可以試試看重新啟動 php5-fpm,將 cgi 中 cache 住的 php script 更新。

 
6 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

 
7 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;
 
7 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

 
8 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();
 
8 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, 看似簡單的幾個步驟蒸騰了一整天...希望能幫到有這需求的人

 
9 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
 
9 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 」。

 
9 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

 
9 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)