片段丢包率热力图

This commit is contained in:
2026-05-20 12:24:37 +08:00
parent 5c1d3d085d
commit deb07aeb3e
6 changed files with 165 additions and 5 deletions

3
.gitignore vendored
View File

@@ -12,4 +12,5 @@
*.toc
*.xdv
**/.DS_Store
/code/
/code/
/.venv/

Binary file not shown.

View File

@@ -8,7 +8,7 @@
云网络的核心思想是服务商将计算和网络基础设施作为一种服务进行售卖Infrastructure as a Service, IaaS的新型计算范式\cite{azodolmolky2013cloudnetworking}。它的核心思想是云网络的服务商出资搭建数据中心、购买网络网络资源将数据中心内的计算、存储等单元连接互联网,其他服务提供商或者个人用户可按需要购买云服务商中提供的资源,并通过互联网访问。与传统的网络依赖与本地硬件进行部署不同,云网络通过虚拟机、虚拟路由器、虚拟交换机、负载均衡、虚拟防火墙等多种技术将已有的物理网络和计算资源抽象为虚拟化的计算资源,提供给不同的用户进行访问。通过网络虚拟化技术,云网络同时减少了计算资源的提供商与用户的成本,因为云网络的虚拟化特性使得资源可以按需用户需求动态分配与计费,用户只会为自己真正使用的资源付费,而云服务商可以通过对虚拟资源在硬件上的整合避免资源分配后的浪费,高效地满足所有用户的资源需求,降低运营成本\cite{luong2017cloudnetworksurvey}
在云网络模型下,用户所能访问的几乎所有资源如计算资源、存储资源以及网络互联资源都是虚拟资源而非物理资源。在云网络中,云服务器、云存储等实例可能分布在不同的物理节点上,所有的资源都需要通过高质量的网络进行互联,因此高性能的虚拟网络是云网络的基础\cite{mogul2012cloudnetworkperf}
% 在云网络模型下,用户所能访问的几乎所有资源如计算资源、存储资源以及网络互联资源都是虚拟资源而非物理资源。在云网络中,云服务器、云存储等实例可能分布在不同的物理节点上,所有的资源都需要通过高质量的网络进行互联,因此高性能的虚拟网络是云网络的基础\cite{mogul2012cloudnetworkperf}
覆盖网络Overlay Network是一种广泛应用于云网络结构的网络虚拟化设计它基于物理的底层网络Underlay Network上通过对资源的逻辑整合而形成的逻辑网络。如图\ref{fig:overlay网络示意},覆盖网络在已有的硬件网络上构建一个虚拟的网络层,使得使用云网络服务的企业和用户可以获得更灵活与稳定的虚拟网络连接。近年来,企业对虚拟化和云网络的需求不断增长,因而将分布在全球各地的云资源进行互联的需求也不断提升。不同的云资源所处的基础设施可能出现异构的情况,使用覆盖网络可以有效地将这些区别隐藏在相同的虚拟网络层抽象之后,极大地简化了部署和配置网络的成本。
@@ -19,7 +19,7 @@
\label{fig:overlay网络示意}
\end{figure}
在跨地域云网络场景中,不同节点之间通常存在多条物理连接路径,这些物理链路具有不同的链路质量和不同的链路价格及计费方式。例如,许多云服务商同时提供公网链路互联与专线链路互联\cite{azure_bandwidthcost,gcp_bandwidthcost}。其中,专线链路通常具有更稳定的传输性能、更低的丢包率与时延,但部署成本与使用成本较高;而公网链路虽然成本较低,却容易受到网络拥塞、跨域路由波动等因素影响,出现高丢包、时延抖动等问题\cite{kataria2024titan}。与此同时,链路质量与网络负载往往还会随着时间动态变化,使得不同链路在不同时间段内呈现出不同的性能特征。随着云计算与实时互联网应用的发展,现代云网络中的跨域流量规模持续增长,用户对于传输质量与服务稳定性的要求也不断提高。传统的覆盖网络服务商为了为用户提供高质量的传输服务,确保能为用户持续稳定提供低延迟、高带宽、低丢包的转发路径,选择尽可能多地使用专线链路构建覆盖网络,而这对运营成本带来了较大的压力。
在跨地域云网络场景中,不同节点之间通常存在多条物理连接路径,这些物理链路具有不同的链路质量和不同的链路价格及计费方式。例如,许多云服务商同时提供公网链路互联与专线链路互联\cite{azure_bandwidthcost,gcp_bandwidthcost}。其中,专线链路通常具有更稳定的传输性能、更低的丢包率与时延,但部署成本与使用成本较高;而公网链路虽然成本较低,却容易受到网络拥塞、跨域路由波动等因素影响,出现高丢包、时延抖动等问题\cite{kataria2024titan}。与此同时,链路质量与网络负载往往还会随着时间动态变化,使得不同链路在不同时间段内呈现出不同的性能特征。随着云计算与实时互联网应用的发展,现代云网络中的跨域流量规模持续增长,用户对于传输质量与服务稳定性的要求也不断提高。传统的覆盖网络服务商为了为用户提供高质量的传输服务,确保能为用户持续稳定提供低延迟、高带宽、低丢包的转发路径,选择尽可能多地使用专线链路构建覆盖网络,而这对运营成本带来了较大的压力。如何在维持网络服务质量保持高带宽、低丢包、低延迟的前提下,尽可能减少构建和运营云网络所需的成本,是各服务商关注的重点。
一些工作意识到了公网链路与专线链路在经常存在定价差异,因而尝试在维持覆盖网络服务质量的前提下,利用覆盖网络易于实时配置的特性,将部分流量转移至质量优秀的公网链路上,以减少专线链路的压力\cite{kataria2024titan,wu2023xron}。这些工作利用低价的公网链路为部分用户提供服务,从而降低部分高价专线的流量,从而在服务流量总量不变的情况下,降低了低价流量的占比,进而降低了链路部署的总成本。
@@ -52,8 +52,8 @@
云服务商只对专线链路的质量提供服务质量保证Service level agreement, SLA而对公网的具体性能没有任何形式的保证。尽管服务商不对公网的性能做出任何保证但这不意味着所有的公网链路质量都远远不如专线。如图\ref{fig:公网片段热力图}所示,部分公网链路有着较低的平均丢包率,质量几乎与专线相当,而只有部分链路,特别是跨域链路的丢包率较高,链路质量较差,与低丢包的专线有较大差距。覆盖网络对用户流量进行转发时,通常将多个不同的网络片段相连组成连接两侧接入网关的路径。由于覆盖网络的内部转发机制通常对端到端的传输透明,两端的客户端只能感知到由多个链路的丢包级联而成的最终丢包率,只要组成转发路径的链路中有至少一条是丢包率较高的跨域公网链路,端到端感知到的丢包率就会明显上升。这导致在跨域连接的场景下,网络编码类工作只能以感知到的高丢包率对在整个路径上转发的包加入大量的冗余,造成了较大的带宽浪费。
\begin{figure}[H]
\centering
公网丢包热力图
\caption{不同公网链路天内平均丢包率热力图}
\includegraphics[width=.8\linewidth]{loss_avg_heatmap.pdf}
\caption{不同公网链路连续七天内平均丢包率热力图}
\label{fig:公网片段热力图}
\end{figure}

Binary file not shown.

10
scripts/loss_avg.csv Normal file
View File

@@ -0,0 +1,10 @@
sender,迪拜,法兰克福,雅加达,开普敦,杭州,呼和浩特,绍兴,深圳,西安
迪拜,n/a,0,0,0,0.3,0.2,0.09,0.02,0.25
法兰克福,0,n/a,0,0,0.11,0.08,0.08,0.02,0.05
雅加达,0,0,n/a,0,0.3,0.4,0.35,0.13,0.22
开普敦,0,0,0.01,n/a,0.05,0.06,0.04,0.01,0.05
杭州,0,0.01,0.01,0,n/a,0,0,0,0
呼和浩特,0.01,0.01,0.02,0,0,n/a,0,0,0
绍兴,0.03,0.01,0.02,0,0,0,n/a,0,0
深圳,0.02,0.02,0.01,0,0,0,0.01,n/a,0.05
西安,0.01,0.02,0.01,0,0,0,0,0,n/a
1 sender 迪拜 法兰克福 雅加达 开普敦 杭州 呼和浩特 绍兴 深圳 西安
2 迪拜 n/a 0 0 0 0.3 0.2 0.09 0.02 0.25
3 法兰克福 0 n/a 0 0 0.11 0.08 0.08 0.02 0.05
4 雅加达 0 0 n/a 0 0.3 0.4 0.35 0.13 0.22
5 开普敦 0 0 0.01 n/a 0.05 0.06 0.04 0.01 0.05
6 杭州 0 0.01 0.01 0 n/a 0 0 0 0
7 呼和浩特 0.01 0.01 0.02 0 0 n/a 0 0 0
8 绍兴 0.03 0.01 0.02 0 0 0 n/a 0 0
9 深圳 0.02 0.02 0.01 0 0 0 0.01 n/a 0.05
10 西安 0.01 0.02 0.01 0 0 0 0 0 n/a

View File

@@ -0,0 +1,149 @@
from __future__ import annotations
import argparse
import csv
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import font_manager
OVERSEAS_CITIES = {"迪拜", "法兰克福", "雅加达", "开普敦"}
def leading_group_size(labels: list[str], group: set[str]) -> int:
size = 0
for label in labels:
if label not in group:
break
size += 1
return size
def configure_fonts() -> None:
"""Prefer common CJK fonts so Chinese city names render correctly."""
preferred_fonts = [
"Microsoft YaHei",
"SimHei",
"Noto Sans CJK SC",
"Source Han Sans SC",
"PingFang SC",
"WenQuanYi Micro Hei",
"Arial Unicode MS",
]
installed_fonts = {font.name for font in font_manager.fontManager.ttflist}
for font in preferred_fonts:
if font in installed_fonts:
plt.rcParams["font.sans-serif"] = [font]
break
plt.rcParams["axes.unicode_minus"] = False
plt.rcParams.update(
{
"font.size": 15,
"axes.titlesize": 19,
"axes.labelsize": 17,
"xtick.labelsize": 15,
"ytick.labelsize": 15,
"figure.dpi": 150,
}
)
def read_loss_matrix(csv_path: Path) -> tuple[list[str], list[str], np.ma.MaskedArray]:
with csv_path.open("r", encoding="utf-8-sig", newline="") as file:
reader = csv.reader(file)
header = next(reader)
receivers = header[1:]
senders: list[str] = []
values: list[list[float]] = []
mask: list[list[bool]] = []
for row in reader:
senders.append(row[0])
value_row: list[float] = []
mask_row: list[bool] = []
for item in row[1:]:
if item.strip().lower() in {"n/a", "na", ""}:
value_row.append(0.0)
mask_row.append(True)
else:
value_row.append(float(item))
mask_row.append(False)
values.append(value_row)
mask.append(mask_row)
return senders, receivers, np.ma.array(values, mask=mask)
def plot_heatmap(csv_path: Path, output_path: Path) -> None:
configure_fonts()
senders, receivers, matrix = read_loss_matrix(csv_path)
fig_width = max(8.2, 0.78 * len(receivers) + 2.6)
fig_height = max(6.2, 0.68 * len(senders) + 2.4)
fig, ax = plt.subplots(figsize=(fig_width, fig_height), constrained_layout=True)
cmap = plt.get_cmap("YlOrRd").copy()
cmap.set_bad(color="#eeeeee")
vmax = max(0.1, float(matrix.max()))
image = ax.imshow(matrix, cmap=cmap, vmin=0, vmax=vmax)
ax.set_xticks(np.arange(len(receivers)), labels=receivers)
ax.set_yticks(np.arange(len(senders)), labels=senders)
ax.set_xlabel("接收端")
ax.set_ylabel("发送端")
# ax.set_title("公网链路平均丢包率")
plt.setp(ax.get_xticklabels(), rotation=35, ha="right", rotation_mode="anchor")
for row_index in range(len(senders)):
for col_index in range(len(receivers)):
if matrix.mask[row_index, col_index]:
text = "-"
color = "#777777"
else:
value = float(matrix[row_index, col_index])
text = "0" if value == 0 else f"{value:.2f}".rstrip("0").rstrip(".")
color = "white" if value > 0.55 * vmax else "#222222"
ax.text(col_index, row_index, text, ha="center", va="center", color=color, fontsize=11)
sender_split = leading_group_size(senders, OVERSEAS_CITIES)
receiver_split = leading_group_size(receivers, OVERSEAS_CITIES)
if 0 < sender_split < len(senders) and 0 < receiver_split < len(receivers):
ax.axhline(sender_split - 0.5, color="#303030", linewidth=2.2)
ax.axvline(receiver_split - 0.5, color="#303030", linewidth=2.2)
ax.set_xticks(np.arange(len(receivers) + 1) - 0.5, minor=True)
ax.set_yticks(np.arange(len(senders) + 1) - 0.5, minor=True)
ax.grid(which="minor", color="white", linewidth=1.2)
ax.tick_params(which="minor", bottom=False, left=False)
colorbar = fig.colorbar(image, ax=ax, shrink=0.88)
colorbar.set_label("平均丢包率(%", fontsize=14)
colorbar.ax.tick_params(labelsize=12)
output_path.parent.mkdir(parents=True, exist_ok=True)
fig.savefig(output_path, bbox_inches="tight")
plt.close(fig)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Plot average public-link loss as a heatmap.")
parser.add_argument("--input", type=Path, default=Path("scripts/loss_avg.csv"), help="Path to loss CSV.")
parser.add_argument(
"--output",
type=Path,
default=Path("figures/loss_avg_heatmap.pdf"),
help="Output figure path, such as figures/loss_avg_heatmap.pdf or .png.",
)
return parser.parse_args()
def main() -> None:
args = parse_args()
plot_heatmap(args.input, args.output)
if __name__ == "__main__":
main()