2026/8/23 21:07:38

SeaweedFS与MinIO深度对比:对象存储选型避坑指南

SeaweedFS与MinIO深度对比:对象存储选型避坑指南 1. 为什么今天还要亲手搭对象存储SeaweedFS 和 MinIO 不是“装完就用”的玩具最近三个月我帮六家不同规模的团队重新梳理了他们的文件存储架构——从初创公司用树莓派跑的测试环境到中型 SaaS 厂商用三台旧服务器撑起日均 200 万次小图上传的生产系统。他们最初都抱着一个朴素想法“找个开源对象存储像 AWS S3 那样用就行。”结果无一例外在第三天就卡在了 bucket 创建失败、跨域请求被拒、磁盘告警不停、权限配置反复失效这些看似基础却极其消耗时间的问题上。而所有这些问题背后几乎都指向同一个现实SeaweedFS 和 MinIO 虽然都标榜“兼容 S3”但它们的设计哲学、故障模式、运维心智模型根本不是一回事。你不能把 MinIO 当成 SeaweedFS 的平替去试也不能拿 SeaweedFS 的文档去硬套 MinIO 的报错。这不是工具好坏的问题而是底层数据组织逻辑的差异——就像你不能用 MySQL 的索引思维去优化 Elasticsearch 的查询。我见过太多人花两天时间部署 MinIO结果在第七天因为storage reached its minimum free disk threshold报错停服两小时只因没理解它默认的--minio-disk-threshold是按整个卷的剩余空间计算而非单个节点也见过团队为解决the requested bucket name is not available连续重启三次 SeaweedFS master最后发现只是因为集群里某台 volume server 的时钟漂移了 12 秒触发了内部一致性校验拒绝创建新 bucket。这些坑官方文档不会写Stack Overflow 上的答案往往过时或片面真正能救命的是你对这两个系统“肌肉记忆”级别的理解它们各自在哪种场景下会突然变脆弱又在哪种配置组合下能稳如磐石。这篇文章不讲“怎么安装”而是带你拆开它们的骨架看清每个螺丝拧在哪儿、为什么这么拧、拧歪了会发出什么异响——尤其当你手头只有两台 8 核 32G 的物理机要扛住未来一年的 PDF 文档归档和用户头像存储时这种理解就是你运维手册里最硬核的一页。2. 设计哲学与核心差异不是“谁更好”而是“谁更适合你的伤口”2.1 SeaweedFS用“文件即对象”的极简主义对抗复杂性SeaweedFS 的设计原点非常清晰它不想做另一个 S3 兼容层它想做分布式文件系统的现代演进。创始人 Chris Lu 在早期博客里反复强调“S3 API 是给云厂商设计的抽象不是给工程师设计的工具。”这句话直接决定了 SeaweedFS 的三个关键选择元数据与数据分离且元数据极度轻量SeaweedFS 的 master 节点只存 bucket 名、volume ID 映射、以及每个 volume 的当前最大 file ID。它不存任何文件名、大小、MIME 类型、甚至不存完整的路径。当你上传photos/2024/06/vacation.jpgSeaweedFS 实际生成一个 8 字节的 fid如3,12345678其中3是 volume ID12345678是该 volume 内的文件序号。真正的文件名、路径、属性全部由客户端或你自己的应用层通过额外的数据库如 PostgreSQL来维护。这带来两个直接后果master 节点内存占用极低百 GB 数据集下通常 100MB集群扩缩容时 master 无需同步海量元数据但代价是你必须自己实现一套“对象目录服务”否则连listObjects这种基础操作都要遍历所有 volume。Volume 是核心调度单元而非节点SeaweedFS 的最小可调度单位是 volume默认 8GB 文件块而不是服务器。你可以把 100 个 volume 分散在 3 台机器上也可以把 1 个 volume 单独放在一台 SSD 服务器上专供高频读取。volume 之间完全独立一个 volume 损坏只影响该 volume 内的文件其他 volume 照常工作。这种设计让 SeaweedFS 在混合硬件环境比如部分节点用 NVMe部分用 SATA下异常灵活但也意味着它没有 MinIO 那种“节点级健康检查”概念——它的健康是 volume 级别的你需要自己监控每个 volume 的 I/O 延迟和错误率。S3 兼容是“翻译层”不是原生能力SeaweedFS 的 S3 网关weed s3本质是一个反向代理它把 S3 请求解析后转换成内部的 FID 操作。这意味着PUT /bucket/key→ 生成 fid → 存入对应 volume → 返回 fid 给客户端 → 客户端需自行记录key → fid映射GET /bucket/key→ 查询本地映射表 → 找到 fid → 向 volume server 发起GET /{fid}请求LIST /bucket?prefixxxx→ 必须遍历所有 volume 的文件列表通过GET /vol/{volume_id}/dir再合并过滤。这个操作在 volume 数量超过 50 个时延迟会明显上升。提示SeaweedFS 的bucket name not available错误90% 以上源于 master 节点无法与某个 volume server 建立 gRPC 连接常见于防火墙、DNS 解析失败、或 volume server 进程崩溃但端口仍被占用。它不是 bucket 名冲突而是集群拓扑感知失败。排查时先curl http://master:9333/dir看返回的 volume 列表是否完整再逐个curl http://volume-server:8080/status检查状态。2.2 MinIO以“企业级 S3 兼容”为锚点构建的强一致性系统MinIO 的出发点截然不同它要成为私有云里的 AWS S3一个开箱即用、行为可预测、审计友好的对象存储。这决定了它所有技术决策都围绕“S3 行为保真度”展开元数据与数据强耦合且元数据丰富MinIO 的每个对象object在底层都对应一个.meta文件JSON 格式里面存着完整的 S3 元数据ETagMD5、Last-Modified、Content-Type、User-Metadata自定义键值对、甚至版本 ID启用版本控制后。这些数据和对象数据块.part文件一起按 erasure coding纠删码或 replication副本策略严格分布在所有参与节点上。这意味着HEAD /bucket/key能瞬间返回完整元数据无需额外查询LIST操作通过读取每个节点上的xl.meta文件即可完成性能稳定但代价是每个对象的元数据写入必须等待所有目标节点确认这带来了更高的写入延迟和更严格的网络要求。节点是核心信任单元集群即“单体”MinIO 将整个集群视为一个逻辑实体。它使用 Raft 协议选举 leader并将所有元数据变更bucket 创建、policy 更新、IAM 用户添加作为 Raft log 复制到多数节点。任何一个节点宕机只要剩余节点数 ≥ N/21N 为总节点数集群就能继续提供读写服务。但这也意味着所有节点必须配置相同的磁盘路径、相同的启动参数、相同的时钟NTP 同步误差需 500ms任意节点磁盘满minimum free disk threshold默认为 10%即剩余空间 总容量 10% 时拒绝写入整个集群都会拒绝新写入这是为了防止某些节点因磁盘满导致 Raft log 无法持久化破坏一致性。S3 兼容是原生 DNA不是翻译MinIO 的 HTTP handler 直接处理 S3 协议的所有细节。PUT请求进来它解析 header、计算签名、校验 policy、分配纠删码组、写入数据块和元数据文件、更新 raft log、广播事件——一气呵成。所以minio bucket set public这样的命令本质是修改了 bucket 的policy.json并写入 raft log所有节点实时同步。这种深度集成带来了极高的协议保真度AWS CLI、s3cmd、各种 SDK 几乎零适配但也让 MinIO 对底层环境更“娇气”。注意accessdenied错误在 MinIO 中有明确的优先级链1) IAM 用户/策略是否存在2) bucket policy 是否显式 deny3) object ACL如果启用4) 网络层 TLS 证书是否匹配S3 endpoint 必须与证书 CN/SAN 一致。很多用户卡在第 4 步以为是权限问题实际是curl -k能通但 SDK 报 accessdenied根源在于 Java/Python SDK 默认校验证书。2.3 关键维度对比一张表看懂何时该选谁维度SeaweedFSMinIO核心定位分布式文件系统S3 是可选网关企业级 S3 兼容对象存储文件系统是实现细节元数据存储Master 节点仅存 volume 映射文件名/属性由应用层管理每个对象自带完整元数据.meta文件与数据块同分布数据分布粒度Volume8GB 块可跨节点自由调度Object任意大小按纠删码组或副本组分布一致性模型最终一致性volume 间无强同步强一致性Raft 协议保证典型部署规模3~20 台服务器硬件异构容忍度高4~16 台服务器推荐同构硬件CPU/内存/磁盘型号一致磁盘空间告警单 volume 满不影响其他 volume需手动清理或扩容任一节点磁盘剩余 10%默认全集群拒绝写入S3 兼容深度支持核心 APIPUT/GET/LIST/DELETE不支持 Multipart Upload 完整生命周期、Bucket Policy 细粒度控制100% 兼容 AWS S3 v4 签名支持完整 Multipart、Versioning、Lifecycle、Replication、Bucket Policy、CORS运维复杂度低master volume server 进程简单但需自建元数据服务中高需监控 raft 状态、磁盘水位、证书有效期、IAM 策略冲突最适合场景高吞吐小文件如图片缩略图、日志片段、混合硬件环境、需要极致写入性能、能接受应用层管理元数据企业文档归档、合规审计、多租户 SaaS、需要标准 S3 工具链AWS CLI、rclone、Cyberduck无缝接入这个对比不是为了分出胜负而是帮你判断如果你的业务核心是“快速存取千万级小图”且已有现成的 Redis 或 PostgreSQL 来存url → fid映射SeaweedFS 的轻量和灵活就是你的护城河但如果你的客户要求“必须用 AWS CLI 上传且上传后立刻能在 Cyberduck 里看到文件”那 MinIO 的协议保真度就是不可替代的刚需。3. 实操部署与避坑指南从裸机到生产可用的每一步3.1 SeaweedFS三步搭建高可用集群附真实压测数据我用三台 Ubuntu 22.04 服务器每台 4C8G1TB SATA HDD实测了 SeaweedFS 集群部署。关键不是“怎么装”而是“怎么避免掉进默认配置的坑”。第一步Master 节点启动必须指定数据中心和机架# 不要只用 weed master必须加 -ip.bind0.0.0.0 和 -default.replication001 # -default.replication001 表示同一 volume 的副本数为 1即不复制这是开发/测试默认值 # 生产环境必须改为 010同一机架内 1 副本或 100跨机架 1 副本 weed master -port9333 -mdir/opt/seaweedfs/master -ip.bind0.0.0.0 \ -default.replication010 \ -dataCenterdc1 -rackrack1实测心得-default.replication是 SeaweedFS 最容易被忽略的致命参数。设为001时所有 volume 都只存一份一旦该 volume server 宕机数据永久丢失。设为010后系统会在同一 rack 的其他 volume server 上自动创建副本但 rack 信息必须在启动 volume server 时通过-rack参数声明否则副本无法调度。第二步Volume Server 启动绑定 rack指定 volume 目录# 服务器 AIP 10.0.1.10 weed volume -port8080 -dir/data/volumes -max100 -mserver10.0.1.10:9333 \ -ip10.0.1.10 -rackrack1 # 服务器 BIP 10.0.1.11 weed volume -port8080 -dir/data/volumes -max100 -mserver10.0.1.10:9333 \ -ip10.0.1.11 -rackrack1 # 服务器 CIP 10.0.1.12 weed volume -port8080 -dir/data/volumes -max100 -mserver10.0.1.10:9333 \ -ip10.0.1.12 -rackrack2这里的关键是-rack参数。我故意将服务器 C 放在rack2这样当设置-default.replication010时系统会优先在rack1内找副本位置若rack1内 volume server 不足则 fallback 到rack2。这模拟了真实的机架隔离。第三步S3 网关启动暴露标准端口禁用匿名访问# 启动网关绑定到 8333 端口避免与 volume server 的 8080 冲突 weed s3 -port8333 -master10.0.1.10:9333 -disable-authfalse \ -access-keyminioadmin -secret-keyminioadmin # 验证curl -X PUT http://localhost:8333/mybucket -H Authorization: AWS4-HMAC-SHA256 ... # 注意SeaweedFS S3 网关默认使用 minioadmin/minioadmin但不支持 IAM 用户所有请求都走这组密钥避坑清单来自真实故障复盘时钟漂移陷阱三台服务器 NTP 同步必须开启。我们曾因服务器 C 的时钟慢了 8 秒导致weed s3启动时无法连接 master报错context deadline exceeded。解决方案sudo timedatectl set-ntp onsudo systemctl restart systemd-timesyncd。Volume 目录权限/data/volumes目录必须由运行weed volume的用户如seaweed拥有且chmod 755。否则 volume server 启动后会静默失败日志里只有一行failed to create volume directory。S3 网关的 CORS 问题SeaweedFS S3 网关默认不带 CORS 头。前端 JS 直传会失败。必须在网关启动后用curl手动设置curl -X PUT http://localhost:8333/mybucket?cors \ -H Authorization: AWS4-HMAC-SHA256 ... \ -d CORSConfigurationCORSRuleAllowedOrigin*/AllowedOriginAllowedMethodPUT/AllowedMethodAllowedMethodGET/AllowedMethodMaxAgeSeconds3000/MaxAgeSeconds/CORSRule/CORSConfiguration压测结果wrk 工具100 并发1000 次请求单 volume server无副本平均延迟 42ms99% 85ms三节点集群010副本平均延迟 58ms99% 120ms写入吞吐提升 3.2x因 volume 分散在多台机器关键发现当一台 volume server如 10.0.1.11宕机后新上传的文件会自动路由到其他 volume server已存在的文件在宕机 server 上仍可通过 S3 网关读取因 master 记录了 fid 映射但LIST操作会变慢需跳过不可达 volume。3.2 MinIO四节点集群部署与“磁盘阈值”生死线MinIO 的四节点部署是生产环境最低可行配置满足N/21 3的 raft quorum。我用四台相同配置的服务器4C8G2TB NVMe进行部署。第一步准备统一的启动脚本关键在每台服务器上创建/opt/minio/run.sh#!/bin/bash export MINIO_ROOT_USERadmin export MINIO_ROOT_PASSWORDStrongPassw0rd123! # 必须 8 位以上含大小写字母数字符号 export MINIO_VOLUMEShttp://10.0.1.10/mnt/disk1 http://10.0.1.11/mnt/disk1 http://10.0.1.12/mnt/disk1 http://10.0.1.13/mnt/disk1 export MINIO_OPTS-C /etc/minio --quiet # 启动命令注意所有节点必须用完全相同的 MINIO_VOLUMES 字符串顺序 /opt/minio/minio server $MINIO_VOLUMES $MINIO_OPTS实操心得MINIO_VOLUMES的 URL 顺序必须严格一致否则 raft 会认为这是不同的集群。我们曾因服务器 D 的 URL 写成http://10.0.1.13/mnt/disk1少了个斜杠导致它无法加入集群日志里全是invalid cluster configuration。解决方案用curl -v http://10.0.1.10:9000/minio/health/live检查每个节点的健康状态再用mc admin info local查看集群视图。第二步启动并初始化首次启动耗时较长# 四台服务器同时执行 sudo bash /opt/minio/run.sh # 等待约 2-3 分钟直到所有节点日志出现 Server started 和 Ready to serve # 默认管理端口 9000S3 端口 9000MinIO 默认 S3 和管理共用端口第三步关键配置加固绕过默认陷阱调整磁盘阈值默认10%太激进。我们的 NVMe 盘写入速度极快但df统计有延迟常因瞬时写入导致误报。修改/etc/default/minio# 添加一行将阈值提高到 15% export MINIO_STORAGE_MIN_FREE15%启用 HTTPS生产必备将证书放在/etc/minio/certs/public.crt和/etc/minio/certs/private.key启动时加-S参数export MINIO_OPTS-S /etc/minio/certs --quiet设置 Bucket Public非全局按需# 使用 mc 命令行工具需提前配置 alias mc alias set myminio https://minio.example.com admin StrongPassw0rd123! mc anonymous set public myminio/mybucket # 这等价于设置 bucket policy允许 GET/HEAD“磁盘阈值”故障复现与解决我们曾在线上环境触发storage reached its minimum free disk threshold。排查步骤mc admin info myminio显示节点 D 的Free: 12.3 GiB (10%)刚好卡在临界点df -h /mnt/disk1显示实际剩余 180GB但du -sh /mnt/disk1/.minio.sys/发现.minio.sys/tmp目录占了 160GB未清理的 multipart upload 临时文件手动清理sudo rm -rf /mnt/disk1/.minio.sys/tmp/*然后mc admin service restart myminio根治方案在启动脚本中加入定期清理# 每天凌晨 2 点清理 tmp 目录 (crontab -l 2/dev/null; echo 0 2 * * * sudo find /mnt/disk1/.minio.sys/tmp -type f -mtime 1 -delete) | crontab -压测结果同样 wrk100 并发四节点集群纠删码42平均延迟 85ms99% 210ms写入吞吐比单节点高 2.8x关键发现当节点 D 宕机后集群立即降级为3节点quorum2仍可读写但mc admin info显示Degraded: true恢复节点 D 后需手动mc admin heal myminio触发数据修复耗时约 15 分钟修复 1TB 数据。4. 核心功能实测与深度解析LIST、权限、备份的真相4.1 LIST 操作为什么 SeaweedFS 慢MinIO 快以及如何优化LIST是对象存储最常用也最容易出问题的操作。它的性能差异直接暴露了两个系统底层设计的鸿沟。SeaweedFS 的 LIST 机制与优化SeaweedFS 的GET /?prefixxxx请求会被 S3 网关转换为向 master 查询所有 volume 列表GET /dir对每个 volume发起GET /vol/{id}/dir?prefixxxx请求合并所有 volume 的响应按字典序排序后返回。这意味着Volume 数量是性能瓶颈100 个 volume 就要发 100 个 HTTP 请求。我们实测volume 数 50 时LIST延迟从 100ms 跳到 500ms。优化方案只有两个方案 A推荐应用层缓存 LIST 结果。用 Redis 缓存bucket/prefix→file list的映射TTL 设为 30 秒。这样 95% 的 LIST 请求走内存只有缓存失效时才穿透到 volume server。方案 B减少 volume 数量。启动 volume server 时用-max500而非默认 100让每个 volume 更大从而减少总数。但要注意单 volume 过大 20GB会影响 rebalance 效率。MinIO 的 LIST 机制与陷阱MinIO 的GET /?prefixxxx直接读取每个节点上的xl.meta文件包含该节点上所有对象的元数据然后合并。这很高效但有一个隐藏陷阱当 bucket 开启 Versioning 时每个 object 的多个版本都存为独立的.meta文件LIST 操作会返回所有版本导致响应体暴增。我们曾遇到一个 bucket 有 50 万个 object每个平均 3 个版本LIST返回的 XML 超过 10MB客户端解析超时。解决方案强制分页永远使用?max-keys1000markerxxx参数避免一次性拉取全部禁用不必要的 Versioning如果业务不需要历史版本创建 bucket 时明确mc mb --region us-east-1 --ignore-existing myminio/mybucket不加--versioning用 Select Object Content 替代 LIST对于需要搜索的场景如“找出所有 2024 年的 PDF”用SELECT * FROM S3Object WHERE year 2024 AND ext pdfMinIO 会直接扫描元数据比 LIST客户端过滤快 10 倍。4.2 权限体系从 IAM 到 Bucket Policy 的实战落地权限是线上事故最高发区域。两个系统的权限模型完全不同必须分开理解。SeaweedFS极简的“一把钥匙开所有锁”SeaweedFS S3 网关只支持一种认证方式静态 Access Key/Secret Key启动时通过-access-key和-secret-key设置。它没有 IAM 用户、没有 Group、没有 Policy。所有请求只要签名正确就拥有该 key 的全部权限相当于 AWS 的 root user。这意味着无法实现多租户隔离你不能给市场部一个 key 只能读marketing/目录给研发部另一个 key 只能写builds/。解决方案只能是应用层网关在 SeaweedFS 前面加一层 Nginx 或 Envoy根据请求的Host或X-Forwarded-For头转发到不同的 S3 网关实例每个实例用不同的 key并用proxy_pass的rewrite功能重写路径。例如location /marketing/ { proxy_pass https://seaweedfs-marketing:8333/; proxy_set_header Authorization ; # 重写请求路径去掉 /marketing/ rewrite ^/marketing/(.*)$ /$1 break; }MinIO完整的 AWS S3 权限模型MinIO 完全实现了 AWS 的 IAM Bucket Policy 双层权限。实操中90% 的权限问题源于策略冲突。典型冲突场景与解法场景现象根本原因解决方案accessdenied即使 bucket policy 允许IAM 用户策略Statement[0]显式Deny了s3:GetObjectMinIO 权限评估遵循“显式 Deny 优先于任何 Allow”原则检查mc admin user policy attach绑定的策略删除或修改Deny语句accessdenied上传后无法读取bucket policy 允许GetObject但未允许ListBucketS3 客户端如 rclone在下载前会先ListBucket检查文件是否存在在 bucket policy 中添加Action: [s3:ListBucket]跨域请求accessdeniedCORS 配置正确但OPTIONS请求返回 403MinIO 的 CORS 是独立于 IAM 的但OPTIONS请求仍需通过 IAM 验证在 IAM 策略中显式允许s3:ListBucket和s3:GetObject即使 CORS 已开放实操命令速查# 创建 IAM 用户最小权限 mc admin user add myminio devuser DevPassw0rd123! # 创建只读策略JSON 文件 read-only.json { Version: 2012-10-17, Statement: [ { Effect: Allow, Action: [s3:GetObject, s3:ListBucket], Resource: [arn:aws:s3:::mybucket, arn:aws:s3:::mybucket/*] } ] } # 绑定策略 mc admin user policy attach myminio read-only.json --userdevuser4.3 备份与迁移别信“一键导出”动手才是真理对象存储的备份从来不是拷贝几个文件那么简单。元数据、版本、策略、证书缺一不可。SeaweedFS 备份Volume Master 元数据双备份Volume 备份/data/volumes目录下的所有*.dat和*.idx文件就是全部数据。用rsync -avz --delete /data/volumes/ backupserver:/backup/seaweed/volumes/即可。Master 元数据备份/opt/seaweedfs/master目录下的master.idx和master.dat是关键。每天定时cp -r /opt/seaweedfs/master /backup/seaweed/master-$(date %F)。恢复流程先恢复 master 目录重启 master再恢复 volume 目录重启 volume server最后weed s3会自动重建 fid 映射。注意SeaweedFS 没有“增量备份”概念。每次备份都是全量。但我们发现*.dat文件是 append-only 的可以用rsync --size-only加速只同步大小变化的文件。MinIO 备份Heal Backup 工具链MinIO 官方推荐mc mirror做冷备但生产环境必须用mc admin heal做热备。标准备份流程每日增量备份mc mirror# 将 mybucket 同步到 NFS 备份服务器 mc mirror --recursive --force --remove --older-than 30d myminio/mybucket backup-nfs/minio-backup/每周全量快照LVM/ZFS对/mnt/disk1所在的 LVM 卷用lvcreate -s -n minio-snap /dev/vg0/lv0创建快照再dd备份。灾难恢复演练每月一次新建四节点集群mc mirror backup-nfs/minio-backup/ mynewminio/mybucketmc admin heal mynewminio修复可能的元数据不一致用mc stat mynewminio/mybucket/object.txt验证 ETag 是否与源集群一致。迁移至其他服务器的血泪教训我们曾将 MinIO 从 Ubuntu 迁移到 CentOSmc mirror后发现所有文件的Last-Modified时间全变成迁移时刻。原因是mc mirror默认不保留x-amz-meta-*头。解决方案加--preserve参数mc mirror --recursive --force --preserve myoldminio/mybucket mynewminio/mybucket这个参数会保留所有用户元数据和修改时间但要求源和目标 MinIO 版本一致≥ RELEASE.2022-10-27T06-14-31Z。5. 常见问题速查表与独家排错技巧以下是我们三年运维 SeaweedFS 和 MinIO 积累的 12 个高频问题按发生频率排序每个都附带一句直击要害的排错口诀。问题现象根本原因排错口诀关键命令/操作the requested bucket name is not availableSeaweedFS master 无法与至少一个 volume server 通信“先 ping volume再 curl status”curl http://volume-ip:8080/statustelnet volume-ip 8080accessdeniedS3 SDK 报错MinIO 证书 CN 与 endpoint 不匹配“SDK 校验证书curl -k 能通≠SDK 能通”openssl x509 -in /etc/minio/certs/public.crt -text | grep Subjectstorage reached its minimum free disk thresholdMinIO 某节点/mnt/disk1/.minio.sys/tmp占满“清 tmp不是清 data”sudo du -sh /mnt/disk1/.minio.sys/tmp/sudo rm -rf /mnt/disk1/.minio.sys/tmp/*LIST操作超时SeaweedFSvolume 数量过多HTTP 并发请求堆积“缓存 prefix别碰 volume”redis-cli SETEX list:mybucket:photos/ 30 file1,file2...mc admin info显示Degraded: trueMinIO raft quorum 未达成节点数不足“数节点看日志quorum N/21”mc admin info my