提供一些上下文,我正在使用gRPC在每一个客户端连接的服务器上实现扇出(流式传输),我将流保存到映射中,当客户端断开连接时,我只是将其从映射中删除,我可以仅使用“实时”客户端/连接来更新地图。
因此初始地图如下所示:
key value
client1 stream
client2 stream
client3 stream
此实现的问题在于,如果客户端多次连接,则只有最后一个客户端(使用相同的key id
)会收到“广播”消息,因为该映射将使用最新的覆盖现有的流一个来自客户端连接,因此我需要使用唯一键或找到一种方法来匹配,关联客户端和连接。
我发现只需切换地图并使用连接ID作为键即可,就像这样
conn_id client_id
conn1 client1
conn2 client2 \
conn3 client2 > 3 connections for client ID 2
conn4 client2 /
这有效并且有助于向所有连接的客户端发送消息,但是如果我想向特定客户端发送消息,则事情变得更加复杂,因为我需要范围/遍历完整映射以将值与想要的客户。
为解决此问题,我使用client_id切换回了地图,但我没有使用流作为值,而是使用了切片列表
key value
client1 [stream, stream, stream]
client2 [stream]
client3 [stream, stream]
问题仍然存在,因为为了向所有客户端发送消息,整个地图仍然需要遍历/范围以获取每个客户端的流并将消息发送给每个客户端。
因此,在不使用数据库的情况下,不知道遵循big O notation的最佳做法可以使用哪种数据结构?
主要是想找到行为与数据库foreign keys类似的东西,可能有2个映射,一个映射保留所有连接,而另一个仅保留每个客户端的状态或连接数,例如:
key value client_id # streams
conn1 client1 client1 1
conn2 client2 client2 3
conn3 client2
conn4 client2
但是,当clients
映射中不再有来自该客户端的连接时,如何从connections
映射中“以原子方式”删除该客户端。
这是一个尝试,但想知道是否可以改进:
https://play.golang.org/p/VEoLsWh6dbJ
package main
import (
"fmt"
"sync"
)
func main() {
clients := &sync.Map{}
for i := 0; i < 100; i++ {
clientID := fmt.Sprintf("client-%d", i%5)
connID := fmt.Sprintf("conn-%d", i)
client, ok := clients.LoadOrStore(clientID, connID)
if ok {
switch c := client.(type) {
case string:
client = []string{c, connID}
case []string:
client = append(client.([]string), connID)
}
clients.Store(clientID, client)
}
}
list := func(k, v interface{}) bool {
fmt.Printf("k: %+v v: %v\n", k, v)
return true
}
clients.Range(list)
// Get all connections
broadcast := func(k, v interface{}) bool {
fmt.Printf("To all connections from= %q\n", k)
for _, v := range v.([]string) {
fmt.Printf("msg to conn = %s\n", v)
}
return true
}
clients.Range(broadcast)
}
在这里,我尝试仅使用地图(易于处理删除)的速度较慢,但分配的字节数较少:
https://play.golang.org/p/hz5uRX-VAvH
package main
import (
"fmt"
"sync"
)
func main() {
clients := &sync.Map{}
for i := 0; i < 100; i++ {
clientID := fmt.Sprintf("client-%d", i%5)
connID := fmt.Sprintf("conn-%d", i)
conns, ok := clients.Load(clientID)
if ok {
conns.(*sync.Map).Store(connID, i)
} else {
conns := &sync.Map{}
conns.Store(connID, i)
clients.Store(clientID, conns)
}
}
list := func(k, v interface{}) bool {
fmt.Printf("k: %v\n", k)
listValue := func(j, l interface{}) bool {
fmt.Printf(" j = %+v\n", j)
return true
}
v.(*sync.Map).Range(listValue)
return true
}
clients.Range(list)
// Get all connections
broadcast := func(k, v interface{}) bool {
fmt.Printf("To all connections from= %q\n", k)
listValue := func(j, l interface{}) bool {
fmt.Printf(" msg to conn = %s -- %v\n", j, l)
return true
}
v.(*sync.Map).Range(listValue)
return true
}
clients.Range(broadcast)
}