基于SDN的流量预测与调度系统:Python源码与Docker部署实战

发布时间:2026/10/7 14:54:48
基于SDN的流量预测与调度系统:Python源码与Docker部署实战 简介本资源为基于SDN的流量预测与调度系统完整项目源码面向计算机、通信、物联网、人工智能等专业的在校学生、教师及企业开发人员可用于毕业设计、课程设计、大作业或初期项目立项演示。项目采用前后端分离架构后端以Python实现流量预测与调度核心逻辑前端基于Vue构建管理界面并支持Docker容器化部署便于快速搭建与迁移运行环境。压缩包共439个文件约11.28MB涵盖56个Python脚本、68个Vue组件、133个JavaScript文件以及Dockerfile、SQL配置、样式与图片资源等结构清晰、模块划分明确。系统内置菜单、部门、角色、用户、字典、地区、附件、接口白名单与操作日志等管理功能权限体系完整默认账号superadmin、密码admin123456可直接登录体验。目前已有202人学习下载代码经过测试运行成功适合在现有基础上二次开发扩展流量调度策略或权限管理功能。1. 从一份 SDN 流量预测与调度源码说起它到底解决什么问题SDN 这三个字母很多人第一次接触是在实验室的 Mininet 拓扑里第二次接触就是在生产网里被问「为什么这条链路又拥塞了」。传统网络里控制面和数据面绑在一起每台设备各管各的转发表流量一上来只能靠 OSPF 或者静态路由硬扛等到链路打满才发现问题。SDN 把控制面抽出来交给控制器交换机只负责按流表转发这就给了我们一个「先看数据、再下决策」的机会——流量预测与调度系统正是踩在这个机会点上的。这份「基于 SDN 的流量预测与调度系统 python 源码 项目说明支持 docker 部署」讲的就是这么一件事用 Python 采集 SDN 控制器上的链路流量数据跑一个时间序列模型预测下一段时间的负载再根据预测结果动态下发流表把流量从拥塞链路挪到空闲链路。它适合三类人做毕设或课程设计想找一个完整闭环的学生、想在自己实验环境里验证调度策略的运维、以及准备把 SDN 思路往实际网络里搬的工程师。docker 部署这一层解决的是「环境装三天、跑通五分钟」的老毛病。2. 流量预测与调度系统的技术骨架控制器、采集、模型、下发四件事2.1 为什么选 Ryu Python 而不是 OpenDaylight控制器是整个系统的入口。常见做法是 Ryu、ONOS、OpenDaylight 三选一这份源码走的是 Ryu 路线理由很实在Ryu 本身就是纯 Python 写的和后面的预测、调度代码同语言不用跨进程调 Java 接口调试的时候一个 pdb 就能断到控制器事件回调里。ONOS 和 ODL 功能更全但装起来重做实验的时候光等 Karaf 启动就够喝一壶。Ryu 的核心是事件驱动。你写一个继承app_manager.RyuApp的类用set_ev_cls装饰器监听EventOFPPacketIn、EventOFPPortStatsReply这类事件控制器收到交换机的消息就会回调你的函数。流量采集靠的就是EventOFPPortStatsReply——控制器周期性向交换机发OFPFlowStatsRequest或OFPPortStatsRequest交换机回包带上每个端口的收发包数和字节数两次采样一减就是这段时间的流量。from ryu.base import app_manager from ryu.controller import ofp_event from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER, set_ev_cls from ryu.ofproto import ofproto_v1_3 from ryu.lib import hub class TrafficMonitor(app_manager.RyuApp): OFP_VERSIONS [ofproto_v1_3.OFP_VERSION] def __init__(self, *args, **kwargs): super(TrafficMonitor, self).__init__(*args, **kwargs) self.datapaths {} # 每 5 秒采样一次间隔太短会加重交换机负担太长预测粒度不够 self.monitor_thread hub.spawn(self._monitor) set_ev_cls(ofp_event.EventOFPStateChange, [MAIN_DISPATCHER, CONFIG_DISPATCHER]) def _state_change_handler(self, ev): datapath ev.datapath if ev.state MAIN_DISPATCHER: self.datapaths[datapath.id] datapath elif ev.state CONFIG_DISPATCHER: self.datapaths.pop(datapath.id, None) def _monitor(self): while True: for dp in self.datapaths.values(): self._request_stats(dp) hub.sleep(5) def _request_stats(self, datapath): ofproto datapath.ofproto parser datapath.ofproto_parser req parser.OFPPortStatsRequest(datapath, 0, ofproto.OFPP_ANY) datapath.send_msg(req)这段代码的逻辑很直白_state_change_handler维护在线交换机列表_monitor起一个协程每 5 秒轮询一次_request_stats发端口统计请求。参数上hub.sleep(5)的 5 秒是采样周期做短时预测比如预测未来 30 秒时这个值别超过 10 秒否则预测窗口和采样窗口对不齐误差会明显放大。OFPP_ANY表示请求所有端口如果只想盯某几个上联口可以换成具体的 port_no 列表能省不少控制器和交换机的 CPU。2.2 流量数据怎么落库、怎么切成训练样本采集到的原始数据是「时间戳 交换机 ID 端口号 rx_bytes tx_bytes」这种结构直接喂给模型是不行的。常见做法是先落 SQLite 或 InfluxDB再按端口聚合成时间序列。SQLite 胜在零配置docker 里挂一个 volume 就能持久化做实验够用InfluxDB 适合数据量大、要按时间范围快速查询的场景但多一个容器要维护。import sqlite3 import time def save_port_stats(conn, dpid, port_no, rx_bytes, tx_bytes): cur conn.cursor() cur.execute( INSERT INTO port_stats (ts, dpid, port_no, rx_bytes, tx_bytes) VALUES (?, ?, ?, ?, ?) , (int(time.time()), dpid, port_no, rx_bytes, tx_bytes)) conn.commit() def build_series(conn, dpid, port_no, window60): 把原始累计字节数转成每秒速率序列 cur conn.cursor() cur.execute( SELECT ts, rx_bytes, tx_bytes FROM port_stats WHERE dpid? AND port_no? ORDER BY ts ASC , (dpid, port_no)) rows cur.fetchall() series [] for i in range(1, len(rows)): dt rows[i][0] - rows[i-1][0] if dt 0: continue rx_rate (rows[i][1] - rows[i-1][1]) / dt tx_rate (rows[i][2] - rows[i-1][2]) / dt series.append((rows[i][0], rx_rate, tx_rate)) return series[-window:]这里有两个容易翻车的点。第一rx_bytes是累计值交换机重启或者计数器溢出会归零直接做差会得到负数必须在build_series里加一层判断遇到负值就丢弃这一段。第二window60表示取最近 60 个采样点按 5 秒一个点算就是 5 分钟的历史做 LSTM 或 GRU 输入时这个长度要跟模型的input_size对上改了一边忘了另一边训练时直接报维度错误。2.3 预测模型选型LSTM、ARIMA 还是 Prophet模型这块没有银弹。ARIMA 轻训练快适合流量平稳、周期性明显的场景缺点是遇到突发流量就抓瞎Prophet 对缺失值和异常值鲁棒调参少但它是为「天级、周级」这种长周期设计的做秒级预测有点杀鸡用牛刀LSTM 能吃多变量输入比如同时看 rx、tx、端口队列长度对突发有一定适应能力代价是要调的东西多——层数、隐藏单元、学习率、batch size哪个不对都训不出东西。这份源码用的是 LSTM输入是过去 60 个时间步的速率输出是未来 1 步的预测值。训练脚本里look_back60、epochs50、batch_size32是三个必调参数。look_back决定模型能「看多远」太小预测滞后太大容易过拟合epochs配合 early stopping 用验证集 loss 连续 5 轮不降就停batch_size在数据量不大的时候别设太大32 或 64 比较稳。import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense def make_dataset(series, look_back60): X, y [], [] for i in range(len(series) - look_back): X.append(series[i:ilook_back]) y.append(series[ilook_back]) return np.array(X), np.array(y) def build_lstm(look_back): model Sequential() # 第一层 LSTM 返回序列第二层只返回最后一步 model.add(LSTM(64, return_sequencesTrue, input_shape(look_back, 1))) model.add(LSTM(32)) model.add(Dense(1)) model.compile(lossmse, optimizeradam) return modelmake_dataset把一维序列滑窗成(样本数, look_back, 1)的三维张量这是 Keras LSTM 要求的输入形状。build_lstm里两层 LSTM 的隐藏单元分别是 64 和 32第一层return_sequencesTrue是为了把序列传给第二层第二层默认False只输出最后一步的隐状态。如果你的流量数据波动特别大可以在Dense前加一层Dropout(0.2)能压一压过拟合但别加太多否则模型学不动。2.4 调度策略预测出来之后流表怎么改预测只是手段调度才是目的。拿到预测值之后系统要判断哪条链路即将拥塞然后把部分流量迁走。常见做法是设两个阈值high_watermark和low_watermark。预测速率超过高水位就触发迁移低于低水位就停止迁移中间留一段缓冲避免在阈值附近来回抖动——这种抖动在控制领域叫「震荡」在网络里就是流表频繁改写交换机 CPU 直接拉满。def decide_reroute(predicted_rate, current_paths, high0.8, low0.5): predicted_rate 是链路带宽利用率预测值范围 0~1 actions [] for path in current_paths: if path[util] high: # 找一条利用率最低的备选路径 backup min(path[backups], keylambda p: p[util]) if backup[util] low: actions.append({ from: path[id], to: backup[id], ratio: 0.3 # 迁移 30% 流量别一次全迁 }) return actionshigh0.8、low0.5这两个阈值不是拍脑袋定的。链路利用率超过 80% 之后排队延迟会非线性上升再往上加流量丢包率涨得很快低于 50% 说明这条链路还有余量适合接收迁移流量。ratio0.3表示每次只迁 30%迁完等下一个采样周期再看效果这是为了防止「迁过头」——把流量全挪到备选路径结果备选路径也拥塞了来回震荡。3. 用 docker 把整套系统跑起来从镜像构建到容器编排3.1 为什么这套系统值得 docker 化SDN 实验环境出了名的难装。Ryu 依赖一堆 Python 包TensorFlow 对 Python 版本和 CUDA 版本挑剔Mininet 又要内核模块支持换台机器就得重来一遍。docker 把这三样东西封进镜像docker compose up一条命令起全套换机器只要镜像在环境就是一致的。这也是为什么这份源码特意强调「支持 docker 部署」——它解决的不是炫技问题是复现问题。3.2 三个容器的职责划分与 Dockerfile 写法整套系统拆成三个容器ryu-controller跑控制器和采集predictor跑模型训练和推理mininet跑拓扑模拟。拆开的好处是各管各的依赖Ryu 不需要 TensorFlow预测容器不需要 OpenFlow 库镜像体积能小不少。# Dockerfile.controller FROM python:3.8-slim WORKDIR /app COPY requirements-controller.txt . RUN pip install --no-cache-dir -r requirements-controller.txt COPY controller/ ./controller/ EXPOSE 6653 8080 CMD [ryu-manager, controller/traffic_monitor.py]# Dockerfile.predictor FROM python:3.8-slim WORKDIR /app COPY requirements-predictor.txt . RUN pip install --no-cache-dir -r requirements-predictor.txt COPY predictor/ ./predictor/ CMD [python, predictor/serve.py]两个 Dockerfile 都基于python:3.8-slim选 3.8 是因为 TensorFlow 2.x 对 3.9 以上支持有坑3.8 是最稳的。--no-cache-dir是为了减小镜像层体积生产环境可以去掉但实验环境建议留着。控制器暴露 6653 是 OpenFlow 默认端口8080 给 REST API 用预测容器不对外暴露端口只通过 compose 内部网络和控制器通信。3.3 docker compose 编排网络、卷、依赖顺序version: 3.8 services: ryu-controller: build: context: . dockerfile: Dockerfile.controller ports: - 6653:6653 - 8080:8080 volumes: - ./data:/app/data networks: - sdn-net predictor: build: context: . dockerfile: Dockerfile.predictor volumes: - ./data:/app/data - ./models:/app/models depends_on: - ryu-controller networks: - sdn-net mininet: image: mininet/mininet:latest privileged: true depends_on: - ryu-controller networks: - sdn-net networks: sdn-net: driver: bridgevolumes把宿主机的./data挂进容器SQLite 文件和训练好的模型都落在这里容器删了数据还在。depends_on只保证启动顺序不保证服务就绪——控制器还没监听 6653mininet 就起来了连不上控制器会报错。稳妥的做法是在 mininet 的启动脚本里加一段重试逻辑或者用healthcheck配合depends_on的condition: service_healthy。privileged: true是给 mininet 用的它要创建虚拟网络接口没有这个权限起不来。3.4 启动、验证、看日志的完整命令# 构建并后台启动 docker compose up -d --build # 看控制器日志确认 OpenFlow 连接建立 docker compose logs -f ryu-controller # 进 mininet 容器手动跑拓扑 docker compose exec mininet python3 topo.py # 检查数据是否落库 docker compose exec ryu-controller sqlite3 /app/data/traffic.db SELECT COUNT(*) FROM port_stats;docker compose logs -f是排查问题的第一入口控制器有没有收到EventOFPStateChange、有没有发出 stats 请求日志里都能看到。sqlite3那条命令用来确认采集链路是通的如果COUNT(*)一直是 0说明要么交换机没连上要么_request_stats没被触发回去查datapaths字典是不是空的。4. 避坑与排查这套系统最容易翻车的五个地方4.1 容器里连不上控制器报 Connection refused现象是 mininet 容器启动后pingall全通但控制器日志里没有任何交换机上线记录。原因通常是 compose 默认网络里服务名能解析但 mininet 里配置的控制器 IP 写的是127.0.0.1容器里的 localhost 指向自己不是控制器容器。解决办法是把控制器地址改成 compose 服务名ryu-controller或者在 compose 里给控制器固定一个 IPmininet 里写那个 IP。4.2 预测结果全是常数模型没学到东西训练完 loss 降不下去预测值基本等于最后一个输入值。血泪经验是数据没归一化。流量速率动辄几 MB/s直接喂给 LSTM梯度要么爆炸要么消失。在make_dataset之前加一层 MinMaxScaler把数据压到 0~1预测完再反归一化回来。另外检查一下look_back是不是比数据总长度还大那样根本切不出样本。4.3 流表下发后流量没变化调度模块算出要迁移流表也下发了但iperf测出来带宽没变。原因多半是流表优先级设低了被默认的 table-miss 流表或者之前的流表盖住了。OpenFlow 里优先级数值越大越优先调度下发的流表priority至少设成 100比默认的 0 高。还有一种可能是流表idle_timeout设得太短刚下发就过期了设成 30 秒以上比较稳。4.4 docker 镜像构建时 pip 装 TensorFlow 卡住国内网络拉 PyPI 慢是常态pip install tensorflow能卡十几分钟。解决办法是在 Dockerfile 里换源RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --no-cache-dir -r requirements.txt。如果还是慢考虑用多阶段构建把依赖装在一个阶段运行阶段只拷贝 site-packages镜像层能复用第二次构建就快了。4.5 采样周期和预测窗口对不上误差忽大忽小现象是模型离线评估 MSE 很低上线之后预测偏差很大。原因是离线训练用的数据是 5 秒一个点上线后控制器负载高采样间隔变成了 7 秒、8 秒时间轴不均匀模型输入和训练分布不一致。解决办法是在build_series里做重采样用 pandas 的resample(5S).mean()把时间轴对齐缺的点用前值填充。这个坑很隐蔽不看时间戳根本发现不了。5. 把预测窗口拉长多步预测与在线学习的取舍单步预测在实际调度里有个尴尬你预测出下一秒的流量但流表下发、交换机生效、流量真正迁移过去这一套下来少说几百毫秒等你迁完预测的那个时刻早过了。所以真正有用的是多步预测——一次预测未来 3 到 5 个采样周期的流量给调度留出决策和执行的时间。多步预测有两种做法。一种是直接多输出把Dense(1)改成Dense(n_steps)一次吐出未来 n 步训练时标签也对应改成series[ilook_back:ilook_backn_steps]。这种做法简单但各步之间没有依赖关系预测出来的曲线可能不平滑。另一种是递归预测用单步模型预测下一步把预测值塞回输入再预测下下步缺点是误差会累积预测 5 步之后基本没法看。我一般会选直接多输出然后在 loss 里给近的步加权比如第一步权重 1.0第五步权重 0.3这样模型会优先保证近期预测准。代码上就是把model.compile(lossmse)换成自定义的加权 MSEimport tensorflow as tf def weighted_mse(weights): def loss(y_true, y_pred): # weights 形状 (n_steps,)近的步权重大 w tf.constant(weights, dtypetf.float32) sq tf.square(y_true - y_pred) return tf.reduce_mean(sq * w) return loss model.compile(lossweighted_mse([1.0, 0.7, 0.5, 0.4, 0.3]), optimizeradam)在线学习是另一个绕不开的话题。网络流量有概念漂移白天和晚上的模式不一样训练集里没有的模式上线后一定会遇到。常见做法是滑动窗口重训每积累 24 小时新数据就用最近 7 天的数据重新训一版模型验证集 loss 比线上模型低就替换否则保持原模型。这个流程可以写成一个 cron 任务放在 predictor 容器里定时跑。别搞太复杂的在线学习算法实验环境里滑动窗口重训的性价比最高也最好排查问题。验证多步预测效果别只看 MSE。MSE 对大的偏差敏感但调度关心的是「预测值有没有跨过阈值」。更实用的指标是方向准确率——预测的涨跌方向和实际一致的比例以及阈值命中率——预测会超 80% 且实际也超了的比例。这两个指标在evaluate.py里加几行就能算出来比 MSE 更能说明模型对调度有没有用。最后说个习惯。我做这类系统永远先在单机不用 docker 跑通最小闭环控制器起得来、数据采得到、模型训得动、流表下得去四步都通了再往 docker 里搬。docker 解决的是分发和复现不解决逻辑错误逻辑没通就上容器出了问题你连是代码错还是环境错都分不清。这套源码的价值不在于模型多先进而在于它把采集、预测、调度、部署串成了一条能跑的链路你在这个骨架上换模型、换调度策略、换拓扑都比从零搭省事。希望帮到你。本文还有配套的精品资源点击获取