资讯详情

资讯详情

对象存储与数据库双写:媒体资源系统设计与避坑指南

简介《媒体数据库与云存储》实验四实验报告以PDF形式完整呈现OpenStack云计算与Swift对象存储实践全过程面向中国传媒大学信息与通信工程学院学生及需要入门云平台部署的云计算学习者。报告从VMware虚拟网络配置、OpenStack环境搭建、Dashboard租户/项目与用户管理、云主机实例创建到Swift客户端认证、容器创建、对象上传与元数据查看/增删改等均有分步截图和命令说明并附思考题解答与public/private子网、路由器通信机制等网络架构分析便于读者对照复现实验、修补课程记录或深入理解IaaS平台核心概念。资源涵盖数据库与云存储课程实验链路含MySQL、SQL、XAMPP等标签相关背景但主体聚焦OpenStack与Swift对象存储。资源包为1个PDF文件大小3.66MB图文结合、操作路径清晰可直接用于课程复习与实操参考。目前已有84人学习下载适合作为云计算与云存储方向实验教学和自学的配套资料。1. 媒体数据库与云存储实验四一门课为什么把对象存储和关系库焊在一起媒体数据库与云存储不是两门独立的课在实验四里它们第一次被焊成一套完整系统媒体文件本身进云存储桶文件的名字、大小、路径、上传人等元数据进数据库业务代码负责让两边保持一致。这个标题下的实验报告本质上是在复刻一个真实的媒体资源系统——无论是教学实训里的图片库还是生产环境的音视频平台核心链路都是同一套。适合直接照做的人群是正在补这门课的在校生、刚接手媒体资源模块的初级后端以及想给本地文件库上云的从业者。下面直接按实验四的关键步骤拆开讲环境怎么搭、双写怎么做、参数怎么设、坑在哪。2. 先把实验环境搭稳媒体数据库选型和阿里云存储桶的创建2.1 媒体数据库选型为什么元数据必须落库实验报告里最容易被跳过的是建库这一步。很多同学觉得图片已经传到存储桶了数据库好像没多大用于是随便建一张表应付。这个想法会在实验四后半段付出代价你要按分组检索媒体、要统计上传量、要按文件名模糊搜索这些操作对象存储桶都没法做OSS只支持前缀列举不支持按内容字段查询。所以“媒体数据库”这个前缀不是修饰是指你必须用一张关系表去描述媒体文件。表结构按常见做法来我会建一个 media_asset 表字段设计至少包含业务主键、对象名、存储桶、文件类型、大小、哈希值、上传时间和状态。对象名用 object_name 单独存不要直接用文件路径做主键因为同名的媒体文件会被存储桶重命名业务上需要稳定的逻辑 ID。给一个建表 SQLCREATE TABLE media_asset ( id BIGINT AUTO_INCREMENT PRIMARY KEY, asset_no VARCHAR(64) NOT NULL UNIQUE COMMENT 业务编号生成后不可变, object_name VARCHAR(512) NOT NULL COMMENT 云存储桶中的对象名, bucket_name VARCHAR(128) NOT NULL COMMENT 对象所在的存储桶, content_type VARCHAR(128) NOT NULL DEFAULT application/octet-stream, file_size BIGINT NOT NULL DEFAULT 0 COMMENT 文件字节数, file_hash CHAR(64) NOT NULL COMMENT 上传时计算的SHA256, upload_status TINYINT NOT NULL DEFAULT 0 COMMENT 0待确认 1已上传 2失败, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_bucket_object (bucket_name, object_name), KEY idx_created_at (created_at) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段 SQL 的要点在于asset_no 用唯一索引而不是直接把自增主键暴露给业务避免遍历存储桶时对上数字主键file_hash 字段用于数据一致性比对后面验证章节会用到upload_status 是给双写补偿留的开关这比把所有状态堆在异常里更直观。utf8mb4 是必须的媒体文件名经常带中文和特殊字符老版本的 utf8 字符集会在这里翻车。2.2 阿里云存储桶的创建从 AccessKey 到权限模型实验对象用阿里云存储桶OSS来做普通上传场景下需要三个基本信息Endpoint、Bucket 名称、AccessKey。Endpoint 形如 oss-cn-shanghai.aliyuncs.com决定了你的存储桶建在哪个地域。Bucket 名称全局唯一创建后不建议随意改。AccessKey 用于程序签名实验阶段可以只用主账号的 AccessKey但生产上一定要改用 RAM 子账号只授予目标 Bucket 的读写权限。理由很简单AccessKey 泄露后的爆炸半径子账号可以限制在一个存储桶内。创建存储桶时权限选择“私有”而不是“公共读”。实验报告里很多人为了省事直接设公共读图片 URL 贴到浏览器就能开。但这会让整个存储桶暴露在公网别人可以遍历你的对象名。实验四的正确做法是Bucket 权限设为私有访问时依赖签名 URL 对外暴露。后面第 4 章会专门讲私有桶访问的坑。关于地域建议把数据库和 Bucket 放在同一地域比如华东 2上海Endpoint 写死 oss-cn-shanghai.aliyuncs.com数据库连接串用内网地址而不是公网地址。实验阶段跨地域看不出问题上线后每次读写都走公网传输费用和延迟都不可控。创建完成后我一般会先用一个最小脚本验证凭证有效。这个脚本在上传正式媒体前把链路打通# put_test_object.py import oss2 endpoint oss-cn-shanghai.aliyuncs.com access_key_id 你的AccessKeyId access_key_secret 你的AccessKeySecret bucket_name lab-media-2024 bucket oss2.Bucket( oss2.Auth(access_key_id, access_key_secret), endpoint, bucket_name ) # 上传一个测试对象验证Endpoint、密钥、Bucket权限这三项配置是否同时正确 result bucket.put_object_from_file(test/hello.txt, hello.txt) print(etag:, result.etag) print(request_id:, result.request_id)这段代码跑通说明网络能到该地域的 OSS 服务、AccessKey 对此 Bucket 有写权限。如果在这一步就报 403 InvalidAccessKeyId 或 NoSuchBucket不要继续往后做先把环境中的这三项复制对齐。request_id 是排查时要记录的信息线上反馈上传失败时把 request_id 丢给技术支持比截图强得多。2.3 最小联通验证确认数据库和存储桶都能写入2.2 的脚本只验证了存储桶还需要验证数据库。我会把第 2.1 的建表 SQL 在 MySQL 里执行一次然后写入一条测试记录再删除确保实验环境账号有建表和改数据的权限。这一步是前置条件检查用实验台账的思路走mysql -h127.0.0.1 -ulab_user -p lab_media media_asset.sql执行无误后再用 pymysql 插入一条临时记录并回查import pymysql conn pymysql.connect( host127.0.0.1, userlab_user, passwordyour_password, databaselab_media, charsetutf8mb4, autocommitTrue ) with conn.cursor() as cur: cur.execute( INSERT INTO media_asset (asset_no, object_name, bucket_name, content_type, file_size, file_hash, upload_status) VALUES (INIT-001, test/hello.txt, lab-media-2024, text/plain, 5, , 0) ON DUPLICATE KEY UPDATE object_nameVALUES(object_name) ) cur.execute(SELECT asset_no, object_name FROM media_asset WHERE asset_noINIT-001) print(cur.fetchone()) conn.close()这里把 autocommit 打开是为了减少实验阶段的连接悬挂。正式代码里会用事务处理但联通验证阶段不需要事务先把“能连、能写、能查”确认下来。INSERT 语句特意加了 ON DUPLICATE KEY UPDATE让你反复跑这个脚本不产生重复记录实验迭代时少踩一次坑。如果实验机没有外网可以用 MinIO 在本地起一个兼容 S3 接口的对象存储服务来验证代码逻辑但实验四既然叫云存储至少一遍要走真实 OSS否则验证不了签名有效期和权限差异这些云特性。3. 实验四的主线任务媒体文件上传与元数据双写3.1 先传文件还是先写库编排顺序不能拍脑袋实验四的核心代码不多真正决定成败的是编排顺序。先写库再传文件的方案在实验报告里很常见流程是INSERT 一条记录状态设为 0上传成功后 UPDATE 状态为 1失败则把记录删掉或标记失败。这个方案的好处是业务 ID 可以预先产生坏处是状态值散落各处很多人忘记做失败清理数据库里堆了一堆 pending 记录对象的实际状态成了一个黑匣子。我一般用先传文件再写库的顺序它的链路由两步构成第一步 put_object 拿到 object_name 和 etag第二步 INSERT media_asset 记录。如果 INSERT 失败调用 delete_object 回滚这个刚上传的对象。这个方案的数据一致性检查最直观上传成功但库里没有记录的情况可以通过 delete_object 立即回收不给存储桶留下孤儿对象。两条路没有绝对对错实验报告里选一种并说清理由即可但代码结构必须是可回滚的。3.2 一段可复现的完整上传实验代码下面是实验四常见做法用 Python 的 oss2 和 pymysql 把一张图片双写到存储桶与数据库。这段代码包含了计算哈希、上传、插入、失败回滚四个环节。import os import uuid import hashlib import datetime import oss2 import pymysql def calc_sha256(file_path): h hashlib.sha256() with open(file_path, rb) as f: for chunk in iter(lambda: f.read(65536), b): h.update(chunk) return h.hexdigest() def upload_media(file_path, asset_no): endpoint oss-cn-shanghai.aliyuncs.com ak_id 你的AccessKeyId ak_secret 你的AccessKeySecret bucket_name lab-media-2024 auth oss2.Auth(ak_id, ak_secret) bucket oss2.Bucket(auth, endpoint, bucket_name) # 对象名按 年/月/日/asset_no 组织避免同一目录文件数过多 now datetime.datetime.now() object_name f{now:%Y/%m/%d}/{asset_no}_{os.path.basename(file_path)} # 先传对象到存储桶 with open(file_path, rb) as f: result bucket.put_object(object_name, f) print(upload etag:, result.etag) return bucket, object_name, os.path.getsize(file_path) def insert_record(asset_no, object_name, bucket_name, file_path): content_type image/jpeg if file_path.endswith((.jpg, .jpeg)) else application/octet-stream file_size os.path.getsize(file_path) file_hash calc_sha256(file_path) conn pymysql.connect( host127.0.0.1, userlab_user, passwordyour_password, databaselab_media, charsetutf8mb4 ) try: with conn.cursor() as cur: cur.execute( INSERT INTO media_asset (asset_no, object_name, bucket_name, content_type, file_size, file_hash, upload_status) VALUES (%s, %s, %s, %s, %s, %s, 1), (asset_no, object_name, bucket_name, content_type, file_size, file_hash) ) conn.commit() except Exception: conn.rollback() raise finally: conn.close() def main(): file_path ./sample.jpg asset_no uuid.uuid4().hex[:12] try: bucket, object_name, _ upload_media(file_path, asset_no) except Exception: print(上传存储桶失败不再写库) return try: insert_record(asset_no, object_name, bucket.bucket_name, file_path) except Exception as e: print(写库失败回滚删除对象:, e) bucket.delete_object(object_name) raise if __name__ __main__: main()逻辑说明main 里先执行 upload_media成功后才执行 insert_recordinsert_record 失败delete_object 回滚。这样“文件传了但库里没有”的场景被压缩到极短的时间窗。参数说明asset_no 用 uuid 的 12 位 hex避免和别人的实验记录撞主键对象名按年月日分目录是为了绕开 OSS 单目录对象数量过多时的列举性能问题content_type 这段用最简判断完全可以用 mimetypes 库做更精确匹配。这里的失败回滚不是终极方案但作为实验四的代码结构逻辑清晰且每步可重放。3.3 读取链路从数据库记录到带签名的可访问 URL写入链路搭完读取链路是实验报告里另一个占分的点。从数据库查到记录后不能直接拼接一个公网 URL 给前端因为 Bucket 是私有的。常见做法是用 oss2 的 sign_url 生成一个临时授权 URL把过期时间参数传进去。def gen_access_url(bucket_name, object_name, expires3600): endpoint oss-cn-shanghai.aliyuncs.com ak_id 你的AccessKeyId ak_secret 你的AccessKeySecret bucket oss2.Bucket(oss2.Auth(ak_id, ak_secret), endpoint, bucket_name) # 生成GET签名URL有效期为expires秒 url bucket.sign_url(GET, object_name, expires) return url签名 URL 的本质是“把 AccessKeyId、有效时间、对象名、请求方法拼进签名串由服务端校验”。实验过程中最容易把桶名称写错API 返回 404 但看不出是桶不存在还是对象不存在。建议在 gen_access_url 里加一句 bucket.object_exists(object_name) 判断方便日志里区分。expires 参数在实验里用 3600 够但做报表导出这类离线任务建议直接把文件抓回服务器再用内部链接分发不要把有效期拉到数天。4. 实验四避坑记录权限、签名、一致性最容易翻车的三个地方4.1 私有桶的图片 URL 打不开现象把数据库里的 object_name 拼成 https://bucket.endpoint/object_name 丢到浏览器返回 AccessDenied。原因Bucket 创建时选了私有而第 3.3 生成的签名 URL 没有透传到前端前端拿的是不带签名的直链。这是实验报告最常见的翻车点代码逻辑是对着“公有读”写的环境里却用的私有桶。解决统一走签名 URL不能有一个代码路径偷偷拼接直链。建议在 SQL 查询后直接调用 gen_access_url把签好的 URL 作为接口返回值下发给前端而不是只返回 object_name。同时确认 Bucket 访问域名用的是 OSS 默认域名如果绑了自定义域名签名 URL 的域名需要和签名参数保持一致否则签名校验会失败。4.2 签名 URL 过期导致页面图片一会儿能开一会儿不能开现象前端打开了页面图片正常过了几分钟再刷新部分图片报 403部分还是好的。原因oss2.sign_url 生成的 URL 带有过期时间参数实验里习惯统一设 3600 秒。前端浏览器缓存了 HTML但图片 URL 已经过期于是出现“页面还在、图挂了”的状态。解决把过期时间设成一个相对业务合理的值比如预览场景 600 秒下载场景 30 秒更稳的做法是让接口每次返回签名 URL 时都重新生成而不是把 URL 存到数据库里重放。签名 URL 一旦生成服务端无法主动撤销只能等它自然过期所以有效期能省的不要多给。4.3 文件传完数据库没有记录存储桶里出现孤儿对象现象上传接口返回失败但去存储桶控制台看对象已经存在数据库里查不到记录重试后同一个文件被传了多次。原因双写流程没有回滚或者写库失败时异常被吞掉。很多实验代码把 insert_record 的 try/except 写成 print 了事没有 delete_object。媒体文件一旦落地 OSS没有数据库索引想靠人工控制台找到它非常痛苦。解决在主流程里用第 3.2 的回滚结构写库失败主动调 delete_object。另外在实验收尾时做一次对账用 list_objects 把 Bucket 中所有对象列出来和 media_asset 表里的 object_name 做差集。会写这个对账脚本实验报告的质量会明显高一个档次。import oss2 import pymysql bucket oss2.Bucket(oss2.Auth(ak, secret), oss-cn-shanghai.aliyuncs.com, lab-media-2024) conn pymysql.connect(host127.0.0.1, userlab_user, passwordyour_password, databaselab_media) cur conn.cursor() # 列举Bucket中全部对象 for obj in oss2.ObjectIterator(bucket): cur.execute(SELECT COUNT(*) FROM media_asset WHERE object_name%s, (obj.key,)) if cur.fetchone()[0] 0: print(孤儿对象:, obj.key) conn.close()这个脚本还可以加一行 bucket.delete_object(obj.key) 做成自动清理但从实验角度我建议先跑只读版确认差集逻辑可靠后再删不然容易出现误删。4.4 文件名编码和类型伪装现象上传“风景_2024.jpg”控制台里对象名变成乱码或者往上传了一个 exeContentType 却写成 image/jpeg。原因put_object_from_file 默认按 UTF-8 处理对象名但数据库连接的 charset 如果还是 utf8 而不是 utf8mb4写入时中文会被转成问号。类型伪装则是客户端可篡改 ContentType你不能信任浏览器传上来的值。解决数据库连接串统一用 utf8mb4对象名在上传前做一次 unicodedata.normalize服务端用 mimetypes.guess_type 识别文件后缀再做一次覆盖既不能盲信用户输入也不能盲信本地文件后缀。文件扩展名改了但文件内容没变的场景这份校验只能兜底安全的验证是读取文件头部的 magic number。5. 参数与边界把存储桶和数据库调成能交付的状态5.1 存储桶权限的几种配置怎么选实验四的存储桶权限配置只有三个主要选项私有、公共读、公共读写。很多人只知道私有和公共读忽略了公共读和公共写同时开是最危险的状态。做实验时选私有再结合签名 URL做内部演示时可以临时公共读但存储桶里不允许存在敏感文件。生产上的标准做法是 Bucket 私有 RAM 子账号 STS 临时凭证STS 这部分实验不要求但报告里能写出它和 AccessKey 的区别属于加分项。权限模型直接决定代码怎么写。如果把 Bucket 设成公共读第 3.3 的签名 URL 逻辑就没意义反而给自己增加复杂度如果坚持私有就必须在每个读取接口串联 gen_access_url。我见过有人一边用公共读一边在代码里做签名最后发现签名 URL 在公共读桶上一样能访问但安全模型已经糊了。选定一种模型全流程保持一致比混搭更接近生产习惯。5.2 数据库连接池和批量写入的实验参数实验里每次上传都新建一个 pymysql 连接数据量小还看不出问题一旦用循环脚本批量灌入 1000 张图片就会报 Too many connections。常见做法是引入连接池比如用 DBUtils 的 PooledDB。连接池参数不是越大越好maxconnections 设为 5 到 10配合 prepingTrue能保证从池里拿到的连接是可用的。批量写入时INSERT 一次一行是性能瓶颈。把多条记录拼成一条多 VALUES 语句或者使用 executemany速度能差出几十倍。实验四如果只是验证流程不需要优化到这一步但报告里如果提到批量场景值得把这两个参数写出来pool 的 maxconnections、batch 的 executemany 容量我一般 500 条一批。5.3 生命周期规则给冷数据降成本的存储桶配置实验报告写到生命周期规则属于进阶内容但它和“云存储”三个字关系最近。OSS 的存储类型分标准、低频、归档、冷归档价格递减但访问成本递增。生命周期规则做的事情就是“超过 XX 天后自动转归档/删除指定前缀对象”。实验里可以建一个 backup/ 前缀写一条规则把它做成 7 天后转低频、30 天后删除用来模拟真实系统的冷数据沉降。配置生命周期规则不要全局一把梭。如果 Bucket 里还有正在被访问的媒体全局沉降会把热数据也拖进归档导致前端拉取时需要先解冻响应时间变成分钟级。正确边界是只针对明确的前缀或标签做沉降规则的“前缀”输入框不是摆设。归档文件不支持秒级取回这条会在实验演示时让人措手不及最好单独测试一次再写进报告。6. 一致性自查与进阶把实验报告变成可交付的习惯实验收尾别急着提交报告先跑三种自查。第一种是用第 4.3 的孤儿对象对账脚本把 Bucket 里的对象和库里的 object_name 做差集第二种是数量对账统计对象总数和数据库记录数是否一致第三种是哈希抽查随机抽几条数据库记录从 OSS HEAD 拿到 ETag 或重新下载后计算 SHA256核对一致。实验数据少时直接全量比对数据多时按比例抽样十天半个月跑一次成了日常机制比出了事故再补救强。进阶我更推荐先做 OSS 版本控制。版本控制开启后上传覆盖会保留历史版本这个开关相当于后悔药能防手滑覆盖开启前先在实验桶里验证确认旧版本对象可以通过控制台回滚再上线。CDN 加速可以作为下一个进阶动作但这里有个坑CDN 会缓存签名 URL如果 URL 带过期参数缓存层级会让过期问题更隐蔽。我的教训是先在任何 CDN 缓存配置里关掉对带签名参数 URL 的缓存再谈加速。我自己的习惯是每次实验结尾都会跑一遍第 4.3 的对账脚本看看库里有没有孤儿对象中途改过权限模型时会把所有调用签名 URL 的地方重新过一遍。这个习惯让我在把报告里的方案做成线上系统后避开过几次事故。实验四练的不是传文件这个动作而是双写场景下的状态一致意识。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →