什么是负载均衡器?
负载均衡器是一种常见的代理服务器,它在一个或多个后端服务器之间分配客户端请求并进行实际工作。
代理是另一个实体的替身,它可以像另一个实体一样行事或代替另一个实体。在(Web)软件中,我们经常将代理视为网络中的一个节点,它在客户端和后端服务器之间转发请求和响应。如果我去朋友家并通过他们的Ring门铃与他们交谈,那么这个门铃就是我们之间的代理。同样,代理服务器提供了客户端(我)和某个后端服务器(我的朋友)之间的网关。就像门铃一样,它充当中间人。我可以向服务器发出请求(与我的朋友交谈),而不需要获得该服务器资源的特权(进入房子)。
代理服务器可以为互联网系统提供增加的安全性、性能和可观察性。
今天,我们将使用Go构建一个负载均衡代理服务器!
构建服务器
我们将从定义LoadBalancer
开始 - 此对象将包含有关服务器的基本信息,包括将处理代理请求的后端服务器以及负载均衡器将侦听的端口。
type LoadBalancer struct {
port string
roundRobinCount int
servers []Server
}
func NewLoadBalancer(port string, servers []Server) *LoadBalancer {
return &LoadBalancer{
port: port,
roundRobinCount: 0,
servers: servers,
}
}
通过包括一个Server
列表 - 代表一组函数的接口 - 而不是硬编码类型,我们可以使用这个负载均衡器与实现基本Server
接口的各种对象一起使用,而不必更改底层代码。为了清晰起见,我们还包括了一个构造函数(NewLoadBalancer
),但我们同样可以直接构造LoadBalancer
。
现在我们需要一个实现Server
接口的对象:
type Server interface {
// Address returns the address with which to access the server
Address() string
// IsAlive returns true if the server is alive and able to serve requests
IsAlive() bool
// Serve uses this server to process the request
Serve(rw http.ResponseWriter, req *http.Request)
}
type simpleServer struct {
addr string
proxy *httputil.ReverseProxy
}
func (s *simpleServer) Address() string { return s.addr }
func (s *simpleServer) IsAlive() bool { return true }
func (s *simpleServer) Serve(rw http.ResponseWriter, req *http.Request) {
s.proxy.ServeHTTP(rw, req)
}
没有错误处理的代码有什么用呢?对于这个例子,我们将使用一种非常简单的形式 - 如果我们看到错误,就退出!
// handleErr prints the error and exits the program
// Note: this is not how one would want to handle errors in production, but
// serves well for demonstration purposes.
func handleErr(err error) {
if err != nil {
fmt.Printf("error: %v\n", err)
os.Exit(1)
}
}
接下来,我们将编写一个帮助程序来实例化我们的服务器。此代码将采用地址并创建一个新的simpleServer
实例,该实例具有一个代理。我们将使用代理来处理我们希望发送到此服务器的任何请求。
func newSimpleServer(addr string) *simpleServer {
serverUrl, err := url.Parse(addr)
handleErr(err)
return &simpleServer{
addr: addr,
proxy: httputil.NewSingleHostReverseProxy(serverUrl),
}
}
现在,让我们设置我们的main
函数来启动负载均衡器。我们将配置它来处理任何请求,而且我们将侦听此LoadBalancer
定义的端口。我们将定义几个simpleServer
实例。addr
是我们将重定向请求的URL。
func main() {
servers := []Server{
newSimpleServer("https://www.google.com"),
newSimpleServer("https://www.bing.com"),
newSimpleServer("https://www.duckduckgo.com"),
}
lb := NewLoadBalancer("8000", servers)
handleRedirect := func(rw http.ResponseWriter, req *http.Request) {
lb.serveProxy(rw, req)
}
// register a proxy handler to handle all requests
http.HandleFunc("/", handleRedirect)
fmt.Printf("serving requests at 'localhost:%s'\n", lb.port)
http.ListenAndServe(":"+lb.port, nil)
}
好了,所以我们已经配置了服务器的基本结构以及大部分需要的部分。最后一步是定义LoadBalancer
接收请求时发生的情况。
// getNextServerAddr returns the address of the next available server to send a
// request to, using a simple round-robin algorithm
func (lb *LoadBalancer) getNextAvailableServer() Server {
server := lb.servers[lb.roundRobinCount%len(lb.servers)]
for !server.IsAlive() {
lb.roundRobinCount++
server = lb.servers[lb.roundRobinCount%len(lb.servers)]
}
lb.roundRobinCount++
return server
}
func (lb *LoadBalancer) serveProxy(rw http.ResponseWriter, req *http.Request) {
targetServer := lb.getNextAvailableServer()
// could optionally log stuff about the request here!
fmt.Printf("forwarding request to address %q\n", targetServer.Address())
// could delete pre-existing X-Forwarded-For header to prevent IP spoofing
targetServer.Serve(rw, req)
}
我们使用getNextAvailableServer()
来确定哪个后端服务器最适合处理我们的请求,通过某种负载均衡算法(在这种情况下,我们使用循环轮询与健康检查相配合)。一旦我们知道将请求转发到哪个服务器,我们将在该服务器的代理上发出请求。代理将将客户端的请求路由到目标,并将目标的响应返回给客户端。
现在,唯一剩下的就是运行服务器并发出请求!
运行服务器
你可以从我的GitHub克隆完整代码,或通过按照本教程操作来生成代码。
一旦你拥有服务器代码,请在终端窗口中导航到该目录并键入go run src/main.go
。你应该会看到终端输出,指示服务器正在运行。
现在,你可以在浏览器中转到localhost:8000
,然后你应该被重定向到servers
中定义的地址之一。当你刷新时,负载均衡代理服务器将你重定向到不同的URL。
请注意,你的浏览器可能会记住localhost:8000
和其中一个给定的服务器地址之间的身份,因此最好使用隐身窗口。还要注意,定义的链接可能会从它们各自的主机中给出错误(例如,在https://google.com
中前往/
会产生错误)。
祝愉快!
译自:https://betterprogramming.pub/building-a-load-balancer-in-go-3da3c7c46f30
评论(0)