在正常的 HTTP 服务流程中,客户端向服务器发送 HTTP 请求,服务器收到后会向客户端返回一个 HTTP 答复。但在实际生产环境中,真正处理请求的服务器和客户端之间通常还会引入一个中介,也就是反向代理服务器,在引入了反向代理服务器之后,客户端会先将请求发送给反向代理服务器,反向代理收到该 HTTP 请求后,并不会自己亲自处理该请求,而是将请求转发给真正的处理业务逻辑的服务器,让它去处理,业务逻辑服务器处理完之后,会先将答复返回给反向代理服务器,再由反向代理服务器返回给客户端。
生活中常见的 NGINX 就常常作为反向代理服务器来使用。那么你可能会问,传统 C/S 模型难道不够好吗,还非要引入一个不知道什么鬼的反向代理来捣乱,引入之后只会把架构变得更复杂,有啥好处?(傲娇状)其实原因很简单,第一反向代理服务器可以当作防火墙来用,它为真正的业务逻辑服务器与外界可能的恶意攻击之间提供了一个屏障,尤其是像银行这种对安全性要求极高的金融机构无论加几个这样的防火墙都不会嫌多。第二点就是它可以提供负载均衡功能,通过使用不同的策略将请求转发给不同的服务器,以达到负载均衡的目的。
幸运的是,引入反向代理,并不会改变传统的 C/S 编程模型,因为客户端无法感知反向代理服务器的存在,他们只会觉得反向代理服务器就是真正处理业务逻辑的服务器。
![]()
下面我们来实现一个简单的反向代理服务器吧,虽然听着很恐怖,但是不用慌,强大的 Golang 标准库已经实现了反向代理的功能了。httputil 中提供的 ReverseProxy 结构体具有将请求重定向的能力( Director ),也具有将答复修改的能力( ModifyResponse ),最重要的是,可以通过 Transport 这个 http.RoundTripper 将我们的代理请求发送出去。
1 | type ReverseProxy struct { |
作为一个代理服务器,虽然不直接处理业务逻辑,但是最基本的对请求的处理和答复的功能还是要有的,与一般的 HTTP 服务器类似,我们的反向代理服务器 ReverseProxy 也需要有 ServeHTTP(http.ResponseWriter, *http.Request) 这样的方法。
1 | type Handler interface { |
所以为了实现代理请求的功能,我们需要定义一个结构体 handler ,使其实现 ServeHTTP(http.ResponseWriter, *http.Request) 方法,在该方法中我们通过 httputil.NewSingleHostReverseProxy 来构造一个反向代理,并将请求通过 proxy.ServeHTTP(w, r) 方法将请求代理到随机的(随机是为了实现负载均衡)服务器上。
1 | servers := []string{ |
下面我们申明一个 http.Server ,在 StartServer 中我们监听指定的端口( bind ),其对应的 Handler 就是我们之前实现的 handler 结构体,用于把请求代理到 remote 上去,这样以来一个基本的反向代理服务器就实现好了。
1 | var srv http.Server |
上述例子中的采用的负载均衡算法只用了非常简单的随机选取策略,这只适合每台服务器性能差别不大的情况,如果服务器之间性能差距较大,我们可以使用 Weighted Round Robin 这样的负载均衡算法。