Hadoop Mapreduce组建 核心环形缓冲区 RingBuff 原理及Go实现

最近在写一个 简单的MapReduce框架 设计到 内存缓冲区的算法 看了下网上好像 还没有 完整实现的 就 模仿了一个 写完 估计得 700行代码.

环形缓冲区
1.为什么要环形缓冲区?
答:使用环形缓冲区,便于写入缓冲区和写出缓冲区同时进行。
2.为什么不等缓冲区满了再spill?
答:会出现阻塞。
3.数据的分区和排序是在哪完成的?
答:分区是根据元数据meta中的分区号partition来分区的,排序是在spill的时候排序。

在这里插入图片描述

在这里插入图片描述

这是环形缓冲区的结构示意图:
1.整个环形缓冲区以赤道为起点,开始向两边读写数据
2.之所以元数据信息全部都是整数,是因为 他只存储分区信息(整数)和kvbuffer在数组中的位置,每个元素局信息占16字节4X4
4.环形缓冲区的数据写入(不考虑spill进行)maptask.MapOutputBuffer.collect();
1.根据bufferindex找到key的长度然后序列化之后进行写入

特点:
在溢出时 会启动另一个携程 此时 不影响 写入 除非空间完全满了 那么变回等待回收空间
还有就是 当 回收空间那一刻 将元空间数据区 的内容移到 kv数据区一测时 便会也会产生 阻塞 这个算法 稍加改进 也可以用来 将内存中 的数据加载到本地 由于是数组 不会浪费多余的空间 不会产生 频繁的内存申请操作 并且读写 可以同时进行 效率会比一般的直接写文件高很多。

package mr

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"strings"
	"sync"
)

const (
	 NMETA int = 4;//元数据一个字段占用字节 byte
	 METASIZE = NMETA * 4; //一整个元数据占用长度
)

func ReadWithSelect(ch chan bool) (x int, err error) {
	select {
	case  <-ch:
		return x, nil
	default:
		return 0, errors.New("channel has no data")
	}
}
type metadata []byte
func (mb metadata) getkey(kvbuffer *[]byte) ([]byte,error){
	d,err := mb.BytesToInt()
	if err != nil{
		return nil,err
	}
	var tmp []byte
	tmp = * kvbuffer
	if d[0]< d[1]+1{
		return tmp[d[0]:d[1]+1],nil
	}
	part1 := tmp[d[0]:]
	part2 := tmp[0:d[1]+1]
	part1 = append(part1, part2...)
	return part1,nil
}

func (mb metadata) compar(kvbuffer *[]byte,str1 []byte) int {
	d,err :=mb.getkey(kvbuffer)
	if err != nil{
		panic(err)
	}
	res := strings.Compare(string(d), string(str1))
	return res

}
func (mb metadata) getKeyByte(kvbuffer *[]byte) []byte {
	d,err :=mb.getkey(kvbuffer)
	if err != nil{
		panic(err)
	}
	return d

}
func (mb metadata) getKV(kvbuffer *[]byte) ([]byte,error){
	d,err := mb.BytesToInt()
	if err != nil{
		return nil,err
	}
	var tmp []byte
	tmp = * kvbuffer
	//fmt.Println("keystart",d[0],"keyend",d[1]+d[3]+1)
	zjy :=  d[1] - d[0] + 1
	if d[0] > d[1]{
		zjy =  len(tmp)  - d[0] + d[1] + 1
	}
	qjx := zjy + d[3]
	if d[0] + qjx -1 >= len(tmp) {
		loc := (d[0] + qjx -1) % len(tmp)
		if d[0]> len(*kvbuffer) -1{
			fmt.Println("DEBUG:键值对太大了",d)
		}
		part1 := tmp[d[0]:]
		part2 := tmp[0:loc+1]
		part1 = append(part1, part2...)
		if len(part1) != qjx {
			fmt.Println("Error:System has a Wrong!",len(part1),d)
			return nil,errors.New("metadata size not math!")
		}
		return part1,nil
	}
	return tmp[d[0]:d[0] + qjx],nil
}

type MetaInt struct {
	equator *int
	kvbuffer *[]byte //指向buffer
	metastart *int //元数据开始
	metaend *int //元数据结尾
	metapoint []*metadata //记录指向每个metadata的指针
	metamark int//记录移动数据
	isexchange chan bool //扇区转换那一刻用于阻塞
}
func (md *MetaInt) init(){
	count := md.getlen()
	p :=make([]*metadata,0)
	for i := 0 ;i<count; i++  {
		m,err := md.get(i)
		if err != nil{
			panic(err)
		}
		//记录指针
		p = append(p,&m)
	}
	md.metapoint = p
	//for _,v := range p {
	//	a,_ := (*v).getKV(md.kvbuffer)
	//	fmt.Println(string(a))
	//
	//}

}

func (md *MetaInt) getlen() int{
	buflen :=len(*md.kvbuffer) -1
	// 判断有没有在环头 和环尾

	if *md.metastart < (*md.metaend % (buflen +1)) {
		return (*md.metastart  + 1 + buflen - *md.metaend  + 1)/ METASIZE
	}
	return (*md.metastart - *md.metaend  + 1 ) / METASIZE //计算环形区的数组长度
}
//倒过来调用
func (md *MetaInt) getInverse(num int)  (metadata,error){
	metalen := len(md.metapoint) -1
	if num > metalen {
		return nil,errors.New("Out Of Bufferbyte")
	}
	metadata,err := md.get(metalen - num )
	return metadata,err

}
func (md *MetaInt) get(num int) (metadata,error){
	res := make([]byte,0)
	var tmp []byte
	tmp = *md.kvbuffer
	//超出数组数组长度报错
	if num > md.getlen() -1{
		return nil,errors.New("Out Of Bufferbyte")
	}
	buflen := len(*md.kvbuffer) -1
	offeset := *md.metastart - (num + 1) * METASIZE //计算偏移 从零索引开始
	if offeset + 1 < 0{
		offeset += 1
		offeset = buflen - ((- offeset) % buflen) + 1
	}else {
		offeset+=1
		res = append(res,tmp[offeset:offeset+METASIZE]...)
		return res,nil
	}
	indexend :=  offeset + METASIZE -1 //如果索引 + 16 个字节 超出数组尾部 则 到环首处理
	//如果索引 是 1022 数组总长 1023 那么 1022:1023 会有 14 个剩余
	// 字节 在 0:13 处理 一半索引落在环尾部 一半落在 环首情况
	if indexend > buflen{
		partinde := buflen - offeset + 1
		res = append(res, tmp[offeset:buflen+1]...)
		res = append(res,tmp[0: METASIZE  - partinde ]...)
		return res,nil
	}//else if offeset>= (*md.metaend + METASIZE) % buflen  && offeset <= *md.metastart {
	return tmp[offeset:indexend+1], nil
}
func NewMetaInt(equator *int,kvbuffer *[]byte,kvstart *int,kvend *int) *MetaInt{
	return &MetaInt{
		equator:  equator,
		kvbuffer: kvbuffer,
		metastart:kvstart,
		metaend:kvend,
		metapoint:nil,
	}
}

//创建元数据
func NewMetaData(keystart int, keyend int,kvpartition int, vallen int) metadata{
	md:= make([]byte,0)
	md = append(md,IntToBytes(keystart)...)
	md = append(md,IntToBytes(keyend)...)
	md = append(md,IntToBytes(kvpartition)...)
	md = append(md,IntToBytes(vallen)...)
	return md
}

//整形转换成字节
func IntToBytes(n int) metadata{
	x := int32(n)
	bytesBuffer := bytes.NewBuffer([]byte{})
	binary.Write(bytesBuffer, binary.BigEndian, x)
	return bytesBuffer.Bytes()
}
//字节转换成整形
func(md metadata) BytesToInt() ([]int,error) {
	buf := make([]byte,4)
	var x []int
	x = make([]int,0)
	bytesBuffer := bytes.NewBuffer(md)
	for{
		_, err := io.ReadFull(bytesBuffer, buf)
		if err != nil{
			if err != io.EOF{
				fmt.Println("Read error",err)
			}else{
				break
			}
		}
		var tmp int
		databuff := bytes.NewBuffer(buf)
		if databuff.Len() == 4{
			tmp = int(binary.BigEndian.Uint32(buf))
			x = append(x, tmp)
		}
	}
	return x,nil
}
func formmat(in []int){
	fmt.Printf("keystart: %d valstart: %d , kvpartition: %d ,vallen: %d nextkeystart: %d \n",in[0],in[1]+1,in[2],in[3],in[1] + in[3] + 1)
}

type  RingBuf struct {
	equator int;   //marks origin of meta/serialization
	lock sync.Mutex

	metaint *MetaInt
	bufstart int;  //溢出时kv数据的起始位置
	bufindex int 	//下次要写入的kv数据的位置
	bufmark int //写出时指针指向的位置
	bufend int 		//溢出时raw数据的结束位置
	bufvoid int //写出数据的截止地方

	kvindex int //下次要插入的索引的位置
	kvend int //溢出时索引的结束位置
	kvstart int //溢出时索引的起始位置

	spiller int //触发溢出的阙值

	spillerprecent float64//触发溢出百分比
	parnum int //分区数

	flg bool //触发溢出标记

	isGcfinisned chan bool

	//bufmark int;     // marks end of record
	//bufvoid int;	// marks the point where we should stop
	 reading at the end of the buffer
	kvbuffer []byte //main output buffer

}
func NewRingBuf(size int) *RingBuf{
	rb := &RingBuf{
		equator:  0,
		bufstart: 0,
		bufindex: 0,
		bufvoid:0,
		bufend:   0, //buf缓冲区的
		kvindex:  size -16 ,
		bufmark:0,
		kvend:    size -1 ,
		kvstart:  size -1,
		kvbuffer: make([]byte,size),
		parnum:10,
		flg:false,
		isGcfinisned:nil,
	}
	rb.isGcfinisned = make(chan bool,1)
	rb.metaint = NewMetaInt(&rb.equator,&rb.kvbuffer,&rb.kvstart,&rb.kvend)
	return rb
}

//计算加上下一个值是否会溢出
func (rb *RingBuf) preSurplusSpace(kvlen int) (float64,error){
	kval :=rb.kvstart - rb.kvend + METASIZE
	if kval < 0 {
		kval = len(rb.kvbuffer) - rb.kvend   + rb.kvstart + METASIZE

	}
	bval := rb.bufindex - rb.bufstart + kvlen

	if bval < 0 {
		bval = rb.bufindex  + len(rb.kvbuffer) - rb.bufstart + kvlen
	}
	val := len(rb.kvbuffer) - kval - bval
	//fmt.Printf("Residual byte:%d Usage ratio:%.0f %s \n",val,float64(val)/float64(len(rb.kvbuffer))* 100,"%")
	res:= float64(val)/float64(len(rb.kvbuffer))* 100
	if res < 0 {
		err := errors.New("capility overflow error!")
		return res,err
	}
	return res,nil

}



func (rb *RingBuf) collect(kv KeyValue){
	keyend := rb.bufindex+ len(kv.Key) -1 //键的结束地址
	vallen := len(kv.Value)
	//检查容量
	space ,err := rb.surplusSpace()
	if err != nil{
		panic(err)
	}
	//容量不足 20%
	if space <= 20.0 && rb.flg == false{
		rb.flg = true
		//读取bufstart 到bufend
		rb.bufend = rb.bufindex
		rb.bufmark = rb.bufstart
		//触发写出
		rb.metaint.metamark = rb.metaint.getlen()
		if _, err := ReadWithSelect(rb.isGcfinisned); err != nil {
			fmt.Println(err)
		}
		go rb.SplliOut()
	}
	isoverflow,_:= rb.preSurplusSpace(len(kv.ToString()))
	if isoverflow < 1.0  {
		//如果读取kv一下子 过大 没触发上面的回收
		if rb.flg == false {
			isoverflow,_:= rb.preSurplusSpace(len(kv.ToString()))
			if isoverflow <1.0 && rb.flg == false{
				rb.flg = true
				//读取bufstart 到bufend
				rb.bufend = rb.bufindex
				rb.bufmark = rb.bufstart
				//触发写出
				rb.metaint.metamark = rb.metaint.getlen()
				if _, err := ReadWithSelect(rb.isGcfinisned); err != nil {
					fmt.Println(err)
				}
				go rb.SplliOut()
			}
		}
		for{
			fmt.Println("ByteBuffer Is Full , Waiting GC!")
			select {
			case <-rb.isGcfinisned:
				goto endGC
			}
		}
		endGC:
		fmt.Println( "GC Is End!")
	}
	//移动移动指针时要加锁
	rb.lock.Lock()
	rb.bufindex = rb.bufindex % len(rb.kvbuffer)
	keyend = keyend % len(rb.kvbuffer)
	//记录要插入的kv的k开始索引
	tmpmark := rb.bufindex
	for i := 0 ;i <  len(kv.ToString());i++{
		rb.kvbuffer[rb.bufindex] = kv.ToString()[i]
		rb.bufindex++
		if rb.bufindex > len(rb.kvbuffer) -1 {
			rb.bufindex = rb.bufindex % len(rb.kvbuffer)
		}

	}
	par := ihash(kv.Key) % rb.parnum
	//传入 buf开始地址 val长度
	if tmpmark>len(rb.kvbuffer){
		fmt.Println("DEBUG:tmpmark  >len(rb.kvbuffer)!")
	}
	rb.addmetadata(tmpmark ,keyend,vallen,par)
	//bufindex 指向 下一次写入的索引
	rb.metaint.init()
	rb.lock.Unlock()
}

//执行溢出操作
func (rb *RingBuf) SplliOut(){
	fmt.Println("Capacity Is Lower than 20% Precent beging gc!")
	buff := make([]byte,0)
	spot := ","
	//var keylen int
	//metapoints := len(rb.metaint.metapoint)
	//对数据进行排序
		for m:= 0; m < rb.metaint.metamark;m++{
			bufmeta,err := rb.metaint.get(m)
			if err != nil{
				fmt.Println("Get Inverse MetaInt Error!")
				panic(err)
			}
			kv,err := bufmeta.getKV(&rb.kvbuffer)
			if err != nil{
				fmt.Println("Spill Out Error While Get KeyValue!")
				panic(err)
			}
			//每次读完 标记下
			rb.bufmark += len(kv)
			rb.bufmark = (rb.bufmark) % len(rb.kvbuffer)
			//循环读取

			buff = append(buff,kv...)
			buff = append(buff,spot...)
			par ,_:= bufmeta.BytesToInt()

			appendToFile("map-out-" +fmt.Sprintf("%d",par[2]) , string(buff))
		}

	//fmt.Println(string(buff))
	//指针 buffer start 置为 end
		var tmpkvend int
		buflen := len(rb.kvbuffer)-1
		//与 kv区背靠背
		tmpkvend = rb.bufend - 1
		if tmpkvend < 0 {
			tmpkvend = -(-(tmpkvend) % (buflen+1)) + buflen + 1
		}
		rb.lock.Lock()
		fmt.Println("Older Between New MetaInt Gap:",rb.metaint.getlen() - rb.metaint.metamark)
	rb.sortMetaData()
	if rb.metaint.getlen() - rb.metaint.metamark > 0 {
			tmpkvend = tmpkvend - METASIZE + 1
			for i := 0;i< rb.metaint.getlen() - rb.metaint.metamark ;i++ {
				md, err := rb.metaint.getInverse(i)
				if err != nil{
					fmt.Println("Get MeatInt Error!")
					panic(err)
				}
				rs,_ := md.getKV(&rb.kvbuffer)
				fmt.Println("gc",string(rs))
				if tmpkvend < 0 {
					a1 := -(-(tmpkvend) % (buflen + 1)) + buflen + 1
					c := 0
					for ix:= a1;ix< buflen+1;ix++{
						rb.kvbuffer[ix] = md[c]
						c++
					}
					cb := 0
					for iz:=c;iz<16;iz++{
						rb.kvbuffer[cb] = md[iz]
						cb++
					}
					tmpkvend = a1
				}else{
					for ia := 0;ia<METASIZE;ia++{
						rb.kvbuffer[tmpkvend + ia] = md[ia]
					}
				}
				tmpkvend = tmpkvend - METASIZE
			}
			rb.kvindex = tmpkvend

			rb.kvend = rb.kvindex + METASIZE -1


			if rb.kvindex <= 0 {
				fmt.Println("DEBUG")
			}
		}else{
			//tmpkvend 没有更新过
			rb.kvindex = tmpkvend - METASIZE + 1
			rb.kvend = tmpkvend
		}


	//检查输出的字节 数大小是否 和 index 和end大小 之间一致
	if rb.bufmark != rb.bufend {
		fmt.Printf("Between Mark And End  Size Has %d Distance Shoudle Be Zero!\n",rb.bufend -rb.bufmark)
		panic("OutPut Size is not Match!\n")
	}
	rb.bufstart = rb.bufend
	//背对背 对齐 kv区
	rb.kvstart = rb.bufend - 1
	if rb.kvstart == -1 {
		rb.kvstart = buflen
	}
	rb.flg = false
	rb.isGcfinisned <- true
	if rb.kvstart<10{
		fmt.Printf("==========>GC now rb.kvstart %d , rb.kvend  %d",rb.kvstart,rb.kvindex)
	}

	rb.lock.Unlock()
}

//对元数据区进行排序
func (rb *RingBuf) sortMetaData()  {
	var tmp []*metadata
	tmp = rb.metaint.metapoint
	for i:=0;i<len(tmp);i++{
		tmp[i] = rb.metaint.metapoint[i]
	}
	start := 0
	end := len(tmp) - 1
	tmp = quickSortMehods(&rb.kvbuffer,tmp,start,end)
	rb.metaint.metapoint = tmp
}

func  quickSortMehods(kvbuffer *[]byte,nums []*metadata, start ,end int) []*metadata{
	if start >= end{
		return nums
	}
	mid := nums[start]
	leftjx := start
	right := end
	for {
		if right == leftjx{
			break
		}
		for {
			if right > leftjx && nums[right].compar(kvbuffer,mid.getKeyByte(kvbuffer)) >= 0{
				right -= 1
			}else {
				break
			}
		}
		nums[leftjx] = nums[right]

		for {
			if leftjx < right && nums[leftjx].compar(kvbuffer,mid.getKeyByte(kvbuffer)) <= 0  {
				leftjx += 1
			}else {
				break
			}
		}
		nums[right] = nums[leftjx]
	}
	nums[leftjx] = mid
	quickSortMehods(kvbuffer,nums,start,leftjx -1 )
	quickSortMehods(kvbuffer,nums,right + 1,end)
	return nums
}

func(rb *RingBuf) WriteOut(content *[]byte){
	buff := bytes.NewBuffer(rb.kvbuffer)
	n ,err:= io.ReadFull(buff,*content)
	if err != nil{
		panic(err)
	}
	if n != len(*content){
		fmt.Println("error!")
	}
}

//计算剩余容量
func(rb *RingBuf) surplusSpace() (float64,error) {
	kval :=rb.kvstart - rb.kvend
	if kval < 0 {
		kval = len(rb.kvbuffer) - rb.kvend  + rb.kvstart

	}
	bval := rb.bufindex - rb.bufstart
	if bval < 0 {
		bval = rb.bufindex + len(rb.kvbuffer) - rb.bufstart
	}
	val := len(rb.kvbuffer) - kval - bval
	//fmt.Printf("Residual byte:%d Usage ratio:%.0f %s \n",val,float64(val)/float64(len(rb.kvbuffer))* 100,"%")
	res:= float64(val)/float64(len(rb.kvbuffer))* 100
	if res < 0{
		err := errors.New("capility overflow error!")
		return res,err
	}
	return res,nil
}

//插入 需要 值的长度 key
func(rb *RingBuf) addmetadata(keyindex int,keyend int,vallen int,par int){
	//插入的起始 插入的 key结尾 插入的分区号 插入的 val长度
	bc := NewMetaData(keyindex,keyend,par,vallen)
	buflen := len(rb.kvbuffer) -1
	if rb.kvindex == -11{
		fmt.Println("debug")
	}
	if rb.kvindex   < 0 {
		a1 := -(-(rb.kvindex) % (buflen+1) ) + buflen + 1
		c := 0
		for ix:= a1;ix< buflen+1;ix++{
			rb.kvbuffer[ix] = bc[c]
			c++
		}
		cb := 0
		for iz:=c;iz<METASIZE;iz++{
			rb.kvbuffer[cb] = bc[iz]
			cb++
		}
		rb.kvindex = a1
	}else{
			for ia := 0;ia<METASIZE;ia++{
				rb.kvbuffer[rb.kvindex + ia] = bc[ia]
			}
	}
	rb.kvend = rb.kvindex
	rb.kvindex = rb.kvindex - METASIZE
}
func appendToFile(file, str string) {
	f, err := os.OpenFile(file, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0660)
	if err != nil {
		fmt.Printf("Cannot open file %s!\n", file)
		return
	}
	defer f.Close()
	f.WriteString(str)
}

GitHUb地址:https://github.com/qiaojinxia/MapReduce

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值