GoMQTT服务器 节点路由树设计
第二章 节点路由树设计
写的不好,别见怪,有错误希望指出,谢谢大家。
文章目录
思路
采用节点路由树来管理mqtt消息需要发送到哪些节点上去,当该节点有消息需要转发到其他节点时,查找该表,得到需要转发的节点信息,再转发。
当然,可以为了性能优化:不一定有通配符和没有通配符都采用路由树,可以通过对没有通配符的主题进行hash,来查找相应的节点,这个需要仔细考虑一下,我暂时只是有这个想法。
添加节点到主题上去,采用对应的主题递归查找添加,比如 a/b/c 先查找a->b->c在c对应的RouteTable中的Nodes中添加该node信息。
删除类似。
当遇到 “+” 号时,在当前层的 “+”号对应的RouteTable中继续递归,不在同一层其他RouteTable中添加,降低复杂度。
查找的时候:
- 当主题是 a/b/c 时,这个最简单,创建一个*map[string]*Node ,取名为rest ,然后先从第一层找到a,然后查看a对应的Children中是否有b,有就从b中继续找c、+和#,有就把这三个的Nodes全部添加到rest中;如果a对应的Children中包括有 +和# 通配符节点,则将 # 中的节点全部添加到rest中,然后在从 + 的Children中查找 c、+和# ,后面步骤差不多,就是添加。
- 当主题是 a/+/c时,创建一个*map[string]*Node ,取名为rest,然后先从第一层找到a,然后将a中#下的Nodes全部添加,然后从其它节点中找第二层,看是否有c、+、#,有就添加
- 当主题是a/#/c、a/# ,它会将a节点下所有的子节点Nodes全部添加,包括a下的子节点
一、节点路由树设计
第一层已经限制不能有 + ,# , $ 通配符,就不能让客户端订阅+/… 、#和$sys,这种太危险了。当然也可以在设备接入时限定订阅的主题到数据库中,设备接入时先查询一下再订阅。
二、代码
主题节点添加、删除与删除
采用读写锁,锁的粒度还是有点大,可以选择一下 sync.Map 来保证并发操作
1.数据设计
import (
"strings"
error2 "study_one_day/gotcp/error"
"sync"
)
/**
全局路由表
*/
type Route struct {
Table *map[string]*RouteTable
}
/**
节点信息
这个可以只要节点ID这个基本不会改变的信息即可,然后根据ID,从节点表中获取信息,这样更好
*/
type Node struct {
Ip string
Port string
Name string
}
/**
主题路由表 结构
负责根据该表确定需要发送到哪些节点上去
*/
type RouteTable struct {
Pic string // 主题名称 要考虑 + # 等通配符
Nodes *map[string]*Node // 当前节点主题下有的节点列表
Children *map[string]*RouteTable // 子节点列表
}
const (
SingleLevel = "+"
MultiLevel = "#"
SystemLevel = "$"
)
// 全局路由表
var RouteTables *Route
// 全局节点 里面是Node类型,在获取到节点数据时更新
var AllNodes *sync.Map
// 读写锁
var rwLock *sync.RWMutex
/**
初始化路由表
*/
func init() {
RouteTables = &Route{&map[string]*RouteTable{}}
AllNodes = &sync.Map{}
rwLock = &sync.RWMutex{}
}
2.添加节点到主题下
/**
该节点添加订阅主题时
不推荐直接添加 #主题 , 主题不能包含空格 不能 /1//3/,这样会被修改为 1/3 , 不能包含中文字符
第一个不能是 + # $
需要添加到主题路由表
*/
func (this *Route) AddNodeInTopic(topic string, node *Node) (bool, *error2.TopicError) {
if len(topic) == 0 {
return false, error2.CreatTopicError("topic not allow nil")
}
topic = strings.TrimSpace(topic)
topics := strings.Split(topic, "/")
// 删除 空字符串
topics = deleteSpace(&topics)
lens := len(topics)
if ok,err := checkIllegalChar(&topics);!ok{
return false,err
}
root := this.Table
// 加锁更新
rwLock.Lock()
// defer 解锁
defer rwLock.Unlock()
if v, ok := (*root)[topics[0]]; ok { // 存在就继续下一层
bip := topics[1:]
//后面没有了,就将node添加到当前RouteTable
if len(bip) == 0 {
(*v.Nodes)[node.Name] = node
} else {
add(v, bip, node)
}
} else { // 不存在就递归创建
loopCreat(lens, root, node, topics)
}
return true, nil
}
/**
添加到当前层
最底层返回true表示,递归回退的第一次要添加改nodeName
*/
func add(tab *RouteTable, topics []string, node *Node) bool {
//if len(topics) == 0 { //代码层判断了,这个就不要了
// return true
//}
root := tab.Children
lens := len(topics)
if topics[0] == MultiLevel {
// # 直接添加
//addNowNode(tab.Nodes, node)
if v, ok := (*root)[MultiLevel]; ok {
addNowNode(v.Nodes, node)
} else {
// 没有就创建
pnod := map[string]*Node{}
pnod[node.Name] = node
(*root)[MultiLevel] = &RouteTable{
Pic: MultiLevel,
Nodes: &pnod,
Children: &map[string]*RouteTable{},
}
}
return true
}
// + 和普通主题
if v, ok := (*root)[topics[0]]; ok { // 存在就继续下一层
bip := topics[1:]
//后面没有了,就将node添加到当前RouteTable
if len(bip) == 0 {
(*v.Nodes)[node.Name] = node
} else {
add(v, bip, node)
}
} else { // 没有就添加
loopCreat(lens, root, node, topics)
}
return true
}
/**
循环创建
*/
func loopCreat(lens int, root *map[string]*RouteTable, node *Node, topics []string) {
for i := 0; i < lens; i++ {
cmp := &map[string]*RouteTable{}
nmp := map[string]*Node{}
if i == lens-1 {
// 添加到当前RouteTable的node列表中
nmp[node.Name] = node
}
(*root)[topics[i]] = &RouteTable{
Pic: topics[i],
Nodes: &nmp,
Children: cmp,
}
root = cmp
}
}
/**
添加到当前map 中
*/
func addNowNode(v *map[string]*Node, node *Node) {
// 不存在 就 添加
//if _,ok := (*(v))[node.Name];!ok{
// (*(v))[node.Name] = node
//}
// 直接添加,这样还可以更新一下
(*(v))[node.Name] = node
}
/**
检查数组第一个是否为非法字符 + # $
*/
func checkIllegalChar(topic *[]string) (bool,*error2.TopicError) {
if len(*topic) == 0{
return false,error2.CreatTopicError("topic is entity")
}
switch (*topic)[0] {
case MultiLevel:
return false,error2.CreatTopicError("topic first is \"#\"")
case SingleLevel:
return false,error2.CreatTopicError("topic first is \"+\"")
case "":
return false,error2.CreatTopicError("topic first is \"\"")
}
if strings.HasPrefix((*topic)[0],SystemLevel){
return false,error2.CreatTopicError("topic first is \"$\" Prefix")
}
return true, nil
}
/**
删除数组中的空字符串
*/
func deleteSpace(topics *[]string) []string {
// 删除 空字符串
for i, v := range *topics {
if v == "" {
*topics = append((*topics)[:i], (*topics)[i+1:]...)
}
}
return *topics
}
3.删除某主题下的某节点
这个应该修改一下,应该判断是否应该直接删除,还是添加一个字段(计数器)表示该节点在这个主题下有多少个客户端订阅了该主题,这样会好一些,每次删除一次,就减一,当为0时再删除该节点。
/**
删除节点在当前主题上的
*/
func (this *Route) DeleteNodeInTopic(topic string, node *Node) (bool, *error2.TopicError) {
topic = strings.TrimSpace(topic)
topics := strings.Split(topic, "/")
lens := len(topics)
if lens == 0 {
return false, error2.CreatTopicError("topic is entity or /")
}
rwLock.Lock()
defer rwLock.Unlock()
if v, ok := (*this.Table)[topics[0]]; ok {
if lens == 1 {
// 删除当前
delete(*v.Nodes, node.Name)
} else {
// 往下搜索删除
deleteNode(v, topics[1:], node)
}
}
// 没有考虑 + #,因为第一层限制不能有这两个
return true, nil
}
/**
删除节点
*/
func deleteNode(tab *RouteTable, topics []string, node *Node) {
lens := len(topics)
if v, ok := (*tab.Children)[topics[0]]; ok {
if lens == 1 {
delete(*v.Nodes, node.Name)
} else {
deleteNode(v, topics[1:], node)
}
}
}
4.查找某主题应该向哪些节点发送消息
/**
根据主题查找节点
*/
func (this *Route) FindNodeByTopic(topic string) ([]string, *error2.TopicError) {
tt := strings.TrimSpace(topic)
topics := strings.Split(tt,"/")
lens := len(topics)
if ok,err := checkIllegalChar(&topics);!ok{
return nil,err
}
// 先创建保存结果的map
rest := map[string]*Node{}
rwLock.RLock()
defer rwLock.RUnlock()
if v, ok := (*this.Table)[topics[0]]; ok {
if lens == 1 {
// 只需要在当前第一层Nodes中查找
// 因为第一层限制了+ # $ 所以不要考虑那么复杂
for _, v := range *v.Nodes{
rest[v.Name] = v
}
} else {
// 往下搜索添加
findNode(v, topics[1:], &rest)
}
mapLen := len(rest)
ret := make([]string,mapLen)
i := 0
for k, _ := range rest {
k1 := k
ret[i] = k1
i++
}
return ret,nil
}
return nil,error2.CreatTopicError("no have this topic")
}
// 查找元素
func findNode(tab *RouteTable, topics []string,rest *map[string]*Node) {
// if topic == # 把下面的全部添加,然后返回
if topics[0] == MultiLevel{
addMulti(tab,rest)
}else if topics[0] == SingleLevel{
// if topic == + 把 + 和 其他主题 下符合条件的添加
addSingle(tab,topics,rest)
}else{
// if topic == 其他主题 把 + 和 其他主题下符合条件的添加,并且添加 # 下的
addOther(tab,topics,rest)
}
}
/**
全部添加
*/
func addMulti(tab *RouteTable,rest *map[string]*Node) {
addNode(tab.Nodes,rest)
addMultiLay(tab,rest)
}
/**
这个是当前这一层的nodes不需要添加的 全部添加方法
*/
func addMultiLay(tab *RouteTable,rest *map[string]*Node) {
for _, v := range *tab.Children{
addMulti(v,rest)
}
}
/**
添加 + 主题的
*/
func addSingle(tab *RouteTable, topics []string,rest *map[string]*Node) {
lens := len(topics)
if lens == 1{
if topics[0] == SingleLevel || topics[0] == MultiLevel{
// 只要把当前层所有都添加
for _,v := range *tab.Children {
for k1, v1 := range *v.Nodes {
(*rest)[k1] = v1
}
}
}else {
// 添加 topics[0] 、+ 、# 的node
if v, ok := (*tab.Children)[topics[0]]; ok {
addNode(v.Nodes,rest)
}
if topics[0] != SingleLevel {
if v, ok := (*tab.Children)[SingleLevel]; ok {
addNode(v.Nodes,rest)
}
}
if topics[0] != MultiLevel{
if v, ok := (*tab.Children)[MultiLevel]; ok {
addNode(v.Nodes,rest)
}
}
}
}else{
if topics[0] == SingleLevel{
// 其它的看后面是否符合
for k, v := range *tab.Children {
// 把该层 # 下的node加上
if k == MultiLevel{
addNode(v.Nodes,rest)
}else{
//else if k == topics[1]{
// // 看后一层,继续递归搜索
// addSingle(v,topics[1:],rest)
//}
v1 := v
addSingle(v1,topics[1:],rest)
}
}
}else if topics[0] == MultiLevel{
addMulti(tab,rest)
}else {
// 从 + 号后面查找
if v, ok := (*tab.Children)[SingleLevel]; ok{
addSingle(v,topics[1:],rest)
}
// 从 topics[0] 后面查找
if v, ok := (*tab.Children)[topics[0]]; ok{
addSingle(v,topics[1:],rest)
}
}
}
}
/**
添加其它主题
*/
func addOther(tab *RouteTable, topics []string,rest *map[string]*Node) {
lens := len(topics)
if lens == 1 {
// 添加 topics[0] 、+ 、# 的node
if topics[0] == SingleLevel {
for _, v := range *tab.Children{
v1 := v
addNode(v1.Nodes,rest)
}
}else if topics[0] == MultiLevel{
for _, v := range *tab.Children{
v1 := v
addMulti(v1,rest)
}
}else {
if v, ok := (*tab.Children)[topics[0]]; ok {
addNode(v.Nodes,rest)
}
if v, ok := (*tab.Children)[SingleLevel]; ok {
addNode(v.Nodes,rest)
}
if v, ok := (*tab.Children)[MultiLevel]; ok {
addNode(v.Nodes,rest)
}
}
return
}
if topics[0] == MultiLevel{
addMulti(tab,rest)
}else if topics[0] == SingleLevel{
addSingle(tab,topics,rest)
}else {
if v, ok := (*tab.Children)[topics[0]]; ok{
addOther(v,topics[1:],rest)
}
if v, ok := (*tab.Children)[SingleLevel]; ok{
addOther(v,topics[1:],rest)
}
if v, ok := (*tab.Children)[MultiLevel]; ok{
addMulti(v,rest)
}
}
}
func addNode(nodes *map[string]*Node ,rest *map[string]*Node) {
for k, v := range *nodes{
v1 := v
k1 := k
(*rest)[k1] = v1
}
}
三 、测试
import (
"fmt"
"study_one_day/gotcp/topic"
)
func main() {
s := "123456789"
for _, i := range s {
_, _ = topic.RouteTables.AddNodeInTopic("a/b/"+string(i), &topic.Node{
Ip: "127.0.0.1",
Port: "8080",
Name: "test" + string(i),
})
}
_, _ = topic.RouteTables.AddNodeInTopic("a/+/c", &topic.Node{
Ip: "127.0.0.1",
Port: "8080",
Name: "test+3",
})
_, _ = topic.RouteTables.AddNodeInTopic("a/+/#", &topic.Node{
Ip: "127.0.0.1",
Port: "8080",
Name: "test+2",
})
_, _ = topic.RouteTables.AddNodeInTopic("a/+/+/c", &topic.Node{
Ip: "127.0.0.1",
Port: "8080",
Name: "test+",
})
_, _ = topic.RouteTables.AddNodeInTopic("a/b/e", &topic.Node{
Ip: "127.0.0.1",
Port: "8080",
Name: "test+e",
})
_, _ = topic.RouteTables.AddNodeInTopic("c/#", &topic.Node{
Ip: "127.0.0.1",
Port: "8080",
Name: "test#",
})
_, _ = topic.RouteTables.AddNodeInTopic("a/#", &topic.Node{
Ip: "127.0.0.1",
Port: "8080",
Name: "test##",
})
_, _ = topic.RouteTables.AddNodeInTopic("c/+/2", &topic.Node{
Ip: "127.0.0.1",
Port: "8080",
Name: "##",
})
_, _ = topic.RouteTables.AddNodeInTopic("a/2/c", &topic.Node{
Ip: "127.0.0.1",
Port: "8080",
Name: "123123",
})
if rest, err := topic.RouteTables.FindNodeByTopic("a/b/#");err == nil{
fmt.Printf("%#v",rest)
}
fmt.Println()
}
四、总结
如果发现 bug ,希望各位看官告诉我一下,小弟看着自己写的代码有点迷了。