2012年12月24日 星期一

[hadoop]Hadoop 小象安裝測試在 Ubuntu

小象要開始跑

在了解大概 hadoop 能做什麼之後,就來安裝試試吧。Michael G. Noll 寫了幾份非常好的教學。 這裡就按照他的教學,一步步重做一遍。就從他的 Running Hadoop On Ubuntu Linux (Single-Node Cluster) 開始讓小象跑。

http://www.michael-noll.com/tutorials/running-hadoop-on-ubuntu-linux-single-node-cluster/

而我這篇是讓我自己這個新手能夠不忘記我是怎麼安裝的筆記。

安裝 ubuntu 在 vmware 裡,完成 vmware-tool 安裝。

這一步是原來教學沒有的步驟。為了大家可以在公司、或自家的電腦試用,所以用 vmware 裝一個虛擬電腦。首先要注意,vmware-tool 的安裝跟 vmware 版本有關。vmware-tool 最重要的是可以讓 host 系統 及 guest 系統用「拖拉」、「複製貼上」的方式,交換檔案及文字。我使用的版本是 vmware 8,在裝好 ubuntu 12.04 之後,vmware-tool 可使用隨附的 8.8.0 版安裝。(vmware 7 可裝 ubuntu 11.04 配合 6.0.2。)在確認完 vmware 版本之後,就不用擔心之後會有問題了。

http://www.ubuntu.com/download/desktop 下載 12.04 LTS,下載 iso 檔。選擇 desktop 是因為有桌面可用,比較方便。

將 ubuntu 安裝完畢,先加入一個使用者 hduser,這是之後專門執行 hadoop 程式的使用者。

$ sudo addgroup hadoop

$ sudo adduser --ingroup hadoop hduser

hduser 要加入 sudoer 名單中,才能安裝 vmware-tool。

先用安裝者的帳號執行

$ sudo visudo

再把 hduser (ALL:ALL)=ALL 加在 root (ALL:ALL)=ALL 底下。

或把 hduser 加到 admin 的 group 裡面。(首先要確定有 admin 的 group。可參考)

$ sudo usermod -g admin hduser

然後切換使用者為 hduser。

接下來,在 vmware 的「VM」選單,按下「install VMware tool」,在虛擬電腦裡,會 mount 一個檔案。點兩下,會使用 archie manager 開啟檔案,把裡面的目錄「vmware-tools-distrib」拖到桌面上。

啟動 terminal,切換到 ~/Desktop/vmware-tools-distrib,執行

$ sudo ./vmware-install.pl

中間經過一連串的問題,都是按 enter 通過。如果有因為任何的錯誤而停下,都是因為 vmware-tools 的版本與 ubuntu 版本不合導致。我花了很多冤枉時間,才知道是版本問題。vmware 出 vmware-tools 程式一定是可以用才釋出,把版本弄對就不用白花時間。

安裝完畢,重開機一次。

設定 SSH

因為 hadoop 使用 ssh 管理它的 node,接下來要設定 ssh。

首先,產生 ssh key。

$ su - hduser # 換成 root 權限
hduser@ubuntu:~$ ssh-keygen -t rsa -P ""
Generating public/private rsa key pair.
Enter file in which to save the key (/home/hduser/.ssh/id_rsa):
Created directory '/home/hduser/.ssh'.
Your identification has been saved in /home/hduser/.ssh/id_rsa.
Your public key has been saved in /home/hduser/.ssh/id_rsa.pub.
The key fingerprint is:
db:35:f4:ae:e3:79:48:d3:95:fa:2d:22:a8:43:5c:dd hduser@ubuntu
The key's randomart image is:
...
hduser@ubuntu:~$

第二行是產生一個不用密碼的 RSA key pari。這樣就不用 hadoop 與 node 溝通時,都要人去打密碼。

再來,把新產生的 key 放到已認證的 key 中。

hduser@ubuntu:~$ cat $HOME/.ssh/id_rsa.pub >> $HOME/.ssh/authorized_keys

因為 desktop 版沒有 ssh server,因此,要加裝 openssh-server



hduser@ubuntu:~$ sudo apt-get install openssh-server


測試一下能不能連上 ssh server。因為這版的 server 預設使用 ECDSA 所以,ssh 的指令要強迫使用 rsa。



hduser@ubuntu:~$ ssh -oHostKeyAlgorithms='ssh-rsa' localhost

The authenticity of host 'localhost (127.0.0.1)' can't be established.
RSA key fingerprint is a3:99:7f:2b:8e:92:34:20:59:2f:2d:10:94:c9:60:74.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'localhost' (RSA) to the list of known hosts.
Welcome to Ubuntu 12.10 (GNU/Linux 3.5.0-17-generic i686)

* Documentation:  https://help.ubuntu.com/

222 packages can be updated.
52 updates are security updates.

Last login: Mon Dec 24 00:56:30 2012 from localhost
hduser@ubuntu:~$ exit
logout
Connection to localhost closed.
hduser@ubuntu:~$


另一個解決辦法是產生 ECDSA,並使用 ECDSA。


hduser@ubuntu:~$ ssh-keygen -t ecdsa -P ""
Generating public/private ecdsa key pair.
Enter file in which to save the key (/home/hduser/.ssh/id_ecdsa):
Your identification has been saved in /home/hduser/.ssh/id_ecdsa.
Your public key has been saved in /home/hduser/.ssh/id_ecdsa.pub.
The key fingerprint is:
27:64:63:de:16:4b:97:f4:58:96:11:46:6d:a2:24:7e hduser@ubuntu
The key's randomart image is:


因為後續的過程中,按照這個教學的話,hadoop 會自然的使用 ECDSA,所以,還是要用 ECDSA 的方式把 key 加到 .ssh/authorized_keys 裡面。


hduser@ubuntu:~$ cat $HOME/.ssh/id_ecdsa.pub >> $HOME/.ssh/authorized_keys


Noll 先生說,如果有遇到問題,要檢查一下在 /etc/ssh/sshd_config 裡:


  • PubkeyAuthentication 應該是 yes。
  • AllowUsers 如果有打開,則要把 hduser 加進去。
  • 如果 ssh server 的設定有異動,要強迫 ssh server 重載設定。 $ sudo /etc/init.d/ssh reload

安裝 java


java 至少要用到 ,我們可以使用指令來檢查 java 版本



hduser@ubuntu:~$ java -version
The program 'java' can be found in the following packages:
* default-jre
* gcj-4.6-jre-headless
* gcj-4.7-jre-headless
* openjdk-7-jre-headless
* openjdk-6-jre-headless
Try: sudo apt-get install <selected package>


新安裝的 ubuntu 12.04 desktop LTS 沒有裝 java runtime。要自己安裝。



hduser@ubuntu:~$ sudo apt-get install default-jre



hduser@ubuntu:~$ java -version
java version "1.7.0_09"
OpenJDK Runtime Environment (IcedTea7 2.3.3) (7u9-2.3.3-0ubuntu1~12.10.1)
OpenJDK Client VM (build 23.2-b09, mixed mode, sharing)
hduser@ubuntu:~$


 


安裝小象


從 Apache 下載 hadoop,目前穩定版是 1.0.4,到 http://www.apache.org/dyn/closer.cgi/hadoop/common/ 它會給你最近的 mirror 站點。下載 1.0.4 版的 hadoop-1.0.4.tar.gz。

 image

使用 firefox 下載,預設會放到 ~/Downloads 裡面。點兩下 hadoop-1.0.4.tar.gz,archive manager 會打開該壓縮檔,把裡面的目錄 hadoop-1.0.4 拉到桌面,改名為 hadoop,再移到 /usr/local 裡面去。使用指令


hduser@ubuntu:~$ sudo mv Desktop/hadoop/ /usr/local/
[sudo] password for hduser:
hduser@ubuntu:~$ ls -l /usr/local
total 36
drwxr-xr-x  2 root   root   4096 Oct 17 07:56 bin
drwxr-xr-x  2 root   root   4096 Oct 17 07:56 etc
drwxr-xr-x  2 root   root   4096 Oct 17 07:56 games
drwxr-xr-x 14 hduser hadoop 4096 Dec 24 01:09 hadoop
drwxr-xr-x  2 root   root   4096 Oct 17 07:56 include
drwxr-xr-x  4 root   root   4096 Oct 17 07:59 lib
lrwxrwxrwx  1 root   root      9 Dec 13 10:10 man -> share/man
drwxr-xr-x  2 root   root   4096 Oct 17 07:56 sbin
drwxr-xr-x  7 root   root   4096 Oct 17 08:00 share
drwxr-xr-x  2 root   root   4096 Oct 17 07:56 src


接下來要設定 .bashrc。(ubuntu 預設是用 bash。)使用指令開啟 .bashrc 來改。


hduser@ubuntu:~$ gedit .bashrc


在檔案的最後加進以下的設定。(原文中的 HADOOP_HOME 的設定已經要改用 HADOOP_PREFIX。)


################# for hadoop settings ##############
# Set Hadoop-related environment variables
export HADOOP_PREFIX=/usr/local/hadoop

# Set JAVA_HOME (we will also configure JAVA_HOME directly for Hadoop later on)
export JAVA_HOME=/usr/lib/jvm/default-java

# Some convenient aliases and functions for running Hadoop-related commands
unalias fs &> /dev/null
alias fs="hadoop fs"
unalias hls &> /dev/null
alias hls="fs -ls"

# If you have LZO compression enabled in your Hadoop cluster and
# compress job outputs with LZOP (not covered in this tutorial):
# Conveniently inspect an LZOP compressed file from the command
# line; run via:
#
# $ lzohead /hdfs/path/to/lzop/compressed/file.lzo
#
# Requires installed 'lzop' command.
#
lzohead () {
    hadoop fs -cat $1 | lzop -dc | head -1000 | less
}

# Add Hadoop bin/ directory to PATH
export PATH=$PATH:$HADOOP_PREFIX/bin

 


其他使用者要用 hadoop 的,也要更新這個 ~/.bashrc


設定 hadoop -- 1


第一個要處理的是 hadoop-env.sh


hduser@ubuntu:~$ gedit /usr/local/hadoop/conf/hadoop-env.sh


把裡面的


# export JAVA_HOME=/usr/lib/j2sdk1.5-sun


的底下,加上


export JAVA_HOME=/usr/lib/jvm/default-java


再來要把 ipv6 關掉。檔案的最後加上

export HADOOP_OPTS=-Djava.net.preferIPv4Stack=true

 

設定 hadoop -- 2


接下來是 core-site.xml,這是設定 hadoop 要在真實檔案系統的位置。因此,先建立一個目錄給 hadoop 使用。


hduser@ubuntu:~$ sudo mkdir -p /app/hadoop/tmp
[sudo] password for hduser:
hduser@ubuntu:~$ ls -l /app/hadoop/
total 4
drwxr-xr-x 2 root root 4096 Dec 24 02:02 tmp
hduser@ubuntu:~$ sudo chown hduser:hadoop /app/hadoop/tmp
hduser@ubuntu:~$ ls -l /app/hadoop/
total 4
drwxr-xr-x 2 hduser hadoop 4096 Dec 24 02:02 tmp
hduser@ubuntu:~$ sudo chmod 750 /app/hadoop/tmp
hduser@ubuntu:~$ ls -l /app/hadoop/
total 4
drwxr-x--- 2 hduser hadoop 4096 Dec 24 02:02 tmp
hduser@ubuntu:~$


把下列的文字,加到 /usr/local/hadoop/conf/core-site.xml 的 <configuration> ... </configuration> 中間:

<!-- In: conf/core-site.xml -->
<property>
<name>hadoop.tmp.dir</name>
<value>/app/hadoop/tmp</value>
<description>A base for other temporary directories.</description>
</property>

<property>
<name>fs.default.name</name>
<value>hdfs://localhost:54310</value>
<description>The name of the default file system. A URI whose
scheme and authority determine the FileSystem implementation. The
uri's scheme determines the config property (fs.SCHEME.impl) naming
the FileSystem implementation class. The uri's authority is used to
determine the host, port, etc. for a filesystem.</description>
</property>

把下列的文字,加到 /usr/local/hadoop/conf/mapred-site.xml 的 <configuration> ... </configuration> 中間:

<!-- In: conf/mapred-site.xml -->
<property>
<name>mapred.job.tracker</name>
<value>localhost:54311</value>
<description>The host and port that the MapReduce job tracker runs
at. If "local", then jobs are run in-process as a single map
and reduce task.
</description>
</property>

把下列的文字,加到 /usr/local/hadoop/conf/hdfs-site.xml 的 <configuration> ... </configuration> 中間:

<!-- In: conf/hdfs-site.xml -->
<property>
<name>dfs.replication</name>
<value>1</value>
<description>Default block replication.
The actual number of replications can be specified when the file is created.
The default is used if replication is not specified in create time.
</description>
</property>

 


如果要對設定檔多了解,可以到以下連結查看:


http://wiki.apache.org/hadoop/GettingStartedWithHadoop


http://hadoop.apache.org/core/docs/current/api/overview-summary.html


格式化 HDFS 的檔案系統


Noll 先生在這非常強調,不要對正在使用中的系統做格式化的動作。該系統資料會消失。

我們這個新系統要啟用,則必須先格式化檔案系統。



hduser@ubuntu:~$ /usr/local/hadoop/bin/hadoop namenode -format


結果如下:


hduser@ubuntu:~$ /usr/local/hadoop/bin/hadoop namenode -format
12/12/24 02:14:23 INFO namenode.NameNode: STARTUP_MSG:
/************************************************************
STARTUP_MSG: Starting NameNode
STARTUP_MSG:   host = ubuntu/127.0.1.1
STARTUP_MSG:   args = [-format]
STARTUP_MSG:   version = 1.0.4
STARTUP_MSG:   build = https://svn.apache.org/repos/asf/hadoop/common/branches/branch-1.0 -r 1393290; compiled by 'hortonfo' on Wed Oct  3 05:13:58 UTC 2012
************************************************************/
12/12/24 02:14:23 INFO util.GSet: VM type       = 32-bit
12/12/24 02:14:23 INFO util.GSet: 2% max memory = 19.33375 MB
12/12/24 02:14:23 INFO util.GSet: capacity      = 2^22 = 4194304 entries
12/12/24 02:14:23 INFO util.GSet: recommended=4194304, actual=4194304
12/12/24 02:14:24 INFO namenode.FSNamesystem: fsOwner=hduser
12/12/24 02:14:24 INFO namenode.FSNamesystem: supergroup=supergroup
12/12/24 02:14:24 INFO namenode.FSNamesystem: isPermissionEnabled=true
12/12/24 02:14:24 INFO namenode.FSNamesystem: dfs.block.invalidate.limit=100
12/12/24 02:14:24 INFO namenode.FSNamesystem: isAccessTokenEnabled=false accessKeyUpdateInterval=0 min(s), accessTokenLifetime=0 min(s)
12/12/24 02:14:24 INFO namenode.NameNode: Caching file names occuring more than 10 times
12/12/24 02:14:25 INFO common.Storage: Image file of size 112 saved in 0 seconds.
12/12/24 02:14:25 INFO common.Storage: Storage directory /app/hadoop/tmp/dfs/name has been successfully formatted.
12/12/24 02:14:25 INFO namenode.NameNode: SHUTDOWN_MSG:
/************************************************************
SHUTDOWN_MSG: Shutting down NameNode at ubuntu/127.0.1.1
************************************************************/
hduser@ubuntu:~$


啟動 cluster


到了要啟動系統的時候,使用指令:

hduser@ubuntu:~$ /usr/local/hadoop/bin/start-all.sh
看到以下的 log 就代表成功了。


hduser@ubuntu:~$ /usr/local/hadoop/bin/start-all.sh
starting namenode, logging to /usr/local/hadoop/libexec/../logs/hadoop-hduser-namenode-ubuntu.out
localhost: starting datanode, logging to /usr/local/hadoop/libexec/../logs/hadoop-hduser-datanode-ubuntu.out
localhost: starting secondarynamenode, logging to /usr/local/hadoop/libexec/../logs/hadoop-hduser-secondarynamenode-ubuntu.out
starting jobtracker, logging to /usr/local/hadoop/libexec/../logs/hadoop-hduser-jobtracker-ubuntu.out
localhost: starting tasktracker, logging to /usr/local/hadoop/libexec/../logs/hadoop-hduser-tasktracker-ubuntu.out
hduser@ubuntu:~$


 

利用 jps 來看是否已啟動


Noll 先生說可以使用 jps 來看啟動是否成功。但新裝的 ubuntu 沒有這東西。



The program 'jps' can be found in the following packages:
* openjdk-6-jdk
* openjdk-7-jdk
Ask your administrator to install one of them


於是,安裝 openjdk-7-jdk



sudo apt-get install openjdk-7-jdk




再試一次



hduser@ubuntu:~$ jps
7516 TaskTracker
7287 SecondaryNameNode
7839 DataNode
12038 Jps
6491 NameNode
6901 JobTracker
hduser@ubuntu:~$

 


也可以用 netstat 來看監聽的 port 是否有開。



hduser@ubuntu:~$ sudo netstat -plten | grep java


停止 single-node cluster

使用這個指令:


hduser@ubuntu:~$ /usr/local/hadoop/bin/stop-all.sh


會看到:



hduser@ubuntu:~$ /usr/local/hadoop/bin/stop-all.sh
stopping jobtracker
localhost: Agent admitted failure to sign using the key.
localhost: stopping tasktracker
stopping namenode
localhost: Agent admitted failure to sign using the key.
localhost: stopping datanode
localhost: Agent admitted failure to sign using the key.
localhost: no secondarynamenode to stop
hduser@ubuntu:~$


原篇照我的方式走,真的太長。因此下一篇來驗證,這個系統真的可以用。

2012年12月20日 星期四

[Android]Market 進不去

送修的 HTC Desire 回來了,要開始安裝 App,於是點了 Market 按鈕,居然發生 404

訊息顯示:

The requested URL /intl/%locale%/mobile/android/market-tos.html was not found on this server. That’s all we know.

嗯。你不知道,我也不知道,然後呢?

花了一、兩個小時爬 google。找到以下連結。

http://forum.xda-developers.com/showthread.php?t=767992

做法很簡單,(1)先把語系切到英文。(2)按一下 Market。讓它跑過去。(3)再切回原來的語系。

好了。要怪誰呢?

2012年12月10日 星期一

[bigdata]小象幫幫忙,Hadoop 能幫什麼忙?

Hadoop 文獻探討

微軟巨量資料策略轉向,全面支援Hadoop

http://www.ithome.com.tw/itadm/article.php?c=77576

微軟表示,全面支援 Apache Hadoop,這消息說明了:

Hadoop 夠熱門,讓微軟無法抵擋。俗話說:「打不動,就加入」,現在微軟就加入了。
這不是第一個微軟採納的開放軟體。jQuery,在2008年加入微軟的 visual studio。
並且 ASP .NET MVC, ASP .NET AJAX 都將採用 jQuery 的技術。(舊消息)

最近熱門關鍵字,從雲端,換成 Big Data,有翻譯成「巨量資料」、有翻譯成「海量資料」,
到底這是什麼東西?為什麼幾乎每個介紹文章都會講到 Hadoop ?為什麼講雲端的時候有的也會提到 Hadoop?說來話長。

何謂Big Data?這問題目前沒辦法正面回答,因為個人認為現在這東西還在成長變化中,
也不是一個標準,所以無法用文字嚴格定義。網路上多數的文章會這麼說:

「因為雲端時代的來臨,資料量增加的速度非常快,所以需要一些新方法來應付這些資料。
巨量資料的特性有 3V,Volume(資料量大),Volecity(產生速度快),Variety(資料多樣性高)
在合理時間及資源下,巨量資料的技術才能夠應付這些需求。」

其實,這還是很模糊的描述。所以,只能知道,巨量資料並不是突然跑出來的東西。但「巨量資料」這個名詞是為了方便討論某些情況下,使用某些技術而產生的新名詞。巨量資料並不是每一個人或每一個企業都會面臨到的,有能力產生上面描述的 3V 的資料,才會有需要使用到巨量資料的技術。

因此,接下來,我們反過來,從被微軟納入解決方案核心的 Hadoop 來看。看 Hadoop 能夠解決什麼問題,至少可以了解,巨量資料大概是什麼東西。

Hadoop 提供的核心功能有二,名字叫 HDFS 以及 MapReduce。HDFS 是 Hadoop Distributed File System 的縮寫。MapReduce 則是 Hadoop 採用分散式計算的技術。也就是,它基本上要解決的問題有兩個,一個是儲存,一個是計算。

在 HDFS 的設計概念是這樣的。在一群電腦裡面,找一台電腦當老大,叫做 Master Node。它必須負責管理所有的電腦(包含自己)的資料的存放狀態、控制讀寫動作。所以裝了一個程式叫 Name Node 來做管理這件事。也裝了一個 Data Node 的程式來放資料。其他的電腦,叫做 Worker Node,裝了一個 Data Node 的程式來放資料。放資料的規則是,先把一個檔案,切成數個小塊(block),每塊為 64 MB,然後, Name Node 會讓檔案塊,分散到各個不同的 Data Node 去,而且,還會讓整個電腦叢集裡,每一個檔案塊,都有三份。若是 Name Node 發現檔案塊遺失或損壞,會開始尋找其他的 Data Node 上的副本來複製,使得整個電腦叢集維持每個檔案塊的數量都有三份。

從以上的設計慨念裡,可以知道:(1)它不怕單一資料存放區的硬體壞掉,壞掉照常工作,並自動回復、(2)它不怕單一檔案的大小超過一個磁碟區的大小、(3)資料存放區的大小,可以動態增加。(註1)

註1:http://www.classcloud.org/cloud/wiki/Hadoop_Lab8

要講 MapReduce 的設計概念,先講解一下,MapReduce 是什麼。在 functional language 裡面可常見到這個字。它其實是兩個英文字連起來的。也就是 Map 及 Reduce。
Map 是一種具有某種行為特色的函數,三言兩語說不清,我舉一個例子來說明。假設我有一個數列A,1~10,我想要得到另一個數列B,它裡面的數字是數列A的每個數字的 2 倍,相當於B = f(A),f(x): y=2x。撇開數學式不看。寫程式會這樣寫(C#):

int[] A = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int[] B = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; //初值化的數列
static int f(int x) { return 2 * x; }

for (int i = 0; i < A.Length; i++)
{
    B[i] = f(A[i]);
}

這程式裡面看到,數列A、數列B,還有函數 f。剩下來的是那個迴圈,它的動作是把每個數列A的數值,經過函數 f 得到的值,放到數列B 對應的位置。只要是可以完成這一類事情的函數,就叫做 Map 函數。這種函數的特色就是可以平行運算。以上述的例子,把 map 函數寫出來,程式再改寫一下會成為:

int[] A = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int[] B = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; //初值化的數列
static int f(int x) { return 2 * x; }

static int[] map(Func<int, int> _func, int[] _a)
{
    int[] _b = new int[_a.Length];
    for (int i = 0; i < _a.Length; i++)
    {
        _b[i] = _func(_a[i]);
    }
    return _b;
}

B = map( f, A)

Reduce 也是一種具備某種行為特色的函數。也是要舉一個例子來說明。假設一個數列A,1~10,要計算從 1 加到 10,程式會寫成:

int f2(int x, int y){ return x + y; }
int[] A = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int C = 0;
for (int i = 0; i < A.Length; i++)
{
    C = f2(C, A[i]);
}

其中,把迴圈部份依然可以改寫成函數,整個程式變成:

int f2(int x, int y){ return x + y; }
static int reduce(Func<int, int, int> _func, int[] _a)
{
    int _c = 0;
    for (int i = 0; i < _a.Length; i++)
    {
        _c = _func(_c, _a[i]);
    }
    return _c;
}

int[] A = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int C = 0;
C = reduce(f2, A);

凡是可以完成這一類工作的函數,都叫做 Reduce 函數。

所以,MapReduce 的技術,是已經在 Hadoop 中準備好可供分散式計算呼叫的 Map,以及 Reduce 的函數。而使用的人,要把計算的函數,寫成可被 Map 或 Reduce 呼叫。如此就可以利用分散式運算來縮短運算時間。例如,最簡單的分散式運算版的 Map,就不用迴圈,改成分給 10 個 Node 各自運算,等全部得到值,放入數列,再回傳數列,依照 Map 的概念來寫程式,就不怕平行化運算時會出問題了。

了解了 MapReduce 的意思之後,就可以看懂介紹 MapReduce 的示意圖了。要用 MapReduce 技術來計算,首先要把程式,寫成可被 Map,Reduce 使用的樣子。在叢集中,Master Node 還是當老大,負責管理所有的 Job,所以安裝一個叫 Job Tracker 的程式,而 Task Tracker 程式,則是裝在所有可以當計算的電腦裡。當運算開始,Master Node 分配 Map 函數要做的 Task 給 Worker Node,分配時會盡量交給資料在附近的 Node,如此會減少資料傳輸時間。Map 函數計算完的結果會存下來。等到的 Map 程式的結果出來,Reduce 程式可緊接著執行,即可將最終結果算出來。

總和來看,Hadoop 核心所提供的兩個功能,解決了檔案存放的問題、解決了系統擴張的問題、解決了系統備援的問題、解決了分散式運算的問題。依此來推測, Hadoop 可以被大家接受成為巨量資料的主流技術,巨量資料的主要需求及適用範圍即是這些東西。但是巨量資料的所有難題,用 Hadoop 就可以解決了嗎?其實並沒有。HDFS 與 MapReduce 只解決了基本的問題,我們可以從Apache 基金會 Hadoop 相關的其他專案的功能來了解 Hadoop 本身做不到的事。

(1) HBase,是專門用在 Hadoop 檔案系統上的資料庫系統。以此推知,Hadoop 沒有資料庫系統。

(2) Hive,是建置在 HDFS 上的分散式資料倉儲系統,可讓使用者以慣用的 SQL 語法,存取檔案中的大型資料集。以此推知,Hadoop 沒有 SQL 語法查詢功能。

(3) Pig,是 Script 語言,可用來撰寫 MapReduce 程式,不懂 Java 也可以使用這個語言開發 MapReduce 程式。以此推知,MapReduce 程式在 Hadoop 系統是用 Java 撰寫,要效能好,就要用 Java 來寫。

(4) ZooKeeper,是監控和協調 Hadoop 分散式運作的集中式服務,提供各個伺服器的配置和運作狀態資訊,用於提供不同 Hadoop 系統角色之間的工作協調。

(5) Mahout,立即可用的 MapReduce 函式庫,常見的數值分析方法,業集分類和篩選方法,都有對應的函數可以呼叫。

這些專案的功能,可以來解決巨量資料的其他需求,而這些需求,不是每個人都需要,所以不在核心功能之中。也可以想見巨量資料的需求,還是因人而異,而且差異很大。

希望這篇有把 Hadoop 能做什麼說清楚,也希望有把巨量資料的其中一個面向說清楚。

2012年11月22日 星期四

[aspx]HttpUtility.UrlDecode 與 Server.UrlDecode 真的不一樣

我以為我經過上次的錯誤,中文亂碼可以不會再出現在我的生活中。

直到我的膝蓋中了一箭。 (這裡)

找了一陣子,原來有人,以前的程式碼用了Server.UrlDecode

HttpUtility.UrlDecode 與 Server.UrlDecode 真的不一樣。

我寫了以下的程式來證明:

Function WriteOutput(ByVal str As String) As String
    Response.AppendHeader("Content-Type", "text/html;charset=UTF-8")
    Dim bs As Byte() = System.Text.Encoding.UTF8.GetBytes(str)
    Response.ContentEncoding = System.Text.Encoding.UTF8
    Response.OutputStream.Write(bs, 0, bs.Length)
    Return "OK"
End Function
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
    WriteOutput("System.Text.Encoding.Default.EncodingName:" + System.Text.Encoding.Default.EncodingName + "<br/>")
    WriteOutput("System.Text.Encoding.Default.WebName:" + System.Text.Encoding.Default.WebName + "<br/>")
    WriteOutput("HttpUtility.UrlDecode:" + HttpUtility.UrlDecode("%e4%b8%ad") + "<br/>")
    WriteOutput("Server.UrlDecode:" + Server.UrlDecode("%e4%b8%ad") + "<br/>")
End Sub

結果這就是不一樣:

image

附帶說明一下,這個現象不一定會在你的伺服器出現。在我的開發環境,如下。

image

我現在還找不到確切指出設定不同的地方。

我只看到兩個 EncodingName 所出現的字串是不同的。

據網路消息,說 Server.UrlDecode 是用系統編碼。可是我還沒辦法找到如何顯示現在系統編碼。

2012年11月20日 星期二

[aspx]不會用設定,不要怪微軟!(原題:好心的微軟幫了倒忙。中文亂碼解決實錄)

更新:要看最後解法,請直接跳最下面

有史以來解決中文亂碼最長的一次。從我2000年開始,踩進可怕的中文混沌中,
從沒有這麼令人生氣的一次了。

原因就是微軟幫倒忙!微軟好心幫忙但是我還是得告訴它我的 charset。

「絕聖棄智,民利百倍。絕仁棄義,民復孝慈。絕巧棄利,盜賊無有。」--老子

故事是這樣開始的。要求是:
「我需要用 post 方法,傳送一筆記錄的資料,而網頁伺服器接到之後,幫忙下 SQL 插入資料。」

你看多簡單的一句話!結果發生亂碼,沒關係,那我就寫個測試的程式,看是傳過去的字就亂了,還是 SQL 執行完才錯。

好,簡化過的發送端程式如下:

Function SendPutRequest(ByVal keyname As String, ByVal keyvalue As String)
    Dim uristr As String = "http://localhost:1490/WebSite1/poid_fail.aspx"
    Dim req As System.Net.HttpWebRequest = System.Net.HttpWebRequest.Create(uristr)
    req.Method = "POST"

    Dim param As String = "a=put"
    param += "&" & "key" & "=" & keyname
    param += "&" & "value" & "=" & keyvalue
    Dim bs As Byte() = Encoding.UTF8.GetBytes(param)
    req.ContentType = "application/x-www-form-urlencoded"
    req.ContentLength = bs.Length

    Using reqStream As System.IO.Stream = req.GetRequestStream()
        reqStream.Write(bs, 0, bs.Length)
    End Using
    Using wr As System.Net.WebResponse = req.GetResponse()
        Dim rs(wr.ContentLength) As Byte
        Dim expectcount As Integer = wr.ContentLength
        Dim readcount As Integer = 0
        Response.ContentEncoding = System.Text.Encoding.UTF8
        readcount += wr.GetResponseStream().Read(rs, 0, wr.ContentLength)
        Response.Write(Encoding.UTF8.GetChars(rs))
    End Using
    Return "OK"
End Function

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
    SendPutRequest("DataName", "中文測試")
End Sub

發送端,就只是一個假裝用 form 打出資料的 request。其中也很簡單的一個 keyname 跟 keyvalue,傳送至伺服端

而,接收端程式(簡化版)很快就寫好了:

<%@ Page Language="VB" %>

<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.OleDb" %>

<script runat="server">
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
        Dim act As String
        Dim insertSql As String = ""
        Try
            act = Request.Form("a")
            If act = "put" Then
                Dim keyname As String
                keyname = Request.Form("key")
                Dim keyvalue As String
                keyvalue = Request.Form("value")
                insertSql = "insert into [poidata] ($columns$) values ($values$)"
                insertSql = insertSql.Replace("$columns$", keyname).Replace("$values$", keyvalue)
                ExecuteSql(insertSql)
            End If
        Catch ex As Exception
            insertSql = ex.Message
        End Try
        Dim contentbody As String = insertSql
        Response.AppendHeader("Content-Type", "text/json")
        Response.ContentEncoding = System.Text.Encoding.UTF8
        Response.OutputStream.Write(Encoding.UTF8.GetBytes(contentbody), 0, System.Text.Encoding.UTF8.GetByteCount(contentbody))
    End Sub
</script>

在開發環境很正常。發送端與接收端都是在同一個電腦上,沒有問題。

image 

把接收端程式丟到真的伺服器去,就變亂碼!:

image

好。很正常的,因為值有中文,沒有 encode 所以變亂碼,我可以接受。
而我還特別把接收端的程式的 response,以 Encoding.UTF8.GetBytes 轉成 byte,然後用 write byte 方式,這是最保險,照字串byte回傳回來的保險方式。

那我 encode 吧。

把傳送端 encode。

param += "&" & "key" & "=" & HttpUtility.UrlEncode(keyname)

param += "&" & "value" & "=" & HttpUtility.UrlEncode(keyvalue)

結果長一模一樣。奇怪了。

image

那我接收端 decode 看看,結果一樣。

那來檢查兩邊的 default encoding 好了。
把接收端的 default encoding 回傳回來看看。

Dim contentbody As String = insertSql + "<br />" + System.Text.Encoding.Default.EncodingName

在開發環境,看起來是這樣

image

把程式丟到真的伺服器上,嗯,都是 big5 啊!

image

啊,等等,不對勁耶,為什麼真的伺服器不是寫「繁體中文 (Big5)」傳回來,而是用「Chinese Traditional (Big5)」這是搞什麼鬼?

後來,同事看我搞很久,決定傳授我妙方。
原本,在 IIS 的設定,我是使用虛擬目錄的方式,指到網站程式所放的目錄。
而他的經驗是,在 IIS 上,指定用「網路應用程式」
他也幫我弄好了。只是,網路位址會移動,而我,只是改別人的程式,不太想去改位址這種事。
如果,只想研究到這裡的人,就去改成「網路應用程式」試試吧。

因為沒辦法、因為不死心,我決定來試試強迫接到 encode 過的字串為目標,
想確定傳過去的 byte 沒有變動。
所以,接收端先不 decode,就接到什麼回寫什麼看看。
再來,因為傳送端我 encode 一次不夠,我再多 encode 一次看看。

param += "&" & "value" & "=" & HttpUtility.UrlEncode(HttpUtility.UrlEncode(keyvalue))

很賭氣的作法。UrlEncode 這種 encode 法,作兩次 encode,要得回原來的字,就要 decode 兩次。只要我拿到不是亂碼的字串,算清楚encode次數,就知道是不是 byte 被動到了。

image

很好,這是好的開始,這看來像是 encode 一次的字串。
那我接收端,就 decode 一次試試。

keyvalue = HttpUtility.UrlDecode(Request.Form("value"))

image

耶,看來好了。

而實際把組好的 SQL 字串執行下去,資料庫裡,也就正常的中文了。

回頭討論,我的傳送端 encode 兩次,我的接收端 decode 一次。
這代表,有某個地方幫我 decode 一次。而且在我的程式執行之前。
按照我的推斷,那一次的 decode 的時候,參考不知道哪裡的編碼,在那裡字串亂掉了。
明明就看不懂中文,還硬要轉,當然就搞亂了。
這從 default encoding name 可以看得出來,雖然都是 Big5,但是一個用中文表示,一個用英文表示,明白的指出,兩個的執行環境是不同的,而這個執行環境的不同,就會導致亂碼。
而我,用兩次 encode,讓我的字串被自動 decode (也就是第一次,不是我的程式做的) 之後,成為 encode 一次的狀態,這時是 ascii 的字串表示式,用這個字串,讓我的程式來 decode 回 .net 裡面的字串。由於資訊保持完整,所以可以完美地解回 .net 字串。問題才得以解決。
確切幫忙 decode 一次的程式我沒有找到,不過應該不脫離 iis,asp .net 這個範圍。
所以這次幫倒忙就算在微軟的頭上!

我想也許會有高手更了解 IIS 或 asp .net 的可以用更漂亮的方式解決這問題吧?
我的確是不了解所以硬幹啊。

更新:

因為無可避免地會被 decode 一次,所以指定正確 charset 一定是正解。指定的方式即在 request 的 Content-Type 裡。如下:

req.ContentType = "application/x-www-form-urlencoded;charset=UTF-8"

接收端程式,不用需 decode,微軟會按指定編碼幫你搞定。

它就是要幫你解,你只好一定要告訴他怎麼解。

2012年10月29日 星期一

[asp]使用VS2008,怎麼對 asp debug

如果使用VS2008,若直接對 asp 按下 debug,會出現

image

經過搜尋,解決方法是,

(1)設定iis,使得該頁可以直接使用。

image

(2)打開VS2008,開啟網站(檔案->開啟->網站),選擇網站所在的目錄

image

(3)附加至處理序(偵錯->附加至處理序),選擇 w3wp.exe (適用於 iis 6)

image

按下附加之後,VS2008,就會進入偵錯模式。

(4)瀏覽器跑一下 asp 網頁,方案總管會出現指令碼文件。

image

(5)雙擊 asp 文件,打開文件後,即可設中斷點,開始正式 debug。

image

 

結尾:

美中不足,不能邊改邊debug,你可以看到這文件是唯讀的。

[asp] 最近又用到 asp,幾個筆記需要記

判斷 access 資料庫某個欄位值為 null

我以為用 if cursor.fields(i).value = null then…可以判斷。我錯了。

要用 if isnull(cursor.fields(i).value) then … 才行。

 

到底權限怎麼開才能寫入?

首先,我的 os 是 windows 7,iis是 7 (應該吧…)

如果是 asp .net 的程式,IIS_USRS 這個使用者要開啟權限到寫入。

如果是 asp 的程式,IUSR 這個使用者要開啟權限到寫入。

 

如何存取utf-8 編碼的文字檔內容?

asp 直接 openTextFile 是不對的。它會按執行環境的預設值讀寫檔案。

我有改過CODEPAGE="65001",無效。

目前使用 ADODB.Stream 可。

有人提供小函數使用。

Sub Save2File (sText, sFile)
    Dim oStream
    Set oStream = CreateObject("ADODB.Stream")
    With oStream
        .Open
        .CharSet = "utf-8"
        .WriteText sText
        .SaveToFile sFile, 2
    End With
    Set oStream = Nothing
End Sub

Function ReadUtf8File(sFile)

    Set objStream = CreateObject("ADODB.Stream")
    With objStream
        .Charset = "utf-8"
        .Type=2
        .mode=3
        .Open
        .loadfromfile sFile
        ReadUtf8File=.readtext
        .Close
    End With
    Set objStream = Nothing

End Function

使用 javascript 的 escape 的文字,asp 怎麼解?

我基本上放棄了。試了幾次都不成功。這個問題很大,需要前後程式配合(html, javascript, asp)。因此會另外再寫詳細的文章。

簡單來說,我後來轉用 htmlencode。那 javascript 怎麼做 htmlencode 或 htmldecode?

以下有兩個小函數可以使用

function htmldecode(strHtmlEncode){
    var div =  document.createElement('div');
    div.innerHTML = strHtmlEncode;
    return div.innerText
}

function htmlencode(strHTML)
{
    var div = document.createElement('div');
    div.innerText = strHTML;
    return div.innerHTML;
}

asp 的程式,直接取值就好,不用解。

2012年10月23日 星期二

[html]用 javascript 查詢在網頁裡有多少定義過的 style?

原來,在 document.styleSheets 裡,存放了我們定義過的 style (class)。
也就是在 <style type="text/css"> 或 <link rel="stylesheet" type="text/css" href="src.css"> 這兩類地方寫的 style。

使用 javascript 可以取出內容。

var count = document.styleSheets.length;
    for (var y=0; y < count; y++)
    {
        var classes = document.styleSheets[y].rules || document.styleSheets[y].cssRules
        for(var x=0;x<classes.length;x++) {
            console.log("sheet"+y.toString()+" rule"+x.toString()+":"+classes[x].selectorText+":"+((classes[x].cssText) ? (classes[x].cssText) : (classes[x].style.cssText)));
        }
    }

2012年9月18日 星期二

[plone]plone.app.theming 1.0 升到 1.1a2

據說,plone 4 開始,佈景主題 (theme) 可以使用 diazo 引擎來更換管理佈景主題。要達成這個功能,plone 要安裝 plone.app.theming 模組。令人混亂的是,在 pypi 看到的是 plone.app.theming,在自己安裝好的 plone site 的 add-on 的管理畫面,是看到 Diazo theme support。光是名稱就把我搞掉一天了。

最近的 plone 到4.2.1,只有 *nix 跟 Mac OS 安裝檔。目前 (2012/09/18),windows 的安裝檔是到 4.2,plone.app.theming 是 1.0,它只有選擇已安裝的佈景主題,或手動指定 rules.xml。對於我來說應該夠了,只是看到 pypi 上 1.1 的描述,很想要試,不知該怎辦,我也找了很久…。看著下載下來的 plone.app.theming-1.1a2.zip 不知從何下手。

後來在那個 zip 檔裡,看到 buildout.cfg 裡有一段:

[versions]
plone.app.theming =

那我就來試試吧。

先把 plone 給關了。

image

然後,編輯 c:\plone42\buildout.cfg,加入 plone.app.theming = 1.1a2 在 [versions] 底下。

[versions]
distribute = 0.6.27
plone.app.theming = 1.1a2

然後,就在命令列下, c:\plone42\ 下指令

C:\Plone42>bin\buildout.exe

結果就出現以下的訊息:

C:\Plone42>bin\buildout.exe
Getting distribution for 'plone.app.theming==1.1a2'.
warning: no previously-included files matching '*pyc' found anywhere in distribu
tion
Got plone.app.theming 1.1a2.
Getting distribution for 'plone.resourceeditor'.
Got plone.resourceeditor 1.0b1.
Getting distribution for 'roman==1.4.0'.
Got roman 1.4.0.
Updating zeo.
Created directory C:\Plone42\parts\zeo
Created directory C:\Plone42\parts\zeo\etc
Created directory C:\Plone42\parts\zeo\var
Created directory C:\Plone42\parts\zeo\log
Created directory C:\Plone42\parts\zeo\bin
Wrote file C:\Plone42\parts\zeo\etc\zeo.conf
Wrote file C:\Plone42\parts\zeo\bin\zeoctl
Changed mode for C:\Plone42\parts\zeo\bin\zeoctl to 777
Wrote file C:\Plone42\parts\zeo\bin\runzeo
Changed mode for C:\Plone42\parts\zeo\bin\runzeo to 777
Generated script 'C:\\Plone42\\bin\\zeo'.
Generated script 'C:\\Plone42\\bin\\zeopack'.
Generated script 'C:\\Plone42\\bin\\repozo'.
Changed mode for C:\Plone42\bin\zeo_runzeo.bat to 755
Changed mode for C:\Plone42\bin\zeo_service.py to 755
Generated script 'C:\\Plone42\\bin\\zeo_service'.
Updating instance.
Generated script 'C:\\Plone42\\bin\\instance'.
Updating run-instance.
Generated script 'C:\\Plone42\\bin\\run-instance'.
Updating run-zeo.
Generated script 'C:\\Plone42\\bin\\run-zeo'.
Updating service.
Updating service-zeo.
*************** PICKED VERSIONS ****************
[versions]
enfold.recipe.winservice = 0.7.2
pillow = 1.7.7
plone.resourceeditor = 1.0b1

*************** /PICKED VERSIONS ***************

 

再啟動 plone。

就看到新的 diazo theme 的控制面版,如下圖。

image

舊的 diazo theme 控制面板,如下圖。

image

剩下的,還是我該怎麼編 theme.html, rules.xml, theme.css 的問題…。

2012年9月6日 星期四

[python] os.rename 失敗?

在 mac 上,想要跨硬碟搬東西,結果 os.rename 就是出現

OSError: [Errno 18] Cross-device link。

試找了一下,用 shutil.move 就可以了。

2012年8月21日 星期二

[python]GUI 開發,使用 tkinter 與 wxpython 的感想

本來首選是 tkinter,他是 python 官方附帶的模組。但我有兩個問題沒有辦法克服。

第一個是文件不好找。python docs 有一篇大約講一下 tk 文件跟 tkinter 對應的規則。這也不能怪人,相同的問題也出現在 jython 上。只是 java 風格的文件比較容易看懂,jython 又比較多人用,文件也比較多。

第二個是,在mac上,有方塊字的問題。連 IDLE 的 python shell 都一樣,應該是無解。也許這就是為什麼 google app engine 在 windows 有提供 GUI 程式,但 mac 沒有的原因吧…。

後來轉而使用 wxpython。文件比較好找[註1]。容易看懂的原因是裡面就是講 python 話。在 mac 使用上也沒有方塊字的問題。只是要多裝 wxpython,安裝檔就 50多 MB。

wxpython 有一個 html 的模組[註2],可惜不能 javascript 互動。如果可以的話,擺一個 html window,剩下就是 html 與 javascript 的事,可以少學很多事情哩。

 

註1:http://wxpython.org/docs/api/wx-module.html

註2:http://docs.wxwidgets.org/2.8/wx_wxhtml.html, http://www.indicthreads.com/1106/incorporating-html-into-wxpython-part-1/, http://wiki.wxpython.org/wxHTML

2012年5月30日 星期三

[python] json 與中文

現場狀況是這樣的,在 json 編碼中,文字部份一定要 utf-8,大家都很清楚了,如果想搞別的編碼,就自己 escape,這種作法算偷吃步。在 python 的 json 模組裡面,算是很貼心(還是很雞婆?),預設就是幫大家把文字都 escape 了。當然使用 ascii 的人都不會有感覺(我想大家也很無奈)。

在 json.dump 裡面,有預設一個 ensure_ascii=True,就是強迫文字 escape 的選項。如果沒去動他的話,以下是出現的結果:

"\u53f0\u6ce5"

如果你確定你的文字是 utf-8,可設定成 ensure_ascii=False,像是:

id_name[stockid].decode('cp950').encode('utf8')

那結果就會是:

"台泥"

這樣就很清楚了。

只是,我光看 escape 完的字串,感覺像是 unicode 編碼而不是 utf-8 編碼耶…。是 json 故意又轉成 unicode 再 escape 的嗎?

2012年5月27日 星期日

[html5]在伺服器端怎麼支援 video 這個標籤

今天遇到的是,如果網頁中有個 video 標籤,伺服器該怎麼辦?一般人也許不會想到這個問題,而我卻遇到了。原因是我自己有寫一個小小 http server 的程式,只是為了要接收幾個按鈕,查詢並執行 ffmpeg 程式幫忙轉檔。小事情就自己動手了。所以,這種事情當然就要自己來處理囉。

從來自 chrome 的 request 之 headers 裡,有看到一個 range: 0- 這個,經過查詢(我當然還是查給人看的,而不是給超人看的文章),這個是通知伺服器說,從檔案的位置 0 開始給,結束位置隨便。感覺很隨便?其實是為了彈性。如此,伺服器該怎麼回應呢?

在 response 裡,要回覆的有

  1. Accept-Ranges: bytes
  2. Content-Range: xxx-ooo/sss

第一個是告訴瀏覽器說,我接受,而且是用 byte 當計算。 (有人說的,官方文件我真的看不懂,太像是給編譯器看的。) 而第二個就是告訴瀏覽器,從位置 xxx 開始(位置是從 0 開始算),到第 ooo 結束。整個檔案的大小是 sss。

舉例來說,一個 256 bytes 的小檔案,一次全部傳送完,Content-Range,就是 0-255/256。如果像大影片檔,就最好是一點一點的傳送,而就沒辦法一次全傳送了。

另外一個重點是回應碼,不是 200,而是 206 (參考),這樣瀏覽器才會再送一次 request 來要求其他的部份。像我提供 mp4 檔案的話,瀏覽器很聰明的先要了前面的部份之後,接下來要求最後的部份,據說是要影片的描述。有支援這種 partial content 的伺服器,大檔案傳送時應該很好用。

除了支援 range 之外,只要把 Content-Type 換成 video/mp4,chrome 就可以看影片。可以拉著時間軸跳到任意時間觀看。

程式的部份長這樣(我是用 python 寫):

self.send_response( 206 )
self.send_header( "Accept-Ranges", 'bytes' )
self.send_header( "Content-Type", 'video/mp4' )
self.send_header( "Content-Range", 'bytes %s-%s/%s' % (offset, end, size) )
self.log_message( 'bytes %s-%s/%s' % (offset, end, size))
self.end_headers()
f = open(src,'rb')
f.seek(offset, os.SEEK_SET )
self.wfile.write( f.read(block) )
f.close()
self.wfile.close()

2012年5月25日 星期五

[html5][gae]Server-Side Event

在 html5 的規格中,有看到一個 Server-Side Event (縮寫 SSE),這是什麼東西呢?是不是我最愛的伺器器主動通知呢?我好奇看了一下 w3schools 的說明,著實令我心動啊。

A server-sent event is when a web page automatically gets updates from a server.

This was also possible before, but the web page would have to ask if any updates were available. With server-sent events, the updates come automatically.

Examples: Facebook/Twitter updates, stock price updates, news feeds, sport results, etc.

這麼多大公司都用了,那我就可以放心的用吧。我們先來看怎麼用,再來講講我的感想吧。

因為我的環境是 Google App Engine,那麼我們就來改寫 w3schools 的範例吧。原來的網頁的程式碼,長這樣:

<!DOCTYPE html>
<html>
<body>
<h1>Getting server updates</h1>
<div id="result"></div>

<script>
if(typeof(EventSource)!=="undefined")
  {
  var source=new EventSource("demo_sse.php");
  source.onmessage=function(event)
    {
    document.getElementById("result").innerHTML+=event.data + "<br />";
    };
  }
else
  {
  document.getElementById("result").innerHTML="Sorry, your browser does not support server-sent events...";
  }
</script>

</body>
</html>

我們只要把 var source=new EventSource("demo_sse.php") 換成 var source=new EventSource("demo_sse")。整個網頁的 javascript 的精神就是,有 onmessage 事件發生的時候,就把事件裡的 data 值取出來,加到 id 為 result 的元素的內容去。所以改完變成:

<!DOCTYPE html>
<html>
<body>
<h1>Getting server updates</h1>
<div id="result"></div>

<script>
if(typeof(EventSource)!=="undefined")
  {
  var source=new EventSource("time");
  source.onmessage=function(event)
    {
    document.getElementById("result").innerHTML+=event.data + "<br />";
    };
  }
else
  {
  document.getElementById("result").innerHTML="Sorry, your browser does not support server-sent events...";
  }
</script>

</body>
</html>

我把上面的內容存成 main.html,放在跟 main.py 同一個目錄下。然後,main.py 的內容修改一下,在 MainHandler 的 get 方法,就改成直讀 main.html 在寫出到 out。

class MainHandler(webapp.RequestHandler):
    def get(self):
        path = os.path.join(os.path.dirname(__file__), 'main.html')
        render_content = codecs.open(path, 'r', encoding='utf8').read()
        self.response.out.write(render_content)

因為網頁會送出 /time 的要求,所以要處理他,必須要接受他。所以在 main() 要修改多一個處理

def main():
    application = webapp.WSGIApplication([('/', MainHandler),
                                          ('/time', TimeHandler),
                                          ],
                                         debug=True)

接下來寫一個 TimeHandler 接收來自網頁的要求,

class TimeHandler(webapp.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/event-stream'
        self.response.headers['Cache-Control'] = 'no-cache'
        p = "data: " + str(datetime.datetime.now()) + "\n\n"
        self.response.out.write(p)
    def post(self):
        pass

好,這樣就行了。試跑看看是不是有新的時間一直新增呢?

image

接下來是感想時間。

這個 Server-Side Event,我原本以為需要很新的 http Server 程式,例如新的 apache 或新的 IIS 或等等的。其實不用。看我的 TimeHandler 就知道,只要你會改 response 的 Content-Type 及 Cache-Control 的內容,就可以使用這個功能。一點也不難。要用得很難也行,進階版的用法請看 html5rocks

再來,他真的是 Server 主動通知嗎?,讓我們來看看網路活動吧,打開 chrome 的檢查元素,點網路。

image

會發現到,每隔幾秒就會有網路活動,從瀏覽器送出來。有寫過的人就會想說,這我自己用 interval 或 timer 寫不就一樣?對,我也覺得一樣。既然一樣,那用哪個都好,html5 提供了這個功能,讓大家不用傷腦筋去管理 timer,也是有方便之處。也同時提醒一下不清楚的人,不要以為這就是伺服器主動通知的事件,這實際上還是 client 端 polling 的方法。

最後啊,我要提醒兩下,

(1)這個 text/event-stream 一律使用 utf-8 編碼,跟 json 一模一樣,所以中文的朋友一定要先轉好。

(2)看文件真的不是一件容易的事,尤其規格的文件這麼大一份,參考來參考去的。越是低階的規格就越大本,我從來不知道在哪裡可以找到正式文件說,遇到兩個換行,就代表傳送結束,所以,如果你真要結束,就一定要連續傳兩個換行。如果你不想要結束,傳送內容前,一定要檢查有沒有兩個換行。當然,有些 http server 也許會幫你加兩個換行在後面。如果出問題,請記得你的內容,有沒有兩個換行在你不想要的地方。

2012年5月5日 星期六

[ffmpeg]轉檔 mp4 (x264 + aac)

有鑑於目前,公認最流行的影片格式是 mp4 ( h.264 + aac )(需要證據),所以我也使用這個格式來儲存新的影片。

mp4 其實算是一種影音容器,在影片部份的編碼可以使用 MPEG 4 或 MPEG AVC (也就是所謂的 h.264,由VideoLan組織寫好的編碼器叫 x264,因此我的標題是寫 x264),在聲音部份的編碼使用 aac。

以 Canon G11 錄下來的影音為 MOV 容器,影像就是 h.264,然聲音部份是 PCM,所以檔案大很多。

在轉檔的部份,vlc 在目前的版本 2.0.1 還沒好很好的轉換。而 ffmpeg 使用了 x264 編碼器及它內建的實驗的 aac 編碼器,我試用過還不錯。所以我用的 ffmpeg 的參數如下:

ffmpeg.exe -y -i 來源檔 -filter:v yadif -acodec aac -ar 44100 -ab 128k -ac 2 -vcodec libx264 -preset slow -crf 20 -async 512 -strict -2 -threads 1 輸出檔

參數說明:

-y 是指覆蓋舊檔,如果有的話
-filter:v yadif 效果如同之前的 deinterlace,沒加的話,我的經驗是顏色會怪怪的
-acodec aac -ar 44100 -ab 128k -ac 2 是說聲音部份用 aac 編碼,採樣 44100 赫茲,聲音頻寬128k,2 聲道
-vcodec libx264 -preset slow -crf 20 是指影像部份用 x264 編碼,使用慢速組態(我想要好一點的畫質),-crf 20 是想要品質固定為優先。
-async 512 是說影音部份的同步,每 512 個聲音採樣就對齊一次
-strict –2 有這個才能使用 aac 編碼,因為目前還在實驗中才會需要這個參數
-threads 1 指定只用一個 thread,這樣在雙核電腦、作業系統為 win 時,只用 25% 的 cpu。因為我的電腦容易熱當,所以用這方式降速,這參數放在 –i 的前面反而無作用。如果沒熱當問題,拿這此參數或指定高一點的數字可以加速編碼時間。

在我喜歡用 python 來幫我輸入參數,因此使用以下的程式來幫忙:

def transcode(inputfile, inputfolder, outfolder):
    exepath = os.path.abspath(u"D:/bin/ffmpeg-20120426-git-a4b58fd-win64-shared/bin/ffmpeg.exe")
    inputfilepath = os.path.join(unicode(inputfolder),unicode(inputfile))
    outputfilepath = os.path.join(unicode(outputfolder),'%s.m4v' % unicode(inputfile))
    cmd = u"%s -y -i \"%s\" -filter:v yadif -acodec aac -ar 44100 -ab 128k -ac 2 -vcodec libx264 -preset slow -crf 20 -async 512 -strict -2 -threads 1 \"%s\"" % (exepath,inputfilepath,outputfilepath)
    os.system(cmd.encode('cp950'))

for i in os.listdir(mov_folder):
    transcode( i, mov_folder, mov_folder)

這樣就很方便了。

想要用圖形介面的人,也可以使用 Handbrake,http://handbrake.fr/
其實就是用 ffmpeg 來轉碼,但我不用是因為Handbrake 就是無法強迫降速轉碼,導致熱當。
我猜只是它的 –thread 參數放在前面導致的。因為我下指令時放在前面就跟沒放一樣。
因此,電腦不熱當,而且轉檔參數常換或喜歡用 gui 來操作的人,就用 Handbrake 吧

2012年3月12日 星期一

[xml]遇到escape也無用,才知道 CDATA 的好

我以前非常的直觀,覺得只要有 escape 特殊符號,就可以解決任何事,
(http://codebeta.blogspot.com/2010/09/xmlescape.html)
直到我遇到一個情況:
在 WebService (http + soap) 裡,內容是xml,裡面又一層 xml。
當微軟的元件(我只用過微軟的開發工具,但我想其他的工具也許也一樣),
把 soap 內容(第一層的 xml)轉回來的時候,也裡面那層的也一起轉換了。
所以內容的 DOM 結構跟預期的不同。

<a><b></b><c>&lt;c1&gt;abcd&lt;/c1&gt;</c><d></d></a>  <!--本來的樣子 c 的內容是個 c1 element 已跳脫—>

<a><b></b><c><c1>abcd</c1></c><d></d></a>  <!--實際的樣子 導致取 c 內容(innerText)不如預期的 &lt;c1&gt;abcd&lt;/c1&gt; 影響後續程式動作-->

這時,應該就是使用 CDATA 來標示內容為文字,避免錯誤。
像這樣:

<a><b></b><c><![CDATA[<c1>abcd</c1>]]></c><d></d></a>

2012年3月7日 星期三

[mongodb]mongod.exe run as service (像服務一樣執行 mongod.exe)

mongodb 最簡單的執行方法,就是直接執行 mongod.exe (在 windows)
如此一來就會在 27017 這個 tcp port 接聽 client 的要求。

若有需求是如同服務一樣,一開機就等著接聽,當然有種方式是放個捷徑在啟動。
但一般,我們還是希望能跟 windows 的服務一樣。
mongod.exe 自己就有辦法做到安裝成服務。

最少最少的指令是

mongod --logpath d:\mongo\logs\logfilename.log 
 --dbpath d:\mongo\data --install

--logpath, --dbpath 都很直白,就不解釋。
--install 就是安裝成服務的意思。與服務有關參數還有:
--remove 移除服務。
--reinstall 重新安裝成服務。


其他較進階的指令,就請看參考
http://www.mongodb.org/display/DOCS/Windows+Service


真的是很貼心啊。

2012年3月3日 星期六

[程式]Object-C

http://www.ithome.com.tw/itadm/article.php?c=71529

因為 Apple 最近的成功,讓這個語言的使用者多了起來。

2012年2月20日 星期一

[python]map 是什麼?

這是一個說明文。

在 python 的內建函式裡,有一個 map 的函式。
map 不是地圖,若是照執行的意義來解釋,就是映射,白話一點就是一對一對應。

怎說?來看一下官方的說明:

map(function, iterable, ...)

Apply function to every item of iterable and return a list of the results. If additional iterable arguments are passed, function must take that many arguments and is applied to the items from all iterables in parallel. If one iterable is shorter than another it is assumed to be extended with None items. If function is None, the identity function is assumed; if there are multiple arguments, map() returns a list consisting of tuples containing the corresponding items from all iterables (a kind of transpose operation). The iterable arguments may be a sequence or any iterable object; the result is always a list.

http://docs.python.org/library/functions.html#map

第一句就是定義,「執行 function,把每一個在 iterable 裡的元素當做參數,把每一個結果裝在一個 list 裡,然後回傳。可以假想,map 是長這樣的一個函數(假設 function 只需一個參數):

def map( function_point, iterable):
    result = []
    for i in iterable:
        r = function_point( i )
        result.append( r )
    return result

這種運算常發生嗎?你仔細想想,每當你的任何一段程式是長成這樣(用 c 來舉例):

for ( int i = 0; i < 100; i++)
{
    do_function( i );
}

就可以改寫成:

map( do_function, [0,1,2,…99])

do_function 是本來就要準備的,而 map 是 python 幫你準備好的(其他語言若沒有,則可以自己寫)。帶來的好處有什麼?

  1. 寫法標準化
  2. 縮短程式碼

除此之外,google 告訴你,

可以分散運算!

只要 do_function 執行時,只要傳入引數有關,map 是可以平行運算的。也就是 map 不一定非得像上面所寫從 0 跑到 99,只要你有能力改進 map 這個程式,分散到各台電腦上跑,執行完的結果都會一樣。(http://research.google.com/archive/mapreduce.html)
在 python 裡就有一個現成的多process 的 map 可用。 (multiprocessing.Pool 的 map。http://docs.python.org/library/multiprocessing.html?highlight=multiprocess#multiprocessing.pool.multiprocessing.Pool.map)

Google 還告訴你,他們的系統就是多採用 MapReduce 的 programming model 才能應付如此大量的資料,分散到各電腦上運算又保證結果正確。MapReduce 算不上是 framework,而叫 programming model。如果把 framework 叫做戰略,那 map/reduce 就叫戰術。因為小到每一個程式的每個段落都可能會出現可數迴圈,只要是可數迴圈,不論是否需要結果都可使用這種寫程式的風格。而這種風格在 functional programming 非常常見。大家可以先不知道 functional programming,但 map 好用就應該要常用。

雖然說,python 是 functional language 所以用 map 很自然。
但是其他語言也不是辦不到。
c/c++ 語言,就用 function pointer 來指定 function。
c#, vb .Net 可以用 delegate 來處理。
偷懶一點,參數用 object,嚴格一點,用 template 指定 type。

試試,loop 換成 map 吧!

關於更詳細的 map / reduce 說明及範例,請參閱以下連結
http://code.google.com/edu/parallel/mapreduce-tutorial.html

[python]multiprocessing pool map

最近在微調程式的效能,第一個我想到的,就是從多執行緒來下手。

google 說得好,如果程式裡有用到 map,那麼就可以從 map 開始改。

python 有提供 multiprocessing pool 就不用自己寫 pool。

不過,有幾點要注意:

  1. 這個是 process pool,所以比較吃資源。
  2. 在執行的時候,由於你寫的 py 會被當成 module 讀進執行。
    所以,一定要判斷自身是否為 __main__。也就是要:

if __name__ == '__main__':
    # do something

若第 2 點沒注意到,就會不斷地重覆遞迴執行到當掉。

給個失敗的例子:

import multiprocessing
def m1(x):
  return x*x
pool = multiprocessing.Pool(processes=3)
pool.map(m1,[‘1’,'2’,'3’,'4’])

給個成功的例子:

import multiprocessing
def m1(x):
  return x*x
if __name__ == '__main__':
  pool = multiprocessing.Pool(processes=3)
  pool.map(m1,[‘1’,'2’,'3’,'4’])

如果本來程式就有按 map / reduce 的方式寫,那換成 multiprocess pool 是很快樂的一件事。
試試吧。

2012年2月17日 星期五

[python]logging 有支援 multi-thread

以往,寫 log 都是很直覺,自己開個檔案就寫下去了。

f = open(filename,mode)
f.write(str(datetime.datetime.now())+ " " + string_to_write + "\n")
f.close()

然而,寫久了的確很煩,於是,進一步的,會寫成一個 module 來簡化。

def log(filename,mode,string_to_write):
  try:
    f = open(filename,mode)
    f.write(str(datetime.datetime.now())+ " " + string_to_write + "\n")
    f.close()
  except:
    if f != None:
      f.close()

但是,最近遇到,我的程式開始有 multi-thread,這樣子開檔寫,
有時會漏 log,有時會干擾,像這樣

2012-02-17 23:47:45.211000 DEBUG  doing ('e02_v2.py',)
2012-02-17 23:47:45.211000 DEBUG  doing ('e02_v2.py',)
',)
2012-02-17 23:47:45.211000 DEBUG  doing ('c17_json_v3.py',)
2012-02-17 23:47:52.215000 DEBUG  done ('e02_v2.py',)

這當然不能忍受,但是,要馬上處理這種問題又不會。
還好,python 內建的 logging 是 thread-safe 就可以應付 multi-thread。(還沒辦法處理 multi-process 寫檔案)

logging 最基本的使用方法:

logging.basicConfig(level=logging.DEBUG,
                  format='%(asctime)s %(levelname)s %(message)s',
                  filename=filename,
                  filemode=mode)
logging.debug(log_string)

第 1 行是設定 logging 的 level、每個記錄的格式、檔名、及開檔模式。
第 5 行就是寫 log。

level 預設是 NOTSET, DEBUG, INFO, WARNING, ERROR 及 CRITICAL 等 6 種。
最後面的等級越高。
舉例來說明,設定的等級為 INFO,則 logging.debug 是不會寫入 log 裡,
要 logging.info, logging.warn, logging.error, logging.critical 才會寫入 log 裡。

記錄的格式,可以使用的變數,這裡就不寫,請參考 python 線上手冊。
這裡要提的是,若我們要的,原來沒提供怎辦?
例如,%(asctime)s 寫出來的是:

2012-02-17 23:52:02,157 DEBUG mylog  doing ('e02_v2.py',)

可是我想要這樣,但 time.strftime 不支援 %f

2012-02-17 23:47:52.215000 DEBUG  done ('e02_v2.py',)

這時候可以這樣改,在format 裡加一個變數名,例如 %(curdatetime)s
然後準備一個 d = {'curdatetime' : datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')}
在寫 log 的時候,帶在 extra 的變數裡。logging.debug(log_string,extra=d)
為了方便,我又打包成一個 module 如下:

import datetime, logging
def log(filename,mode,log_string):
  logging.basicConfig(level=logging.DEBUG,
                    format='%(curdatetime)s %(levelname)s %(message)s',
                    filename=filename,
                    filemode=mode)
  d = {'curdatetime':datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')}
  logging.debug(log_string,extra=d)

這樣就不用每次寫 log 都自帶一個 extra 的 dict。

用了 logging,也就不怕 multi-thread 了。

當然 logging 還有很多其他功能。以後不用自己寫了。

http://docs.python.org/library/logging.html#logrecord-attributes

2012年2月13日 星期一

[vbnet]right

有時候,在專案裡,想用 right 卻發生

取得控制項右邊緣和其容器左邊緣之間的距離

那就改用

Microsoft.VisualBasic.Right

也許調整 imports 有用?

2012年2月12日 星期日

[mongodb]MongoDB 的特色

接下來翻譯 www.mongodb.org 首頁的部份。就交待過去。
用大括號括起來的文字是原文,
有些不是很好翻譯的字、詞,
專有名詞,不常見的名詞,
或翻譯字不是很一致的情況,
我就會在譯文後面緊跟大括號原文。

MongoDB (名詞來自 “humongous”) 是一個可擴展{scalable},高效能{high-performance},開放原始碼{open source} 的 NoSQL database。
它是用 C++ 寫的,它的特色是:

  • 文件導向儲存{Document-oriented storage}
    擁有動態架構{schema}的JSON風格文件{JSON-style document} ,簡單有力。
  • 全索引支援{Full Index Support}
    可對任何屬性{attribute}做索引,就跟以前一樣。
  • 重覆{Replication}及高可用性{High Availability}
    可跨 LANs 及 WANs 做鏡像,可用做擴展及安心。(譯:果然,要「信達雅」很難啊,先直譯好了。)
  • 自動分片{Auto-Sharding}
    橫向擴展不影響功能。
  • 查詢{Querying}
    豐富,文件基礎{document-based}的查詢。
  • 快速就地更新{Fast In-Place Updates}
    Atomic modifiers for contention-free performance.(譯:我想想這要怎麼一句話講完…。)
  • Map/Reduce
    靈活的聚合和數據處理。
  • GridFS
    可存任意大小的檔案,不會把你的儲存區搞複雜。
  • 商業支援
    提供企業等級的支援、訓練、顧問服務。

2012年1月18日 星期三

[mongodb]mongo database 芒果資料庫-開端

Mongo database 是資料庫的一種。
屬於 NoSQL 的這一種。
從學校學資料庫的時候,還有碰過不是關聯式資料庫的嗎?

在近年來,資料庫的應用環境變化,
讓所謂 NoSQL 的資料庫大顯身手,
這些資料庫聲稱更能適應網路應用,更能適應資料變化,更能適應分散管理,
而芒果資料庫,就是其中之一。

它採用所謂的 document-oriented,讓它的每筆資料(也就是一個 document,也有人按舊習慣叫 record),
都是個 dictionary 物件,可以任意加屬性(或按舊習慣稱做 field),
一群你認為同類的 document,就裝在一個 collection 裡面。
對於用過 javascript 的人都知道,dictionary 跟 class 是一體的兩面,
只要拿得到 dictionary 的序列化表示,javascript 就等於拿到一個物件,
在網頁上的應用當然超方便,server side 或 client 的程式就不用想如何從 table 轉換成物件。
靈活的 field 增減,也不用太過擔心 schema 的變化,
field 的值也不只限單一值,也可以是個 list,或是個 dictionary,可像個樹一樣的結構長大,
面對一個人有三個電話號碼的這種資料,是輕鬆自然又直覺,不用像以前一樣用分隔符號分開,
然後限制哪些符號不行在值裡面出現之類的地雷。

當然開頭一定是因為宣傳廣告吸引了我,才會開始想要用他。
http://www.mongodb.org
既然有想,就開始試用吧,不用不會知道優缺點的。
如果你也有類似的需求,你可以試試。

類似的資料庫還有 Apache 的 Cassandra ,
http://www.datastax.com/docs/0.8/introduction/index#getting-started-with-cassandra