如何解决 Golang WAF 服务的竞争条件?
How can I solve race condition for Golang WAF service?
首先,对于糟糕的英语表示抱歉。
我尝试用Golang开发一个WAF(Web Application Firewall)服务。一切都在map[string]*Struct{}
内存中。当请求到来时,我将请求 header 的主机设置为映射到处理函数中。
host,err := GetHost(r.Host)
func GetHost(host string) (*Host,error){
split, _, err := net.SplitHostPort(host)
if err == nil {
host = split
}
if data, val := hosts[host]; val {
return data, nil
}
return nil,errors.New("host not found!)
}
//hosts is a map for all host, key is host and value is host struct.
问题是,当服务收到大量请求时,地图会变得混乱。例如;
host 是 example.com
但 hosts["example.com"]
给出了另一个无关的值。
type Server struct {
mu sync.RWMutex
Host *models.Host
}
func (c *Server) handler(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mutex.Lock()
defer mutex.Unlock()
var err error
c.Host, err = GetHost(r.Host)
if err != nil {
w.WriteHeader(http.StatusBadGateway)
w.Write([]byte(r.Host + " not found!!"))
return
}
//it's going on..Edited part
所以,我尝试使用mutex
和wg
来解决这个问题,但没有用。欢迎提出任何建议。
您认为需要从 HTTP 处理程序序列化对共享状态的访问是正确的(因为服务器在专用 goroutine 中处理每个 HTTP 请求)。否则,您的程序确实会遇到同步错误,这可能会在执行过程中表现为数据竞争,正如您似乎已经经历过的那样; Go 工具链随附的 race detector 可能会使用它。
可以说,序列化对该共享状态的访问的最简单方法是使用一些互斥体。但是,您需要小心。您对 mutex.Unlock
的延迟调用存在问题,原因至少有一个,可能有两个:
- 一般来说,您应该努力保持关键部分(代码中被对
Lock
和Unlock
的调用包围的部分)尽可能“小”和“便宜”。简而言之,关键部分应该只做内存处理,而不是 I/O 东西。在这里,在处理每个请求的整个过程中都需要持有锁,这很可能会导致您的服务器发生大量争用。
- 虽然您在处理程序中省略了代码的末尾,但我猜 (?) 您稍后也会获取锁以更新地图(如果之前未遇到当前请求的主机) .但是,由于包
sync
导出的 none 互斥锁类型是可重入的,您可能会遇到死锁:由于对 Unlock
的调用被推迟,互斥锁将只有当你的处理程序终止时才会被释放。
一个解决方案是避开 defer
并将关键部分限制为对 GetHost
函数的调用。
另一项改进是 eliminate global state,以获得更好的可测试性等。您可以通过简单地将 hosts
地图存储在 Server
的字段中来使您的 hosts
地图成为非全球地图结构并将 GetHost
声明为 *Server
.
上的方法
type Server struct {
mu sync.RWMutex
Host *models.Host
hosts map[string]*Host
}
func (srv *Server) GetHost(host string) (*Host, error){
split, _, err := net.SplitHostPort(host)
if err == nil {
host = split
}
if data, exists := srv.hosts[host]; exists {
return data, nil
}
return nil, errors.New("host not found!")
}
func (srv *Server) handler(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mutex.Lock()
srv.Host, err := srv.GetHost(r.Host)
mutex.Unlock()
if err != nil {
w.WriteHeader(http.StatusBadGateway)
w.Write([]byte(r.Host + " not found!!"))
return
}
// possibly acquire and release the lock again
// for further treatment of the hosts map
})
}
我可能遗漏了一些东西,但必须承认我没有看到为每个请求更新 Server
结构的 Host
字段的意义...
首先,对于糟糕的英语表示抱歉。
我尝试用Golang开发一个WAF(Web Application Firewall)服务。一切都在map[string]*Struct{}
内存中。当请求到来时,我将请求 header 的主机设置为映射到处理函数中。
host,err := GetHost(r.Host)
func GetHost(host string) (*Host,error){
split, _, err := net.SplitHostPort(host)
if err == nil {
host = split
}
if data, val := hosts[host]; val {
return data, nil
}
return nil,errors.New("host not found!)
}
//hosts is a map for all host, key is host and value is host struct.
问题是,当服务收到大量请求时,地图会变得混乱。例如;
host 是 example.com
但 hosts["example.com"]
给出了另一个无关的值。
type Server struct {
mu sync.RWMutex
Host *models.Host
}
func (c *Server) handler(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mutex.Lock()
defer mutex.Unlock()
var err error
c.Host, err = GetHost(r.Host)
if err != nil {
w.WriteHeader(http.StatusBadGateway)
w.Write([]byte(r.Host + " not found!!"))
return
}
//it's going on..Edited part
所以,我尝试使用mutex
和wg
来解决这个问题,但没有用。欢迎提出任何建议。
您认为需要从 HTTP 处理程序序列化对共享状态的访问是正确的(因为服务器在专用 goroutine 中处理每个 HTTP 请求)。否则,您的程序确实会遇到同步错误,这可能会在执行过程中表现为数据竞争,正如您似乎已经经历过的那样; Go 工具链随附的 race detector 可能会使用它。
可以说,序列化对该共享状态的访问的最简单方法是使用一些互斥体。但是,您需要小心。您对 mutex.Unlock
的延迟调用存在问题,原因至少有一个,可能有两个:
- 一般来说,您应该努力保持关键部分(代码中被对
Lock
和Unlock
的调用包围的部分)尽可能“小”和“便宜”。简而言之,关键部分应该只做内存处理,而不是 I/O 东西。在这里,在处理每个请求的整个过程中都需要持有锁,这很可能会导致您的服务器发生大量争用。 - 虽然您在处理程序中省略了代码的末尾,但我猜 (?) 您稍后也会获取锁以更新地图(如果之前未遇到当前请求的主机) .但是,由于包
sync
导出的 none 互斥锁类型是可重入的,您可能会遇到死锁:由于对Unlock
的调用被推迟,互斥锁将只有当你的处理程序终止时才会被释放。
一个解决方案是避开 defer
并将关键部分限制为对 GetHost
函数的调用。
另一项改进是 eliminate global state,以获得更好的可测试性等。您可以通过简单地将 hosts
地图存储在 Server
的字段中来使您的 hosts
地图成为非全球地图结构并将 GetHost
声明为 *Server
.
type Server struct {
mu sync.RWMutex
Host *models.Host
hosts map[string]*Host
}
func (srv *Server) GetHost(host string) (*Host, error){
split, _, err := net.SplitHostPort(host)
if err == nil {
host = split
}
if data, exists := srv.hosts[host]; exists {
return data, nil
}
return nil, errors.New("host not found!")
}
func (srv *Server) handler(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mutex.Lock()
srv.Host, err := srv.GetHost(r.Host)
mutex.Unlock()
if err != nil {
w.WriteHeader(http.StatusBadGateway)
w.Write([]byte(r.Host + " not found!!"))
return
}
// possibly acquire and release the lock again
// for further treatment of the hosts map
})
}
我可能遗漏了一些东西,但必须承认我没有看到为每个请求更新 Server
结构的 Host
字段的意义...