我們一起聊聊什么是正向代理和反向代理
從字面意思上看,代理就是代替處理的意思,一個對象有能力代替另一個對象處理某一件事。
代理,這個詞在我們的日常生活中也不陌生,比如在購物、旅游等場景中,我們經常會委托別人代替我們完成某些任務。在技術領域,這個概念也被廣泛應用,尤其是在計算機網絡通信和程序設計中,代理扮演著相當重要的角色,涉及控制訪問、安全保護、能力擴展等復雜而強大的方面。
網絡通信中的代理
在計算機網絡中,說到代理,經常會談到正向代理和反向代理的概念。
在詳細展開前,我們先使用一個比喻來形象的理解下這兩個概念:小明去飯館吃飯,正向代理就像是小明的朋友幫他去點餐,服務員并不知道最終吃飯的人是小明;而反向代理則像是飯館的服務員,他們決定把小明的訂單送到哪個廚師手里去做。通過這個比喻,我們可以初步感受到正向代理和反向代理在角色和功能上的不同。
搞清楚網絡通信中的代理和反向代理,大家只要弄明白兩件事:你在公司的電腦是怎么訪問到外網的,你部署的網站或者API又是怎么被外網訪問到的。
公司電腦上網
首先看公司電腦上網:公司里的電腦一般不會直接連接到互聯網,它們通常在一個內網環境中,這既有成本的考慮,也有安全控制的需要。辦公電腦一般會先連接到交換機,交換機再連接到路由器,路由器再連接到互聯網。
在這些連接中,交換機只是一個小透明,辦公電腦可以看到路由器,路由器也可以看到辦公電腦,所以交換機不是我們這里所說的代理。
這里真正的代理是路由器,辦公電腦訪問網絡時,請求先到達路由器,路由器做個請求來源的登記,記下這個請求是從哪臺電腦發出的,然后再發到互聯網上。請求出了路由器,互聯網上能夠看到的就是這個路由器,而看不到你的辦公電腦。數據從遠程服務器返回時,也是先到達這個路由器,路由器再根據之前做的請求來源登記,將數據轉發到對應的辦公電腦上。
這種場景下,路由器就是一個正向代理,代理內網電腦訪問互聯網。
圖片
除了使用路由器這種比較常見的代理方式,其實還有很多方式,比如在瀏覽器中配置HTTP代理,只允許通過瀏覽器訪問外網。
網站被外網訪問
再看網站或者API是怎么被外網訪問到的:通常情況下,大家的服務器也是放在內網中的,直接暴露在互聯網上會有安全風險,也不利于管理。所以,我們會在服務器和互聯網之間設置一個代理服務器,通常是Nginx或者LVS這種負載均衡器。當外網的用戶想要訪問你的網站或API時,他們的請求首先會發送到這個代理服務器上。
這個代理服務器就是一個反向代理。
圖片
反向代理服務器接到請求后,它知道內網中哪臺服務器能提供這個服務,于是它就把請求轉發給對應的服務器。服務器處理完這個請求后,再把結果發送回反向代理服務器,最后由反向代理服務器返回給外網的用戶。
對比
以上就是計算機網絡中正向代理和反向代理的基本原理和應用場景,我們再做一個對比,加深印象。
正向代理和反向代理的區別主要體現在它們服務的對象和用途上:
對比項 | 正向代理(Forward Proxy) | 反向代理(Reverse Proxy) |
服務對象 | 客戶端 | 服務器 |
主要用途 | - 幫助客戶端訪問無法直接訪問的資源 - 進行訪問控制和緩存以提高速度和安全性 | - 隱藏服務器真實IP地址 - 提供負載均衡功能 - 提高服務器訪問速度和安全性 |
工作方式 | - 客戶端配置代理服務器,請求先發送至代理服務器 - 代理服務器代為訪問目標服務器并返回資源給客戶端 | - 客戶端請求發送至反向代理服務器 - 反向代理服務器根據配置轉發請求到內部網絡的特定服務器 - 從服務器獲取響應后返回給客戶端 |
舉例說明 | - 使用瀏覽器設置代理服務器,所有上網請求經由代理服務器訪問互聯網資源 | - 根據負載均衡策略將用戶請求分發到不同服務器處理 |
簡單來說,正向代理是客戶端的代理,幫助客戶端訪問到無法直接獲取的資源;反向代理是服務器的代理,幫助服務器平滑處理來自各方的請求。
程序設計中的代理
在程序設計中,也有一個代理模式,雖然和網絡中的正向代理或反向代理的概念不完全一樣,但本質上它們都是代理的概念,都是作為中介提供隔離、隱藏、控制訪問和功能增強等作用。
Just show me the code! 現在我們用Go來編寫一個代理的實例程序,假設我們有一個資源類,我們希望在訪問這個資源時,記錄訪問次數,并在資源不再被引用時自動釋放資源。
首先,定義一個資源接口Resource和實現這個接口的資源類MyResource:
package main
import (
"fmt"
)
// Resource 接口定義了資源需要實現的方法
type Resource interface {
Use()
Release()
}
// MyResource 是實現了Resource接口的資源類
type MyResource struct{}
func (r *MyResource) Use() {
fmt.Println("Using MyResource")
}
func (r *MyResource) Release() {
fmt.Println("Releasing MyResource")
}
然后,定義一個代理的類 ResourceProxy,它包含了對資源的引用和引用計數,同時它也實現了Resource接口。
// ResourceProxy 是代理的結構體,包含資源和引用計數
type ResourceProxy struct {
resource Resource
refCount int
}
// NewResourceProxy 是ResourceProxy的構造函數
func NewResourceProxy(resource Resource) *ResourceProxy {
return &ResourceProxy{resource: resource, refCount: 1} // 初始引用計數為1
}
// Use 方法增加引用計數并使用資源
func (sr *ResourceProxy) Use() {
sr.refCount++
fmt.Printf("Resource is used %d times\n", sr.refCount)
sr.resource.Use()
}
// Release 方法減少引用計數,當計數為0時釋放資源
func (sr *ResourceProxy) Release() {
sr.refCount--
if sr.refCount == 0 {
sr.resource.Release()
} else {
fmt.Printf("Resource is still used by %d references\n", sr.refCount)
}
}
最后我們使用這個代理:
func main() {
resource := &MyResource{}
proxyRef := NewResourceProxy(resource)
proxyRef.Use() // 使用資源,引用計數增加
proxyRef.Release() // 釋放一次引用,引用計數減少到0,資源被釋放
// Output:
// Resource is used 1 times
// Using MyResource
// Releasing MyResource
}
這個簡單的例子演示了代理在資源管理中的應用,可以根據實際需要添加更多復雜的邏輯,比如錯誤處理、同步控制、日志記錄等。
在程序設計中,代理模式是一種結構型設計模式,它讓我們能提供一個替代品來代表另一個對象,這個替代品控制著對原對象的訪問,可以在訪問原對象前后進行一些額外處理。
通過上邊的示例,我們可以發現代理模式的三個主要角色:
- 抽象主題(Subject):定義了代理和真實主題的共用接口,這樣在任何使用真實主題的地方都可以使用代理。
- 真實主題(Real Subject):實現了抽象主題的具體類,代表了實際的對象,是最終要使用的對象。
- 代理(Proxy):包含對真實主題的引用,控制著對真實主題的訪問,并可能負責創建和刪除它。通常會做一些額外的事情來實現自己的價值。
在代碼實際實現時,代理模式其實有多種不同的實現,包括:
- 遠程代理(Remote Proxy):為一個對象在不同的地址空間(通常是不同計算機上的服務)提供局部代表。常見的如RPC、gRPC等,通過本地代理對象,客戶端可以像調用本地接口一樣訪問遠程服務,而無需關心網絡通信的細節。
- 虛擬代理(Virtual Proxy):通過它來存放實例化需要很長時間的真實對象。常見的就是懶加載,比如加載一個大文件或者從數據庫中讀取大量數據,我們不希望在程序啟動時就立刻加載,而是希望在真正需要這些數據的時候才去加載它們。
- 保護代理(Protection Proxy):控制對原始對象的訪問。用于對象應該有不同訪問權限的時候。
- 智能引用(Smart Reference):當對象被引用時,提供一些額外的操作,比如計算對象被引用的次數。上邊提供的代碼示例就是一個智能引用的例子。
這里就不展示更多的代碼了,關鍵是在合適的時機使用恰當的代理模式來解決問題,這需要細細體會。
做個簡單的小結,代理模式就像程序中的一個“中間人”,在不需要直接訪問某個對象,或者直接訪問某個對象不太方便或者不符合需求時,代理模式提供了一個非常靈活的解決方案。
正如本文所探討的,代理模式在網絡通信和程序設計中都扮演著重要的角色。它通過提供一個中間層,增強了系統的安全性、靈活性和可維護性。掌握代理,我們就擁有了在合適的場景下解決問題的一種強大能力。希望本文的討論能對你有一點用處。