200 likes | 379 Views
Map 端 spill 详解. 俞灵 2011-09-19. 一个简单的例子. Wordcount 的 map 函数 public void map(LongWritable key, Text value, OutputCollector<Text, IntWritable> output, Reporter reporter) throws IOException { String line = value.toString();
E N D
Map端spill详解 俞灵 2011-09-19
一个简单的例子 • Wordcount的map函数 • public void map(LongWritable key, Text value, • OutputCollector<Text, IntWritable> output, • Reporter reporter) throws IOException { • String line = value.toString(); • StringTokenizer itr = new StringTokenizer(line); • while (itr.hasMoreTokens()) { • word.set(itr.nextToken()); • output.collect(word, one); • } • } • output.collect(word, one);是如何实现的呢
Mapreduce-64前collect原理 • 原理 • 使用三个数组 • kvoffsets 记录索引 • Kvindices key value索引 • Kvbuffer 存储record • Kvoffsets或Kvbuffer 达到limit便开始spill • 按照partition对record排序并spill
参数解释 • 初始值 • sm = Io.sort.mb << 20 • rp = io.sort.record.percent • sp = io.sort.spill.percent • 三个数组长度 • kvoffsets = new int[ sm*rp /16] • Kvindices = new int[ 3*sm*rp /16] • Kvbuffer = new byte[sm - sm*rp ] • 两个limit • softBufferLimit = kvbuffer.length * sp • softRecordLimit = kvoffsets.length * sp
初始状态 kvoffsets kvindices kvbuffer …. …. kvstart bufstart kvend bufend kvindex bufindex bufmark
加入一条记录 • (key,val)长度分别为(3,4) kvoffsets kvindices kvbuffer …. partition …. kvstart 0 bufstart 3 kvend bufend kvindex bufmark=7 bufindex=7
参数设置 • 设置参数 • sp=0.8 • rp=0.05 • sm=32000 • recordNum=sm*rp/16=1000 • kvlimit=recordNum*sp=800 • bufLen=sm*(1-rp)=30400 • buflimit=bufLen*sp=24320
Spill开始前 kvoffsets kvindices kvbuffer 0 partition …. kvstart 3 0 bufstart … 3 kvend partition bufend 5 kvindex 7 800 ….. bvlimit=800 bufmark=9022 bufindex=9022 buflimit=24320 buflvoid= 30400
Spill结束后 kvoffsets kvindices kvbuffer 0 partition kvstart 3 0 bufstart … 3 kvend partition bufend 5 kvindex 7 800 ….. 900 bvlimit=800 bufmark=9924 9924 bufindex=9925 buflimit=24320 buflvoid= 30400
unlock collect(key, value) • Collect流程 写key spilllock 计算kvfull,kvsoftlimit Key是否被分割 yes 是否达到limit且在spill 移动key,重置kvoid yes no 开始spill no 写value 是否full yes no 写索引,更新kvindex 等待spill结束 是否full no yes
Write 计算buffull,warp Spilling? yes no • write流程 是否有记录 yes no 计算buflimit no 达到limit或写不下 yes yes 写得下 Start spill no 写得下 MapBufferTooSmall no yes Wait spill no 写得下 yes 复制数据更新bufindex
流程解释 • 三个触发spill的地方 • kv达到limit(kvstart == kvend && kvsoftlimit) • buf达到limit • kvstart == kvend, • kvend != kvindex 有record • bufsoftlimit || (buffull && !wrap)) • 单条记录过长 • kvend == kvindex 无record • buffull && !wrap • Wrap • True:可以存下单条记录,需要调头 • 处理一条记录垮首尾的情况
流程解释 • Block的地方 • Kvfull • buffull && !wrap (无法写入下一条记录) • 单条记录超过剩余容量 • MapBufferTooSmallException • spillSingleRecord • Bufvoid的作用 • 可用buf大小 • 防止key调头(利于spill时计算) • 问题 • 内存未充分利用 • Block地方两个,程序慢
Mapreduce-64 • 原理 • 把三个数组合并成一个数组 • 总内存超过限制才spill • 问题 • 单个数组如何存储int和byte • Creates a view of byte buffer as an int buffer • kvmeta = ByteBuffer.wrap(kvbuffer).asIntBuffer(); • 如何存储索引和record • 双向存储
开始spill,重置参数 collect(key, value) unlock • Collect流程 计算bufremain Bufremain是否小于0 写key yes no Key是否被分割 lock yes yes 是否在spill 移动key,重置kvoid no 计算used, buflimit no 写value 是否可回收内存l 回收内存 no 写索引,更新kvindex yes 是否要spill no yes
BufferTooSmall no Write() 是否有记录 计算bufremain yes • Write流程 Startspill重置bufmark Bufremain是否小于0 是否blockwrite no yes lock no yes 计算可用空间 等待spill yes 计算blockwrite 是否blockwrite yes 是否在spill yes no no no 是否blockwrite 复制数据更新bufindex 回收内存 yes 是否可回收内存l no
流程解释 • Spill条件 • bufsoftlimit && kvindex != kvend(有记录) • 下一条记录过长 • Block的情况: blockwrite • distkvi <= distkve? distkvi <= len + 2 * METASIZE: distkve <= len || distanceTo(bufend, kvbidx) < 2 * METASIZE; • Bufremain的计算 • min( distanceTo(bufindex, 4*kvindex) - 2 * METASIZE,softLimit - bUsed) - METASIZE; • min( distanceTo(bufend, newPos), min(distanceTo(newPos, 4*kvend), softLimit) ) - 2 * METASIZE • 预留两个metasize?
sortAndSpill流程 • 修改前后流程不变 • 获取文件名及输出流 • 获取需要spill的kv首尾位置 • 根据partition对kv排序 • 根据是否需要combine进行输出 • 由partitnion从小到大输出 • 相同partition合并成一条记录输出 • Spill计数器加一,关闭输出流