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 小果子 阅读(223) | 评论 (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任务,主要有这么三个步骤:CopySortReduce(参见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.MergeQueueRawKeyValueIteratorReduceTask.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 小果子 阅读(1280) | 评论 (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 小果子 阅读(392) | 评论 (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 小果子 阅读(325) | 评论 (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 小果子 阅读(8320) | 评论 (1)编辑 收藏
仅列出标题
共58页: First 6 7 8 9 10 11 12 13 14 Last