第一次接触 MyCat 这种数据库中间件的时候,很多人的第一反应是:它到底在项目里扮演什么角色?配置文件为什么有那么多层嵌套?分片后数据到底落在哪?这些困惑我在学习第一天全都经历过。这篇内容就按我自己实际摸索的顺序来写——从 MyCat 是什么、部署结构、三大核心配置文件的拆解,再到分片操作和分片规则的选型,最后把第一天最容易踩的坑一起列出来。
如果你正在评估分库分表方案,或者刚开始读 MyCat 文档但被 schema.xml、rule.xml 绕得头疼,这篇应该能帮你把学习路径理顺。
1. 为什么需要 MyCat:从单数据库遇到瓶颈说起
1.1 单库单表时代的“舒适区”是怎么被打破的
大多数项目起步阶段都是一台 MySQL 搞定所有读写,数据量小、并发低的时候,这套架构确实省心。但业务一旦起来,最先扛不住的不是 SQL 本身,而是数据库的底层能力:连接数打满、慢查询变多、磁盘 IO 持续飙高、存储空间不够用。这时候你可能想到升级硬件,但单机 MySQL 的写入能力和容量始终有物理上限,加配置也只是把“告警时间”往后拖。
真正走上分库分表这条路,通常是在单库已经优化过 SQL、加过缓存、做过读写分离之后,仍然扛不住数据量增长才做的决定。拆分思路有两种:一种是垂直拆分,把用户、订单、商品这些不同业务域拆到独立库;另一种是水平拆分,把同一张表的数据按某个字段分散到多个库多张表。水平拆分解决的是单表数据量过大、写入并发过高的问题,但代价也很明显——原本一条 SQL 能完成的事情,现在变成了应用层自己决定“这条数据应该去哪个库”。
我见过不少项目在这里踩坑。应用层写一个路由组件,按订单号哈希到不同库,刚开始没问题,但后续要扩容、要跨库统计、要做多库事务,维护成本直线上升。这也是 MyCat 这类中间件存在的意义:把“数据该去哪台数据库”这个路由逻辑从业务代码里剥离出来,集中放到配置层解决。
1.2 分库分表之后,应用的复杂度到底高在哪
如果你给订单表做了水平拆分,最简单的插入和查询就多了两层心智负担。插入时,你要根据订单号算出它属于哪个库;查询时,如果条件里没有订单号,你根本不知道去哪台机器找数据,只能全库扫描再合并。更麻烦的是跨库关联查询,原本一个 JOIN 就能查出来的订单和订单明细,拆分后可能落在不同物理库,JOIN 直接失效。
此外还有几个隐形问题:分布式环境下的主键不能再用数据库自增,否则多个分片可能生成相同 ID;一个事务里同时修改两个库的数据,普通数据库事务管不了;某些统计查询要在多个分片分别执行再合并结果,应用侧还得自己处理。这些复杂度不会因为你用了哪款组件就消失,MyCat 能做的,是把其中一部分标准化、透明化。
1.3 MyCat 帮你解决了哪些具体问题
从使用者的角度,MyCat 给我的第一印象是一个“透明代理”。应用连接它时,就像连接一台普通的 MySQL,不需要修改 JDBC 驱动,也不需要在 SQL 里写特殊分片语法。它能处理的主要问题包括:
- 分片路由:根据配置的分片字段和规则,自动决定 SQL 到哪个数据节点执行。
- 结果合并:不带分片键的查询会被广播到多个分片,MyCat 负责把结果集合并排序。
- 读写分离:在一组主从 MySQL 中,把读请求按配置分发到 readHost 或 writeHost。
- 全局序列:解决多分片下主键重复的问题,提供多种分布式 ID 生成策略。
- 运维管理:通过 9066 管理端口可以查看数据源状态、连接数、分片分布等。
但也要清醒一点:MyCat 不是万能的。它解决的是路由和读写分发问题,跨分片 JOIN、跨库事务这些分布式数据库通病,依然要靠业务模型设计来规避。学习第一天最好就把这个边界记住,后面能少踩很多坑。
2. 首次启动 MyCat 前,我建议你先弄懂的部署架构
2.1 三个配置文件完成“逻辑到物理”的映射
MyCat 的部署看起来复杂,其实就是在应用和真实 MySQL 之间多了一层 Java 进程。通过 8066 端口对外提供 MySQL 协议,内部再把 SQL 转发给真正存储数据的 MySQL 实例。在整个体系中,有三个配置文件承载了大部分核心逻辑:
- server.xml:配置连接用户、权限、系统参数、全局序列类型。
- schema.xml:定义逻辑库、逻辑表、数据节点和数据主机的映射关系。
- rule.xml:定义分片规则和对应的算法函数。
用一句话概括它们的关系:server.xml 决定了谁能连进来,schema.xml 决定了逻辑表对应哪些物理库,rule.xml 决定了数据怎么分。初学阶段最容易犯的错误就是只盯着其中一个文件改,实际上它们是一套整体,改错一个字段整条链路都会断。
2.2 安装前的环境准备
MyCat 本身是 Java 应用,所以环境里要先有 JDK,通常建议用 JDK 1.8。MySQL 的版本也不建议太老,至少支持你业务中要用到的 SQL 语法。我第一次安装时省了一个步骤,结果折腾了很久——就是没有确认 MySQL 的连接账号权限。MyCat 连后端数据库时用的不是根账号的业务账号,而是你在 schema.xml 的 dataHost 里配置的账号,这个账号需要有访问对应库表的权限。
部署过程不复杂,假设你拿到了二进制压缩包:
tar -zxvf MyCat-server-xxx.tar.gz cd mycat/bin ./mycat console这里我建议第一次启动用console而不是start,两者区别在于:console会把日志直接打到当前终端,如果配置有问题能立刻看到报错;start是后台运行,日志写到 logs 目录,启动失败时不太好定位原因。
2.3 启动验证、端口说明和常见“启动失败”
MyCat 成功后,对外暴露两个端口:8066 是业务连接端口,应用连接时使用;9066 是管理端口,用于查看运行状态和加载配置。用 MySQL 客户端直接连一下就知道是否成功:
mysql -uroot -p123456 -h127.0.0.1 -P8066 -DTESTDB如果连接报错或者看不到逻辑库,优先去 logs/mycat.log 翻异常。最常见的启动失败原因一般是三类:JAVA_HOME 没配置、schema.xml 格式写错、8066/9066 端口被占用。别急着怀疑配置复杂,先把基础环境排查完再说。
3. 配置文件详解:schema.xml 是理解 MyCat 的钥匙
3.1 schema 标签与逻辑表定义
schema.xml 是三大配置文件里最关键的一份,它定义了你“逻辑上看到的东西”和“物理上存在的东西”之间的对应关系。最外层是 schema 标签,name 属性就是应用连接的逻辑库名,后面在 8066 端口建连时指定的库名就是它。
<mycat:schema xmlns:mycat="http://io.mycat/"> <schema name="TESTDB" checkSQLschema="true" sqlMaxLimit="100"> <table name="user_info" dataNode="dn1,dn2,dn3,dn4" rule="mod-long" /> <table name="global_config" dataNode="dn1,dn2,dn3,dn4" type="global" /> </schema> </mycat:schema>这里有几个属性值得细看。checkSQLschema="true"表示如果应用传入的 SQL 里带着TESTDB.这种库名前缀,MyCat 会帮忙去掉,避免和实际物理库名冲突;sqlMaxLimit="100"是给没有 LIMIT 的查询自动加上限制,避免一次查出全表数据。这条属性在分片场景下很重要,因为不带分片键的全表查询会广播到所有分片,再合并起来的数据量可能非常夸张。
table标签的name是逻辑表名,dataNode表示这张表的数据落在哪些数据节点上,rule指定这张表使用哪条分片规则。特别注意:如果某个逻辑表不配置 rule,MyCat 会默认把数据放到第一个 dataNode 上,这在某些时候可以用来做“不分片的小表”,但如果你的目标是分片,漏配 rule 会导致数据全堆在一个节点。
3.2 dataNode:逻辑到物理库的“封装层”
dataNode 是理解 schema.xml 的中间层。它本身不直接写 IP 端口,而是通过 dataHost 找到真实 MySQL,再通过 database 属性指定具体物理库名。
<dataNode name="dn1" dataHost="host1" database="db_user_1" /> <dataNode name="dn2" dataHost="host1" database="db_user_2" /> <dataNode name="dn3" dataHost="host2" database="db_user_3" /> <dataNode name="dn4" dataHost="host2" database="db_user_4" />我建议给物理库命名时带上编号,例如db_user_1、db_user_2,这样将来排查数据分布、做数据迁移时一眼就能看出哪个节点对应哪个分片。如果图省事把四个 dataNode 都指向同一个物理库,分片的效果就完全体现不出来了。
3.3 dataHost、writeHost、readHost 与心跳检测
dataHost 用来定义一组真实的 MySQL 实例及连接参数。它里面可以配置一个或多个 writeHost,每个 writeHost 下又可以嵌套 readHost,也就是从库。MyCat 通过心跳检测来判断数据源是否存活。
<dataHost name="host1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1"> <heartbeat>select user()</heartbeat> <writeHost host="hostM1" url="192.168.1.10:3306" user="mycat" password="123456"> <readHost host="hostS1" url="192.168.1.11:3306" user="mycat" password="123456" weight="1" /> </writeHost> </dataHost>heartbeat通常配置一个非常轻量的查询,比如select user(),它会被定时执行,一旦失败,MyCat 会认为该数据源不可用,并根据 switchType 决定是否自动切换到备用 writeHost。balance控制读写分离的粒度,简单来说 balance=0 时读写都走 writeHost,balance=1 表示在读请求分配到 readHost 与 writeHost 之间做分发,balance=2 表示读请求只走 readHost。不同版本存在细节差异,一定要结合自己用的版本文档确认。
writeType=0意味着写操作固定发到第一个 writeHost,writeType=1则表示写操作随机分发到多个 writeHost。生产环境一般保持默认的 writeType=0,配合 switchType=1 让 MyCat 自动做主备切换,这样当主库故障时能更快接管。
3.4 一个最小可运行的 schema.xml 实例
把上面这些串起来,一个最小可运行的 schema.xml 大致长这样:
<mycat:schema xmlns:mycat="http://io.mycat/"> <schema name="TESTDB" checkSQLschema="true" sqlMaxLimit="100"> <table name="user_info" dataNode="dn1,dn2,dn3,dn4" rule="mod-long" /> <table name="global_config" dataNode="dn1,dn2,dn3,dn4" type="global" /> </schema> <dataNode name="dn1" dataHost="host1" database="db_user_1" /> <dataNode name="dn2" dataHost="host1" database="db_user_2" /> <dataNode name="dn3" dataHost="host2" database="db_user_3" /> <dataNode name="dn4" dataHost="host2" database="db_user_4" /> <dataHost name="host1" maxCon="100" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1"> <heartbeat>select user()</heartbeat> <writeHost host="hostM1" url="192.168.1.10:3306" user="mycat" password="123456" /> </dataHost> <dataHost name="host2" maxCon="100" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1"> <heartbeat>select user()</heartbeat> <writeHost host="hostM2" url="192.168.1.20:3306" user="mycat" password="123456" /> </dataHost> </mycat:schema>提示:我第一次运行时卡了很久,最后发现是两个 dataHost 的 MySQL 用户名密码不一致,导致部分分片连不上。这类问题要靠
show @@datasource在 9066 管理口查看,光看应用连接日志很容易忽略。
4. server.xml 和 rule.xml:权限、序列与分片规则怎么配合
4.1 server.xml 的用户配置
server.xml 负责配置 MyCat 对外暴露的用户账号,以及可以访问的逻辑库。它的作用很像 MySQL 自身的用户权限表,只不过这里是给逻辑库做的权限控制。
<user name="root" defaultAccount="true"> <property name="password">123456</property> <property name="schemas">TESTDB</property> <property name="readOnly">false</property> </user>schemas属性指定该用户能访问的逻辑库,多个库用逗号分隔。如果只配了 TESTDB,那用户连接后只能看到 TESTDB 下的逻辑表。readOnly设为 true 时,该用户只能执行查询类 SQL。这里的账号密码和物理 MySQL 的账号密码没有关系,它是应用连接 MyCat 时用的第一道身份凭证。
4.2 system 配置与全局序列
server.xml 中还有个 system 标签,里面包含很多系统级参数,其中初学者最关心的就是全局序列类型。分片表如果使用了数据库自增主键,在多个分片下一定会产生重复 ID,因为每个分片都从 1 开始自增。
<system> <property name="sequnceHandlerType">1</property> </system>注意一点:这个属性名在官方文档和网上大量示例里都是sequnceHandlerType,这不是我打错字,是历史遗留的拼写,照抄即可。取值含义大致是:0 表示本地文件方式,1 表示数据库方式,2 表示时间戳方式,3 表示 Zookeeper 分布式方式。
本地文件方式适合单机 MyCat 测试,多机部署时容易产生不一致;数据库方式需要在物理库中维护一张全局序列表,多个 MyCat 节点通过数据库记录获取 ID 区间,能避免重复;分布式场景更推荐 ZK 或时间戳方式。学习第一天不管是哪种,先保证插入数据不出现主键冲突,再去研究它们的性能和一致性差别。
4.3 rule.xml 里的 tableRule 与 function
rule.xml 的结构可以拆成两层:tableRule 定义“哪张表按哪个字段怎么分”,function 定义“这个怎么分到底用什么算法实现”。以最常见的取模规则为例:
<tableRule name="mod-long"> <rule> <columns>id</columns> <algorithm>mod-long</algorithm> </rule> </tableRule> <function name="mod-long" class="io.mycat.route.function.PartitionByMod"> <property name="count">4</property> </function>tableRule 里的columns是分片键,也就是路由时依赖的字段;algorithm对应 function 的名字。function 标签里的 class 是实现分片算法的 Java 类,count=4表示分成 4 个分片。当 MyCat 遇到user_info表的插入 SQL 时,会取出id值,交给PartitionByMod类计算id % 4,得到的结果就是数据节点的编号。
4.4 从配置到路由:一条 SQL 是这样找到分片的
把整个流程串起来看,执行一条 SQL 时 MyCat 内部大概经过这么几步:首先解析 SQL 拿到操作的表名和条件;然后根据 schema.xml 找到这张表对应的逻辑表配置,确认它绑定了哪些 dataNode 和哪条 rule;接着按 rule 里的分片键把路由值提取出来,交给 function 计算得到目标分片索引;最后把 SQL 分发到对应分片的物理库执行并返回结果。
如果查询条件里没有分片键,MyCat 就无法判断去哪台机器,只能把 SQL 广播到所有 dataNode 再合并结果。这也是理解分片操作最关键的一点:分片键是否出现在 SQL 中,直接决定这条 SQL 是“精准路由”还是“全分片扫描”。
5. 分片操作实战:从建表到跨分片查询
5.1 先确认逻辑表建到了哪个分片
启动 MyCat 后,用客户端连接 8066 端口,执行建表语句:
CREATE TABLE user_info ( id BIGINT NOT NULL, name VARCHAR(64), PRIMARY KEY(id) );MyCat 对建表语句的处理是“广播”给该表关联的所有 dataNode,因此四个分片后端都会出现这张表。你可以登录后端 MySQL 分别查一下,确认db_user_1到db_user_4中都有user_info表,这说明逻辑表到物理表的映射已经建立了。
接着插入几条数据验证路由:
INSERT INTO user_info(id, name) VALUES (1, 'A'); INSERT INTO user_info(id, name) VALUES (5, 'B');按照id % 4的规则,id=1 落到 dn1,id=5 因为 5 % 4 = 1 也落到 dn1。去后端对应库看看,你会发现这些数据没有平均分布,而是严格按取模结果落位。
5.2 路由规则验证:插入和查询分别走哪个分片
为了让你看得更直观,我列一个模 4 分片下的路由结果:
| 操作 | SQL | 路由结果 |
|---|---|---|
| 插入 | id=1 | 分片 1(1 % 4 = 1) |
| 插入 | id=4 | 分片 0(4 % 4 = 0) |
| 更新 | where id=7 | 分片 3(7 % 4 = 3) |
| 查询 | where id=7 | 分片 3 |
| 查询 | where name='A' | 全部分片广播再合并 |
前四行都能精准路由到单个分片,最后一行因为条件里没有 id,MyCat 没法判断,只能去四个分片分别执行再合并结果。数据量小的时候看起来没问题,一旦单表过千万,这种查询就是灾难。
5.3 全局表:跨分片 JOIN 的常规解法
分片之后,JOIN 成了比较麻烦的问题。如果两张表都在四个分片上,MyCat 基本上做不到跨分片 JOIN。最常见的解法之一是把“变化频率很低、各分片都需要”的表配置成全局表。
在 schema.xml 里给 logic 表加type="global"即可。MyCat 会把全局表复制到所有 dataNode 上,并且每次对全局表做 DML 时都会同步给所有分片。这样你在任意分片内做本地 JOIN 时,都能拿到完整数据。典型的适合场景是省市区字典、商品类目、配置参数这类低频变更的数据。全局表不能太大,否则每次写操作的成本会乘以分片数量。
5.4 ER 分片表:让子表和父表落到同一个分片
订单表和订单明细表是一对多的关系,通常需要 JOIN。如果两张表各自独立路由,订单在主分片,明细却跑到了别的分片,JOIN 就做不了。MyCat 的 ER 分片表可以解决这个问题,它让子表按照父表的分片键来路由。
<table name="order_info" dataNode="dn1,dn2,dn3,dn4" rule="mod-long"> <childTable name="order_item" joinKey="order_id" parentKey="id" /> </table>这样配置后,order_item 表插入时会根据 order_id 计算分片,保证和 order_info 的 id 落在同一个分片。查询订单及其明细时,MyCat 可以在单个分片内完成 JOIN,不用跨节点汇总。这是“按业务维度分片”的典型做法——分片键不是随意选的,而是围绕最核心的高频查询来设计。
5.5 管理端口能查到什么
在 9066 管理端口连接 MyCat 后,可以用几条命令快速确认运行状态:
show @@version; show @@database; show @@datasource; show @@datanode;show @@datasource会列出每个数据主机的连接信息和心跳状态,哪台 MySQL 连不上、哪个分片状态异常都能看到。排查“为什么某条数据查不出来”这种问题,我一般先用它确认后端数据源是否健康,再去看路由规则。
6. 分片规则选型:算法那么多,怎么选才不会翻车
6.1 常用分片算法一览
rule.xml 里可以配置很多分片算法,第一天不可能全弄懂,但至少要了解它们各自擅长什么,避免选错方向。下面这张表列了最常用的几种:
| 分片规则 | 实现原理 | 适合场景 | 扩容影响 |
|---|---|---|---|
| mod-long(取模) | 分片键对分片数取余 | 数字主键、数据分布均匀、等值查询为主 | 扩分片基本需要重新分布数据 |
| auto-sharding-long(范围) | 按数值区间映射分片 | 范围查询多、能预估数据分布 | 新增分片比较平滑 |
| sharding-by-intfile(枚举) | 按枚举值映射分片 | 地区、来源等有限枚举值 | 修改映射文件即可扩展 |
| sharding-by-murmur(一致性哈希) | 哈希环映射 | 字符串分片键、避免热点 | 扩容迁移成本相对可接受 |
| 按日期分片 | 按年月日映射 | 日志、时序数据、冷热分离 | 按时间自然扩展 |
范围分片和取模分片是新手最先接触的两种,也是文档里默认示例最多的两种。取模实现简单、数据分布均匀,但扩容时通常需要把已有数据重新打散;范围分片的管控直观、扩容容易,可一旦某个区间写入特别集中,又容易形成热点。没有绝对最好的规则,只有合适不合适。
6.2 取模和范围:最常用,但扩容要提前想
取模分片的优点在等值查询场景下非常明显,比如按用户 ID 取模,查询单个用户资料时精准路由一次,速度极快。缺点体现在扩容时,如果原来 4 个分片要扩到 8 个,4 % 4 = 0 的数据在 8 分片下会变成 4 % 8 = 4,大量数据要迁移。这也是为什么很多老项目宁可保持长期不扩分片,也不愿意动取模规则。
范围分片则适合有明显区间属性的数据。比如按订单号段划分,1 到 100 万在分片 1,100 万到 200 万在分片 2。这种规则扩容时只需要在规则里追加新区间,已有数据不用搬家。但要注意,如果业务集中在新的区间,流量会被压到新增分片上,反而形成新的热点,前期设计时要对数据增量有基本判断。
6.3 枚举与一致性哈希:处理“强隔离”和“字符串键”
sharding-by-intfile适合数据天然带有分类属性的场景,比如按省份编码分库。它通过一个映射文件把枚举值对应到分片号,各省数据物理隔离,查询指定省份的数据时路由非常干脆。缺点也很明显:如果某个省份的数据量比其他省大很多,分片就会倾斜。
sharding-by-murmur则适合分片键是字符串、且分布无法预估的场景。它使用一致性哈希算法把数据均匀散列到多个分片,可以减少热点,扩容时的数据迁移成本也比取模低。代价是范围查询基本不可能精准路由,只适合等值查询为主的业务。
6.4 按业务场景选择规则的实践建议
根据我实际跑过的项目经验,选分片规则时可以先问自己三个问题:分片键是什么类型?分片键会不会出现在绝大多数查询条件里?未来一年内有没有扩容计划?如果分片键是数字且查询以等值为主,mod-long 是最快上手的选项;如果查询里经常有范围条件,auto-sharding-long 或按日期分片更合适;如果真的无法选定分片键,或者分片键是随机字符串,murmur 是兜底方案。
分片规则的最佳实践不是“哪个算法高级选哪个”,而是“哪个分片键让业务查询尽量精准路由”。学习第一天能把这层关系想清楚,比背下所有算法名字有用得多。
7. 第一天踩过的坑:能避一个是一个
7.1 分片键没进查询条件,全分片扫描
刚配置好分片后,最容易犯的错误就是在代码里写类似WHERE name = 'A'的查询,不带分片键,结果 MyCat 把 SQL 广播到所有分片。测试环境数据量小,你根本感知不到问题,上线后才发现这个查询要遍历四台物理库再合并结果。解决思路不是去优化 SQL 让 MyCat 更快,而是从业务上约束:所有查询必须带分片键,或者为主键之外的访问路径建立映射关系。
7.2 全局序列、自增主键和重复 ID
分片表如果沿用 MySQL 的自增主键,等数据写到两个分片时,两边都会生成 ID=1 的记录,主键冲突如期而至。正确做法是使用 MyCat 的全局序列功能,或者让应用自己生成分布式 ID。注意,MyCat 的全局序列不是“MySQL 自增的替代品”,它更像一个发号器,你要在插入 SQL 里主动获取序列值,或者在应用里生成后再插入。
7.3 事务、JOIN 和分布式限制
分布式事务是 MyCat 这类中间件绕不过去的短板。普通事务在分片场景下只能保证单分片内的 ACID,跨分片的事务提交并不像本地事务那样可靠。所以设计业务时应有意识地把“一次事务要修改的数据”放在同一个分片里,比如按用户 ID 分片后,用户的订单、收货地址、购物车都尽量路由到同一个分片。万一确实需要跨分片操作,就自己引入应用层的补偿机制,而不是指望中间件替你搞定一切。
7.4 字符集、时区和端口这些看似无关的小配置
分片服务跑通后,检数据发现中文乱码,或者日期差 8 个小时,这类问题通常和分片路由无关,而是连接参数问题。应用连接 MyCat 时要在 JDBC URL 里显式指定characterEncoding=utf8,后端真实的 MySQL 连接也需要保持一致的字符集。时区问题一般出现在服务器时间标准不一致时,检查物理 MySQL 的time_zone、system_time_zone以及 JVM 默认时区,三个地方最好统一。
7.5 再看看日志,别被“启动成功”骗了
第一次启动 MyCat 时,如果看到端口监听就以为万事大吉,等到业务连接时才暴露问题,排查成本会高很多。比较好的习惯是启动后立刻用管理端口查询数据源状态,并去后端 MySQL 看实际连接情况。一旦出现连接失败,优先翻logs/mycat.log,里面会直接告诉你配置文件的哪个标签写错了、哪台数据主机连不上。把这些验证动作变成习惯,后面处理生产问题会从容很多。
我在实际使用中最大的体会是:学习 MyCat 的第一天,不要试图把所有分片规则都配一遍,更不要一上来就上最复杂的集群方案。先把一个 mod-long 规则从建表、插数据、查数据、看分片这四步跑通,确认你能准确预测每条 SQL 到底走了哪个分片,再逐步引入全局表、ER 分片和读写分离。分库分表这件事,本质上是业务模型的取舍,配置文件只是把你做的取舍固化下来,理解这一点,后面的路才会越走越顺。