在分布式系统中,图存储的问题。
在项目中,需要多个代理服务器共享一个用户行为访问图。
我们通过多个代理服务器共享一个redis数据库中存储的图,来实现多个代理服务器图同步的问题。文章解决的问题有两个:
- 怎样通过redis存储图数据结构?
通过将图中的节点转化为json格式的string数据,来实现图中*Node
节点的存储。从redis中取出数据时,将string数据解码为*Node
节点 - 怎样解决图中节点的子节点在各个代理服务器上的地址不同的问题
通过id来唯一标识节点及其子节点。在服务器上重构图模型时,通过节点id映射节点地址。
在单一服务器上存储图
在同一个代理中实现用户访问图很容易。只需存储当前节点,以及子节点的地址即可。
type Node struct {
URL string
ToURL map[string]int // 用于记录当前节点可以访问到的url节点,以及已经访问的次数。
Children []*Node // 用于记录当前节点的子节点地址
}
然而,因为在不同的代理服务器(或不同的进程)中,内存地址是本地的,无法跨进程或跨服务器共享。
当在Redis这样的中心化数据存储中保存数据时,不能存储内存地址,因为这些地址对于其他系统是没有意义的。
解决这个问题的方法是在序列化节点时不存储内存地址,而是存储能够唯一标识子节点的信息。
分布式系统上存储图
为了解决这个问题,我们为每个节点引入一个id,通过id唯一的表示一个图中的节点,并记录其子节点。
type Node struct {
ID int
URL string
ToURL map[string]int // 用于记录当前节点可以访问到的url节点,以及已经访问的次数。
Children map[int]*Node // 用于记录当前节点的子节点id以及对应的Node值。
}
在结构中,通过节点的id
唯一的标识一个节点,并通过Children中的id
记录当前节点的子节点。
为了能够通过id
访问子Node
节点,需要通过map[int]*Node
来找出子节点id
对应的*Node
节点。
前边已经提到,内存地址对于不同的服务器来说是没有意义的,因此在redis中存储数据时,我们不用存储map[int]*Node
。只需存储子节点对应的id。
图中节点转换为json字符串
我们通过redis存储Node
节点。键为id
,值为Node
数据结构经处理后转换为的json字符串。(redis无法存储复杂的数据结构,因此需要把图中节点转化为json字符串)
通过以下函数将map[int]*Node
转化为[]int
即子节点。
func SerializeNode(node *Node) (string, error) {
// 创建一个临时结构体来存储节点数据,不包含子节点指针
type TempNode struct {
ID int
URL string
ToURL map[string]int
Children []int
}
// 创建一个子节点ID列表
childIDs := make([]int, 0, len(node.Children))
for id := range node.Children {
childIDs = append(childIDs, id)
}
// 使用临时结构体来序列化
tempNode := TempNode{
ID: node.ID,
URL: node.URL,
ToURL: node.ToURL,
Children: childIDs,
}
nodeJSON, err := json.Marshal(tempNode)
if err != nil {
return "", err
}
return string(nodeJSON), nil
}
现在已经成功的在redis中通过id唯一的标识一个节点及其子节点了。值得注意的是,我们通过json中的Marshal函数将Node
数据结构转换为了json字符串,因此在读取时,需要将json字符串还原为Node
节点。
解码json字符串为图中节点
下面我们编写一个反序列化函数,来将json字符串重新转化为Node
节点
func DeserializeNode(nodeJSON string) (*Node, error) {
type TempNode struct {
FatherID int
ID int
URL string
ToURL map[string]int
Children []int
}
var tempNode TempNode
err := json.Unmarshal([]byte(nodeJSON), &tempNode)
if err != nil {
return nil, err
}
// 创建新的Node实例
node := &Node{
FatherID: tempNode.FatherID,
ID: tempNode.ID,
URL: tempNode.URL,
ToURL: tempNode.ToURL,
Children: make(map[int]*Node),
}
return node, nil
}
通过json中的Unmarshal函数我们将从redis中提取的nodeJSON字符串还原为Node
格式。