DAS/NAS/SAN与RAID 2.0+:存储架构原理及排障实践
简介:这份华为HCNA-Storage培训教材V4.0面向准备考取华为存储认证的工程师、数据中心运维人员及存储技术初学者,系统讲解企业级存储的基础概念与主流架构。内容由存储技术概述切入,区分狭义与广义存储定义,梳理硬件架构、软件组件与存储解决方案三大部分,并重点对比DAS(含内部与外部两类)、NAS、SAN三种架构的连接方式、协议差异与适用场景,同时延伸至IP SAN、分布式存储、软件定义存储以及低端与中端存储产品的特性,帮助读者建立选型与规划思路。压缩包内为1个PDF文档,约39.06MB,排版完整、图文配套,适合作为通读教材或查阅手册。该资料已有1241人学习下载,可用于认证备考、日常方案设计与技术扫盲,为后续实验与实战打下概念基础。
1. 从一块硬盘到一套存储系统:为什么"存储"从来不是一个盒子
刚翻开 HCNA-Storage 这类入门存储认证教材的人,很容易把"存储"当成一个盒子:买了、插上、划几个 LUN 就完事。教材开篇先把定义分成两半——狭义的存储指具体介质,软盘、CD、DVD、硬盘、磁带;广义的存储指数据中心里那套由存储硬件系统、软件系统、存储网络和解决方案叠起来的整体。这个分法不是咬文嚼字,它决定了你后面读 DAS、NAS、SAN、分布式、软件定义存储时的站位:你是在挑一台设备,还是在设计一条数据从主机到介质的完整路径。
HCIA 存储方向的题库里,DAS/NAS/SAN 的区别几乎必考,但考的是结论,考不出动机。真正值得搞懂的是这些名词底下那层共用结构:同一条 SCSI 指令,可以被塞进并行总线、FC 帧、TCP 报文里,传输介质换了三轮,上层语义一个字没改。理解了这层,一份两百页的培训教材在你眼里就不再是五六个并列的产品类别,而是一套被反复复用的抽象在换外壳。
2. 块与文件:访问语义才是架构分叉的真正起点
教材把 DAS 定义成"为服务器提供块级的存储服务(不是文件系统级)",把 NAS 定义成"文件级的数据访问和共享"。这两句话是全篇的分水岭。块和文件不是两种设备形态,而是两种分工:谁负责解释这些字节。把这一点想透,后面所有架构差异都是它的推论。
2.1 块级访问:把一维地址空间原样交出去
块设备对外只暴露一件事:从 0 开始编号、长度为 N 个逻辑块的一维地址空间。主机发一条 READ(10),写明起始 LBA 和要读多少个块,设备把对应字节搬回内存,全程不问这块数据属于哪个文件、哪个目录、有没有人正在写它。文件系统、inode、目录树、权限位,全部住在主机侧。
这解释了 DAS 那句"块级服务,不是文件系统级"的含义:设备只当搬运工,元数据的一致性由主机的文件系统保证。也解释了另一件常被忽略的事——为什么同一块裸盘能被格式化成 ext4 也能被格式化成 XFS,甚至能被两个文件系统抢着挂载,因为设备本身没有"这块归谁"的概念。
def build_read10(lba: int, blocks: int) -> bytes: # READ(10) 的 CDB 固定 10 字节,多字节字段一律大端序 cdb = bytearray(10) cdb[0] = 0x28 # 操作码:10 字节 CDB 版本的读命令 cdb[1] = 0x00 # 控制位:DPO/FUA 全关,允许走设备缓存 cdb[2:6] = lba.to_bytes(4, "big") # 起始逻辑块地址,4 字节,上限 2^32 块 cdb[6] = 0x00 # group number,多数实现填 0,保留位 cdb[7:9] = blocks.to_bytes(2, "big") # 传输长度,单位是"块"不是"字节" cdb[9] = 0x00 # control 字节,NACA 与链接位 return bytes(cdb)这段代码里有两个字段值得盯住。第一,LBA 只有 4 字节,512 字节块下寻址上限是 2TB,超过就得换 READ(16) 把 LBA 扩到 8 字节。第二,传输长度是 2 字节、单位是块,一次最多 65535 块,512 字节块下约 32MB,所以再大的 I/O 到了设备侧也是被切成多条命令排队下发的。块级存储的性能特征、队列深度怎么算、为什么大块顺序写比随机写划算得多,根子都在这 10 个字节里。
2.2 文件级访问:NAS 为什么必须自带一套操作系统
NAS 做的事正好反过来:把"路径到数据"的解析过程搬进设备内部。客户端发来的不是 LBA,而是\\share\report.xlsx偏移 4096 到 8192 字节这一段。设备要自己解析路径、查目录项、校验权限、维护打开文件的句柄、处理并发写的锁和缓存一致性。这些活需要一个完整的文件系统实现、一个完整的协议栈,也就需要一个自己的操作系统。
教材里那句"NAS 设备使用自己的操作系统和集成的硬件、软件组件",读起来平淡,实际是 NAS 和"一台装了文件共享服务的通用服务器"的本质差别。通用服务器的 CPU 要分给业务进程、要跑一堆无关服务,文件 I/O 只是它众多任务里的一个;NAS 把资源压在一件事上,砍掉通用操作系统的调度噪声,网络栈和文件系统走的是精简过的路径,所以教材说它"执行文件 I/O 比一般用途的服务器更好",也所以它能接入更多客户机、反过来整合掉一堆分散的文件服务器。
2.3 SCSI 指令集:三条架构共用的一套语义
这里有个反直觉的地方:NAS 看起来和 SCSI 毫无关系,可它内部管盘、做 RAID、访问后端磁盘,用的还是 SCSI 指令集;外部 DAS 在多数情况下经由 FC 或 SCSI 协议通信;FC SAN 的典型协议组是 SCSI + Fiber Channel;IP SAN 的典型协议是 iSCSI,教材的原话是"定义了 SCSI 指令集在 IP 网络中传输的封装方式"。四条路,一套语言。
常见的实现思路是把这件事拆成三层:最上面是命令集,READ/WRITE/INQUIRY/REPORT LUNS 这些 CDB;中间是架构模型,把两端抽象成 initiator 和 target;最下面是承载,FCP、iSCSI、SAS、SRP 各自负责把 CDB 和数据装进自己的帧或 PDU。换传输层不换语义,代价是每层都得自带可靠性机制——FC 用 buffer-to-buffer credit 做流控,iSCSI 用 command sequence number 做窗口和乱序重组。
理解了 initiator/target 这个抽象,很多配置项就不用死记了:target 侧的"LUN 映射"是把内部卷编号翻译成对外呈现的 LUN ID;initiator 侧的"启动器名"是给 target 认人用的身份标识;多路径是同一个 target LUN 走了多条链路时在 initiator 侧做的聚合。这些概念在 FC 和 iSCSI 上完全同构,换协议不用重新学一遍。
3. DAS、NAS、SAN:同一份 SCSI 语义的三条传输路径
教材把三种架构并排讲,很容易读成三个并列的产品类别。换个角度看,它们回答的是同一个问题的三个版本:主机和介质之间那段路,用什么协议走、能走多远、能挂几个。
3.1 内部 DAS 与外部 DAS:距离和设备数这两个硬约束的来处
内部 DAS 的存储设备挂在服务器机箱内部的并行或串行总线上,教材点了三条限制:物理总线有距离限制,只能支持短距离高速传输;能连接的设备数目有限;设备放在机箱内占空间,还影响其他部件的维护。
这三条不是产品取舍,是电气特性。并行总线靠多根线同时传一个字的多个 bit,频率一高,线间 skew 和串扰就把眼图糊掉,所以并行 ATA 到了 133MB/s 就顶住了;串行总线把时钟嵌进数据流,速率能上去,但背板走线和连接器密度仍然限制着机箱内能塞多少盘。设备数限制则来自总线仲裁和地址位宽,一条总线能挂几个 target id 是定死的。
外部 DAS 把这些约束松开了一半:存储设备移到机箱外,经由 FC 或 SCSI 协议直连。距离拉长了,设备数上去了,还能把存储集中到一处管理。但拓扑没变,主机到设备仍是一条专线,一台服务器占了,第二台接不进来——除非中间加一台交换机做交换,那一步跨出去,就走到 SAN 的地界了。DAS 和 SAN 的分界不在协议,在"有没有一张可以共享的交换网络"。
3.2 NAS 的协议栈:CIFS 和 NFS 各自管什么
NAS 的定位教材写得很明确:连接到一个局域网的基于 IP 的文件共享设备,经由文件级的数据访问和共享提供存储资源。它跑在 IP 上,用 TCP/IP 传输,用 CIFS、NFS 提供远程文件服务。
NFS 和 CIFS 的差别不只是 Windows 与 UNIX。NFSv3 是无状态设计:每个请求自带完整凭证和文件句柄,服务端不保存会话,客户端崩溃重启后重发即可,服务端不需要"恢复"什么。CIFS/SMB 从设计之初就是有状态的:先协商版本,再建会话,再连树,再打开文件拿句柄,每一步都在服务端留下状态。有状态换来更强的语义——字节范围锁、机会锁、目录变更通知,这些在无状态模型里做起来很别扭。
代价也很直接:状态越重,断线重连和故障切换越麻烦,延迟对语义的影响越大。这就是为什么 NAS 上跑数据库、跑虚拟机镜像盘总觉得别扭——锁、缓存一致性、元数据操作的开销都压在协议层,而块设备把这些责任一股脑交给了主机的文件系统或数据库自己。教材说 NAS 适合文件共享和服务器整合,适合什么其实不重要,从协议语义就能推出它不适合什么。
3.3 FC SAN 和 IP SAN:iSCSI 封装的到底是什么
教材给 FC SAN 的定义是"用在服务器和存储资源之间的、专用的、高性能的网络体系,为了实现大量原始数据的传输而做了专门优化",紧接着补了一句很精辟的话:可以把 FC SAN 看成 SCSI 协议在长距离应用上的扩展,典型协议组是 SCSI + Fiber Channel。
FC 适合这件事有两个原因,教材也点了:一方面能传大块数据,另一方面能远距离传输。背后的机制是 FC 自己的帧结构和信用式流控——发送端要先拿到接收端给的 credit 才能发帧,链路不会因为突发流量丢帧,也就不需要 TCP 那套"丢了再退避重传"的拥塞控制。代价是 FC 是一张独立的网:交换机、HBA、线缆、运维体系都是另一套,所以它的市场集中在高端企业级,性能、冗余度和数据可获得性要求都高的场景。
IP SAN 走另一条路:TCP/IP 做底层传输,以太网做承载,典型协议是 iSCSI。封装这件事要说准——iSCSI 把 CDB 放进 PDU 的头部段,把数据放进数据段,加上序号和摘要字段,剩下的可靠有序传输交给 TCP。被封装的是命令与数据,不被封装的是块语义本身,主机侧看到的还是一块裸盘,照样要自己格式化、自己做文件系统。
# 1. 发现阶段:只向门户询问"你有哪些 target",此时不建立会话 iscsiadm -m discovery -t sendtargets -p 10.0.0.10:3260 # 2. 登录阶段:与指定 target 完成登录协商,内核 SCSI 中间层随后枚举 LUN iscsiadm -m node -T iqn.2024-01.example:array0 -p 10.0.0.10:3260 --login # 3. 确认阶段:打印会话详情,核对 target 名与已挂接的 scsi disk iscsiadm -m session -P 3 | grep -E "Target|Attached scsi disk"这三条命令对应 iSCSI 的三个阶段,参数含义也不难:-m discovery切到发现模式,-t sendtargets指定发现方式,-p是门户地址;-m node操作用节点记录,--login才真正建立会话;-P 3打印会话的详细信息。机制上说,登录协商完成后,内核的 SCSI 中间层会为目标上的每个 LUN 生成一个 sd 设备节点,主机这才"看见"盘。如果第三条命令里只看到 Target 却没有 Attached scsi disk,问题基本就落在 LUN 映射和启动器白名单上,跟网络连通性无关——这是排查时最先要切开的两段。
| 维度 | 内部 DAS | 外部 DAS | NAS | FC SAN | IP SAN |
|---|---|---|---|---|---|
| 访问粒度 | 块 | 块 | 文件 | 块 | 块 |
| 承载协议 | 并行/串行总线 | FC 或 SCSI | TCP/IP + CIFS/NFS | SCSI over FC | SCSI over TCP |
| 典型距离 | 机箱内 | 数十米内直连 | 局域网 | 数据中心级 | 局域网到园区 |
| 共享能力 | 独占 | 独占 | 多客户端共享 | 多主机经交换网络共享 | 多主机经以太网共享 |
| 典型场景 | 单机系统盘 | 小规模外挂 | 文件共享、服务器整合 | 高要求企业级核心业务 | 中低端块存储接入 |
4. 存储软件栈:镜像、重构、分层都发生在哪一层
教材在讲低端和中端产品时,把大量篇幅给了功能名字:全冗余双控、RAID 2.0+、SmartTier、SmartCache、SmartThin、自愈合。名字是产品层的话术,机制是另一回事。这一章把它们拆回三个问题:控制器在什么位置、对什么数据、做了什么动作。
4.1 双控冗余:冗余的到底是什么
"全冗余的双控系统架构"这句话里,被冗余的对象不是硬盘,而是"写请求的确认"和"控制路径"。两个控制器各有 CPU、缓存、电源、到盘的通路,正常时各管一部分 LUN,故障时对端接管。
关键在于写缓存怎么保证不丢。常见的实现思路是:写请求先进入控制器 A 的缓存,A 立刻把它镜像到 B 的缓存,两边都落地之后才向主机回写完成。这样 A 掉电,B 手里还有一份完整数据,接管后可以继续刷盘;反过来,只写本地缓存就回确认,掉电瞬间主机以为写成功、数据实际还在内存里,这种丢数据往往事后完全查不出来,属于典型的玄学事故。
多控环境下还要处理路径归属。LUN 有归属控制器,主机如果从非归属控制器的路径发请求,请求要经由内部互联转发到归属端,延迟会高出一截。于是有了 ALUA 这类非对称访问语义:target 主动告诉 initiator 哪些路径是优化的、哪些是非优化的,多路径软件据此设置优先级。切换演练之后性能腰斩,多半就是这一步没配对。
4.2 RAID 2.0+:把重建粒度从"盘"改成"块"
传统 RAID 的重建痛点很直白:一块盘坏了,重建要读整个条带组的每一块盘,把校验算出来写到热备盘上。盘越大、盘组越宽,重建窗口越长;窗口里所有盘都在满负荷读,形成热点盘;重建期间再坏一块,整组数据就没了。这是"扩容越快、风险越大"这个悖论的来处。
RAID 2.0+ 换了个思路:先把每块物理盘切成固定大小的 chunk,把来自不同盘的 chunk 组成 chunk group,在 chunk group 上做 RAID 切出 extent,再由 extent 组成 LUN。教材的说法是"通过全虚拟化机制实现数据灵活调度""30 分/TB 数据重构""0 热点硬盘""坏道自修复"。
# 极简化的 RAID 2.0+ 地址映射:LUN LBA -> (物理盘, 盘内偏移) CHUNK_SIZE = 256 * 1024 # 每块盘切出的 chunk 大小,单位字节 EXTENT_CHUNKS = 4 # 一个 extent 由几个 chunk 组成,决定条带宽度 def lba_to_disk(lun_lba: int, ckg: list[list[int]]) -> tuple[int, int]: # 先定位 LUN 地址落在第几个 extent、extent 内偏移多少 extent_id, offset = divmod(lun_lba, CHUNK_SIZE * EXTENT_CHUNKS) # 再在 extent 内定位第几个 chunk、chunk 内偏移多少 chunk_idx, in_chunk = divmod(offset, CHUNK_SIZE) # ckg 是映射表:[extent 编号][条带内位置] -> 物理盘号 disk = ckg[extent_id][chunk_idx] return disk, in_chunk映射表是这套机制的核心。LUN 的地址空间被切成 extent,每个 extent 落在哪些盘的哪些 chunk 上,由这张表决定,逻辑地址和物理位置彻底解耦。直接后果有三个:重建只处理"有数据的 extent",空 extent 完全不用管,所以每 TB 的重构时间被压到分钟级;重建的读压力被摊到整个 chunk group,不再集中轰一个盘组;扩容时新盘的 chunk 直接进池参与分配,不必重算全量校验。
"坏道自修复"是同一套机制的自然延伸:某个 chunk 读到坏道,控制器从同组其他 chunk 算出这块数据,写到健康位置并更新映射表,整个过程对上层 LUN 完全透明。传统 RAID 遇到坏道只能交给上层文件系统去报错,差别就在这里。
4.3 分层、缓存与精简:热点统计和写时分配
教材把中端存储的精细化能力归成几个名字:SmartTier/SmartCache/SmartMotion 做热点统计与自动迁移,SmartThin 做写时分配与空间回收。名字不同,底下是同一类问题——数据的访问频次不均匀,空间的占用也不均匀,而固定分配把两者都当成了均匀。
分层存储的前提是能统计。控制器按固定周期采样各 LUN 各段的 IO 次数和块大小,算出热度,超过阈值就把这一段迁到 SSD 层,冷下来再迁回 HDD 层。最容易翻车的地方是迁移本身消耗的带宽:迁移是后台任务,和业务 IO 抢同一批盘,所以真实实现都会给迁移限速,并在业务高峰推迟迁移。教材说性能提升三倍,是在稳态下测出来的,迁移动作本身的开销不会写进这句话里。
精简配置的前提是写时分配。创建 LUN 时只登记逻辑容量,不落物理空间;主机第一次写到某个块,控制器才分配物理块并更新映射表。麻烦的地方在回收:主机删掉文件,文件系统知道这些块空了,存储并不知道,映射表里还老老实实挂着。要有 UNMAP/TRIM 这类语义把"这些块我不要了"传下去,池子才可能真正瘦回来。
注意:精简池的容量告警和实际数据量是两回事。池子里除了有效数据,还压着快照占用的空间、未回收的孤儿块、以及写时分配的元数据开销,按逻辑容量估算使用率一定会算错。
5. 避坑与排查:从现象倒推是哪一层出的问题
教材给的是正确状态下的模型,真实环境里更多是"不对劲"。下面几条是存储交付和运维里出现频率最高的,每条按现象、原因、解决三段写,重点不在结论,在怎么从现象定位到层。
5.1 现象:LUN 划好了、掩码也配了,主机就是看不到盘
现象:主机侧fdisk -l看不到新盘,iscsiadm -m session -P 3能列出 target 名但没有 attached scsi disk,存储侧显示 LUN 在线且健康。
原因:这类问题九成出在"认人"环节。一是启动器名对不上,主机换了 HBA 或重装系统后 IQN 变了,存储侧白名单里还是旧的那个;二是 LUN ID 冲突,同一个 target 下两个 LUN 用了相同编号,主机枚举时后一个被覆盖;三是多路径软件还没重扫,内核 SCSI 层没收到 rescan 通知。
解决:先在主机侧确认实际 IQN,再去存储侧核对白名单,别凭记忆;然后在主机上触发 SCSI 总线重扫,让内核重新走一遍 REPORT LUNS;最后才怀疑映射。这里有一条血泪经验——排障顺序要从"身份"往下走,先证身份对、再证枚举到、最后才查路径和性能,反着查会把时间全花在网络上。
5.2 现象:NAS 共享目录 Windows 能读不能写,UNIX 侧一切正常
现象:同一个共享目录,UNIX 客户端用 NFS 挂载后读写正常,Windows 客户端用 CIFS 挂载后能打开、能读,新建文件和保存全部失败,权限报错。
原因:两套权限模型在同一份目录上打架。NFS 侧认的是 UID/GID,CIFS 侧认的是 ACL,中间要做身份映射;映射表里没有对应用户时,写操作被降级成匿名访问,而目录权限只给了属主可写,于是表现为"能读不能写"。有些实现里 root squash 也会参与进来,让 root 写同样被拒。
解决:先把两侧的身份映射表对齐,确认 Windows 账号被映射到正确的 UID;再核对目录的属主和权限位,注意 NFS 的 umask 和 CIFS 的继承 ACL 谁覆盖谁;最后确认共享本身没有被设成只读。这类问题适合在交付前用两个客户端各写各读一遍,别等业务上线才发现。
5.3 现象:双控切换演练之后,业务性能掉了一半
现象:切换前业务平稳,手动切换控制器再切回来之后,告警清零、LUN 全部在线、链路状态正常,但业务侧响应时间翻倍,存储侧看 IOPS 掉了不少。
原因:LUN 的归属控制器在切换过程中漂走了,回来时没有自动漂回,主机侧的路径一直走非归属控制器的优化路径,每次 IO 都要经内部互联转发。多路径软件如果没有正确识别 ALUA 的优先级,也会一直挑非优先路径发请求。
解决:先看路径状态,确认每条路径标的是优化还是非优化;再核对 LUN 当前归属和主机实际使用路径是否一致;最后调整多路径的优先级策略,把优化路径设为优先。切换演练一定要带性能验证,只验连通性等于没验。
5.4 现象:精简配置的池子没写多少数据就快满了
现象:池子标称 100TB,业务实际写入不到 40TB,使用率已经飘红,扩容申请单都批下来了。
原因:精简池里占用空间的除了有效数据,还有快照占用的写时复制块、删文件后没有回收的孤儿块、以及元数据开销。文件系统删数据只是改了自己的位图,存储侧的映射表并不会自动释放,UNMAP 没开或者间隔太长,空间就一直挂着。
解决:先区分"数据占用"和"快照占用",把快照单独统计;再确认文件系统侧的 UNMAP 是否开启、存储侧是否接收;最后核对池子的超额订阅比有没有设得过高。精简配置不是免费的,超额订阅比一旦超过可回收能力,池子早晚会被"看不见的占用"填满。
5.5 现象:扩容之后的重构时间比估算的长很多
现象:加了一批盘做扩容,按重构速率估算三小时能完成,实际跑了十几个小时,同期业务延迟明显抖动。
原因:重构是后台任务,和业务 IO 抢同一批盘;控制器通常会做限速,把重构带宽压到某个比例以下避免影响业务,限速加上业务高峰叠加,实际速率远低于理论值。chunk 大小和 extent 分布也会影响,碎片化的映射表让重构时的读放大更严重。
解决:先看重构速率曲线和业务 IO 曲线是否反向相关,确认是限速在起作用;再评估是否需要在业务低峰期手工提速;长期看要调 chunk 大小和 extent 分布策略,减少碎片。扩容计划里必须把重构窗口当成业务风险窗口来排,而不是当成后台静默完成的事。
6. 分布式与软件定义:把存储池从机柜里搬出来之后的取舍
教材对分布式存储的描述只有两句话,却点到了要害:将标准 x86 服务器的本地 HDD、SSD 组织成大规模存储资源池,把数据分散存储到多个数据存储服务器上;目前多借鉴分布式文件系统的思路,先在众多服务器上搭一个分布式文件系统,再在其上实现存储业务。软件定义存储则是把这套池子对非虚拟化环境和虚拟机统一暴露成标准的 SCSI 和 iSCSI 接口。
6.1 接口不变,控制面外移
这里的设计取舍值得琢磨。分布式存储把数据面打散到几十上百台 x86 服务器上,控制面(元数据、映射、故障检测、重建调度)单独抽出来,集中判断、分散执行。它没有发明新接口,对外仍然是 SCSI 和 iSCSI——主机侧不需要为新架构改任何一行代码,这是标准接口省下的大量适配成本。
代价同样明显。集中式阵列的控制器是专门为存储设计的,缓存、队列、流控都在一块板子上,延迟的确定性很好;分布式存储每一次 IO 都要经过网络、要跨节点确认副本,尾延迟的抖动天然更大。教材里中端存储强调"前端带宽超出业界 50%""SPC-1 性能超出业界 30%",讲的是确定性;分布式讲"弹性扩展""按需增长",讲的是上限。这两件事很难同时拿满。
6.2 中端存储的全协议覆盖说明了什么
教材说中端产品"FC/ISCSI/NFS/CIFS/FTP/FCoE/IB 全协议灵活覆盖",并且兼容 VAAI/VASA/SRM/SMI-S 这类标准。这其实是厂商对上面那个取舍给出的中间答案:不赌某一种架构会赢,而是把所有协议栈都做进同一个控制器的软件里,让用户按业务挑路径。代价是协议栈的复杂度和测试矩阵成倍膨胀——一套代码要同时伺候块和文件,还要保证在每种协议下的性能都不塌,这是工程上的硬活。
我自己的习惯是,看到这类全协议覆盖的产品,不先看它支持多少种协议,而先看它每种协议下的性能曲线是不是一个量级。支持列表是销售语言,性能曲线才是工程语言。
6.3 我的验证习惯
从那以后,我每次评估一套存储方案,都强制走一遍同一个顺序:先确定这份数据是块语义还是文件语义,再确定访问它的主机有几种、会不会增长,然后才去看具体产品支不支持。这个顺序反过来——先看产品功能列表再找场景——踩过的坑太多了,最后总会买回一堆用不上的协议许可。希望帮到你。
本文还有配套的精品资源,点击获取