简易文件系统-用Go语言从零开始设计(四) 文件系统冗余措施 一致性hash算法

14 篇文章 1 订阅

一、一致性hash算法

一致性Hash介绍     

分布式均匀算法--hash性一致算法--hash slot

上面是大致介绍,根据自身理解,其只是更随机更均匀的分配。本系统需要同一md5元数据不能分配到同一文件节点中,因此需要对其进行改造。

也就是计算元数据key时,会遍历对应key中是否有相同md5值的数据,如果有则寻找下一个key对应的node,直到找到为止。

本来一致性Hash能快速根据key获取信息,不用存储Key对应的文件信息,但由于本系统需求,需要存储空间记录信息。

Go语言一致性Hash代码为网上开源,只是对其功能进行修改

一致性Hash算法

package main

//一致性哈希(Consistent Hashing)

import (
	"fmt"
	"hash/crc32"
	"sort"
	"strconv"
	"sync"
)

const DEFAULT_REPLICAS = 2000

type HashRing []uint32

func (c HashRing) Len() int {
	return len(c)
}

func (c HashRing) Less(i, j int) bool {
	return c[i] < c[j]
}

func (c HashRing) Swap(i, j int) {
	c[i], c[j] = c[j], c[i]
}

type Node struct {
	Id     string
	Weight int
}

func NewNode(id string, weight int) *Node {
	return &Node{
		Id:     id,
		Weight: weight,
	}
}

type Consistent struct {
	Nodes     map[uint32]Node
	numReps   int
	Resources map[string]bool
	NodeIdMap map[string]Node
	ring      HashRing
	sync.RWMutex
}

func NewConsistent() *Consistent {
	return &Consistent{
		Nodes:     make(map[uint32]Node),
		numReps:   DEFAULT_REPLICAS,
		Resources: make(map[string]bool),
		NodeIdMap: make(map[string]Node),
		ring:      HashRing{},
	}
}

func (c *Consistent) FindNode(NodeId string) (*Node, error) {
	c.RLock()
	defer c.RUnlock()
	node, exit := c.NodeIdMap[NodeId]
	if exit {
		return &node, nil
	} else {
		return nil, fmt.Errorf("没有找到HashID%s\n", NodeId)
	}

}

func (c *Consistent) Add(node *Node) bool {
	c.Lock()
	defer c.Unlock()

	if _, ok := c.Resources[node.Id]; ok {
		return false
	}

	count := c.numReps * node.Weight
	for i := 0; i < count; i++ {
		str := c.joinStr(i, node)
		c.Nodes[c.hashStr(str)] = *(node)
	}
	c.Resources[node.Id] = true
	c.NodeIdMap[node.Id] = *node
	c.sortHashRing()
	return true
}

func (c *Consistent) sortHashRing() {
	c.ring = HashRing{}
	for k := range c.Nodes {
		c.ring = append(c.ring, k)
	}
	sort.Sort(c.ring)
}

func (c *Consistent) joinStr(i int, node *Node) string {
	return node.Id + "-" + strconv.Itoa(i)
}

// MurMurHash算法 :https://github.com/spaolacci/murmur3
func (c *Consistent) hashStr(key string) uint32 {
	return crc32.ChecksumIEEE([]byte(key))
}

func (c *Consistent) Get(key string) Node {
	c.RLock()
	defer c.RUnlock()

	hash := c.hashStr(key)
	i := c.search(hash)

	return c.Nodes[c.ring[i]]
}

// 获取与输入nodeid不重复的Node
func (c *Consistent) GetNotRepeatNode(key string, Keyid []string) (Node, error) {
	c.RLock()
	defer c.RUnlock()
	Nodeid := make([]string, 0)
	// KeyId 转化 NodeId
	for i := 0; i < len(Keyid); i++ {
		hash := c.hashStr(Keyid[i])
		i := c.search(hash)
		Nodeid = append(Nodeid, c.Nodes[c.ring[i]].Id)
	}

	hash := c.hashStr(key)
	i := c.search(hash)
	// 存在相同ID
	if ContainSameString(c.Nodes[c.ring[i]].Id, Nodeid) {
		// 对Hash环遍历
		for index := (i + 1) % len(c.ring); (index % len(c.ring)) != i; index++ {
			if !ContainSameString(c.Nodes[c.ring[index%len(c.ring)]].Id, Nodeid) {
				return c.Nodes[c.ring[index%len(c.ring)]], nil
			}

		}
		return c.Nodes[c.ring[i]], fmt.Errorf("无法找到不重复节点")
	}
	// 不存在相同ID
	// 直接返回

	return c.Nodes[c.ring[i]], nil

}

func ContainSameString(key string, data []string) bool {
	for i := 0; i < len(data); i++ {
		if key == data[i] {
			return true
		}
	}
	return false
}

func (c *Consistent) search(hash uint32) int {

	i := sort.Search(len(c.ring), func(i int) bool { return c.ring[i] >= hash })
	if i < len(c.ring) {
		if i == len(c.ring)-1 {
			return 0
		} else {
			return i
		}
	} else {
		return len(c.ring) - 1
	}
}

func (c *Consistent) Remove(node *Node) {
	c.Lock()
	defer c.Unlock()

	if _, ok := c.Resources[node.Id]; !ok {
		return
	}

	delete(c.Resources, node.Id)
	delete(c.NodeIdMap, node.Id)
	count := c.numReps * node.Weight
	for i := 0; i < count; i++ {
		str := c.joinStr(i, node)
		delete(c.Nodes, c.hashStr(str))
	}
	c.sortHashRing()
}

二、文件系统冗余措施

由于每份元数据存储n份,并且如果掉线Filenode小于n,则用户可以读取其数据。

当FileNode掉线时需要对FileNode进行重新平衡

// 重新分配资源
func RedistributionResource() {
	// 数据库读取所有元数据信息
	Metdatas, err := msql.GetMetdata()
	if err != nil {
		fmt.Println(err)
		return
	}
	// 按照读取顺序排序的VirtualIDMap
	VirtualIdMap := MetdataBuildVirtualMap(Metdatas)
	// 获取当前在线服务器ID
	//OnLindeNodeId := GetOnLineFileNode()
	// 对元数据进行遍历
	for i := 0; i < len(Metdatas); i++ {
		// 获取元数据id
		mdata := Metdatas[i]
		// 获取相同VirtualiDMap中比自己前的MetaDataID
		HightRankIds := GetHightRankMetdata(mdata, VirtualIdMap[mdata.VirtualID])
		ItemNode, err := HashConsistent.GetNotRepeatNode(mdata.MetaDataID, HightRankIds)
		if err != nil {
			fmt.Println("RedistributionResource", err)
			continue
		}
		// 当前记录ID与期望不符合,开始转移数据
		if ItemNode.Id != mdata.MetaDataNodeId {
			go MoveMetadata(mdata, ItemNode.Id, VirtualIdMap)
		}
	}
}

// 转移数据
func MoveMetadata(mdata Metadata, ItemNodeID string, VirtualDataMap map[string][]Metadata) error {
	OldItemId := mdata.MetaDataNodeId
	// 判断 目标机器ItemNodeID 是否在线
	if !IsFileNodeOnline(ItemNodeID) {
		fmt.Println("目标机器未在线转移数据失败", ItemNodeID)
		return fmt.Errorf("目标机器未在线转移数据失败", ItemNodeID)
	}
	// 判断需要转移数据所在节点是否在线
	if IsFileNodeOnline(OldItemId) {
		return MoveMetadataFromOnlineNode(mdata, ItemNodeID)
	} else {
		return MoveMetdateFromOffLineNode(mdata, ItemNodeID, VirtualDataMap)
	}
}

// 从在线节点转移数据
func MoveMetadataFromOnlineNode(mdata Metadata, ItemNodeID string) error {
	// 开始读取其文件
	Data, err := ServerReadMetdata(mdata)
	if err != nil {
		fmt.Println("MoveMetdateFromOffLineNode", err)
		return err
	}
	mdata.Data = Data
	// 插入数据
	err = ServerMetadataInsert(mdata, ItemNodeID, 4)
	if err != nil {
		fmt.Println("MoveMetdateFromOffLineNode1", err)
		return err
	}

	// 删除老节点的数据
	err = DeleteMetaData(mdata, false)
	if err != nil {
		fmt.Println("MoveMetdateFromOffLineNode2", err)
		return err
	}
	// 数据库修改数据
	mdata.MetaDataNodeId = ItemNodeID
	err = msql.MetadaDataUpdate(mdata)
	return err
}

// 从离线节点转移数据
func MoveMetdateFromOffLineNode(mdata Metadata, ItemNodeID string, VirtualDataMap map[string][]Metadata) error {
	// 获取相同虚拟数据ID的其他数据 从其他数据那读取再转移至目标机器
	mdata.MetaDataNodeId = ItemNodeID
	OtherMetdata := VirtualDataMap[mdata.VirtualID]
	for i := 0; i < len(OtherMetdata); i++ {
		if OtherMetdata[i].MetaDataID != mdata.MetaDataID {
			// 判断其所在机器是否在线
			if IsFileNodeOnline(OtherMetdata[i].MetaDataNodeId) {
				// 开始读取其文件
				Data, err := ServerReadMetdata(OtherMetdata[i])
				if err != nil {
					fmt.Println("MoveMetdateFromOffLineNode", err)
					continue
				}
				mdata.Data = Data
				// 插入数据
				err = ServerMetadataInsert(mdata, ItemNodeID, 4)
				if err != nil {
					fmt.Println("MoveMetdateFromOffLineNode1", err)
					continue
				}
				// 数据库修改数据
				mdata.MetaDataNodeId = ItemNodeID
				err = msql.MetadaDataUpdate(mdata)
				return nil
			}
		}
	}
	return fmt.Errorf("未转移成功%s", mdata.MetaDataID)
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值