最近 Azure Application Gateway V2 已经在 Azure China 上线,今儿我们蹭个热度就着 AGWv2 来介绍一下如何通过 Connection Drain 实现应用平滑变更。什么是 Connection Drain, AGW 作为七层 HTTP 负载均衡,可以作为应用服务器或 Web 服务器的前端暴露出来,用户可以通过访问 AGW 上的公网 IP 来访问后端的应用服务器或 Web 服务器,在后端服务器在运行过程中难免需要做在线的版本迭代和变更,当我们对后端服务器做在线变更时通常我们需要考虑一个问题是,此次变更是否影响当前业务,是否需要安排变更窗口,是否需要发出应用停机公告,有没有一种方法可以已近乎无损的方式对现有业务进行在线升级呢?Connection Drain 就是问题的答案,通过在应用程序网关上开启 Connection Drain 后, 当我们对后端服务器进行在线升级时,可以通过讲现有后端服务器从应用程序网关后端池移除,移除时新建请求将不在向该后端服务器发送,已建立请求将继续保持在该后端服务器直至请求结束或 Connection Drain 超时时间过期。上述机制相当于在后端服务器做 offline 下线操作过程中,提供了一个缓冲窗口让现有正在进行的请求完成,来减少在线升级对在运行请求的影响。
下面我们一起通过一个实验来测试验证一下,首先进行 AGW Connection Drain 的配置,这个配置非常简单,在 Portal 上 AGW 的 HTTP 配置界面下配置即可,勾选启用后会有一个可配置参数,Connection Drain Timeout time,这个参数是用来限定最大时间窗口,即如果在该时间窗口内在运行的请求还没有完成该请求将被主动关闭。
在下面测试实验中,我们用代码仿真一个长连接的客户请求,该客户请求由 Websocket 仿真,在该长连接执行过程我们在 AGW 上将该连接的执行服务器从后端池摘除,客户端仿真的代码中会打印一个摘除操作的起始时间,当 Connection Drain Timeout 时间超时后,客户端代码在现有 socket 上会抛出异常,在程序推出前再次打印时间,通过校验截至时间和起实时间的差值与 所设置的 Connection Drain Timeout 时间。
客户端代码:
import socket import time import datetime target_host = "X.X.X.X" # your AGW frontend Public IP target_port = 80 # create a socket object client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) def decode_frame(frame): opcode_and_fin = frame[0] payload_len = frame[1] print(payload_len) encrypted_payload = frame [2 : payload_len] payload = bytearray([ encrypted_payload[i] for i in range(payload_len - 2)]) return payload # connect the client client.connect((target_host,target_port)) # send some data request = "GET /chat HTTP/1.1 Host:%s Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13 " % target_host close = "GET /index.html HTTP/1.1 Host:%s " % target_host data = "PUT /chat HTTP/1.1 Host: " #% target_host frame = [0] frame += [len(data)] frame_to_send = bytearray(frame) + bytearray(data, 'utf-8') mark = 0 while True: val = input("run again?") if val == "no": client.send(close.encode()) response = client.recv(4096) http_response = repr(response) print(http_response) client.close() break else: if mark == 1: print(datetime.datetime.now()) while True: try: client.send(frame_to_send) response = client.recv(4096) http_response = response.decode('utf-8') time.sleep(2) except: print(datetime.datetime.now()) raise else: client.send(request.encode()) # receive some data response = client.recv(4096) http_response = response.decode('utf-8') print('response', http_response) mark = 1
实验中 AGW 后端池中有两台后端服务器,后端服务器中通过 Socket 仿真服务端。服务器端代码可以参阅(https://github.com/nonokangwei/agwconnectiondrain/blob/master/httpserver.py),将正在处理仿真 WebSocket 的后端服务器移除后端池后,我们查看客户端代码的输出:
2019-10-29 14:41:25.915321 2019-10-29 14:47:39.002720
Traceback (most recent call last):
File "c:/Users/wekang/Documents/socketdemo/.vscode/httpclient.py", line 52, in <module>
client.send(frame_to_send)
ConnectionAbortedError: [WinError 10053] An established connection was aborted by the software in your host machine
可以看到前后的时间差为大概6 mins,我们实际在上述的截图中看到的预设的 300秒时间是可以对应上的,有所延迟是因为我们将服务器移除后端池的配置生效需要一些时间。在生效后我们可以看到 WebSocket 仿真客户端还可以一直运行,即在处理的请求没有中断,期间我们用其他客户端仿真新建请求,所有新建请求都不会发送到已移除的后端服务器上。
今天的内容就先到这里,希望小功能大不同对大家有所帮助。