前言
Global Accelerator (GA) 是 AWS 提供的網路加速服務,透過提早 TCP Termination 與品質較佳的 AWS 骨幹網路來加速傳輸,來達到網路傳輸加速效果,其原理類似於 Proxy。
原理
透過 Slow-Start、 Bandwidth-Delay Product 我們可以知道延遲對於「網速」有很大的影響,這裡的網速指的不只是頻寬,而是「從開始 TCP 握手到收完回應 4 次揮手」的耗時。
因此提早 TCP Termination 可以加速提升 Window Size,可以儘早進入高品質的 AWS 骨幹網路(但不一定是最好)之外,還可以在骨幹中使用 Jumbo Frame 傳輸,因此效率較好。
綜合以上概念,如果 Client 本身網路品質不錯,那麼 GA 對於小檔案傳輸的改善有限;如果是較大一點的檔案可以受惠於 TCP Termination 以儘早提升 TCP Window Size,加上骨幹網路使用 Jumbo Frames 傳輸可減少 TCP overhead,以提高傳輸效率。
註:通常付越多錢的上網用戶可以優先享有較近、較快、較穩的線路,傳輸品質肯定比較好。因為好的線路頻寬有限,ISP 會因為線路價格考量來安排優先級。
效能量測方法
根據文件,可以到 AWS Global Accelerator Speed Comparison 網站 測試不同地點的加速效果。
官方也有提供效能量測分析文章 Measuring AWS Global Accelerator performance and analyzing results 來說明如何測試,像是可以透過 iPerf、MTR 等工具每小時做 1000 次採樣等。
以下提供簡易版 HTTP 測試程式碼:
import statistics
import time
import requests
times = 5
direct_url = "https://c7b8c7e-12d0-8f3-4c7-8ef01f8a843-0.direct.us-east-1.prod.endpoints.ubiquity.aws.a2z.com/api/burst/51200"
ga_url = "https://c7b8c7e-12d0-8f3-4c7-8ef01f8a843-0.ubiquity.us-east-1.prod.endpoints.ubiquity.aws.a2z.com/api/burst/51200"
direct_result = []
ga_result = []
def print_statistics(text, v):
print(text)
print(" min: " + str(min(v)))
print(" max: " + str(max(v)))
print(" avg: " + str(statistics.mean(v)))
if __name__ == "__main__":
for i in range(times):
t1 = time.time()
requests.get(direct_url)
t2 = time.time()
direct_result.append(t2 - t1)
t1 = time.time()
requests.get(ga_url)
t2 = time.time()
ga_result.append(t2 - t1)
print("## Loop times: " + str(times))
print_statistics("=== Direct ===", direct_result)
print_statistics("=== GA ===", ga_result)
結果:
## Loop times: 5
=== Direct ===
min: 1.2958009243011475
max: 1.525893211364746
avg: 1.3494491577148438
=== GA ===
min: 0.9617481231689453
max: 1.1492066383361816
avg: 1.0054255485534669
效能量測陷阱
因為 GA 預設啟用 TCP Termination 功能,如果「從送出請求後開始計算傳輸時間」會使得 GA 耗時看起來比直接連線高一些。T 同事當時舉例給我聽時我滿驚訝的,沒想到會有這種陷阱,以下就舉例給大家看一下。
假設地理位置、延遲設置如下:
用 GA: Client <-5-> GA <----95----> NLB
直接連: Client <-------100---------> NLB
假設耗時計算為「從請求送出開始計算」,且 NLB 與後端 Target 請求處理時間為 0,以下為事件時間軸與耗時:
用 GA:
(已經建立完 TCP 連線)
--> 5(送出請求)
<-- 5(GA ACK)
##客戶端認為請求已送出,開始計算耗時##
--> 95(請求到 NLB,此時不用計算 ACK,因為跟請求處理為同步進行)(但這個 95 跟上一個 ACK 是同步進行,因此實際上要算 90)
<-- 95(NLB 回應請求)
<-- 5(GA 回應給 Client)
##客戶端收到回應##
=========================
實際耗時 = 5 + 5 + (95-5) + 95 + 5 = 200
客戶端認為耗時 = 95 + 95 + 5 = 195
直接連線:
(已經建立完 TCP 連線)
--> 100(送出請求)
<-- 100(NLB ACK)
##客戶端認為請求已送出,開始計算耗時##
<-- 0(NLB 回應請求,因為假設請求處理時間為 0,因此會跟上一個 ACK 同時間送出封包,在此就把時間設定為 0)
##客戶端收到回應##
=========================
實際耗時 = 100 + 100 = 200
客戶端認為耗時 = 0
總結以上資訊,我們可以看到不論 用 GA 還是 直接連線 的總耗時是一樣的,但計算方式不同會誤以為 GA 耗時較高。因此比較好的方式還是從「請求送出前就開始計時,直到收到回應才停止」會是比較好的計算方式。
實驗
口說無憑,我們來寫程式驗證這件事情。下面程式有四個迴圈:
- 前 2 個迴圈是對 GA 與 NLB 做 2 種計時
- 建立連線前
- 發送請求後
- 後 2 個迴圈一樣對 GA 與 NLB 發起連線,只是在送出請求前會先 sleep 一秒鐘,並且在送出請求後才開始計時
- 這個是要確保 GA 已經跟 NLB 握手完成
(以下公網 IP 地址經過變造,如有雷同純屬巧合)
import socket
import statistics
import time
nlb_ip = "13.215.123.77"
ga_ip = "15.197.219.136"
times = 5
file_size = (
12 * 1024 * 1024
) # 12MB, please make sure this value is smaller than your actual response size
payload = b"GET / HTTP/1.1\r\nHost: "
no_sleep_nlb_t1 = []
no_sleep_nlb_t2 = []
no_sleep_ga_t1 = []
no_sleep_ga_t2 = []
sleep_nlb = []
sleep_ga = []
def recvall(sock, n):
# Helper function to recv n bytes or return None if EOF is hit
data = bytearray()
while len(data) < n:
packet = sock.recv(n - len(data))
if not packet:
return None
data.extend(packet)
return data
"""Without sleep
The normal behavior of users. Compare between "whole time" and "response time".
It illustrates the wrong calculation method reverses the result.
"""
for i in range(times):
t1 = time.time()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = (nlb_ip, 80)
sock.connect(server_address)
sock.sendall(payload + nlb_ip.encode() + b"\r\n\r\n")
t2 = time.time()
# sock.recv(10)
recvall(sock, file_size)
end = time.time()
no_sleep_nlb_t1.append(end - t1)
no_sleep_nlb_t2.append(end - t2)
for i in range(times):
t1 = time.time()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = (ga_ip, 80)
sock.connect(server_address)
sock.sendall(payload + ga_ip.encode() + b"\r\n\r\n")
t2 = time.time()
# sock.recv(10)
recvall(sock, file_size)
end = time.time()
no_sleep_ga_t1.append(end - t1)
no_sleep_ga_t2.append(end - t2)
"""With sleep
Make sure GA has forward the request to origin for fairness.
Just for comparison, this behavior doesn't exist in real world.
"""
for i in range(times):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = (nlb_ip, 80)
sock.connect(server_address)
time.sleep(1)
sock.sendall(payload + nlb_ip.encode() + b"\r\n\r\n")
start = time.time()
# sock.recv(10)
recvall(sock, file_size)
end = time.time()
sleep_nlb.append(end - start)
for i in range(times):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = (ga_ip, 80)
sock.connect(server_address)
time.sleep(1)
sock.sendall(payload + ga_ip.encode() + b"\r\n\r\n")
start = time.time()
# sock.recv(10)
recvall(sock, file_size)
end = time.time()
sleep_ga.append(end - start)
def print_statistics(text, v):
print(text)
print(" min: " + str(min(v)))
print(" max: " + str(max(v)))
print(" avg: " + str(statistics.mean(v)))
print("## Loop times: " + str(times))
print_statistics("=== No sleep NLB (whole time) ===", no_sleep_nlb_t1)
print_statistics("=== No sleep NLB (after request) ===", no_sleep_nlb_t2)
print_statistics("=== No sleep GA (whole time) ===", no_sleep_ga_t1)
print_statistics("=== No sleep GA (after request) ===", no_sleep_ga_t2)
print_statistics("=== Sleep NLB (after request) ===", sleep_nlb)
print_statistics("=== Sleep GA (after request) ===", sleep_ga)
結果
- 從 東京(ap-northeast-1)連 新加坡(ap-southeast-1)請求 249 bytes 檔案
- 「No sleep NLB (whole time)」與「No sleep GA (whole time)」結果差不多,因為請求檔案小加速效果不大
- 可以看到「No sleep NLB (after request)」時間比「No sleep GA (after request)」少了一半!這就是錯誤計算方式導致的結果
- 「Sleep NLB (after request) 」與「Sleep GA (after request) 」結果差不多,因為 Sleep 起了作用
## Loop times: 5
=== No sleep NLB (whole time) ===
min: 0.13748788833618164
max: 0.1437385082244873
avg: 0.14207563400268555
=== No sleep NLB (after request) ===
min: 0.06859135627746582
max: 0.07164549827575684
avg: 0.0708247184753418
=== No sleep GA (whole time) ===
min: 0.13784289360046387
max: 0.1636338233947754
avg: 0.143807315826416
=== No sleep GA (after request) ===
min: 0.13602924346923828
max: 0.16110968589782715
avg: 0.14150776863098144
=== Sleep NLB (after request) ===
min: 0.06822395324707031
max: 0.07148408889770508
avg: 0.06983485221862792
=== Sleep GA (after request) ===
min: 0.0698554515838623
max: 0.07162070274353027
avg: 0.07061533927917481
- 從 東京(ap-northeast-1)連 新加坡(ap-southeast-1)請求 12MiB 檔案
- 因為是大檔案請求,GA 明顯有作用
- 「No sleep NLB (after request)」時間沒有比 GA 短,因為大檔案傳輸時間遠大於 TCP Termination 提早回應 ACK 時間
## Loop times: 5
=== No sleep NLB (whole time) ===
min: 0.9568026065826416
max: 2.7421250343322754
avg: 1.3382397174835206
=== No sleep NLB (after request) ===
min: 0.8890306949615479
max: 2.670349597930908
avg: 1.268001174926758
=== No sleep GA (whole time) ===
min: 0.8354196548461914
max: 0.8420989513397217
avg: 0.8386162281036377
=== No sleep GA (after request) ===
min: 0.8332135677337646
max: 0.8386590480804443
avg: 0.8356685638427734
=== Sleep NLB (after request) ===
min: 0.9029538631439209
max: 0.993417501449585
avg: 0.9541794776916503
=== Sleep GA (after request) ===
min: 0.7665829658508301
max: 0.7844345569610596
avg: 0.7727417945861816
常見問題
- 為何 GA 沒有明顯加速
- 可能使用者網路品質本來就很好,甚至比 AWS GA 還好
- 可能是 Client 因為一些因素沒有就近訪問 GA 節點(繞遠路了)
- 可能 GA 節點、骨幹出問題
- 如何診斷問題
- 請遇到連線較慢問題的 Client 造訪 GA Speedtest、 GA Diagnostics 頁面,並將 Trace ID 提供給 Support 工程師調查
- 為何 GA 會造成一些奇怪的問題
- 請開案例問 Support 工程師
comments powered by Disqus