almost 4 years ago

我是谷阿莫,今天要來講一個孤兒怨的故事。

某天「長得一臉我超屌的Unix星球主角A」閒閒沒事幹生了一個「我超淫亂小孩B」

並期待「我超淫亂小孩B」未來能有什麼美好的光景

但糟糕的是「我超淫亂小孩B」人如其名,超淫亂

一出生馬上跟人亂搞生出了C

因此「我超淫亂小孩B」立刻精盡人亡死掉了

喔你不要問我為什麼一出生馬上又能懷孕生小孩

主角威能你懂嗎?這可不是你們愚蠢人類能懂der~

因為B死前沒有留下任何訊息告訴A說他生了小孩

於是C就成為了孤兒,很悲憤,就跑去當孤兒怨的主角

最後孤兒怨C變成了小惡魔(daemon),這就是孤兒怨的由來,科科

故事講完了 直接將上面的故事翻譯成C code

// 主角A生了小孩B
pid_t child = fork();
if (child > 0) {
  // 主角A期待B有所作為
  wait(NULL); 
} else ( child == 0 ) {
  // B一出生後卻馬上生小孩
  pid_t grandchild = fork();
  if (grandchild > 0) {
    // 所以B精盡人亡了
    exit(0);
  } else if (grandchild == 0 ){
    // 於是孤兒怨 C在這裡產生了...
    execl("/bin/ls", "ls", "-al");
  } else {
    printf("B難產\n");
  }
} else {
  printf("A難產\n");
}

這時C就是一個daemon,其parent會變成init process (pid=1)
這時如果C死掉,不會成為zombie process,因為殭屍和惡魔不一樣
大魔王如果自己死掉就是死掉,電影就結束了,人類又戰勝了!好棒棒

daemon你去查Google會發現他剛好叫小惡魔,但又可以是小精靈,意思是看不見,不屬於任何人,不受他人擺佈這樣
不過有人把它翻譯成「守護進程

但需要時,還是可以傳送訊號(signal)給小惡魔做溝通,科科

 
about 4 years ago

有兩種方式,簡單做個筆記
長度不太一樣

NSUUID().UUIDString
//"9E398006-E0E3-45A5-8712-BED3E6675BC4"


NSProcessInfo.processInfo().globallyUniqueString
//"73F64B9F-52B2-49FA-A7E4-829C8F8FAE3A-35559-0000321B98BF3AD1"
 
over 4 years ago

下面這些schema code在mongoose v3.8下運作正常

var OrderSchema = new Schema({
    orderId: { type: String, required: true },
    createdDate: { type: Date, required: true },
    ...
    paymentType: {
        type: String,
        subType: String
    },
    paymentResult: {
        ...
    },
    _user: { type: Schema.Types.ObjectId, ref: 'User', required: true}
});

但我把mongoose升級到v4.0.5版的時候,儲存資料時出現驗證錯誤(Validation Error)

var newOrder = new DB.Order({
    orderId: orderId,
    createdDate: new Date().getTime(),
    paymentType: {
        type: 'CREDIT',
        subType: 'INSTANT'
    },
    paymentResult: {
        ......
    },
    _user: user._id
});
newOrder.save(function(err) { console.log(err); });

err的錯誤內容是

{ [ValidationError: Order validation failed]
  message: 'Order validation failed',
  name: 'ValidationError',
  errors: 
   { paymentType: 
      { [CastError: Cast to String failed for value "[object Object]" at path "paymentType"]
        message: 'Cast to String failed for value "[object Object]" at path "paymentType"',
        name: 'CastError',
        kind: 'String',
        value: [Object],
        path: 'paymentType' } } }

我觀察了一下其他沒問題的code
發現如果我把定義paymentType的方式從

createdDate: { type: Date, required: true },
....
paymentType: {
    type: String,
    subType: String
},

改成

createdDate: { type: Date, required: true },
....
paymentType: {
    type: { type: String } ,
    subType: { type: String }
},

就都正常了
看起來「SomeField : SomeType」在subdocs上的定義,在版本4已經不能使用了
不過官網的範例還是這樣寫
而且從Mongoose 版本3到4的升級指南(migration guide)沒有提到這一點

所以我就到GitHub上的Mongoose開了一個Issue

Update

GitHub的Issue已經有人回覆了 看來是我耍笨了,因為paymentType的type: String 他就誤以為整個paymentType是String型別了 所以我用Object寫進去當然後出現Validation錯誤,真的是好笨啊... 可是為何v3沒事呢

沒事不要亂 npm update 啊,你可以看出來這是信用卡收款的資料
要是客戶沒有寄信來抱怨,我就不知道到底發生了什麼事,還好他沒有當我是詐騙

如果你真的要在production mode上做npm update
建議在package.json裡面,千萬不要像我直接在裡面使用*號,不管怎樣都會更新到最新版。

{
  "name": "MyApp",
  "version": "1.0.2",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "body-parser": "^1.8.4",
    "mongoose": "*"
    ...
  }
}

建議改成
"~4.0.5" 代表 4.0.x (Bug修正等等更新,舉例:會更新4.0.6, 4.1.2就不會)
"^4.0.5" 則相當於會更新到4.x.x (新版,有新功能,但會相容,舉例:會更新4.1.0, 5.0.1就不會)
"4.0.5" 永遠只會用4.0.5這版本(但是有Bug你npm update就更新不到了)

這裏我用了
"*" 則會永遠是最新的版本
才造成此次的烏龍,我應該使用^才對
照理說你用npm install mongoose --save時,預設他都會給你用^才對
我不知道是哪次自己亂改改到的
不管如何,給大家參考看看囉

看看這筆延遲了三個小時、PayPal重試通知我的Server 10次才拿到可愛的HTTP 200 回傳碼

 
almost 5 years ago

最近接了某單位的案子,結果導致我過年期間還要抱著我的筆電用繼續趕工
為什麼呢?溝通不良,這真的很令人憤怒。
如果你是老闆或不懂技術,可以直接看最後的總結

當初我來是應徵「前端」的工作
已經做好UI(User Interface)的準備
以及我在乎的UX(User Experience),等待指示到底要做什麼工作

但我卻什麼訊息也沒收到
只叫我先畫Wireframe。問題是,我到底要做什麼?什麼叫給我設計就好?
根本沒有人告訴我到底要做什麼阿!這就好像你跟某人合作,跟他說,我想要開一間店,但你什麼訊息都不告訴他
他怎麼會知道到底要開什麼店?服飾店嗎?還是餐廳?

後來我去開會,才知道原來要做一個比賽用的平台,開會前幾個小時我一直在講我的UI、UX哲學
他們聽得很不耐煩,露出一副我怎麼會這樣的表情
我跟他說「我只做前端」「我只做前端」「我只做前端」(很重要所以要重複三次)
最後PM卻說,對啊!所以你要架設伺服器、寫後台、設計資料庫
聽到這裡我心中一把火整個上來,這明明是你的錯誤。我都懷疑是裝傻還是真的知道
後來他才開始不耐煩地說我們要做什麼,然後又是擺出那副我怎麼會不知道的樣子
連按個投影片鍵盤下一頁也用那種半澤直樹裡,小木曾忠生式的敲桌子法,腦中開始滴黑血啊~~

幸好我有一點點後端的經驗,因為JavaScript非常熟悉,所以也選了 Node.js 作為後端的語言

所以你們應徵的是 Full stack developer(全端工程師),嗯,下次拜託別弄錯囉!
最後,2/15開會才知道要做這些,卻馬上訂個Deadline 在3/7完成。
既然這麼趕為什麼你們不會早一點開始做?早一點告訴我做什麼不會更好嗎?
還說春節也請你趕工一下,你們難道不會不好意思嗎?
我除夕夜吃完飯後一整晚沒睡繼續趕工,然後隔天下午2點才又睡一下。
雖然我知道沒時間了,但還是想要打這篇文章發洩一下

以下是給老闆們的總結重點

這年頭如果你指望工程師幫你做出好東西、和你一起成長
請老闆在心想如何賺大錢之餘、先去做一下功課吧
至少知道自己在說什麼、不要惹怒工程師
溝通對工程師而言非常重要,別以為對宅宅工程師就不需要任何社交能力
否則你絕對找不到真心為你工作的好工程師

 
almost 5 years ago

我在上一篇文章最後,把我的node.js升級到0.12.0 (注意,Ubuntu的預設repo還在0.10.x版,所以如果你用apt-get install安裝的還會是舊版的)。

想要讓原本的專案正常運作,但發現失敗了
原來是fs的錯誤處理出問題
來看看以下Code吧

錯誤示範 Bad Example
var fileStream = fs.createReadStream(localFilePath);
// ....省略...

fileStream.on('error', function(err) {
    // 錯誤示範 Bad Example: do not use errno, use code instead

    if (err.errno === 32) { 
        // 代表檔案或資料夾不存在 File not found

    } else {
        // 其他錯誤

    }
 });

其中,err.errno === 32時,代表檔案不存在的錯誤(錯誤示範)
當時我第一次用Node.js時,自以為聰明,覺得數字判斷會比較快,於是特地使用 errno

沒想到我升級到Node.js v0.12.0時,這個errno卻變了!變成了 -2!

於是就把檔案不存在判斷成其他錯誤了。在這裡應該使用err.code來判斷才是正確的做法。
像是檔案不存在的錯誤,err.code會是'ENOENT'
因此用err.code === 'ENOENT'才是正確的做法。
才會確保多種node.js版本的相容性

正確示範 Good Example
var fileStream = fs.createReadStream(localFilePath);
// ....省略...

fileStream.on('error', function(err) {
    if (err.code === 'ENOENT') { 
        // 代表檔案或資料夾不存在 File not found

    } else {
        // 其他錯誤 Other Errors

    }
 });
 
almost 5 years ago

今天我在使用歐付寶金流串接API時,他們要求把http參數按照abc字母排列來計算Hash(CheckMacValue)(有夠爛),而且排序要不分大小寫,查了MDN的資料,發現localeCompare可行,於是想用localeCompare來做不分大小寫的排列。預設直接arr.sort()是會分大小寫的,而且大寫會排在前面

在我的Chrome 40.0.2214.111 (64-bit),跑以下JavasScript

var arr = ['Benzene','Aspirin','carbon','Copper'];
arr.sort(function (a, b) {
    return a.localeCompare(b, 'en', {'sensitivity': 'base'});
});

會是以下結果:

["Aspirin", "Benzene", "carbon", "Copper"]

很正確,因為大小寫忽略,所以carbon的第二個字母:a,理所當然排在Coppero前面了

但在我的Node.js v0.10.33上
會是以下結果

[ 'Aspirin', 'Benzene', 'Copper', 'carbon' ]

第二個字母o卻排到了a前面,和直接arr.sort()結果一樣。

Node.js v0.10.x上臨時解決辦法:比較時把字全部轉成小寫

var arr = ['Aspirin','Benzene','carbon','Copper'];
arr.sort(function (a, b) {
    return a.toLowerCase().localeCompare(b.toLowerCase());
});

真是奇怪的問題,看來Node.js使用的JavaScript還不支援localeCompare的sensitivity設定
返回結果和直接arr.sort()是一樣的。希望寫在這邊可以避免大家繞遠路!

MDN資料裡面最下面有browser compability,看來這並無法跨平台啊!Server上是固定環境沒話說,但在Client的JavaScript就最好別用到這個功能了。

EDIT:

把Node.js升級到v0.12.0之後,就有支援、沒有問題了

因此如果你要做package給人家用,最好還是不要使用此功能。
因為現在在Ubuntu 14.04下,預設repo用apt-get install nodejs安裝的版本,還會是舊版的v0.10.x的。

 
almost 5 years ago

前言

今天我在 DigitalOcean 的512MB RAM的VPS上
想要 gem install bundler,一直安裝失敗,看錯誤內容是寫

There is insufficient memory for the Java Runtime Environment to continue.
Native memory allocation (malloc) failed to allocate 524288 bytes for committing reserved memory.

應該是RAM不夠用了,JVM想要Allocate 512MB的RAM,但是硬體上不敷使用,因此想藉由啟用虛擬記憶體來解決此問題

如果你跟我一樣是用 DigitalOcean VPS 的話
預設是不會有配置任何Swap的,其他VPS我不清楚,但我猜應該預設都沒有啟用Swap,畢竟讀寫頻繁會增加他們固態硬碟的負擔啊!這樣的結果就是,當你的512MB的RAM不夠用時,你的程式就無法執行。

為什麼不直接升級到1GB或2GB的RAM? 因為我的伺服器同時正在跑一個Node.js的App,而且Google分析顯示線上人數有數百人,升級要先關機1~2分鐘才能處理完成,不敢冒然關機而且小氣不想加錢的狀況下使用了Swap來解決。

確認是否已啟用swap

首先,先確認你有沒有啟用Swap,你可以在 terminal 執行

free -m

應該就可以看到類似以下的內容

         total       used       free     shared    buffers     cached
Mem:           490        484          6          0         17        284
-/+ buffers/cache:        182        307
Swap:            0          0          0

如果你的Swap,total的部分是0,像上面那樣,那代表你沒有啟用swap。若有,本篇教學就失效,請停止操作。

設定 Swap File

下面的指令,我們把Swap file建立在 /var/swapfile
通常如果你是安裝在電腦上,Swap會放在不同的磁碟分割區,不過這裡我們是用VPS
不能做磁碟分割的動作,因此我們建立在同一個分割中。

現在要決定你的 Swap 大小。通常,Swap大小會是1~2倍RAM的大小,也就是虛擬記憶體大小
例如我的VPS是512MB RAM,那我就設定為兩倍,也就是1GB。
(註:DigitalOcean的512 RAM其實是指MiB,實際可用約490MB,不過不管了)

接下來的所有指令都要sudo權限,因此我們先用su比較方便

sudo su #輸入密碼

cd /var
fallocate -l 1G swapfile
chmod 600 swapfile

這樣一來,我就有一個1GB的Swap file了。
最後一行的 chmod 指令非常重要,我們要確保這個 swapfile 只能被 root 讀寫。否則會有嚴重的安全性問題。

接下來我們就能啟用swap了

mkswap /var/swapfile
swapon /var/swapfile

查看swap的狀態,確定啟用了

swapon -s

如果你有看到你的 /var/swapfile 那麼你就大功告成了。
但是,你會發現重新開機後沒作用了。

讓 Swap file 重開機後也能自動啟動

我們要在/etc/fstab檔下方新增一個指示,讓系統開機時自動啟用 swap。
執行下方語法來新增。

注意:請確定你跟我一樣是裝在/var/swapfile,不然請自行修改正確的路徑,否則將會無法開機

echo "/var/swapfile    none    swap    sw    0    0" >> /etc/fstab

微調設定

基本上,到這邊已經設定完成了。但如果你在乎效能,請繼續看下去
我當時設定到這邊,就直接開始安裝,而且沒有發生記憶體不足的錯誤了。
但是我當時有開另外一個ssh視窗 ,用htop監看RAM和Swap的使用量,結果發現
RAM明明還很多(還有150mb左右),卻就開始用Swap了。

這有什麼問題?
要知道 Swap File就是當你RAM不夠用時,就改用硬碟來當作RAM。
硬碟的存取速度遠低於RAM,即使是固態硬碟,還是比RAM慢很多,這樣會讓你的程式效能降低
而且明明RAM還撐得住,為什麼要選擇速度慢很多的硬碟呢?

調整 swappiness

swappiness 參數是一個介於0~100的數值,當數值越接近100,代表系統越積極使用Swap,當數值越接近0,系統將會盡量使用RAM。預設值是 60

你可以用下面的指令來看現在的設定

cat /proc/sys/vm/swappiness

我這裡看到是60。
對VPS而言,這不是一台一般使用者用的桌面電腦,因此我們能用時盡量榨乾RAM會比較好,畢竟硬碟效能比較差
我們應該把它調整到較低的數字,這裡我把它調整到10。

sysctl vm.swappiness=10

同樣,如果要讓他重開機後也能生效,我們要修改sysctl.conf設定檔。

echo "vm.swappiness = 10" >> /etc/sysctl.conf

這樣就完成囉!