在了解過世界最大的PHP站點,Facebook的后臺技術后,今天我們來了解一個百萬級PHP站點的網站架構:Poppen.de。Poppen.de是德國的一個社交網站,相對Facebook、Flickr來說是一個很小的網站,但它有一個很好的架構,融合了很多技術,如 Nigix、MySql、CouchDB、Erlang、Memcached、RabbitMQ、PHP、Graphite、Red5以及Tsung。

            Poppen.de目前有200萬注冊用戶數、2萬并發用戶數、每天20萬條私有消息、每天25萬登錄次數。而項目團隊有11個開發人員,兩個設計,兩個系統管理員。該站點的商業模式采用免費增值模式,用戶可以使用搜索用戶、給好友發送消息、上載圖片和視頻等功能。

            如果用戶想享受不受限制發送消息和上載圖片,那么就得根據需要支付不同類型的會員服務,視頻聊天及網站其他服務也采用同樣的策略。

            Nginx

            Poppen.de 所有的服務都是基于Nginx服務上的。前端有兩臺Nginx服務器在高峰期提供每分鐘15萬次請求的負載,每個機器已經有四年壽命,并且只有一個CPU 和3GB RAM。Poppen.de擁有三臺獨立的圖像服務器,由三臺Nginx服務器為*.bilder.poppen.de提供每分鐘8萬次請求服務。

            Nginx 架構中一個很酷的設計就是有很多請求是由Memcached處理的,因此請求從緩存中獲取內容而不需要直接訪問PHP機器。比如,用戶信息頁(user profile)是網站需要密集處理的內容,如果把用戶信息頁全部緩存到Memcached上,那么請求直接從Memcached上獲取內容。 Poppen.de的Memcached每分鐘可以處理8000次請求。

            架構中有三個Nginx圖像服務器提供本地圖像緩存,用戶上載圖 像到一個中央文件服務器。當向這三個Nginx之一中請求圖像時,如果服務器本地中沒有存在該圖像,則從中央文件服務器下載到該服務器上作緩存并提供服 務。這種負載均衡的分布式圖像服務器架構設計可以減輕主要存儲設備的負載。

            PHP-FPM

            該網站運行在PHP- FPM上。共有28臺雙CPU、6GB內存的PHP機器,每個機器上運行100個PHP-FPM的工作線程。使用啟用了APC的PHP5.3.x。 PHP5.3可以降低CPU和內存使用率的30%以上。

            程序代碼是基于Symfony1.2框架之上開發的。一是可以使用外部資源,二是 能夠提高項目開發進度,同時在一個著名的框架上可以讓新開發人員更容易加入到團隊中來。雖然沒有任何事情都是十全十美的,但可以從Symfony框架中得 到很多好處,讓團隊可以更多的精力放在Poppen.de的業務開發上去。

            網站性能優化使用XHProf,這是Facebook開源出來的一個類庫。這個框架非常容易個性化和配置,能夠可以緩存大部分高代價的服務器計算。

            MySQL

            MySQL是網站主要的RDBMS。網站又幾個MySql服務器:一臺4CPU、32GB的服務器存儲用戶相關信息,如基本信息、照片描述信息等。這臺機器已經使用了4 年,下一步計劃會使用共享集群來替換它。目前仍基于這個系統上進行設計,以簡化數據訪問代碼。根據用戶ID進行數據分區,因為網站中大部分信息都是以用戶 為中心的,如照片、視頻、消息等。

            有三臺服務器按主-從-從配置架構提供用戶論壇服務。一臺從服務器負責網站自定義消息存儲,到現在有 2.5億條消息。另外四臺機器為主-從配置關系。另外由4臺機器配置成NDB族群專門服務于密集型寫操作數據,如用戶訪問統計信息。

            數據表設計盡量避免關聯操作,盡可能緩存最多的數據。當然,數據庫的結構化規范已經完全被破壞掉了。因此,為了更容易搜索,數據庫設計創建了數據挖掘表。大部分表是MyISAM型表,可以提供快速查找。現在的問題是越來越多的表已經全表鎖住了。Poppen.de正考慮往XtraDB存儲引擎上遷移。

            Memcached

            網站架構中Memcached應用相當多,超過45GB的高速緩存和51個節點。緩存了Session會話、視圖緩存以及函數執行緩存等。架構中有一個系統 當記錄被修改時可以自動地把數據更新到緩存中去。未來改善緩存更新的可能方案是使用新的Redis Hash API或者MongoDB。

            RabbitMQ

            在 2009年中開始在架構中使用RabbitMQ。這是一個很好的消息解決方案,便于部署和集中到這個架構中去,在LVS后運行了兩臺RabbitMQ服務 器。在上個月,已經把更多的東西集成到該隊列中,意味著同一時刻有28臺PHP服務器每天要處理50萬次請求。發送日志、郵件通知、系統消息、圖像上載等 更多的東西到這個隊列中。

            應用PHP-FPM中的fastcgi_finish_request()函數集成隊列消息,可以把消息異步發 送到隊列中。當系統需要給用戶發送HTML或JSON格式響應時,就調用這個函數,這樣用戶就沒有必要等到PHP腳本清理。

            這個系統可以改善架構資源管理。例如,在高峰期服務每分鐘可以處理1000次登錄請求。這表示有1000并發更新用戶表保存用戶的登錄時間。由于使用了隊列機制,可以 按相反的順序來運行這些查詢。如果需要提高處理速度,只需要增加更多的隊列處理者即可,甚至可以增加更多的服務器到這集群中去,而不需要修改任何配置和部 署新節點。

            CouchDB

            日志存儲CouchDB運行在一臺機器上。在這臺機器上可以根據模塊/行為進行日志查詢 /分組,或者根據錯誤類型等等。這對定位問題非常有用。在使用日志聚合服務CouchDB之前,不得不逐臺登錄到PHP服務器上設法日志分析定位問題,這 是非常麻煩的。而現在把所有的日志集中到隊列中保存到CouchDB中,可以集中進行問題檢查和分析。

            Graphite

            網站使用Graphite采集網站實時信息并統計。從請求每個模塊/行為到Memcached的命中和未命中、RabbitMQ狀態監控以及Unix負載等等。Graphite服務平均每分鐘有4800次更新操作。實踐已經證實要監測網站發發生什么是非常有用的,它的簡單文本協議和繪圖功能可以方便地即插即 用的方式用于任何需要監控的系統上。

            一件很酷的事情是使用Graphite同時監控了網站的兩個版本。一月份部署了Symfony框架新 版本,以前代碼作為一個備份部署。這就意味著網站可能會面臨性能問題。因此可以使用Graphite來對兩個版本在線進行對比。

            發現新版本上的Unix負載表較高,于是使用XHProf對兩個版本進行性能分析,找出問題所在。

            Red5

            網站為用戶也提供了兩種類型的視頻服務,一種是用戶自己上載的視頻,另外一種是視頻聊天,用戶視頻互動和分享。到2009年年中,每月為用戶提供17TB的流量服務。

            Tsung

            Tsung 是一個Erlang編寫的分布式基準分析工具。在Poppen.de網站中主要用于HTTP基準分析、MySQL與其他存儲系統(XtraDB)的對比分 析。用一個系統記錄了主要的MySQL服務器的流量,再轉換成Tsung的基準會話。然后對該流量進行回放,由Tsung產生數以千計的并發用戶訪問實驗 室的服務器。這樣就可以在實驗環境中與真實場景非常接近。

          posted @ 2012-03-22 12:55 小馬歌 閱讀(185) | 評論 (0)編輯 收藏
           
          對于大多數web應用來說,數據庫都是一個十分基礎性的部分。如果你在使用PHP,那么你很可能也在使用MySQL—LAMP系列中舉足輕重的一份子。

          對于很多新手們來說,使用PHP可以在短短幾個小時之內輕松地寫出具有特定功能的代碼。但是,構建一個穩定可靠的數據庫卻需要花上一些時日和相關技能。下面列舉了我曾經犯過的最嚴重的11個MySQL相關的錯誤(有些同樣也反映在其他語言/數據庫的使用上)。。。

          1、使用MyISAM而不是InnoDB

          MySQL有很多數據庫引擎,但是你最可能碰到的就是MyISAM和InnoDB。

          MySQL 默認使用的是MyISAM。但是,很多情況下這都是一個很糟糕的選擇,除非你在創建一個非常簡單抑或實驗性的數據庫。外鍵約束或者事務處理對于數據完整性 是非常重要的,但MyISAM都不支持這些。另外,當有一條記錄在插入或者更新時,整個數據表都被鎖定了,當使用量增加的時候這會產生非常差的運行效率。

          結論很簡單:使用InnoDB。

          2、使用PHP的mysql函數

          PHP自產生之日就提供了MySQL庫函數(or near as makes no difference)。很多應用仍然在使用類似mysql_connect、mysql_query、mysql_fetch_assoc等的函數,盡管PHP手冊上說:

          如果你在使用MySQL v4.1.3或者更新版本,強烈推薦使用您使用mysqli擴展。

          mysqli(MySQL的加強版擴展)有以下幾個優點:

          可選的面向對象接口 
          prepared表達式,這有利于阻止SQL注入攻擊,還能提高性能 
          支持更多的表達式和事務處理 
          另外,如果你想支持多種數據庫系統,你還可以考慮PDO。

          3、沒有處理用戶輸入

          這或者可以這樣說#1:永遠不要相信用戶的輸入。用服務器端的PHP驗證每個字符串,不要寄希望與JavaScript。最簡單的SQL注入攻擊會利用如下的代碼:

          $username = $_POST["name"];   $password = $_POST["password"];   $sql = "SELECT userid FROM usertable WHERE username='$username' AND password='$password';";   // run query...  

          只要在username字段輸入“admin';--”,這樣就會被黑到,相應的SQL語句如下:

          SELECT userid FROM usertable WHERE username='admin';

          狡猾的黑客可以以admin登錄,他們不需要知道密碼,因為密碼段被注釋掉了。

          4、沒有使用UTF-8

          美國、英國和澳大利亞的我們很少考慮除英語之外的其他語言。我們很得意地完成了自己的“杰作”卻發現它們并不能在其他地方正常運行。

          UTF-8解決了很多國際化問題。雖然在PHP v6.0之前它還不能很好地被支持,但這并不影響你把MySQL字符集設為UTF-8。

          5、相對于SQL,偏愛PHP

          如果你接觸MySQL不久,那么你會偏向于使用你已經掌握的語言來解決問題,這樣會導致寫出一些冗余、低效率的代碼。比如,你不會使用MySQL自帶的AVG()函數,卻會先對記錄集中的值求和然后用PHP循環來計算平均值。

          此外,請注意PHP循環中的SQL查詢。通常來說,執行一個查詢比在結果中迭代更有效率。

          所以,在分析數據的時候請利用數據庫系統的優勢,懂一些SQL的知識將大有裨益。

          6、沒有優化數據庫查詢

          99%的PHP性能問題都是由數據庫引起的,僅僅一個糟糕的SQL查詢就能讓你的web應用徹底癱瘓。MySQL的EXPLAIN statement、Query Profiler,還有很多其他的工具將會幫助你找出這些萬惡的SELECT。

          7、不能正確使用數據類型

          MySQL提供了諸如numeric、string和date等的數據類型。如果你想存儲一個時間,那么使用DATE或者DATETIME類型。如果這個時候用INTEGER或者STRING類型的話,那么將會使得SQL查詢非常復雜,前提是你能使用INTEGER或者STRING來定義那個類型。

          很多人傾向于擅自自定義一些數據的格式,比如,使用string來存儲序列化的PHP對象。這樣的話數據庫管理起來可能會變得簡單些,但會使得MySQL成為一個糟糕的數據存儲而且之后很可能會引起故障。

          8、在查詢中使用*

          永遠不要使用*來返回一個數據表所有列的數據。這是懶惰:你應該提取你需要的數據。就算你需要所有字段,你的數據表也不可避免的會產生變化。

          9、不使用索引或者過度使用索引

          一般性原則是這樣的:select語句中的任何一個where子句表示的字段都應該使用索引。

          舉 個例子,假設我們有一個user表,包括numeric ID(主鍵)和email address。登錄的時候,MySQL必須以一個email為依據查找正確的ID。如果使用了索引的話(這里指email),那么MySQL就能夠使用 更快的搜索算法來定位email,甚至可以說是即時實現。否則,MySQL就只能順序地檢查每一條記錄直到找到正確的email address。

          有的人會在每個字段上都添加索引,遺憾的是,執行了INSERT或者UPDATE之后這些索引都需要重新生成,這樣就會影響性能。所以,只在需要的時候添加索引。

          10、忘記備份!

          雖然比較罕見,但是數據庫還是有崩潰的危險。硬盤有可能損壞,服務器有可能崩潰,web主機提供商有可能會破產!丟失MySQL數據將會是災難性的,所以請確保你已經使用了自動備份或者已經復制到位。

          11、Bonus mistake-不考慮使用其他數據庫

          對于PHP開發人員來說,MySQL可能是使用最廣泛的數據庫系統,但并不是唯一的選擇。PostgreSQL和Firebird是最強有力的競爭者:這個兩者都是開源的,而且都沒有被公司收購。微軟提供了sql server Express,甲骨文提供了10g Express,這兩者都是企業級數據庫的免費版本。有時候,對于一個較小的web應用或者嵌入式應用,SQLite也不失為一個可行的替代方案。 
          posted @ 2012-03-22 10:03 小馬歌 閱讀(310) | 評論 (0)編輯 收藏
           
               摘要: 先看下圖中的場景,客戶端A和B,以及服務器server都保存了同一個文件,最初,A、B和server上的文件內容都是相同的(記為File.1)。某一時刻,B修改了文件內容,上傳到SERVER上(記為File.2)。客戶端A這時試圖向服務器SERVER更新文件到最新內容,也就是File.1更新為File.2。上面這個場景很常見,例如現在流行的網盤。假設我有一個文件a.txt在網盤上,上班時在公司的單...  閱讀全文
          posted @ 2012-03-19 16:45 小馬歌 閱讀(1106) | 評論 (0)編輯 收藏
           

          人人網首頁拖拽上傳詳解

            早在公元2011年6月3日傍晚,人人網推出了一個很裝B且完全無視IE瀏覽器的功能——拖拽上床。哦,Sorry, 是拖拽上傳。本文將重點介紹實現拖拽上傳的幾個HTML5技術:Drag&DropFileReader APIFormData

            關于這個拖拽上傳,其實國外有很多網站已經有這樣的應用,最早推出拖拽上傳應用的是Gmail,它支持標準瀏覽器下拖拽本地文件到瀏覽器中作為郵件的附件發送。人人網的這個拖拽上傳也是同理,可以讓使用標準瀏覽器的用戶通過簡單的拖拽行為,將本地文件夾中的照片直接上傳到人人網,用戶體驗能得到提升的同時,也希望借此機會推廣一下標準瀏覽器,淘汰IE。人人網當時也向廣大用戶推出升級瀏覽器活動,并喊出口號:”工欲善其計算機,必先利其瀏覽器”。本次拖拽上傳的宣傳口號:你敢”脫”,我就敢上傳…

          人人網 - 拖拽上傳

            言歸正題,首先看看效果,大家如果有人人網帳號的話可以在首頁試一試拖拽上傳功能,下面是演示視頻:

          拖拽上傳應用主要使用了以下HTML5技術:

          • Drag&Drop : HTML5基于拖拽的事件機制.
          • File API :  可以很方便的讓Web應用訪問文件對象,File API包括FileListBlobFileFileReaderURI scheme,本文主要講解拖拽上傳中用到的FileList和FileReader接口。
          • FormData : FormData是基于XMLHttpRequest Level 2的新接口,可以方便web應用模擬Form表單數據,最重要的是它支持文件的二進制流數據,這樣我們就能夠通過它來實現AJAX向后端發送文件數據了。

          HTML5 Drag&Drop 拖拽事件

            關于Drag&Drop拖拽事件,之前我寫過一篇專門介紹的文章《給力的 Google HTML5 訓練營(HTML5 Drag&Drop 拖拽、FileReader實例教程)》,那篇文章詳細講解了Drag & Drap事件的原理和代碼實例,這里的拖拽上傳實現原理基本上是一樣的,大家有興趣或不太了解的話可以先看看那篇文章,我在這里就不再過多啰嗦了~下面直接出拖拽上傳簡要代碼實例:

          var oDragWrap = document.body;

           

          //拖進
          oDragWrap.addEventListener(’dragenter’, function(e) {
           e.preventDefault();
          }, false);

          //拖離
          oDragWrap.addEventListener(’dragleave’, function(e) {
           dragleaveHandler(e);
          }, false);

          //拖來拖去 , 一定要注意dragover事件一定要清除默認事件
          //不然會無法觸發后面的drop事件
          oDragWrap.addEventListener(’dragover’, function(e) {
           e.preventDefault();
          }, false);

          //扔
          oDragWrap.addEventListener(’drop’, function(e) {
           dropHandler(e);
          }, false);

          var dropHandler = function(e) {
          //將本地圖片拖拽到頁面中后要進行的處理都在這
          }

          獲取文件數據 HTML5 File API

            在之前那篇文章中我也有介紹過關于File API中的FileReader接口,作為 File API 的一部分,FileReader 專門用于讀取文件,根據 W3C 的定義,FileReader 接口 “提供一些讀取文件的方法與一個包含讀取結果的事件模型”。關于FileReader的詳細介紹和代碼實例大家可以先去看看那篇文章

            今天我著重介紹一下File API中的FileList接口,它主要通過兩個途徑獲取本地文件列表,一是<input type=”file”>的表單形式,另一種則是e.dataTransfer.files拖拽事件傳遞的文件信息。很顯然,我們這里會用到后者。

          var fileList = e.dataTransfer.files;

            使用files方法將會獲取到拖拽文件的數組形勢的數據,每個文件占用一個數組的索引,如果該索引不存在文件數據,將返回null值。可以通過length屬性獲取文件數量.

          var fileNum = fileList.length;

            拖拽上傳需要注意的是需要判斷兩個條件,1:拖拽的是文件不是頁面中的元素; 2:拖拽的是圖片而不是其它文件,可以通過file.type屬性獲取文件的類型

          //檢測是否是拖拽文件到頁面的操作
          if (fileList.length === 0) {return;};
          //檢測文件是不是圖片
          if (fileList[0].type.indexOf(’image’) === -1) {return;}

            下面讓我們來看看如何結合之前的拖拽事件來實現拖拽圖片并在頁面中進行預覽:

          var dropHandler = function(e) {
           e.preventDefault();

           

           //獲取文件列表
           var fileList = e.dataTransfer.files;

           //檢測是否是拖拽文件到頁面的操作
           if (fileList.length == 0) {return;};

           //檢測文件是不是圖片
           if (fileList[0].type.indexOf(’image’) === -1) {return;}

           //實例化file reader對象
           var reader = new FileReader();
           var img = document.createElement(’img’);

           reader.onload = function(e) {
            img.src = this.result;
            oDragWrap.appendChild(img);
           }
           reader.readAsDataURL(fileList[0]);

          }

            這里有一個簡單的拖拽圖片預覽的Demo

            這時你如果用FireBug等類似調試工具查看DOM的話,會看到<img>標簽的src屬性是一個超長的文件二進制數據,所以如果DOM有很多這類圖片,那就要當心瀏覽器性能了,因為這些數據極大地擴充的頁面的代碼量,而每次頁面的reflow都會對瀏覽器形成很大的負擔,So,如果這些圖片還在DOM中,那就盡量不要做動畫或任何重繪操作,如果真的要做就盡量讓圖片脫離文檔流,讓其絕對定位比較靠譜。

          補充:可以使用window.URL.createObjectURL(file)來獲取文件的URL(Chrome下用window.webkitURL.createObjectURL(file)),這種方式獲取的URL要比上面說的readAsDataURL簡短很多。而且可以省去使用FileReader。這里感謝BinBinLiao的留言建議:) 下面是使用readAsDataURL與createObjectURL生成的代碼對比:

          readasdataurl-vs-createobjecturl

          優化后的代碼:(紅色為優化的代碼)

          var dropHandler = function(e) {
           e.preventDefault();

           

           var fileList = e.dataTransfer.files;  //獲取文件列表
           var img = document.createElement(’img’);

           //檢測是否是拖拽文件到頁面的操作
           if (fileList.length == 0) {return;};

           //檢測文件是不是圖片
           if (fileList[0].type.indexOf(’image’) === -1) {return;}
           

           if (window.URL.createObjectURL) {
            //FF4+
            img.src = window.URL.createObjectURL(fileList[0]);
           } else if (window.webkitURL.createObjectURL) {
            //Chrome8+
            img.src = window.webkitURL.createObjectURL(fileList[0]);
           } else {
            //實例化file reader對象
            var reader = new FileReader();

           

            reader.onload = function(e) {
             img.src = this.result;
             oDragWrap.appendChild(img);
            }
            reader.readAsDataURL(fileList[0]);
           }

          }

            需要注意的是,window.URL.createObjectURL是有生命周期的,也就意味著你每用此方法獲取URL,其生命周期都會和DOM一樣,它會單獨占用內存,所以當刪除圖片或不再需要它是,記得用window.URL.revokeObjectURL(file)來釋放其內存。當然,如果你沒有釋放,刷新頁面也是可以釋放的。

          AJAX上傳圖片(file.getAsBinary & FormData)

            既然已經獲取到了拖拽到web頁面中圖片的數據,下一步就是將其發送到服務器端了。

            話說HTML5時代之前,AJAX傳輸文件二進制流數據是不可能完成的事情,而現在我們完全可以通過file.getAsBinary獲取文件的二進制數據流,進而將其當做XHR的data數據傳送到后端,8過由于Chrome不支持file的getAsBinary方法,FF3.6+支持此方法。所以Chrome就要另尋它法了,這時我們發現XMLHttpRequest Level 2中的FormData接口完美解決了這個問題,它可以很快捷的模擬Form表單數據并通過AJAX發送至后端,FormData的支持情況是FF5及以上支持,Chrome12及以上支持。

             file.getAsBinary獲取文件流很簡單,但是要想上傳數據,就要模擬一下表單的數據格式了,首先看看模擬表單的js代碼, FormData模擬表單數據時更是簡潔,不用麻煩的去拼字符串,而是直接將數據append到formdata對象中即可:

          var xhr = new XMLHttpRequest();
          var url = ‘http://upload.renren.com/……’;
          var boundary = ‘———————–’ + new Date().getTime();
          var fileName = file.name;

           

          xhr.open(”post”, url, true);
          xhr.setRequestHeader(’Content-Type’, ‘multipart/form-data; boundary=’ + boundary);

          if (window.FormData) {
           //Chrome12+
           var formData = new FormData();
           formData.append(’file’, file);
           formData.append(’hostid’, userId);
           formData.append(’requestToken’, t);

           data = formData;
          } else if (file.getAsBinary) {
           //FireFox 3.6+
           data = “–” +
           boundary +
           crlf +
           ”Content-Disposition: form-data; ” +
           ”name=\”" +
           ’file’ +
           ”\”; ” +
           ”filename=\”" +
           unescape(encodeURIComponent(file.name)) +
           ”\”" +
           crlf +
           ”Content-Type: image/jpeg” +
           crlf +
           crlf +
           file.getAsBinary() +
           crlf +
           ”–” +
           boundary +
           crlf +
           ”Content-Disposition: form-data; ” +
           ”name=\”hostid\”" +
           crlf +
           crlf +
           userId +
           crlf +
           ”–” +
           boundary +
           crlf +
           ”Content-Disposition: form-data; ” +
           ”name=\”requestToken\”" +
           crlf +
           crlf +
           t +
           crlf +
           ”–” +
           boundary +
           ’–’;
          }

          xhr.send(data);

          首先表單數據headers頭信息需要以下兩項:

          • Content-Type : 設置其為multipart/form-data來模擬表單數據
          • boundary : 表單數據中的分隔符,用于分隔不同的文件或表單項,這是服務器端設置的格式。

          發送時的post數據類似這樣:

          ————————-1323611763556
          Content-Disposition: form-data; name=”file”; filename=”4.jpg”
          Content-Type: image/jpeg

           

          ÿØÿà?JFIF?…這里是文件二進制流…~iúoî­5P%-vãîHü 4QHgÿÙ
          ————————-1323611763556
          Content-Disposition: form-data; name=”hostid”

          229421603
          —————————–1323612996486

          Content-Disposition: form-data; name=”requestToken”

          369009193
          ————————-1323611763556–

          好了,現在文件上傳成功后你就可以按照平常AJAX的操作來進行后續處理了。

          最后,再來總結一下拖拽上傳的技術要點:

          1. 監聽拖拽:監聽頁面元素的拖拽事件,包括:dragenter、dragover、dragleave和drop,一定要將dragover的默認事件取消掉,不然無法觸發drop事件。如需拖拽頁面里的元素,需要給其添加屬性draggable=”true”;
          2. 獲取拖拽文件:在drop事件觸發后通過e.dataTransfer.files獲取拖拽文件列表,.length屬性獲取文件數量,.type屬性獲取文件類型。
          3. 讀取圖片數據并添加預覽圖:實例化FileReader對象,通過其readAsDataURL(file)方法獲取文件二進制流,并監聽其onload事件,將e.result賦值給img的src屬性,最后將圖片append到DOM中。
          4. 發送圖片數據:使用file.getAsBinary 和 FormData分別模擬表單數據AJAX提交文件流。

          OK,拖拽上傳就講到這里,歡迎大家一起探討。

          posted @ 2012-03-19 16:35 小馬歌 閱讀(2237) | 評論 (0)編輯 收藏
           
               摘要: BOS(Bonita Open Solution) 是一個開源的 BPM 解決方案,有 3 個主要部分構成:Bonita Studio : 用戶可以根據 BPMN 標準以圖標的形式來設計和修改業務流程。同時也可以連接其已有的 信息系統 ( 例如 , ERP&n...  閱讀全文
          posted @ 2012-03-17 12:10 小馬歌 閱讀(766) | 評論 (0)編輯 收藏
           
               摘要: 如果你想知道你的服務器正在做干什么,你就需要了解一些基本的命令,一旦你精通了這些命令,那你就是一個 專業的 Linux 系統管理員。有些 Linux 發行版會提供 GUI 程序來進行系統的監控,例如 SUSE Linux 就有一個非常棒而且專業的工具 YaST,KDE 的 KDE System Guard 同樣很出色。當然,要使用這些工具,你必須在服務器跟前進行操作,而且這些 GUI 的程序占用了...  閱讀全文
          posted @ 2012-03-16 20:24 小馬歌 閱讀(228) | 評論 (0)編輯 收藏
           

          HTML5提供了一組API用來獲取用戶的地理位置,如果瀏覽器支持且設備具有定位功能,就能夠直接使用這組API來獲取當前位置信息。

          該API是navigator對象的一個屬性 – Geolocation。目前除了ie內核瀏覽器外,其他瀏覽器的最新版本基本都支持Geolocation。同時,移動設備IOS 3.0+ 和 Android 2.0+ 系統也支持它,現在很多移動設備的應用加入了地理定位的元素。

          那么我們接下來看如何使用Geolocation API:

          一、檢查瀏覽器是否支持Geolocation

          var hasGeolocation = !!(navigator.geolocation);
          if(hasGeolocation){
          alert(“瀏覽器支持hasGeolocation”);
          }

          二、navigator.geolocation 的方法:

          * navigator.geolocation有三個方法,分別是getCurrentPosition()、watchPosition()和clearWatch()

          getCurrentPosition()方法

          * getCurrentPosition()方法檢索用戶的當前位置,但只檢索一次。當該方法被腳本調用時,方法以異步的方式來嘗試獲取宿主設備的當前位置。

          * 該方法最多可以有三個參數:

          geolocationSuccess:帶回當前位置的回調(callback)(必需的)
          geolocationError:有錯誤發生時使用的回調(可選的)
          geolocationOptions:地理位置選項(可選的)

          調用如下所示:


          navigator.geolocation.getCurrentPosition(geolocationSuccess, geolocationError, geolocationOptions);

          watchPosition()方法

          * watchPosition()方法定期輪詢用戶的位置,查看用戶的位置是否發生改變。其最多可帶三個參數。

          調用如下所示:


          var watchPositionHandler = navigator.geolocation.watchPosition(geolocationSuccess, geolocationError, geolocationOptions);

          clearWatch()方法

          * clearWatch()方法終止正在進行的watchPosition(),該方法只能帶一個參數。在調用時,其找到之前已經開始了的watchID參數并立即停止它。

          調用如下所示:


          navigator.geolocation.clearWatch(watchID);

          三、navigator.geolocation返回一個Position對象:

          * Position對象有兩個屬性:timestamp和coords。timestamp屬性表示地理位置數據的創建時間,coords屬性又包含七個屬性:

          coords.latitude:估計緯度
          coords.longitude:估計經度
          coords.altitude:估計高度
          coords.accuracy:所提供的以米為單位的經度和緯度估計的精確度
          coords.altitudeAccuracy:所提供的以米為單位的高度估計的精確度
          coords.heading: 宿主設備當前移動的角度方向,相對于正北方向順時針計算
          coords.speed:以米每秒為單位的設備的當前對地速度

          * 注意altitude, altitudeAccuracy, heading, speed不一定被瀏覽器支持,所以大家最好看一下官方規范的描述,多一些了解。

          四、注意事項

          * Geolocation App是不能直接訪問設備的,只能通過請求瀏覽器來訪問設備;
          * Geolocation涉及到用戶隱私,在獲取 Geolocation 的時候,需要先征求用戶的意思。
          * Geolocation目前沒有比較好的前端兼容解決方案,但是在移動設備 iOS 和 Android上,我們可以大膽嘗試使用。

          posted @ 2012-03-16 15:25 小馬歌 閱讀(360) | 評論 (0)編輯 收藏
           

          不得不佩服下谷歌Chrome團隊,利用HTML5和CSS3實現了一本相當漂亮的在線電子書:《關于瀏覽器和互聯網20件事》。

          訪問地址:http://www.20thingsilearned.com

          話說這本電子書已經出來很久了,不過今天來看依然覺得相當的贊。我們無需刷新頁面,就可以來回切換電子書頁面,這正是OPOA(One Page One Application)的完美體現。

          現在正在學習關于history API這方面的東西,所以特別感興趣的是他們如何使用window.history.pushState()和window.history.replaceState()來做頁面之間的不刷新切換。

          今天查閱了一些資料,基本上對history API有了一個基本了解。

          首先要說的就是history是個全局,即window.history。看到這個變量名你一定很熟悉,因為經常可以看到用window.history.back()或者window.history.go(-1)來返回上一頁的JavaScript代碼。

          所以history并不是什么新東西,在HTML4的時代,我們可以使用它的這幾個屬性和方法:

          length:歷史堆棧中的記錄數。

          back():返回上一頁。

          forward():前進到下一頁。

          go([delta]):delta是個數字,如果不寫或為0,則刷新本頁;如果為正數,則前進到相應數目的頁面;若為負數,則后退到相應數目的頁面。

          現在,HTML5為其又添加了以下2個方法:

          pushState(data, title [, url]):往歷史堆棧的頂部添加一條記錄。data為一個對象或null,它會在觸發window的popstate事件(window.onpopstate)時,作為參數的state屬性傳遞過去;title為頁面的標題,但當前所有瀏覽器都忽略這個參數;url為頁面的URL,不寫則為當前頁。

          replaceState(data, title [, url]):更改當前頁面的歷史記錄。參數同上。這種更改并不會去訪問該URL。不過目前只有Safari 5.0+、Chrome 8.0+、Firefox 4.0+和iOS 4.2.1+支持。如果想兼容老瀏覽器的話,可以試試History.js,而且它還修正了一些bug。

          當然,在移動平臺上,我們可以大膽嘗試html5的history API。ios3.0+ 和Android2.0+ 平臺的內置瀏覽器對history都比較完美了,利用它我們可以web app更趨向與native app。

          下面,推薦幾篇文章:

          .Manipulating the browser history

          .Session history and navigation

          .Manipulating History for Fun & Profit

          posted @ 2012-03-16 15:24 小馬歌 閱讀(2023) | 評論 (0)編輯 收藏
           

          嘛,起因是黑子大叔在微博上的一條@信息,找起了這個的實現,看了一圈google的中文信息內似乎還沒有怎么提到這個的內容,就發表上來。
          詳細效果就是類似于用Firefox4+/Chrome 5+/Safari 5+/Opera 11.5+登錄新浪微博后看到的個人時間軸,在翻頁時可以觀察到這個效果,地址欄URL變動,但是頁面沒有刷新,用firebug觀察也觀察不到刷新整個頁面,只有ajax請求的翻頁數據。從先前的理解來說,URL的修改必然引起(請注意我不是在說通過錨點修改)瀏覽器重載頁面,但利用HTML5新加入的history.pushState();和history.replaceState();可以完全自己維護一個歷史記錄列表繞開歷史記錄完全由瀏覽器控制的機制,從而實現比錨點更加完美的一種頁內更新的體驗。
          代碼方面很簡單,只要在需要修改url的地方插入一行:
          window.history.pushState({"html":response.html,"pageTitle":response.pageTitle}, 'title', urlPath);
          //三個參數,分別為:狀態對象,標題(目前被瀏覽器忽略),地址
          即可在歷史記錄里面產生一個新的歷史記錄(另一個replaceState方法參數完全相同,只是替代掉當前的狀態)。
          在體驗上,非常接近于使用錨點(window.location = “#foo”;),但是mozilla的文檔提出了以下三點好處:
          新的URL可以是和原始頁面URL同域下的任何URL,如果用錨點,只能更新#后面的部分
          僅在需要時更新URL,錨點的歷史記錄在相同時不會創建(即當前已經是”#foo”的情況下,如果再將當前頁面錨點設置為”#foo”,不會產生新的歷史記錄)
          可以記錄下任意類型的數據,使用錨點的話將要自己維護一份歷史數據列表或者把數據轉碼到一個字符串里
          (翻譯&描述的有點別扭,見笑了,不過其實應該自己也能體會到這些好處才是)
          我自己實現的一個例子:
          http://vifix.cn/atelier/demos/html5-update-browser-url-without-reloading-page
          代碼:
          <?php
          if(!isset($_REQUEST['page'])){
              $page = 1;
          }
          else{
              $page = intval($_REQUEST['page']);
          }
           
          if(isset($_REQUEST['ajaxload'])){
              echo "第{$page}頁的內容";
              die;
          }
          ?>
          <!DOCTYPE html>
          <html>
          <head>
          <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
          <title>HTML5 修改瀏覽器url而不刷新頁面</title>
          <script type="text/javascript">
          var domLoaded = function(){
              if(ua != null && ua[1] < 10){
                  alert('您的瀏覽器不支持');
                  return ;
              }
           
              if(location.href.indexOf("?") > -1){
                  var urlparts = location.href.match(/(.+?)\?.+/i);
                  var urlbase = urlparts[1];
              }
              else{
                  var urlbase = location.href;
              }
              var page = <?php echo $page;?>;
              var ua = window.navigator.userAgent.match(/msie (\d\.\d)/i);
              var content = document.getElementById("content");
              var loading = document.getElementById("loading");
           
              window.history.replaceState(
                  {
                      content:content.innerHTML,
                      page:page
                  },
                  page,
                  urlbase + (page > 1 ? '?page=' + page : '' )
              );
           
              var ajax = new XMLHttpRequest();
              var ajaxCallback = function(){
                  if(ajax.readyState == 4){
                      loading.style.display = 'none';
                      content.innerHTML = ajax.responseText;
                      window.history.pushState(
                          {
                              content:content.innerHTML,
                              page:page
                          },
                          page,
                          urlbase + "?page=" + page
                      );
                      next.href = urlbase + "?page=" + (page + 1);
                  }
              };
           
              var next = document.getElementById('next');
              var nextClickEvent = function(event){
                  if(loading.style.display != 'block'){
                      loading.style.display = 'block';
                      page++;
                      ajax.open('GET', urlbase + '?page=' + page + '&ajaxload=on', true);
                      ajax.onreadystatechange = ajaxCallback;
                      ajax.send('');
                      event.preventDefault();
                  }
              };
              next.addEventListener('click', nextClickEvent, false);
           
              var popstate = function(){
                  content.innerHTML = history.state.content;
                  page = history.state.page;
              };
              window.addEventListener('popstate', popstate, false);
          };
           
          try{
              window.addEventListener('DOMContentLoaded', domLoaded, false);
          }
          catch(e){
              alert('您的瀏覽器不支持');    
          }
          </script>
          </head>
          <body>
              <p id="content">
                  <?php echo "第{$page}頁的內容";?>
              </p>
              <p>
                  <a id="next" href="?page=<?php echo $page+1;?>">下一頁</a>
              </p>
              <div id="loading" style="display:none;">
                  加載中
              </div>
          </body>
          </html>
          mozilla的文檔
          https://developer.mozilla.org/en/DOM/Manipulating_the_browser_history
          stackoverflow上的相關問題:
          http://stackoverflow.com/questions/3338642/updating-address-bar-with-new-url-without-hash-or-reloading-the-page
          http://stackoverflow.com/questions/824349/modify-the-url-without-reloading-the-page
          posted @ 2012-03-16 15:18 小馬歌 閱讀(2602) | 評論 (0)編輯 收藏
           

          coreseek 是在Sphinx 基礎上開發的全文檢索軟件, 具 體介紹詳見文尾 附 錄

          =========================================================

          一、       安裝環境及介紹

          1.      前提環境

          系 統環境:centos5

          操 作用戶:root

          文 中粗體字為需用戶輸入的命令內容

           

          2.       源碼包準備

          下 載MMSEG 分詞源碼包 

          wget  http://www.coreseek.cn/uploads/sources/mmseg3_0b3.tar.gz

          下 載coreseek 源碼包 

          wget  http://www.coreseek.cn/uploads/sources/csft3_0b4.tar.gz

          解 壓縮源碼包

          tar -xzvf mmseg3_0b3.tar.gz

          tar -xzvf csft3_0b4.tar.gz

           

           

          3.      安裝環境準備

          安 裝g++ 編譯環境

          yum install g++

          yum install gcc

          yum install make

           

          安 裝python 開發組件

          yum install python

          yum install python-dev

           

           

           

          安 裝make

          yum install make

           

          4.      編譯mmseg

          cd mmseg.3.0b3/

          ./configure --prefix=/ var / eyou /mmseg

          make

          make install

           

          5.      編譯coreseek

          cd csft3_0b4/

          ./configure --prefix=/ var / eyou /coreseek --with-python --with-mysql --with-mmseg-includes=/ var / eyou /mmseg/include/mmseg --with-mmseg-libs=/ var / eyou /mmseg/lib/

          make & make install

           

           

          此步安裝完成后, 將在/ var / eyou / 下生成 coreseek 目錄

          coreseek 目錄中有三個目錄分別為bin 、 etc 、var

          bin 中 存有sphinx 用到的一些執行文件 包括 indexer 索引建立 search 查詢工具 searchd 查詢服務器 等

          etc 中是配置文 件,該目錄中 有一個sphinx.conf. disk ,這個相當于sphinx 的 配置例子文件,我們以這個文件為藍本,重新創建一個空白內容的sphinx.conf ,存放在 etc

           

           

          6.      創建dict 目錄

          創建字典目錄:

          mkdir /var/eyou/coreseek/dict/

          產生字典步驟:

          cd /root/soft/ mmseg.3.0b3/data

          /var/eyou/mmseg/bin/mmseg -u unigram.txt

          產生了unigram.txt.uni , 移到相應目錄。

          cp unigram.txt.uni /var/eyou/coreseek/dict/uni.lib

          創建 / var / eyou /coreseek/dict/mmseg.ini

          內容:

          [mmseg]

          merge_number_and_ascii=1;

          number_and_ascii_joint=-;

          compress_space=0;

          seperate_number_ascii=1;

          #merge_number_and_ascii: 字母和數字連續出現是非切分

          #number_and_ascii_joint: 連接數字和字母可用的符號,如'-' '.' 等

          #compress_space :暫時無效

          #seperate_number_ascii :是否拆分數字,如 1988 -> 1/x 9/x 8/x 8/x

           

          7.       php 調用 sphinx api

           

            通過官方API 調用Sphinx ,具體為:

          coreseek 安裝目錄有一個API 目錄,里面有三個PHP 文 件:test.php ,test2.php 和sphinxapi.php 。 sphinxapi.php 是sphinx 調用接口封裝文件(這個文件就是官方提供的php 調 用API ),test.php 是一個在命令行下執行的查詢例子文件,test2.php 是 一個生成摘要的 例子文件。

           

          8.       配置 sphinx.conf

               具體參見sphinx.conf 的 注釋說明

          9.       啟動服務

           

          建 立索引

          /var/eyou/coreseek/bin/indexer  --config  /var/eyou/coreseek/etc/sphinx.conf

          啟 動 searchd 服務

          /var/eyou/coreseek/bin/searchd --config /var/eyou/coreseek/etc/sphinx.conf

           

           

           

          二、       附錄 :全文檢索----coreseek

           

           

          1.    全文搜索與數據庫搜索的區別

          o          專為全文搜索優化,效率更高
          由于典型的數據庫系統要考慮用戶的“增刪改查”等多種復雜操作,因此其存取數據的方式需要考察綜合考慮各種應用;而全文搜索的數據存取方式 只考慮快速讀取,相比數據庫的查詢,要快10 倍或更多。(即使啟用了數據庫內置的全文搜索功能,這個結論仍成立)。

          o          支持復雜的查詢表達式
          數據庫系統的查詢,往往只支持“AND ” 或 "OR" 等有限的模式,而全文檢 索不但支持"AND" 、“OR ” 查詢,還支持“NOT ”、“近似”、 “整句”等多種查詢方式;同時相比數據庫系統,進行在一定范圍內查詢時也更高效

          o          支持按相關度排序
          數據庫查詢出的結果,往往按照數據庫內置的排序規則進行排序,往往只能按時間、按點擊等有效的排序規則進行;全文搜索除了能夠支持數據庫的 排序規則外,還支持按照結果的相關度排序,這往往會給訪問者帶來更大的便利。

          o          支持中文分詞
          數據庫提供的全文搜索功能往往不支持中文分詞(或僅提供二元切分),導致某些短語檢索不到或出現大量不相干的數據;中文全文檢索系統支持中 文分詞,進一步過濾了不相干的數據。

          2.    自建全文搜索與使用Google 等第三方網站提供的站內全文搜索的區別

          o          對網站設備有要求
          自建全文搜索往往需要站長有至少一臺獨立主機,而使用第三方提供的全文搜索對站點的要求低,虛擬主機即可;不過,出現全文搜索需求的站點通 常已經有自己的獨立主機了。

          o          索引更新更及時
          由于搜索服務在第三方托管,其往往只能按照一定的規則定期更新索引庫(往往是幾小時、甚至幾天才更新一次索引),您網站上的新出現的內容往 往不能及時被搜索到;
          使用自建全文搜索,可以保證您網站上新出現的內容可以”立即“被檢索到。

          o          更適應您的網站
          由于中文需要進行分詞的特性,導致沒有一套通用的詞庫可以適用于全部網站,要得到優秀的檢索結果需要定制一套適用于您網站的詞庫;
          采用第三方的搜索服務,您是無法修改第三方廠商的詞庫的,而使用自建全文搜索則無此問題。

          o          更有利于您網站的數據整合
          有些網站不止是論壇,往往還包括內容管理(CMS) 、商城等多種應用,而使用數據庫搜索往往需要用戶在各個系統 中都進行搜索才能找到內容;
          第三方的檢索無法區別各個系統的不同。而自建的全文搜索可以有效的區分各個數據來源的不同數據,真正做到一次搜索應有盡有,從而改善您網站 的訪問體驗,增加您網站的點擊率。

          o          避免您的訪問者遇到某些尷尬
          第三方的搜索結果頁面不是您可以控制修改的,有推薦一些可能會使您網站訪問者尷尬的搜索短語的可能。而使用自建全文搜索則完全無此問題。
          posted @ 2012-03-16 10:29 小馬歌 閱讀(814) | 評論 (1)編輯 收藏
          僅列出標題
          共95頁: First 上一頁 38 39 40 41 42 43 44 45 46 下一頁 Last 
           
          主站蜘蛛池模板: 嘉禾县| 安陆市| 林西县| 阜康市| 宝鸡市| 昌宁县| 牡丹江市| 鄂伦春自治旗| 林甸县| 宁津县| 织金县| 大竹县| 海宁市| 衡阳县| 高州市| 九龙城区| 扎鲁特旗| 翁牛特旗| 沙田区| 安西县| 荥经县| 崇义县| 阜新| 德格县| 扎兰屯市| 八宿县| 金寨县| 观塘区| 缙云县| 资阳市| 禄劝| 武夷山市| 博罗县| 当雄县| 临漳县| 扶沟县| 旺苍县| 左贡县| 渝北区| 田东县| 安多县|