2014年12月21日 星期日

初探 Python Decorator

最近剛巧聽到朋友介紹 Python 的 Decorator。
一開始以為是指 design pattern,後來才發現原來是Pyhton特有的一種語法糖,其實有點合成函數的味道。

舉例來說,有一個取得商品價錢的函式:
def get_price(product_id):
    ...
    return price

但是為了慶祝聖誕節,有的商品打八折,有的商品則是大打五折
而該打幾折的程式是由另一個函式判斷:
def christmas_discount(func):
    discount = get_discount()
    def get_christmas_price(product_id):
        return func(product_id) * discount
    return get_christmas_price

這時我們可以用這兩個程式合成一個新的程式:
get_price(21) // ==> 200
get_price = christmas_discount(get_price)
get_price(21) // ==> '160'

但是我們可以用 decorator語法糖「掛上」合成的函式:
@christmas_discount
def get_price(product_id):
    ...
    return price

get_price(21) // ==> '160'

Functional Programming vs OOP:

在OOP中也有所謂的裝飾者模式,具體的講解可以參考 良葛格的文章
這裡就可以看出:原本許多在OOP需要特別實作的設計模式,在Python或JavaScript等函數式語言中都能夠很直觀、輕鬆的被實作出來。
常見的Factory模式也是一樣的道理,在JS中能夠被輕易的實作。

2014年12月19日 星期五

Git 小筆記

跟 git 太不熟了,在這邊堆放一些常用筆記

Branch:

git branch //看local branch(星號代表現在所在的branch)
git branch <bar> // 新增 "bar" branch
git branch -D <foo> // 刪除 "foo" branch


Checkout:

git checkout <bar> // 切換到 local 的 "bar" branch
git checkout -b <NewBranch> // 新創一個 branch 並同時 checkout 它
註:如需從origin拉下一個branch,需在本地創一個branch,checkout 後再拉 "origin/branchname"。eg. "git pull origin test"


回溯:

git log // 取得過往的commit,其中以 hex code 為 id
git checkout <hex code> // checkout 過往 commit
git show // 顯示當前所在
git checkout <branchname> // 回到 local 最新commit,eg. "git checkout master"


Tag:

git tag -l // 列出所有 tags
git tag -a <tag name> <hex code> // 針對某一 commit 打 tag
git push origin --tags // 上傳所有本機 tags 到 遠端

2014年8月28日 星期四

[SQL] Common Table Expression (CTE)

CTE 簡單來說就是依據一定的查詢條件建立一個暫時的 table ,以便重複性的 Subquery 查詢。

舉例來說,我需要從 pc, laptop, printer 三個 Table 取最貴的產品出來,想像的寫法可能是如此:

SELECT model FROM (
    SELECT * FROM pc
    UNION
    SELECT * FROM laptop
    UNION
    SELECT * FROM printer
)
WHERE price=(
    SELECT MAX(price) FROM (
        SELECT * FROM pc
        UNION
        SELECT * FROM laptop
        UNION
        SELECT * FROM printer
    )
)

我們會發現三個 table 的 UNION 被重複使用,非常麻煩。
這時我們可以將這三個 table 的 UNION 暫存成 CTE,藉由以下的方式。

WITH product AS (
    SELECT * FROM pc
    UNION
    SELECT * FROM laptop
    UNION
    SELECT * FROM printer
)

SELECT model FROM product
WHERE price=(
    SELECT MAX(price) FROM product
)

TADA~~~是不是簡單明瞭又方便勒?

2014年8月9日 星期六

ES6 新玩意兒 - Generator

ㄜ....其實不太能算是新玩意了,畢竟小弟也好一陣子沒有繼續追JS的近況(廢)。
實在是因為前陣子臉書上太多好友在提Kao和CO,當初用的express好像過時了一樣....

「不行!我還沒老啊!」

身為一個技術潮人,臨時抱佛腳是必要的,就從ES6的新規範 - Generator(迭代器) 開始吧。


宣告方式

Generator 的宣告方式比較特別,有點像是 function ,基本上是長這樣:

function * gen(){
    console.log("start!");
    yield "hello";
    yield "I'm Kevin";
    console.log("end!");
}

這個 gen 是一個ㄜ.....函式?總之,使用他後會返回一個 generator,像這樣:

var g = gen(); // g 是一個 Generator


這時,就可以拿它來做這樣的事:

r = g.next(); // "start!"
console.log(r); // { value: "hello", done: false }
r = g.next();
console.log(r); // { value: "I'm Kevin", done: false }
r = g.next(); // "end!"
console.log(r); // { value: undefined, done: true }


Generator.next() 回傳什麼?Value & Done!

value的形式不拘,端看generator的yield後接著什麼,會被放到value中。
至於 done 則是個單純的 boolean,告訴你這個 generator 玩完了沒。

看到這邊,聰明的你應該可以得到一個結論,generator中的yield有點像是中斷點,當 next() 被執行時,generator內的程式碼開始被執行,直到下一個 yield 的那一行。直到 generator的最後一行,回傳 { done: true }。


Generator 也能接收資料

在迭代的過程中,我們也是可以將變數傳入next中的:

function * gen(){
    var got = yield "hello";
    yield got;
}

var g = gen();

g.next("Good morning!"); // "Hello"
g.next(); // "Good morning!"

TADA~~~很好玩吧?


Generator 的特性及用途

因為迭代器中斷函數時,是不會卡死其他程式碼的,這樣的特性非常適合拿來做同步處理。

舉例來說....

舉例來說....

舉例來說....

舉不出來,看看大大們怎麼說好了:


基本上主要是用作「非同步流程控管」及「解決回調地獄(callback hell)」用途使用。


2014年7月29日 星期二

[JS] nextTick() 介紹與應用


Node.js是單線程的,除了系統IO之外,在它的事件輪詢過程中,同一時間只會處理一個事件。你可以把事件輪詢想象成一個大的隊列,在每個時間點上,系統只會處理一個事件。即使你的電腦有多個CPU核心,你也無法同時並行的處理多個事件。但也就是這種特性使得node.js適合處理I/O型的應用,不適合那種CPU運算型的應用。在每個I/O型的應用中,你只需要給每一個輸入輸出定義一個回調函數即可,他們會自動加入到事件輪詢的處理隊列里。當I/O操作完成後,這個回調函數會被觸發。然後系統會繼續處理其他的請求。

在這種處理模式下,process.nextTick()的意思就是定義出一個動作,並且讓這個動作在下一個事件輪詢的時間點上執行。我們來看一個例子。例子中有一個foo(),你想在下一個時間點上調用他,可以這麽做:
function foo() {
    console.error('foo');
}
 
process.nextTick(foo);
console.error('bar');

執行結果將會是:
bar
foo

我們也可以使用setTimeout()函數來達到貌似同樣的執行效果:
setTimeout(foo, 0);
console.log('bar');

但在內部的處理機制上,process.nextTick()和setTimeout(fn, 0)是不同的,process.nextTick()不是一個單純的延時,他有更多的特性,這裡有兩者的效能比較
更精確的說,process.nextTick()定義的調用會創建一個新的子堆棧。在當前的棧里,你可以執行任意多的操作。但一旦調用netxTick,函數就必須返回到父堆棧。然後事件輪詢機制又重新等待處理新的事件,如果發現nextTick的調用,就會創建一個新的棧。


什麼時候會使用到 nextTick ?

因為JavaScript是單線程的Runtime,如果有需要長時間的運算,往往會卡死其他的程式,例如事件觸發的 function。
更多應用情境可以參考原出處

也可以參考Fred的這篇:探討 NODE.JS 的非同步機制

2014年2月19日 星期三

在development模式下,assets 與 precompiled assets 衝突的解決方法

因為自己的開發環境效能往往比production server 還好,有時候我們為了求快,會直接在local 將 assets precompile 過,上傳到 production。
但繼續在 development 模式下開發時,就有個很機車的情況出現了,在 public/assets 中已經 compile 過的 assets 也被 require 進來,造成某些 lib 被 require 兩次而造成不正常的情況,當有新的變更時也會被舊的蓋過去。

查了一下發現可以針對 development 模式對 asset 路徑加上prefix:
  config.assets.prefix = "/dev/assets"

這樣子 request 的路徑就會變成 /dev/assets/application.js ,避免被precompile的assets搞亂。



2014年2月12日 星期三

修正 PG::UniqueViolation: ERROR: Duplicate Key Value Violates Unique Constraint 'Your_table_name_pkey' 問題

PG::UniqueViolation: ERROR: Duplicate Key Value Violates Unique Constraint 'Your_table_name_pkey'

今天在新產品上遇到了這個 error,查了一下發現是因為 postgres 因為不明原因而把 seq 搞亂了,以致於新增資料時使用到了既有資料的 id 。

了解問題所在後,循以下作法順利解決了問題:
  1.  rails db production
  2.  SELECT setval('your_table_id_seq', (SELECT MAX(id) FROM your_table));
打完收工~


2014年1月8日 星期三

Rails 的 i18n fallback 問題

當在 config/application.rb 中指定了 config.i18n.fallbacks 語系順序後,別忘了要 comment 掉 produciton.rb 中的 config.i18n.fallbacks = true
production 下的語系順序才會乖乖實現。

2014年1月3日 星期五

Unicorn + Nginx 配置及設定

最近部屬公司的新產品 - flyTutor 的時候,嘗試了不同的架構,從以往的 passenger-plugin on Nginx 換成了Unicorn 搭配 Nginx 的「反向代理」架構

What's "Reverse Proxy Model" ?

在提起 Unicorn 及 Nginx 的配置之前,我們得先了解一下「反向代理機制」,下面這張圖應該可以幫助你了解:

(轉自: ROR實戰聖經)


在用戶端向 server 作請求的時候,不是直接與 app server 連結 (中間那三個),而是先連到 web server ,通常是 Apache 或 Nginx,再由 web server 指派 request 給 app server、或直接 serve static assets。
所以在這樣的架構下你可以想像你的系統需要常駐著以下的服務:
  1. web server
  2. 數個 app server
  3. DB server

這樣的架構是與常見的 Apache/Nginx + Passenger 架構有所不同的,需要作一點觀念上的調適。下面我們就來介紹一下如何做相關的配置。


Nginx 配置

以下是預設的 Nginx 設定檔 - nginx.conf:
user deployer; # 定義操作 nginx 的使用者
worker_processes 1; # 定義 worker 的數量

error_log  /var/log/nginx/error.log;
pid        /var/run/nginx.pid;

events {
  worker_connections  1024;
}

http {

  include       /etc/nginx/mime.types;
  default_type  application/octet-stream;

  access_log    /var/log/nginx/access.log;

  sendfile on;
  tcp_nopush on;
  tcp_nodelay on;

  keepalive_timeout  65;

  gzip  on;
  gzip_http_version 1.0;
  gzip_comp_level 2;
  gzip_proxied any;
  gzip_vary off;
  gzip_types text/plain text/css application/x-javascript text/xml application/xml application/rss+xml application/atom+xml text/javascript application/javascript application/json text/mathml;
  gzip_min_length  1000;
  gzip_disable     "MSIE [1-6]\.";

  server_names_hash_bucket_size 64;
  types_hash_max_size 2048;
  types_hash_bucket_size 64;

  include /etc/nginx/conf.d/*.conf;
  include /etc/nginx/sites-enabled/*;

}

上面這段預設的設定檔有兩個地方需要注意:
第一行的使用者身分必須為負責 deploy 的用戶,否則常常會出現 403 權限問題,因為未經設定的話 nginx 會沒有權限存取 deployer 的檔案,如 js, css。
第二行的 worker 數量建議別設定超過 cpu 數目,多了也沒好處。

再來就是重頭戲了,我們要開始加入 app server 的設定,這段設定需要加在 http 的括號內:
  upstream unicorn {
    server 127.0.0.1:8080 fail_timeout=0;
  }

  server {
    listen 80 default deferred;
    server_name flytutor.com;
    root /var/www/flytutor/public;

    location ^~ /assets/ {
      gzip_static on;
      expires max;
      add_header Cache-Control public;
    }

    location / {
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_redirect off;

      if (!-f $request_filename) {
        proxy_pass http://unicorn;
      }

    }

    error_page 500 502 503 504 /500.html;
    client_max_body_size 4G;
    keepalive_timeout 10;
  }


upstream:

首先我們先來講解一下第一部分的 upstream。首先,upstream 後的「unicorn」並非一定的,端看你希望怎麼命名,但是要注意的是 proxy_pass 也需作對應的設定。再來,upstream 內的 server 可以不只一個,如果你有數個 app server 在不同的 host 的話,你也可以做如下的設定:
upstream flytutor.com {
  server 192.168.0.111:8080 weight=3;
  server 192.168.0.222:8080 weight=2;
  server 192.168.0.333:8080 weight=3;
}
其中 weight 代表的是「被配發 request 的權重」,當 weight 越高的時候被分配到的機率越大,可以做 loading balance。

而除了走 TCP/IP 外,如果是在 web server 和 app server 都在同一個主機的情況下,你也可以使用 socket 來做設定,基本上效能會比 TCP/IP 好一點點(因為少了 header):
  upstream unicorn {
    server unix:/tmp/unicorn.sock fail_timeout=0;
  }

server:

接下來我們要介紹一下 server 的設定,基本上這邊指的 server 就是指 app server,一台 nginx server 可以依 server 設定派發 request ,所以可以做 vhost 的應用,例如判斷 request 的不同網域 (設定檔中的 "server_name")、或是同網域但是不同 port (設定檔中的 "listen"),來派發到不同的服務。
proxy_pass 很重要,許多網友都死在這個地方,切記要將他設置成 upstream 的命名,例如 "http://<upstream_name>"。
你可以想像當一個 request 經過 location(path), server_name(domain), listen(port) 重重篩選後,你卻不指派 proxy_pass 的目的地給他,讓這個 request 就此隱沒在數據流中,成了「digital phantom」,你於心何忍?


Unicorn 配置

unicorn 的配置通常是放在 rails project 中的 config 資料夾,長得像這樣:
app_root = "/var/www"
app_name = "flytutor"
listen "127.0.0.1:10101" #, :backlog => 2048 #這邊要跟nginx虛擬主機檔中upstream內定義的務必一樣
worker_processes 2 #看情況開
preload_app false
timeout 30
 
module Rails
  class <<self
    def root
      File.expand_path(__FILE__).split('/')[0..-3].join('/')
    end
  end
end

_working_directory = File.join(app_root, app_name)
working_directory _working_directory
logs_path = "#{_working_directory}/log"
pid "#{_working_directory}/tmp/pids/unicorn.pid"
stderr_path "#{logs_path}/unicorn.stderr.log"
stdout_path "#{logs_path}/unicorn.stdout.log"
 
GC.respond_to?(:copy_on_write_friendly=) and GC.copy_on_write_friendly = true
 
before_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!
  
  old_pid = "#{Rails.root}/tmp/pids/unicorn.pid.oldbin"
  if File.exists?(old_pid) && server.pid != old_pid
    begin
      Process.kill("QUIT", File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
      puts "Send 'QUIT' signal to unicorn error!"
    end
  end
end
 
after_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end

這邊要注意的地方有兩個:

  1. listen 的對象必須與 nginx.conf 中 upstream 設定的一樣,unicorn 與 nginx 就是靠這行設定連起來的。還有,如果是走 TCP/IP 的設定,盡量不要使用 1024 以下的 port 以免 deployer 沒有權限啟動 unicorn 導致失敗(你總不能老用 root 權限作 deploy 吧?)。
  2. unicorn 是採用 master-slave 機制,啟動 unicorn 後叫出  process 列表你就會了解的。而這裡的 worker_processes 的數目顧名思義就是指定 worker 的數目。開多少個才好?基本上沒有一定的標準答案,還是要依你的使用情況及環境做測試後才知道,如果懶得想的話就直接設定為「CPU 數目 + 1」吧。


操作 unicorn

許多網友在網路上搜尋到的啟動法是以 unicorn_rails 來啟動:/usr/bin/unicorn_rails -c config/unicorn.rb -E $RAILS_ENV -D。但現在 unicorn 官方已經不建議使用這種作法而是直接改用 unicorn 來啟動/重啟 unicorn

Hot restart with zero down time

許多人之所以選擇 unicorn 或許就是因為他的 hot restart 特性。如果對這有興趣的人可以參考這篇



2014年1月1日 星期三

vagrant 初體驗與故障排除

1. 30 秒認識 vagrant:

Vagrant 是一個 ruby 寫的工具,它是一個 DSL 讓開發者可以輕易控制 VirtualBox 的 VM 。用它可以輕鬆管理和制作我們理想中的開發環境。
  $ gem install vagrant #安裝 vagrant
  $ vagrant box add ubuntu http://cloud-images.ubuntu.com/vagrant/quantal/current/quantal-server-cloudimg-i386-vagrant-disk1.box #安裝新的 Vagrant Package。這裡的 ubuntu 是一個預先做好的空的 ubuntu 12.10 (intel-based)
  $ vagrant init ubuntu
  $ vagrant up
box 檔可以在 http://www.vagrantbox.es/ 下載


2. 設定:

此時就可以使用 vangrant ssh 連到vm了,預設使用者/密碼為 vangrant/vangrant
接著編輯本地資料夾中的 Vagrantfile,修改 config.vm.network :hostonly, "33.33.33.33" 將ip改成自己想要的ip,這裡以 "33.33.33.33" 為例。修改完後要執行 vangrant reload


3. 故障排除:


無法以 SSH 連接 VM:

[default] Failed to connect to VM!
Failed to connect to VM via SSH. Please verify the VM successfully booted
by looking at the VirtualBox GUI.
當出現上面的錯誤訊息時,可以照 http://vagrant.wikia.com/wiki/Usage 上的步驟來排除,基本上就是將 Vagrantfile 中的 config.vm.boot_mode = :gui 設定打開。再重新 vagrant up,在產生的 GUI 中輸入 sudo dhclient eth0


Guest additions not matchs:

[default] The guest additions on this VM do not match the install version of
VirtualBox! This may cause things such as forwarded ports, shared
folders, and more to not work properly. If any of those things fail on
this machine, please update the guest additions and repackage the
box.


Reference:

http://reality.hk/posts/2011/12/21/vagrant
http://gogojimmy.net/2013/05/26/vagrant-tutorial/