一、一致性hash算法
上面是大致介绍,根据自身理解,其只是更随机更均匀的分配。本系统需要同一md5元数据不能分配到同一文件节点中,因此需要对其进行改造。
也就是计算元数据key时,会遍历对应key中是否有相同md5值的数据,如果有则寻找下一个key对应的node,直到找到为止。
本来一致性Hash能快速根据key获取信息,不用存储Key对应的文件信息,但由于本系统需求,需要存储空间记录信息。
Go语言一致性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)
}