最近在写一个 简单的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