• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            xiaoguozi's Blog
            Pay it forword - 我并不覺的自豪,我所嘗試的事情都失敗了······習慣原本生活的人不容易改變,就算現狀很糟,他們也很難改變,在過程中,他們還是放棄了······他們一放棄,大家就都是輸家······讓愛傳出去,很困難,也無法預料,人們需要更細心的觀察別人,要隨時注意才能保護別人,因為他們未必知道自己要什么·····

            shell操作數據庫:

             

              1. 超級用戶相關:

                     1. #進入數據庫admin

            use admin

                     2. #增加或修改用戶密碼

                      db.addUser('name','pwd')

                     3. #查看用戶列表

                      db.system.users.find()

                     4. #用戶認證

                      db.auth('name','pwd')

                     5. #刪除用戶

                      db.removeUser('name')

                     6. #查看所有用戶

                      show users

                     7. #查看所有數據庫

                      show dbs

                     8. #查看所有的collection

                      show collections

                     9. #查看各collection的狀態

                      db.printCollectionStats()

                    10. #查看主從復制狀態

                      db.printReplicationInfo()

                    11. #修復數據庫

                      db.repairDatabase()

                    12. #設置記錄profiling,0=off 1=slow 2=all

                      db.setProfilingLevel(1)

                    13. #查看profiling

                      show profile

                    14. #拷貝數據庫

                      db.copyDatabase('mail_addr','mail_addr_tmp')

                    15. #刪除collection

                      db.mail_addr.drop()

                    16. #刪除當前的數據庫

                      db.dropDatabase()

                   

               2. 增刪改

                     1. #存儲嵌套的對象

            db.foo.save({'name':'ysz','address':{'city':'beijing','post':100096},'phone':[138,139]})

             

                     2. #存儲數組對象

            db.user_addr.save({'Uid':'yushunzhi@sohu.com','Al':['test-1@sohu.com','test-2@sohu.com']})

             

                     3. #根據query條件修改,如果不存在則插入,允許修改多條記錄

                        db.foo.update({'yy':5},{'$set':{'xx':2}},upsert=true,multi=true)

                     4. #刪除yy=5的記錄

                        db.foo.remove({'yy':5})

                     5. #刪除所有的記錄

                        db.foo.remove()

             

               3. 索引

                     1. #增加索引:1(ascending),-1(descending)

                     2. db.foo.ensureIndex({firstname: 1, lastname: 1}, {unique: true});

                     3. #索引子對象

                     4. db.user_addr.ensureIndex({'Al.Em': 1})

                     5. #查看索引信息

                     6. db.foo.getIndexes()

                     7. db.foo.getIndexKeys()

                     8. #根據索引名刪除索引

                     9. db.user_addr.dropIndex('Al.Em_1')

             

              4. 查詢

                     1. #查找所有

                    2. db.foo.find()

                    3. #查找一條記錄

                    4. db.foo.findOne()

                    5. #根據條件檢索10條記錄

                    6. db.foo.find({'msg':'Hello 1'}).limit(10)

                    7. #sort排序

                    8. db.deliver_status.find({'From':'ixigua@sina.com'}).sort({'Dt',-1})

                     9. db.deliver_status.find().sort({'Ct':-1}).limit(1)

                    10. #count操作

                    11. db.user_addr.count()

                    12. #distinct操作,查詢指定列,去重復

                    13. db.foo.distinct('msg')

                    14. #”>=”操作

                    15. db.foo.find({"timestamp": {"$gte" : 2}})

                    16. #子對象的查找

                    17. db.foo.find({'address.city':'beijing'})

               5. 管理

                     1. #查看collection數據的大小

                     2. db.deliver_status.dataSize()

                     3. #查看colleciont狀態

                     4. db.deliver_status.stats()

                     5. #查詢所有索引的大小

                     6. db.deliver_status.totalIndexSize()

             

            5. advanced queries:高級查詢


            條件操作符 
            $gt : > 
            $lt : < 
            $gte: >= 
            $lte: <= 
            $ne : !=
            、<> 
            $in : in 
            $nin: not in 
            $all: all 
            $not:
            反匹配(1.3.3及以上版本) 

            查詢 name <> "bruce" and age >= 18 的數據 
            db.users.find({name: {$ne: "bruce"}, age: {$gte: 18}}); 

            查詢 creation_date > '2010-01-01' and creation_date <= '2010-12-31' 的數據 
            db.users.find({creation_date:{$gt:new Date(2010,0,1), $lte:new Date(2010,11,31)}); 

            查詢 age in (20,22,24,26) 的數據 
            db.users.find({age: {$in: [20,22,24,26]}}); 

            查詢 age取模10等于0 的數據 
            db.users.find('this.age % 10 == 0'); 
            或者 
            db.users.find({age : {$mod : [10, 0]}}); 

            匹配所有 
            db.users.find({favorite_number : {$all : [6, 8]}}); 
            可以查詢出{name: 'David', age: 26, favorite_number: [ 6, 8, 9 ] } 
            可以不查詢出{name: 'David', age: 26, favorite_number: [ 6, 7, 9 ] } 

            查詢不匹配name=B*帶頭的記錄 
            db.users.find({name: {$not: /^B.*/}}); 
            查詢 age取模10不等于0 的數據 
            db.users.find({age : {$not: {$mod : [10, 0]}}}); 

            #
            返回部分字段 
            選擇返回age和_id字段(_id字段總是會被返回
            db.users.find({}, {age:1}); 
            db.users.find({}, {age:3}); 
            db.users.find({}, {age:true}); 
            db.users.find({ name : "bruce" }, {age:1}); 
            0
            false, 非0為true 

            選擇返回age、address和_id字段 
            db.users.find({ name : "bruce" }, {age:1, address:1}); 

            排除返回age、address和_id字段 
            db.users.find({}, {age:0, address:false}); 
            db.users.find({ name : "bruce" }, {age:0, address:false}); 

            數組元素個數判斷 
            對于{name: 'David', age: 26, favorite_number: [ 6, 7, 9 ] }記錄 
            匹配db.users.find({favorite_number: {$size: 3}}); 
            不匹配db.users.find({favorite_number: {$size: 2}}); 

            $exists
            判斷字段是否存在 
            查詢所有存在name字段的記錄 
            db.users.find({name: {$exists: true}}); 
            查詢所有不存在phone字段的記錄 
            db.users.find({phone: {$exists: false}}); 

            $type
            判斷字段類型 
            查詢所有name字段是字符類型的 
            db.users.find({name: {$type: 2}}); 
            查詢所有age字段是整型的 
            db.users.find({age: {$type: 16}}); 

            對于字符字段,可以使用正則表達式 
            查詢以字母b或者B帶頭的所有記錄 
            db.users.find({name: /^b.*/i}); 

            $elemMatch(1.3.1
            及以上版本) 
            為數組的字段中匹配其中某個元素 

            Javascript查詢和$where查詢 
            查詢 age > 18 的記錄,以下查詢都一樣 
            db.users.find({age: {$gt: 18}}); 
            db.users.find({$where: "this.age > 18"}); 
            db.users.find("this.age > 18"); 
            f = function() {return this.age > 18} db.users.find(f); 

            排序sort() 
            以年齡升序asc 
            db.users.find().sort({age: 1}); 
            以年齡降序desc 
            db.users.find().sort({age: -1}); 

            限制返回記錄數量limit() 
            返回5條記錄 
            db.users.find().limit(5); 
            返回3條記錄并打印信息 
            db.users.find().limit(3).forEach(function(user) {print('my age is ' + user.age)}); 
            結果 
            my age is 18 
            my age is 19 
            my age is 20 

            限制返回記錄的開始點skip() 
            從第3條記錄開始,返回5條記錄(limit 3, 5) 
            db.users.find().skip(3).limit(5); 

            查詢記錄條數count() 
            db.users.find().count(); 
            db.users.find({age:18}).count(); 
            以下返回的不是5,而是user表中所有的記錄數量 
            db.users.find().skip(10).limit(5).count(); 
            如果要返回限制之后的記錄數量,要使用count(true)或者count(非0) 
            db.users.find().skip(10).limit(5).count(true); 

            分組group() 
            假設test表只有以下一條數據 
            { domain: "www.mongodb.org" 
            , invoked_at: {d:"2009-11-03", t:"17:14:05"} 
            , response_time: 0.05 
            , http_action: "GET /display/DOCS/Aggregation" 

            使用group統計test表11月份的數據count:count(*)、total_time:sum(response_time)、avg_time:total_time/count; 
            db.test.group( 
            { cond: {"invoked_at.d": {$gt: "2009-11", $lt: "2009-12"}} 
            , key: {http_action: true} 
            , initial: {count: 0, total_time:0} 
            , reduce: function(doc, out){ out.count++; out.total_time+=doc.response_time } 
            , finalize: function(out){ out.avg_time = out.total_time / out.count } 
            } ); 



            "http_action" : "GET /display/DOCS/Aggregation", 
            "count" : 1, 
            "total_time" : 0.05, 
            "avg_time" : 0.05 
            }
            ]


            windows 服務啟動:
            E:\APMServ5.2.6\MongoDb\bin>mongod --logpath E:\APMServ5.2.6\MongoDb\logs\MongoDB.log --logappend --dbpath E:\APMServ5.2.6\MongoDb\data --directoryperdb --serviceName MongoDB --install
            posted @ 2012-12-24 17:02 小果子 閱讀(247) | 評論 (0)編輯 收藏

            二. 分布式計算(Map/Reduce)

            分布式式計算,同樣是一個寬泛的概念,在這里,它狹義的指代,按Google Map/Reduce框架所設計的分布式框架。在Hadoop中,分布式文件系統,很大程度上,是為各種分布式計算需求所服務的。我們說分布式文件系統就是加了分布式的文件系統,類似的定義推廣到分布式計算上,我們可以將其視為增加了分布式支持的計算函數。 從計算的角度上看,Map/Reduce框架接受各種格式的鍵值對文件作為輸入,讀取計算后,最終生成自定義格式的輸出文件。而從分布式的角度上看,分布 式計算的輸入文件往往規模巨大,且分布在多個機器上,單機計算完全不可支撐且效率低下,因此Map/Reduce框架需要提供一套機制,將此計算擴展到無 限規模的機器集群上進行。依照這樣的定義,我們對整個Map/Reduce的理解,也可以分別沿著這兩個流程去看。。。
            在Map/Reduce框架中,每一次計算請求,被稱為作業。在分布式計算Map/Reduce框架中,為了完成這個作業,它進行兩步走的戰略,首先是將其拆分成若干個Map任務, 分配到不同的機器上去執行,每一個Map任務拿輸入文件的一部分作為自己的輸入,經過一些計算,生成某種格式的中間文件,這種格式,與最終所需的文件格式 完全一致,但是僅僅包含一部分數據。因此,等到所有Map任務完成后,它會進入下一個步驟,用以合并這些中間文件獲得最后的輸出文件。此時,系統會生成若 干個Reduce任務,同樣也是分配到不同的機器去執行,它的目標,就是將若干個Map任務生成的中間文件為匯總到最后的輸出文件中去。當然,這個匯總不總會像1 + 1 = 2那么直接了當,這也就是Reduce任務的價值所在。經過如上步驟,最終,作業完成,所需的目標文件生成。整個算法的關鍵,就在于增加了一個中間文件生成的流程,大大提高了靈活性,使其分布式擴展性得到了保證。。。

            I. 術語對照

            和分布式文件系統一樣,Google、Hadoop和....我,各執一種方式表述統一概念,為了保證其統一性,特有下表。。。

            文中翻譯 Hadoop術語 Google術語 相關解釋
            作業 Job Job 用戶的每一個計算請求,就稱為一個作業。
            作業服務器 JobTracker Master 用戶提交作業的服務器,同時,它還負責各個作業任務的分配,管理所有的任務服務器。
            任務服務器 TaskTracker Worker 任勞任怨的工蜂,負責執行具體的任務。
            任務 Task Task 每一個作業,都需要拆分開了,交由多個服務器來完成,拆分出來的執行單位,就稱為任務。
            備份任務 Speculative Task Buckup Task 每一個任務,都有可能執行失敗或者緩慢,為了降低為此付出的代價,系統會未雨綢繆的實現在另外的任務服務器上執行同樣一個任務,這就是備份任務。

            II. 基本架構

            與分布式文件系統類似,Map/Reduce的集群,也由三類服務器構成。其中作業服務器,在Hadoop中稱為Job Tracker,在Google論文中稱為Master。前者告訴我們,作業服務器是負責管理運行在此框架下所有作業的,后者告訴我們,它也是為各個作業分配任務的核心。與HDFS的主控服務器類似,它也是作為單點存在的,簡化了負責的同步流程。具體的負責執行用戶定義操作的,是任務服務器,每一個作業被拆分成很多的任務,包括Map任務Reduce任務等,任務是具體執行的基本單元,它們都需要分配到合適任務服務器上去執行,任務服務器一邊執行一邊向作業服務器匯報各個任務的狀態,以此來幫助作業服務器了解作業執行的整體情況,分配新的任務等等。。。
            除了作業的管理者執行者,還需要有一個任務的提交者,這就是客戶端。與分布式文件系統一樣,客戶端也不是一個單獨的進程,而是一組API,用戶需要自定義好自己需要的內容,經由客戶端相關的代碼,將作業及其相關內容和配置,提交到作業服務器去,并時刻監控執行的狀況。。。
            同作為Hadoop的實現,與HDFS的通信機制相同,Hadoop Map/Reduce也是用了協議接口來進行服務器間的交流。實現者作為RPC服務器,調用者經由RPC的代理進行調用,如此,完成大部分的通信,具體服 務器的架構,和其中運行的各個協議狀況,參見下圖。從圖中可以看到,與HDFS相比,相關的協議少了幾個,客戶端與任務服務器,任務服務器之間,都不再有 直接通信關系。這并不意味著客戶端就不需要了解具體任務的執行狀況,也不意味著,任務服務器之間不需要了解別家任務執行的情形,只不過,由于整個集群各機 器的聯系比HDFS復雜的多,直接通信過于的難以維系,所以,都統一由作業服務器整理轉發。另外,從這幅圖可以看到,任務服務器不是一個人在戰斗,它會像 孫悟空一樣招出一群寶寶幫助其具體執行任務。這樣做的好處,個人覺得,應該有安全性方面的考慮,畢竟,任務的代碼是用戶提交的,數據也是用戶指定的,這質 量自然良莠不齊,萬一碰上個搞破壞的,把整個任務服務器進程搞死了,就因小失大了。因此,放在單獨的地盤進行,愛咋咋地,也算是權責明確了。。。
            與分布式文件系統相比,Map/Reduce框架的還有一個特點,就是可定制性強。文件系統中很多的算法, 都是很固定和直觀的,不會由于所存儲的內容不同而有太多的變化。而作為通用的計算框架,需要面對的問題則要復雜很多,在各種不同的問題、不同的輸入、不同 的需求之間,很難有一種包治百病的藥能夠一招鮮吃遍天。作為Map/Reduce框架而言,一方面要盡可能的抽取出公共的一些需求,實現出來。更重要的, 是需要提供良好的可擴展機制,滿足用戶自定義各種算法的需求。Hadoop是由Java來實現的,因此通過反射來實現自定義的擴展,顯得比較小菜一碟了。 在JobConf類中,定義了大量的接口,這基本上是Hadoop Map/Reduce框架所有可定制內容的一次集中展示。在JobConf中,有大量set接口接受一個Class<? extends xxx>的參數,通常它都有一個默認實現的類,用戶如果不滿意,則可自定義實現。。。

            III. 計算流程

            如果一切都按部就班的進行,那么整個作業的計算流程,應該是作業的提交 -> Map任務的分配和執行 -> Reduce任務的分配和執行 -> 作業的完成。而在每個任務的執行中,又包含輸入的準備 -> 算法的執行 -> 輸出的生成,三個子步驟。沿著這個流程,我們可以很快的整理清晰整個Map/Reduce框架下作業的執行。。。

            1、作業的提交

            一個作業,在提交之前,需要把所有應該配置的東西都配置好,因為一旦提交到了作業服務器上,就陷入了完全自動化的流程,用戶除了觀望,最多也就能起一個監督作用,懲治一些不好好工作的任務。。。
            基本上,用戶在提交代碼階段,需要做的工作主要是這樣的:
            首先,書寫好所有自定的代碼,最起碼,需要有Map和Reduce的執行代碼。在Hadoop中,Map需要派生自Mapper<K1, V1, K2, V2>接口,Reduce需要派生自Reducer<K2, V2, K3, V3>接口。這里都是用的泛型,用以支持不同的鍵值類型。這兩個接口都僅有一個方法,一個是map,一個是reduce,這兩個方法都直接受四個參數,前兩個是輸入的相關的數據結構,第三個是作為輸出相關的數據結構,最后一個,是一個Reporter類的實例,實現的時候可以利用它來統計一些計數。除了這兩個接口,還有大量可以派生的接口,比如分割的Partitioner<K2, V2>接口。。。
            然后,需要書寫好主函數的代碼,其中最主要的內容就是實例化一個JobConf類的對象,然后調用其豐富的setXXX接口,設定好所需的內容,包括輸入輸出的文件路徑,Map和Reduce的類,甚至包括讀取寫入文件所需的格式支持類,等等。。。
            最后,調用JobClientrunJob方法,提交此JobConf對象。runJob方法會先行調用到JobSubmissionProtocol接口所定義的submitJob方法,將此作業,提交給作業服務器。接著,runJob開始循環,不停的調用JobSubmissionProtocol的getTaskCompletionEvents方法,獲得TaskCompletionEvent類的對象實例,了解此作業各任務的執行狀況。。。

            2、Map任務的分配

            當一個作業提交到了作業服務器上,作業服務器會生成若干個Map任務,每一個Map任務,負責將一部分的輸入轉換成格式與最終格式相同的中間文件。通常一個作業的輸入都是基于分布式文件系統的文件(當然在單機環境下,文件系統單機的也可以...),因為,它可以很天然的和分布式的計算產生聯系。而對于一個Map任務而言,它的輸入往往是輸入文件的一個數據塊,或者是數據塊的一部分,但通常,不跨數據塊。因為,一旦跨了數據塊,就可能涉及到多個服務器,帶來了不必要的復雜性。。。
            當一個作業,從客戶端提交到了作業服務器上,作業服務器會生成一個JobInProgress對象,作為與 之對應的標識,用于管理。作業被拆分成若干個Map任務后,會預先掛在作業服務器上的任務服務器拓撲樹。這是依照分布式文件數據塊的位置來劃分的,比如一 個Map任務需要用某個數據塊,這個數據塊有三份備份,那么,在這三臺服務器上都會掛上此任務,可以視為是一個預分配。。。
            關于任務管理和分配的大部分的真實功能和邏輯的實現,JobInProgress則依托JobInProgressListenerTaskScheduler的子類。TaskScheduler,顧名思義是用于任務分配的策略類(為了簡化描述,用它代指所有TaskScheduler的子類...)。它會掌握好所有作業的任務信息,其assignTasks函數,接受一個TaskTrackerStatus作為參數,依照此任務服務器的狀態和現有的任務狀況,為其分配新的任務。而為了掌握所有作業相關任務的狀況,TaskScheduler會將若干個JobInProgressListener注冊到JobTracker中去,當有新的作業到達、移除或更新的時候,JobTracker會告知給所有的JobInProgressListener,以便它們做出相應的處理。。。
            任務分配是一個重要的環節,所謂任務分配,就是將合適作業的合適任務分配到合適的服務器上。不難看出,里面 蘊含了兩個步驟,先是選擇作業,然后是在此作業中選擇任務。和所有分配工作一樣,任務分配也是一個復雜的活。不良好的任務分配,可能會導致網絡流量增加、 某些任務服務器負載過重效率下降,等等。不僅如此,任務分配還是一個無一致模式的問題,不同的業務背景,可能需要不同的算法才能滿足需求。因此,在 Hadoop中,有很多TaskScheduler的子類,像Facebook,Yahoo,都為其貢獻出了自家用的算法。在Hadoop中,默認的任務 分配器,是JobQueueTaskScheduler類。它選擇作業的基本次序是:Map Clean Up Task(Map任務服務器的清理任務,用于清理相關的過期的文件和環境...) -> Map Setup Task(Map任務服務器的安裝任務,負責配置好相關的環境...) -> Map Tasks -> Reduce Clean Up Task -> Reduce Setup Task -> Reduce Tasks。在這個前提下,具體到Map任務的分配上來。當一個任務服務器工作的游刃有余,期待獲得新的任務的時候,JobQueueTaskScheduler會按照各個作業的優先級,從最高優先級的作業開 始分配。每分配一個,還會為其留出余量,已被不時之需。舉一個例子:系統目前有優先級3、2、1的三個作業,每個作業都有一個可分配的Map任務,一個任 務服務器來申請新的任務,它還有能力承載3個任務的執行,JobQueueTaskScheduler會先從優先級3的作業上取一個任務分配給它,然后再 留出一個1任務的余量。此時,系統只能在將優先級2作業的任務分配給此服務器,而不能分配優先級1的任務。這樣的策略,基本思路就是一切為高優先級的作業服務,優先分配不說,分配了好保留有余力以備不時之需,如此優待,足以讓高優先級的作業喜極而泣,讓低優先級的作業感慨既生瑜何生亮,甚至是活活餓死。。。
            確定了從哪個作業提取任務后,具體的分配算法,經過一系列的調用,最后實際是由JobInProgressfindNewMapTask函數完成的。它的算法很簡單,就是盡全力為此服務器非配且盡可能好的分配任務, 也就是說,只要還有可分配的任務,就一定會分給它,而不考慮后來者。作業服務器會從離它最近的服務器開始,看上面是否還掛著未分配的任務(預分配上的), 從近到遠,如果所有的任務都分配了,那么看有沒有開啟多次執行,如果開啟,考慮把未完成的任務再分配一次(后面有地方詳述...)。。。
            對于作業服務器來說,把一個任務分配出去了,并不意味著它就徹底解放,可以對此任務可以不管不顧了。因為任務可以在任務服務器上執行失敗,可能執行緩慢,這都需要作業服務器幫助它們再來一次。因此在Task中,記錄有一個TaskAttemptID,對于任務服務器而言,它們每次跑的,其實都只是一個Attempt而已,Reduce任務只需要采信一個的輸出,其他都算白忙乎了。。。

            3、Map任務的執行

            與HDFS類似,任務服務器是通過心跳消息,向作業服務器匯報此時此刻其上各個任務執行的狀況,并向作業服務器申請新的任務的。具體實現,是TaskTracker調用InterTrackerProtocol協議的heartbeat方法來做的。這個方法接受一個TaskTrackerStatus對象作為參數,它描述了此時此任務服務器的狀態。當其有余力接受新的任務的時候,它還會傳入acceptNewTasks為true的參數,表示希望作業服務器委以重任。JobTracker接收到相關的參數后,經過處理,會返回一個HeartbeatResponse對象。這個對象中,定義了一組TaskTrackerAction,用于指導任務服務器進行下一步的工作。系統中已定義的了一堆其TaskTrackerAction的子類,有的對攜帶的參數進行了擴充,有的只是標明了下ID,具體不詳寫了,一看便知。。。
            當TaskTracker收到的TaskTrackerAction中,包含了LaunchTaskAction,它會開始執行所分配的新的任務。在TaskTracker中,有一個TaskTracker.TaskLauncher線程(確切的說是兩個,一個等Map任務,一個等Reduce任務),它們在癡癡的守候著新任務的來到。一旦等到了,會最終調用到Task的createRunner方法,構造出一個TaskRunner對象,新建一個線程來執行。對于一個Map任務,它對應的Runner是TaskRunner的子類MapTaskRunner, 不過,核心部分都在TaskRunner的實現內。TaskRunner會先將所需的文件全部下載并拆包好,并記錄到一個全局緩存中,這是一個全局的目 錄,可以供所有此作業的所有任務使用。它會用一些軟鏈接,將一些文件名鏈接到這個緩存中來。然后,根據不同的參數,配置出一個JVM執行的環境,這個環境 與JvmEnv類的對象對應。
            接著,TaskRunner會調用JvmManagerlaunchJvm方 法,提交給JvmManager處理。JvmManager用于管理該TaskTracker上所有運行的Task子進程。在目前的實現中,嘗試的是池化 的方式。有若干個固定的槽,如果槽沒有滿,那么就啟動新的子進程,否則,就尋找idle的進程,如果是同Job的直接放進去,否則殺死這個進程,用一個新 的進程代替。每一個進程都是由JvmRunner來管理的,它也是位于單獨線程中的。但是從實現上看,這個機制好像沒有部署開,子進程是死循環等待,而不 會阻塞在父進程的相關線程上,父線程的變量一直都沒有個調整,一旦分配,始終都處在繁忙的狀況了。
            真實的執行載體,是Child,它包含一個 main函數,進程執行,會將相關參數傳進來,它會拆解這些參數,并且構造出相關的Task實例,調用其run函數進行執行。每一個子進程,可以執行指定 個數量的Task,這就是上面所說的池化的配置。但是,這套機制在我看來,并沒有運行起來,每個進程其實都沒有機會不死而執行新的任務,只是傻傻的等待進 程池滿,而被一刀斃命。也許是我老眼昏花,沒看出其中實現的端倪。。。

            4、Reduce任務的分配與執行

            比之Map任務,Reduce的分配及其簡單,基本上是所有Map任務完成了,有空閑的任務服務器,來了就給分配一個Job任務。因為Map任 務的結果星羅棋布,且變化多端,真要搞一個全局優化的算法,絕對是得不償失。而Reduce任務的執行進程的構造和分配流程,與Map基本完全的一致,沒 有啥可說的了。。。
            但其實,Reduce任務與Map任務的最大不同,是Map任務的文件都在本地隔著,而Reduce任務需要到處采集。這個流程是作業服務器經 由此Reduce任務所處的任務服務器,告訴Reduce任務正在執行的進程,它需要的Map任務執行過的服務器地址,此Reduce任務服務器會于原 Map任務服務器聯系(當然本地就免了...),通過FTP服務,下載過來。這個隱含的直接數據聯系,就是執行Reduce任務與執行Map任務最大的不 同了。。。

            5、作業的完成

            當所有Reduce任務都完成了,所需數據都寫到了分布式文件系統上,整個作業才正式完成了。此中,涉及到很多的類,很多的文件,很多的服務器,所以說起來很費勁,話說,一圖解千語,說了那么多,我還是畫兩幅圖,徹底表達一下吧。。。
            首先,是一個時序圖。它模擬了一個由3個Map任務和1個Reduce任務構成的作業執行流程。我們可以看到,在執行的過程中,只要有人太慢, 或者失敗,就會增加一次嘗試,以此換取最快的執行總時間。一旦所有Map任務完成,Reduce開始運作(其實,不一定要這樣的...),對于每一個 Map任務來說,只有執行到Reduce任務把它上面的數據下載完成,才算成功,否則,都是失敗,需要重新進行嘗試。。。
            而第二副圖,不是我畫的,就不轉載了,參見這里, 它描述了整個Map/Reduce的服務器狀況圖,包括整體流程、所處服務器進程、輸入輸出等,看清楚這幅圖,對Map/Reduce的基本流程應該能完 全跑通了。有這幾點,可能圖中描述的不夠清晰需要提及一下,一個是在HDFS中,其實還有日志文件,圖中沒有標明;另一個是步驟5,其實是由 TaskTracker主動去拉取而不是JobTracker推送過來的;還有步驟8和步驟11,創建出來的MapTask和ReduceTask,在 Hadoop中都是運行在獨立的進程上的。。。

            IV. Map任務詳請

            從上面,可以了解到整個Map和Reduce任務的整體流程,而后面要啰嗦的,是具體執行中的細節。Map任務的輸入,是分布式文件系統上的, 包含鍵值對信息的文件。為了給每一個Map任務指定輸入,我們需要掌握文件格式把它分切成塊,并從每一塊中分離出鍵值信息。在HDFS中,輸入的文件格 式,是由InputFormat<K, V>類來表示的,在JobConf中,它的默認值是TextInputFormat類(見getInputFormat),此類是特化的FileInputFormat<LongWritable, Text>子類,而FileInputFormat<K, V>正是InputFormat<K, V>的子類。通過這樣的關系我們可以很容易的理解,默認的文件格式是文本文件,且鍵是LongWritable類型(整形數),值是Text類型(字符串)。僅僅知道文件類型是不夠的,我們還需要將文件中的每一條數據,分離成鍵值對,這個工作,是RecordReader<K, V>來做的。在TextInputFormat的getRecordReader方法中我們可以看到,與TextInputFormat默認配套使用的,是LineRecordReader類,是特化的RecordReader<LongWritable, Text>的子類,它將每一行作為一個記錄,起始的位置作為鍵,整行的字符串作為值。有了格式,分出了鍵值,還需要切開分給每一個Map任務。每一個Map任務的輸入用InputSplit接口表示,對于一個文件輸入而言,其實現是FileSplit,它包含著文件名、起始位置、長度和存儲它的一組服務器地址。。。
            當Map任務拿到所屬的InputSplit后,就開始一條條讀取記錄,并調用用于定義的Mapper,進行計算(參見MapRunner<K1, V1, K2, V2>和MapTask的run方法),然后,輸出。MapTask會傳遞給Mapper一個OutputCollector<K, V>對象,作為輸出的數據結構。它定義了一個collect的函數,接受一個鍵值對。在MapTask中,定義了兩個OutputCollector的子類,一個是MapTask.DirectMapOutputCollector<K, V>,人如其名,它的實現確實很Direct,直截了當。它會利用一個RecordWriter<K, V>對象,collect一調用,就直接調用RecordWriter<K, V>的write方法,寫入本地的文件中去。如果覺著RecordWriter<K, V>出現的很突兀,那么看看上一段提到的RecordReader<K, V>,基本上,數據結構都是對應著的,一個是輸入一個是輸出。輸出很對稱也是由RecordWriter<K, V>和OutputFormat<K, V>來協同完成的,其默認實現是LineRecordWriter<K, V>和TextOutputFormat<K, V>,多么的眼熟啊。。。
            除了這個非常直接的實現之外,MapTask中還有一個復雜的多的實現,是MapTask.MapOutputBuffer<K extends Object, V extends Object>。有道是簡單壓倒一切,那為什么有很簡單的實現,要琢磨一個復雜的呢。原因在于,看上去很美的往往帶著刺,簡單的輸出實現,每調用一 次collect就寫一次文件,頻繁的硬盤操作很有可能導致此方案的低效。為了解決這個問題,這就有了這個復雜版本,它先開好一段內存做緩存,然后制定一個比例做閾值,開一個線程監控此緩存。collect來的內容,先寫到緩存中,當監控線程發現緩存的內容比例超過閾值,掛起所有寫入操作,建一個新的文件,把緩存的內容批量刷到此文件中去,清空緩存,重新開放,接受繼續collect。。。
            為什么說是刷到文件中去呢。因為這不是一個簡單的照本宣科簡單復制的過程,在寫入之前,會先將緩存中的內存,經過排序、合并器 (Combiner)統計之后,才會寫入。如果你覺得Combiner這個名詞聽著太陌生,那么考慮一下Reducer,Combiner也就是一個 Reducer類,通過JobConf的setCombinerClass進行設置,在常用的配置中,Combiner往往就是用用戶為Reduce任務 定義的那個Reducer子類。只不過,Combiner只是服務的范圍更小一些而已,它在Map任務執行的服務器本地,依照Map處理過的那一小部分數 據,先做一次Reduce操作,這樣,可以壓縮需要傳輸內容的大小,提高速度。每一次刷緩存,都會開一個新的文件,等此任務所有的輸入都處理完成后,就有 了若干個有序的、經過合并的輸出文件。系統會將這些文件搞在一起,再做一個多路的歸并外排,同時使用合并器進行合并,最終,得到了唯一的、有序的、經過合 并的中間文件(注:文件數量等同于分類數量,在不考慮分類的時候,簡單的視為一個...)。它,就是Reduce任務夢寐以求的輸入文件。。。
            除了做合并,復雜版本的OutputCollector,還具有分類的功能。分類,是通過Partitioner<K2, V2>來定義的,默認實現是HashPartitioner<K2, V2>,作業提交者可以通過JobConf的setPartitionerClass來自定義。分類的含義是什么呢,簡單的說,就是將Map任務的輸出,劃分到若干個文件中(通常與Reduce任務數目相等),使得每一個Reduce任務,可以處理某一類文件。這樣的好處是大大的,舉一個例子說明一下。比如有一個作業是進行單詞統計的,其Map任務的中間結果應該是以單詞為鍵,以單詞數量為值的文件。如果這時候只有一個Reduce任務,那還好說,從全部的Map任務那里收集文件過來,分別統計得到最后的輸出文件就好。但是,如果單Reduce任務無法承載此負載或效率太低,就需要多個Reduce任務并行執行。此時,再沿用之前的模式就有了問題。每個Reduce任務從一部分Map任務那 里獲得輸入文件,但最終的輸出結果并不正確,因為同一個單詞可能在不同的Reduce任務那里都有統計,需要想方法把它們統計在一起才能獲得最后結果,這 樣就沒有將Map/Reduce的作用完全發揮出來。這時候,就需要用到分類。如果此時有兩個Reduce任務,那么將輸出分成兩類,一類存放字母表排序 較高的單詞,一類存放字母表排序低的單詞,每一個Reduce任務從所有的Map任務那里獲取一類的中間文件,得到自己的輸出結果。最終的結果,只需要把各個Reduce任務輸出的,拼接在一起就可以了。本質上,這就是將Reduce任務的輸入,由垂直分割,變成了水平分割。Partitioner的作用,正是接受一個鍵值,返回一個分類的序號。它會在從緩存刷到文件之前做這個工作,其實只是多了一個文件名的選擇而已,別的邏輯都不需要變化。。。
            除了緩存、合并、分類等附加工作之外,復雜版本的OutputCollector還支持錯誤數據的跳過功能,在后面分布式將排錯的時候,還會提及,標記一下,按下不表。。。

            V. Reduce任務詳情

            理論上看,Reduce任務的整個執行流程要比Map任務更為的羅嗦一些,因為,它需要收集輸入文件,然后才能進行處理。Reduce任務,主要有這么三個步驟:Copy、SortReduce(參見ReduceTask的run方法)。所謂Copy,就是從執行各個Map任務的服務器那里,收羅到本地來??截惖娜蝿?,是由ReduceTask.ReduceCopier類來負責,它有一個內嵌類,叫MapOutputCopier, 它會在一個單獨的線程內,負責某個Map任務服務器上文件的拷貝工作。遠程拷貝過來的內容(當然也可以是本地了...),作為MapOutput對象存 在,它可以在內存中也可以序列化在磁盤上,這個根據內存使用狀況來自動調節。整個拷貝過程是一個動態的過程,也就是說它不是一次給好所有輸入信息就不再變 化了。它會不停的調用TaskUmbilicalProtocol協議的getMapCompletionEvents方 法,向其父TaskTracker詢問此作業個Map任務的完成狀況(TaskTracker要向JobTracker詢問后再轉告給它...)。當獲取 到相關Map任務執行服務器的信息后,都會有一個線程開啟,做具體的拷貝工作。同時,還有一個內存Merger線程和一個文件Merger線程在同步工 作,它們將新鮮下載過來的文件(可能在內存中,簡單的統稱為文件...),做著歸并排序,以此,節約時間,降低輸入文件的數量,為后續的排序工作減 負。。。
            Sort,排序工作,就相當于上述排序工作的一個延續。它會在所有的文件都拷貝完畢后進行,因為雖然同步有做著歸并的工作,但可能留著尾巴,沒 做徹底。經過這一個流程,該徹底的都徹底了,一個嶄新的、合并了所有所需Map任務輸出文件的新文件,誕生了。而那些千行萬苦從其他各個服務器網羅過來的 Map任務輸出文件,很快的結束了它們的歷史使命,被掃地出門一掃而光,全部刪除了。。。
            所謂好戲在后頭,Reduce任務的最后一個階段,正是Reduce本身。它也會準備一個OutputCollector收集輸出,與MapTask不同,這個OutputCollector更為簡單,僅僅是打開一個RecordWriter,collect一次,write一次。最大的不同在于,這次傳入RecordWriter的文件系統,基本都是分布式文件系統, 或者說是HDFS。而在輸入方面,ReduceTask會從JobConf那里調用一堆getMapOutputKeyClass、 getMapOutputValueClass、getOutputKeyComparator等等之類的自定義類,構造出Reducer所需的鍵類型, 和值的迭代類型Iterator(一個鍵到了這里一般是對應一組值)。具體實現頗為拐彎抹角,建議看一下Merger.MergeQueue,RawKeyValueIterator,ReduceTask.ReduceValuesIterator等等之類的實現。有了輸入,有了輸出,不斷循環調用自定義的Reducer,最終,Reduce階段完成。。。

            VI. 分布式支持

            1、服務器正確性保證

            Hadoop Map/Reduce服務器狀況和HDFS很類似,由此可知,救死扶傷的方法也是大同小異。廢話不多說了,直接切正題。同作為客戶端,Map /Reduce的客戶端只是將作業提交,就開始搬個板凳看戲,沒有占茅坑的行動。因此,一旦它掛了,也就掛了,不傷大雅。而任務服務器,也需要隨時與作業 服務器保持心跳聯系,一旦有了問題,作業服務器可以將其上運行的任務,移交給它人完成。作業服務器,作為一個單點,非常類似的是利用還原點(等同于 HDFS的鏡像)和歷史記錄(等同于HDFS的日志),來進行恢復。其上,需要持久化用于恢復的內容,包含作業狀況、任務狀況、各個任務嘗試的工作狀況 等。有了這些內容,再加上任務服務器的動態注冊,就算挪了個窩,還是很容易恢復的。JobHistory是歷史記錄相 關的一個靜態類,本來,它也就是一個干寫日志活的,只是在Hadoop的實現中,對日志的寫入做了面向對象的封裝,同時又大量用到觀察者模式做了些嵌入, 使得看起來不是那么直觀。本質上,它就是打開若干個日志文件,利用各類接口來往里面寫內容。只不過,這些日志,會放在分布式文件系統中,就不需要像 HDFS那樣,來一個SecondXXX隨時候命,由此可見,有巨人在腳下踩著,真好。JobTracker.RecoveryManager類是作業服 務器中用于進行恢復相關的事情,當作業服務器啟動的時候,會調用其recover方法,恢復日志文件中的內容。其中步驟,注釋中寫的很清楚,請自行查 看。。。

            2、任務執行的正確和速度

            整個作業流程的執行,秉承著木桶原理。執行的最慢的Map任務和Reduce任務,決定了系統整體執行時間(當然,如果執行時間在整個流程中占 比例很小的話,也許就微不足道了...)。因此,盡量加快最慢的任務執行速度,成為提高整體速度關鍵。所使用的策略,簡約而不簡單,就是一個任務多次執行。 當所有未執行的任務都分配出去了,并且先富起來的那部分任務已經完成了,并還有任務服務器孜孜不倦的索取任務的時候,作業服務器會開始炒剩飯,把那些正在 吭哧吭哧在某個服務器上慢慢執行的任務,再把此任務分配到一個新的任務服務器上,同時執行。兩個服務器各盡其力,成王敗寇,先結束者的結果將被采納。這樣 的策略,隱含著一個假設,就是我們相信,輸入文件的分割算法是公平的,某個任務執行慢,并不是由于這個任務本身負擔太重,而是由于服務器不爭氣負擔太重能 力有限或者是即將撒手西去,給它換個新環境,人挪死樹挪活事半功倍。。。
            當然,肯定有哽咽的任務,不論是在哪個服務器上,都無法順利完成。這就說明,此問題不在于服務器上,而是任務本身天資有缺憾。缺憾在何處?每個作業,功能 代碼都是一樣的,別的任務成功了,就是這個任務不成功,很顯然,問題出在輸入那里。輸入中有非法的輸入條目,導致程序無法辨識,只能揮淚惜別。說到這里, 解決策略也浮出水面了,三十六計走位上,惹不起,還是躲得起的。在MapTask中的 MapTask.SkippingRecordReader<K, V>和ReduceTask里的 ReduceTask.SkippingReduceValuesIterator<KEY,VALUE>,都是用于干這個事情的。它們的原 理很簡單,就是在讀一條記錄前,把當前的位置信息,封裝成SortedRanges.Range對象,經由Task的 reportNextRecordRange方法提交到TaskTracker上去。TaskTracker會把這些內容,擱在TaskStatus對象 中,隨著心跳消息,匯報到JobTracker上面。這樣,作業服務器就可以隨時隨刻了解清楚,每個任務正讀取在那個位置,一旦出錯,再次執行的時候,就 在分配的任務信息里面添加一組SortedRanges信息。MapTask或ReduceTask讀取的時候,會看一下這些區域,如果當前區域正好處于 上述雷區,跳過不讀。如此反復,正可謂,道路曲折,前途光明啊。。。

            VII. 總結

            對于Map/Reduce而言,真正的困難,在于提高其適應能力,打造一款能夠包治百病的執行框架。Hadoop已經做得很好了,但只有真正搞清楚了整個流程,你才能幫助它做的更好。。。
            posted @ 2012-12-19 19:18 小果子 閱讀(1312) | 評論 (0)編輯 收藏
            本文圖片來自 Ricky Ho 的博文 MongoDB 構架MongoDB Architecture),這是個一聽就感覺很寬泛的話題,但是作者在文章中確實對 MongoDB 由內至外的架構進行了剖析。本文截取了其文章中的幾張重點架構示意圖片進行簡單描述。希望對大家有用。
            MongoDB 數據文件內部結構

            1. MongoDB 在數據存儲上按命名空間來劃分,一個 collection 是一個命名空間,一個索引也是一個命名空間
            2. 同一個命名空間的數據被分成很多個 Extent,Extent 之間使用雙向鏈表連接
            3. 在每一個 Extent 中,保存了具體每一行的數據,這些數據也是通過雙向鏈接連接的
            4. 每一行數據存儲空間不僅包括數據占用空間,還可能包含一部分附加空間,這使得在數據 update 變大后可以不移動位置
            5. 索引以 BTree 結構實現
              在 MongoDB 中實現事務

            眾所周知,MongoDB 只支持對單行記錄的原子性修改,并不支持對多行數據的原子操作。但是通過上圖中的變態操作,實際你也可以自己實現事務。其步驟如圖所未:
            • 第 1 步:先記錄一條事務記錄,將要修改的多行記錄的修改值寫到里面,并設置其狀態為 init(如果這時候操作中斷,那么在重新啟動時,會判斷到他處于 init 狀態,從而將其保存的多行修改操作應用到具體的行上)
            • 第 2 步:然后更新具體要修改的行,將剛才寫的事務記錄的標識寫到它的 tran 字段中
            • 第 3 步:將事務記錄的狀態從 init 變成 pending(如果在這時候操作中斷,那么在重新啟動時,會判斷到它的狀態是 pending 的,這時候查看其所有對應的多條要修改的記錄,如果其 tran 有值,那么就進行第 4 步,如果沒值,說明第 4 步已經執行過了,直接將其狀態從 pending 變成 commited 了就行)
            • 第 4 步:將需要修改的多條記錄的相應值修改了,并且 unset 掉之前的 tran 字段
            • 第 5 步:將事務記錄那一條的狀態從 pending 變成 commited,事務完成

                    其實上面的步驟并不罕見,在支持事務的 DBMS 中,其事務原子性提交的保證大多都與上面類似。其實事務記錄的 tran 那條記錄,就類似于這些 DBMS 中的 redolog 一樣。

              MongoDB 數據同步

            上圖是 MongoDB 采用 Replica Sets 模式的同步流程
            • 紅色箭頭表示寫操作寫到 Primary 上,然后異步同步到多個 Secondary 上
            • 藍色箭頭表示讀操作可以從 Primary 或 Secondary 任意一個上讀
            • 各個 Primary 與 Secondary 之間一直保持心跳同步檢測,用于判斷 Replica Sets 的狀態

                    分片機制


            • MongoDB 的分片是指定一個分片 key 來進行,數據按范圍分成不同的 chunk,每個 chunk 的大小有限制
            • 有多個分片節點保存這些 chunk,每個節點保存一部分的 chunk
            • 每一個分片節點都是一個 Replica Sets,這樣保證數據的安全性
            • 當一個 chunk 超過其限制的最大體積時,會分裂成兩個小的 chunk
            • 當 chunk 在分片節點中分布不均衡時,會引發 chunk 遷移操作

                    服務器角色

             

             上面講了分片的標準,下面是具體在分片時的幾種節點角色
            • 客戶端訪問路由節點 mongos 來進行數據讀寫
            • config 服務器保存了兩個映射關系,一個是 key 值的區間對應哪一個 chunk 的映射關系,另一個是 chunk 存在哪一個分片節點的映射關系
            • 路由節點通過 config 服務器獲取數據信息,通過這些信息,找到真正存放數據的分片節點進行對應操作
            • 路由節點還會在寫操作時判斷當前 chunk 是否超出限定大小,如果超出,就分列成兩個 chunk
            • 對于按分片 key 進行的查詢和 update 操作來說,路由節點會查到具體的 chunk 然后再進行相關的工作
            • 對于不按分片 key 進行的查詢和 update 操作來說,mongos 會對所有下屬節點發送請求然后再對返回結果進行合并

                    更多詳細內容請看原文:MongoDB Architecture

             

            posted @ 2012-12-19 11:52 小果子 閱讀(419) | 評論 (0)編輯 收藏

            Mongo是一個高性能,開源,無模式的文檔型數據庫,它在許多場景下可用于替代傳統的關系型數據庫或鍵/值存儲方式。Mongo使用C++開發,提供了以下功能:

            ◆面向集合的存儲:適合存儲對象及JSON形式的數據。

            ◆動態查詢:Mongo支持豐富的查詢表達式。查詢指令使用JSON形式的標記,可輕易查詢文檔中內嵌的對象及數組。

            ◆完整的索引支持:包括文檔內嵌對象及數組。Mongo的查詢優化器會分析查詢表達式,并生成一個高效的查詢計劃。

            ◆查詢監視:Mongo包含一個監視工具用于分析數據庫操作的性能。

            ◆復制及自動故障轉移:Mongo數據庫支持服務器之間的數據復制,支持主-從模式及服務器之間的相互復制。復制的主要目標是提供冗余及自動故障轉移。

            ◆高效的傳統存儲方式:支持二進制數據及大型對象(如照片或圖片)。

            ◆自動分片以支持云級別的伸縮性(處于早期alpha階段):自動分片功能支持水平的數據庫集群,可動態添加額外的機器。

            MongoDB的主要目標是在鍵/值存儲方式(提供了高性能和高度伸縮性)以及傳統的RDBMS系統(豐富的功能)架起一座橋梁,集兩者的優勢于一身。根據官方網站的描述,Mongo適合用于以下場景:

            ◆網站數據:Mongo非常適合實時的插入,更新與查詢,并具備網站實時數據存儲所需的復制及高度伸縮性。

            ◆緩存:由于性能很高,Mongo也適合作為信息基礎設施的緩存層。在系統重啟之后,由Mongo搭建的持久化緩存層可以避免下層的數據源過載。

            ◆大尺寸,低價值的數據:使用傳統的關系型數據庫存儲一些數據時可能會比較昂貴,在此之前,很多時候程序員往往會選擇傳統的文件進行存儲。

            ◆高伸縮性的場景:Mongo非常適合由數十或數百臺服務器組成的數據庫。Mongo的路線圖中已經包含對MapReduce引擎的內置支持。

            ◆用于對象及JSON數據的存儲:Mongo的BSON數據格式非常適合文檔化格式的存儲及查詢。

            自然,MongoDB的使用也會有一些限制,例如它不適合:

            ◆高度事務性的系統:例如銀行或會計系統。傳統的關系型數據庫目前還是更適用于需要大量原子性復雜事務的應用程序。

            ◆傳統的商業智能應用:針對特定問題的BI數據庫會對產生高度優化的查詢方式。對于此類應用,數據倉庫可能是更合適的選擇。

            ◆需要SQL的問題

            MongoDB支持OS X、Linux及Windows等操作系統,并提供了Python,PHP,Ruby,Java及C++語言的驅動程序,社區中也提供了對Erlang及.NET等平臺的驅動程序。

            posted @ 2012-12-19 11:34 小果子 閱讀(351) | 評論 (0)編輯 收藏
            今天弄了下android webview下的幾個頁面。原先以為android 4+把 webview的viewport屬性忽略掉了。
            但是今天弄了下。加了個 authorizationView.getSettings().setUseWideViewPort(true);
            viewport 的幾個屬性重新起作用。(測試環境,4.0+的幾個版本)

            但是又遇到幾個問題,就是html里有input的時候。獲取焦點的時候,android會重新縮放到原來模式,看源碼:
            /**
                 * Called in response to a message from webkit telling us that the soft
                 * keyboard should be launched.
                 
            */
                
            private void displaySoftKeyboard(boolean isTextView) {
                    InputMethodManager imm = (InputMethodManager)
                            getContext().getSystemService(Context.INPUT_METHOD_SERVICE);

                    
            // bring it back to the default level scale so that user can enter text
                    boolean zoom = mZoomManager.getScale() < mZoomManager.getDefaultScale();
                    
            if (zoom) {
                        mZoomManager.setZoomCenter(mLastTouchX, mLastTouchY);
                        mZoomManager.setZoomScale(mZoomManager.getDefaultScale(), false);
                    }
                    
            if (isTextView) {
                        rebuildWebTextView();
                        
            if (inEditingMode()) {
                            imm.showSoftInput(mWebTextView, 0, mWebTextView.getResultReceiver());
                            
            if (zoom) {
                                didUpdateWebTextViewDimensions(INTERSECTS_SCREEN);
                            }
                            
            return;
                        }
                    }
                    
            // Used by plugins and contentEditable.
                    
            // Also used if the navigation cache is out of date, and
                    
            // does not recognize that a textfield is in focus.  In that
                    
            // case, use WebView as the targeted view.
                    
            // see http://b/issue?id=2457459
                    imm.showSoftInput(this0);
                }
            從源碼可以看到,webview當要彈起鍵盤的時候,會判定當前的縮放比例與默認大小(我測試了下,我自己的版本的默認值是1.5),
            當你網頁viewport設置initial-scale=0.5時,當input 獲取焦點的時候,android會放大到原來模式,不是我們想要的,網上查了下相關,
            有個解決方案:
            wv.setOnFocusChangeListener(new View.OnFocusChangeListener() {

                    @Override
                    
            public void onFocusChange(View v, boolean hasFocus) {
                        
            // TODO Auto-generated method stub
                        try {
                            Field defaultScale = WebView.class
                                    .getDeclaredField("mDefaultScale");
                            defaultScale.setAccessible(true);
                            
            float _s = defaultScale.getFloat(wv);
                            defaultScale.setFloat(wv, scale);
                            
            float x = wv.getScale();
                            
            int i = 0;
                        } catch (Exception e) {
                            e.printStackTrace();
                            
            try {
                                Field defaultZoom = WebView.class
                                        .getDeclaredField("mZoomManager");
                                defaultZoom.setAccessible(true);
                                Field defaultScale = defaultZoom.getType()
                                        .getDeclaredField("mDefaultScale");
                                defaultScale.setAccessible(true);
                                defaultScale.setFloat(defaultZoom.get(wv), scale);
                            } catch (Exception ee) {
                                ee.printStackTrace();
                            }
                        }
                    }
                });
            但是作者碰到另外一個問題,引用自原話:
            as it showed, I using reflect to find the field 'mDefaultScale' to control the WebView.
            But it doesn
            't work on Android 4.1.1 (Google Nexus), and I catch an exception —— java.lang.NoSuchFieldException: mDefaultScale.
            Then I list the fileds and found the framework source seems being changed(I can only reach a field called 'mProvider').

            So how can I fix the problem (I haven
            't got the source yet)? Thanks for reading my question with my poor English, Thx :)

            PS: maybe a online framework source review website is helpful but I can
            't found one, if you can provide me one, it will be great. :P

            完了我自己測試了,發現此方案解決不了。但是引出了另外一問題,就是不用android版本下的webview實現是不一樣的,其實看代碼就能看出,
            原先webview有mDefaultScale字段,但是后來應該挪到mZoomManager里去了,但是我發現我手機上webview 實現和作者遇到的問題一樣,只有mProvider成員,
            沒有mZoomManager,所以只能尋求源碼,千辛萬苦,終于找到
            http://androidxref.com/4.2_r1/xref/frameworks/base/core/java/android/webkit/WebViewClassic.java,
            mProvider 其實類型就是WebViewClassic(自己看下源碼實現),簡要提下,WebProvider只是一個接口,作為WebView的一個成員,
            創建時用了factory來,完了看下幾個工廠類,最后是webviewclassic實例)。
             對于Jerry Bean 4.2這個版本(我一個手機就是自己刷的rom),webview的實現又換了個,所以要拿到默認縮放的成員,如下:
            try {  
                                Field defaultScale 
            = WebView.class  
                                        .getDeclaredField(
            "mDefaultScale");  
                                defaultScale.setAccessible(
            true);  
                                
            float sv = defaultScale.getFloat(authorizationView);
                                defaultScale.setFloat(authorizationView, xxx);  
                            } 
            catch (SecurityException e) {  
                                e.printStackTrace();  
                            } 
            catch (IllegalArgumentException e) {  
                                e.printStackTrace();  
                            } 
            catch (IllegalAccessException e) {  
                                e.printStackTrace();  
                            } 
            catch (NoSuchFieldException e) {  
                                e.printStackTrace();  
                                
            try {  
                                    Field zoomManager;   
                                    zoomManager 
            = WebView.class.getDeclaredField("mZoomManager");  
                                    zoomManager.setAccessible(
            true);  
                                    Object zoomValue 
            = zoomManager.get(authorizationView);  
                                    Field defaultScale 
            = zoomManager.getType().getDeclaredField("mDefaultScale");  
                                    defaultScale.setAccessible(
            true);  
                                    
            float sv = defaultScale.getFloat(zoomValue);
                                    defaultScale.setFloat(zoomValue, xxx);  
                                } 
            catch (SecurityException e1) {  
                                    e1.printStackTrace();  
                                } 
            catch (IllegalArgumentException e1) {  
                                    e.printStackTrace();  
                                } 
            catch (IllegalAccessException e1) {  
                                    e.printStackTrace();  
                                } 
            catch (NoSuchFieldException e1) {  
                                    e1.printStackTrace();  
                                    
                                    
            try {
                                        Field mProviderField 
            = WebView.class.getDeclaredField("mProvider");  
                                        mProviderField.setAccessible(
            true);
                                        
            //mProviderField.getClass()
                                        Object webviewclassic = mProviderField.get(authorizationView);  
                                        
                                        Field zoomManager 
            = webviewclassic.getClass().getDeclaredField("mZoomManager");   
                                        zoomManager.setAccessible(
            true);
                                        Object zoomValue 
            = zoomManager.get(webviewclassic);  
                                        Field defaultScale 
            = zoomManager.getType().getDeclaredField("mDefaultScale");  
                                        defaultScale.setAccessible(
            true);  
                                        
            float sv = defaultScale.getFloat(zoomValue);
                                        defaultScale.setFloat(zoomValue, xxx);  
                                    }
            catch(Exception e2)
                                    {
                                        e2.printStackTrace();
                                    }
                                }  
                            }

            雖然可以拿到,并且設置成功,但是在我的手機上還是不能解決input 獲取焦點后自動放大,
            完了想了下,有個實現模式可以參考:當input 獲取焦點時,js調用java設置默認放縮率,設置前保存原有值,失去焦點后重新設置原來值,不然跳轉到其他頁面的時候,你會發現比例不對了。:)。

            因為放大后雙擊還是還原回原來樣子。所以暫且不來糾結這個東西了。因為不同android版本的如果webview實現不一致的話,這代碼就不起作用了 :)
            posted @ 2012-12-18 20:09 小果子 閱讀(8380) | 評論 (1)編輯 收藏
            僅列出標題
            共58頁: First 6 7 8 9 10 11 12 13 14 Last 
            A级毛片无码久久精品免费| 99久久精品国内| 久久国产精品无码网站| 久久久久久精品免费免费自慰| 国产福利电影一区二区三区,免费久久久久久久精 | 2022年国产精品久久久久| 日韩美女18网站久久精品| 久久精品国产亚洲7777| 国产AⅤ精品一区二区三区久久| 久久99精品国产一区二区三区| 久久亚洲欧美国产精品| 久久午夜综合久久| 久久人搡人人玩人妻精品首页| 狠狠人妻久久久久久综合蜜桃| 国产精品免费久久久久电影网| 色偷偷888欧美精品久久久| 国产福利电影一区二区三区久久久久成人精品综合 | 久久国产免费观看精品| 久久久国产乱子伦精品作者| 三级三级久久三级久久| 国产精品久久久久久久app | 中文精品99久久国产| 麻豆久久久9性大片| 超级97碰碰碰碰久久久久最新| 久久久精品久久久久影院| 久久AV无码精品人妻糸列| 男女久久久国产一区二区三区 | 91精品国产91久久久久久青草 | 四虎亚洲国产成人久久精品| 99久久做夜夜爱天天做精品| 日韩人妻无码精品久久久不卡| 国产亚洲欧美成人久久片| 久久精品亚洲精品国产欧美| 久久伊人精品一区二区三区| 丁香狠狠色婷婷久久综合| 很黄很污的网站久久mimi色| 久久天天躁狠狠躁夜夜2020一| 狠狠色婷婷久久一区二区三区| 久久国产香蕉一区精品| 无码人妻精品一区二区三区久久 | 欧美激情一区二区久久久|