
最近在一个老系统改造的项目里我又把 MyCAT 翻了出来。起因很朴素单库单表的数据量已经压到几千万行几个核心查询的响应时间从几百毫秒涨到两三秒但整套业务的预算和团队规模决定了短期内不可能去搞一套重型分布式数据库。于是老办法重新上场——在应用和 MySQL 之间插一层数据库中间件用分库分表把压力摊开。这次用的就是Mycat-server-1.6.7.6-release-windows-linux.rar这个跨平台发布包。这个包说白了就是一个 Java 写的数据库中间件的运行时分发包解压即用Windows 和 Linux 共用同一套 jar只在启动脚本和服务封装上做了区分。它的作用是让应用像连一个普通 MySQL 一样连上 MyCAT而 MyCAT 在背后把一条 SQL 拆解、路由、下发到一个或多个真实的 MySQL 实例再把结果合并回来。适合谁看手里已经有存量 MySQL、数据量开始顶到天花板、又不想大动应用代码的开发和运维以及想搞明白分库分表中间件到底怎么落地、不想只看概念图的人。下面我按自己实际部署的顺序来讲从包里的东西讲起到配置、启动、验证、排错尽量把每一步的为什么也说清楚。1. 压缩包拆解一个rar里塞了哪些东西各自管什么1.1 解压后的目录清单与文件职责把这个 rar 解压出来根目录名一般是mycat里面固定是这么几个文件夹bin、conf、lib、logs部分版本还会带catlet、algorithm、test。别小看这个目录结构它基本决定了你后面所有操作的位置。bin目录放的是启动入口。Windows 下是startup.bat、mycat.batLinux 下是startup.sh、mycat另外还有一套wrapper相关的可执行文件。这里的mycat脚本是给运维用的统一入口mycat start、mycat stop、mycat restart、mycat status都走它内部其实是调用 Java Service Wrapper 去拉起 JVM 进程。conf目录是整个中间件的大脑也是你后面改得最多的地方。核心三件套是server.xml、schema.xml、rule.xml分别管用户和逻辑库、逻辑表和真实数据节点的映射、分片规则。此外还有wrapper.confJVM 参数、日志路径、log4j2.xml日志级别、sequence_conf.properties全局序列的本地配置、partition-hash-int.txt这类分片枚举文件。这些文件里除三件套之外最容易被忽略但最要命的是wrapper.conf。lib目录是依赖集合里面有一个主 jar名字带版本号加上几十个第三方 jar。这里有个坑如果你自己去替换某个依赖的版本很容易引起类冲突导致启动时报NoSuchMethodError或者ClassNotFoundException。我的建议是除非明确知道在干什么否则lib目录一个字节都别动。logs目录在首次启动前是空的启动之后会生成wrapper.log和mycat.log。前者记录 JVM 层面和 wrapper 层面的启动信息后者是业务日志。排错时先看wrapper.log因为进程起不来的时候mycat.log根本不会产生。1.2 跨平台包的差异不在 jar在启动壳子很多人拿到windows-linux这种二合一包会疑惑同一个包怎么在两个系统上跑答案很简单lib目录里的 jar 是平台无关的字节码真正的差异全在bin和conf/wrapper.conf里。Windows 侧依赖.bat脚本去设置JAVA_HOME、拼接 classpath、传递 JVM 参数Linux 侧依赖.sh脚本用nohup或者 wrapper 的原生二进制去拉起进程。wrapper.conf里也有差异比如路径分隔符、是否注册成系统服务、日志文件的换行处理。这也是为什么你从 Linux 机器上直接scp一份解压好的目录到 Windows 上经常会启动失败——脚本的换行符和路径写法对不上。我个人的习惯是把 rar 原包留一份在版本库里做基线每台机器上单独解压不要跨平台拷贝解压后的目录。这个习惯帮我省过好几次莫名其妙的启动问题。1.3 解压后的第一个动作确认包是完整的在动手改配置之前先做三件确认。第一确认压缩包在传输过程中没有损坏解压时没有报 CRC 错误。第二确认lib目录下的主 jar 能被正常打开可以用jar tf或者随便一个解压工具看一眼能不能列出内容。第三确认 JDK 版本。MyCAT 1.6.x 系列对 JDK 的要求是 1.7 及以上但实践中最稳的是 JDK 8。用 JDK 11 及以上会遇到一些反射相关的警告甚至报错因为中间件内部用了不少老写法。我试过在 JDK 17 上跑 1.6.7.6能起来但日志里会刷一堆Illegal reflective access而且个别分片算法在高并发下有异常。这种老版本中间件配 JDK 8 是最省心的组合。提示不要用系统自带的精简版 JRE 去跑wrapper 启动时依赖一些 JDK 才有的工具类用 JRE 可能出现启动脚本执行到一半就退出的情况。2. 部署前的架构定位MyCAT不是装完就能用的中间件2.1 逻辑库、逻辑表、dataNode、dataHost的四层映射这是我最想强调的一点。MyCAT 跟 Nginx 那种装完配个 upstream 就完事的组件不一样它要求你在脑子里先有一张映射图配置只是把这张图翻译成 XML。这张图有四个层次从外到内依次是逻辑库schema、逻辑表table、数据节点dataNode、数据主机dataHost。逻辑库是应用看到的库名比如ORDER_DB应用连上 8066 端口之后use ORDER_DB就能用。逻辑表是应用看到的表名比如t_order。数据节点是某个真实库里的某个 schema比如dn1对应order_db_1这个库。数据主机描述的是这个真实库在哪台机器上、用什么账号密码连、连接池开多大。一条 SQL 进来之后MyCAT 先根据逻辑库和逻辑表找到对应的 dataNode 列表再根据分片规则和 SQL 里的分片键值决定落到哪几个 dataNode最后通过 dataHost 里的连接池把 SQL 下发到真实 MySQL。这条链路任何一环配错表现都不一样逻辑表没配报表不存在dataNode 配错报连不上库dataHost 配错报连接超时。搞清楚这个对应关系排错时就能快速定位到是哪一层的问题。2.2 三个核心配置文件的分工边界server.xml管的是谁能连、能连哪个逻辑库。里面定义用户名、密码、可访问的 schema 列表、是否只读。还有几个全局参数藏在这里比如sequnceHandlerType决定全局序列用哪种实现方式defaultSqlParser决定 SQL 解析器1.6 里通常用 druid 解析器。schema.xml管的是逻辑对象到物理对象的映射是三个文件里最长的。里面分三段schema段定义逻辑库和逻辑表dataNode段定义数据节点dataHost段定义物理连接。这里的balance、writeType、switchType三个属性决定了读写分离和故障切换的行为配错了不会报错但流量走向会完全不是你想要的。rule.xml管的是怎么分。里面分tableRule和function两部分前者把表和分片算法绑定后者定义算法的具体参数。常见的算法有取模PartitionByMod、枚举PartitionByFileMap、范围PartitionByRangeMod、一致性哈希PartitionByMurmurHash。这三个文件的加载顺序是 server → schema → rule任何一个有语法错误整个启动就会失败。所以改配置之后不要直接重启生产先在测试环境跑一遍。2.3 1.6.7.6这个版本在系列里的位置1.6 系列是 MyCAT 用得最广的一条线特点是稳定、文档多、社区案例多缺点是 SQL 兼容性有边界、跨分片 join 能力有限。1.6.7.6 属于这条线的后期维护版本相比早期版本在连接池回收、心跳检测、部分 SQL 解析上做了修补。选这个版本的实际理由通常是手上有一批老项目就用的 1.6升级到 2.x 要改配置和驱动风险收益不划算。我自己也是这个逻辑——新项目如果要做分库分表可以考虑更新的方案存量系统续命1.6.7.6 是个稳妥选择。但要有心理预期它不会帮你解决所有问题尤其是复杂子查询和跨库事务该在应用层拆的还得拆。3. Windows端实操startup.bat之外你必须改的三处地方3.1 JDK与JAVA_HOME的确认顺序Windows 上部署最容易翻车的地方不是配置是环境。startup.bat里会去读JAVA_HOME如果这个变量指向的是一个路径带空格的目录比如默认装在Program Files下脚本里的引号处理偶尔会出问题。我的做法是把 JDK 装到一个不带空格、不带中文的短路径下比如D:\jdk8然后设置系统级JAVA_HOME。设置完之后必须开一个新的命令行窗口去验证因为环境变量的修改对已经打开的窗口不生效。验证命令是echo %JAVA_HOME%和java -version。两步都要看光有JAVA_HOME但PATH里没有%JAVA_HOME%\bin一样会出问题。还有一个细节如果机器上装了多个 JDKPATH里的顺序决定java -version的输出。而startup.bat用的是JAVA_HOME拼出来的路径两者可能不是同一个 JDK。这种脚本用一个、命令行用另一个的情况排查起来非常费劲建议一开始就统一。3.2 端口规划与冲突检查MyCAT 默认开两个端口8066 是数据端口应用连这个9066 是管理端口用来执行show 系列的管理命令。这两个端口在server.xml里可以通过property nameserverPort和property namemanagerPort改。Windows 上先做冲突检查netstat -ano | findstr 8066如果有输出记下 PID 再去任务管理器里看是哪个进程。我遇到过一次8066 被一个残留的旧 MyCAT 进程占着新进程启动报端口绑定失败但wrapper.log里的提示非常隐晦只写了Address already in use没说是哪个端口。后来是逐个端口排查才定位的。端口规划上建议跟运维约定一个固定段比如 8066/9066 给主实例备实例用 8067/9067。不要用随机端口后面配防火墙和监控的时候会很痛苦。3.3 启动脚本的执行逻辑与闪退排查路径双击startup.bat之后窗口一闪而过是 Windows 上最常见的现象。原因通常是 JVM 参数过大导致内存分配失败或者配置文件语法错误导致主类启动即退出。排查顺序我总结成这么几步。第一步打开一个命令行窗口cd到bin目录手动执行startup.bat这样窗口不会关闭能看到报错。第二步如果脚本本身就没输出有用信息去看logs\wrapper.log文件末尾一般会有Exception或者ERROR字样。第三步如果是内存相关的报错去conf\wrapper.conf里改wrapper.java.additional那段把-Xmx调小。默认配置可能给了 2G 或者更大在 32 位 JVM 或者内存紧张的机器上直接分配失败。wrapper.conf里还有一行wrapper.java.command它指向 java 可执行文件的路径。如果这一行是相对路径或者写死的绝对路径跟实际 JDK 位置不一致启动也会失败。这一处建议直接改成%JAVA_HOME%\bin\java由脚本注入或者写成明确的绝对路径。注意改完wrapper.conf一定要保存成 ANSI 或者 UTF-8 无 BOM 格式。带 BOM 的 UTF-8 会让 wrapper 读取配置的第一个 key 时多出几个不可见字符表现就是某个配置项怎么改都不生效。4. Linux端实操从解压目录到systemd托管服务4.1 目录规划与运行用户Linux 上我不建议把 MyCAT 解压到/root下或者直接用 root 跑。标准做法是建一个专用用户比如mycat把目录放到/opt/mycat然后chown -R mycat:mycat /opt/mycat。为什么强调这件事因为 MyCAT 运行期间会往logs目录写文件还会生成临时文件。如果用 root 跑起来后续想改成普通用户就会遇到权限问题日志文件属主不对进程起不来。反过来如果先用普通用户跑了后面要用 root 手工改配置改完记得把属主改回去。解压命令在 Linux 上要注意rar 格式不是默认支持的需要先装解压工具。解压之后执行chmod x bin/*.sh bin/mycat否则脚本没有执行权限./mycat start会报 permission denied。4.2 用systemd托管进程MyCAT 自带的mycat start是基于 wrapper 的能后台运行但机器重启之后不会自动拉起进程挂了也不会自动重启。生产环境我会写一个 systemd unit 来托管。unit 文件的关键几行是这样的思路Typeforking因为 wrapper 启动后会 fork 出真正的 JVM 进程ExecStart指向mycat startExecStop指向mycat stopPIDFile指向 wrapper 生成的 pid 文件User和Group指定运行用户Restarton-failure配合一个合理的RestartSec。这里有个细节Typeforking必须配合PIDFile才能让 systemd 正确追踪进程否则systemctl status会显示启动超时。另一个细节是LimitNOFILE。MyCAT 是连接密集型组件每个 dataHost 的连接池乘以实例数再加上应用侧的连接文件描述符消耗很快。默认的 1024 很容易不够建议设到 65535。4.3 日志落盘与轮转策略logs目录下的mycat.log在高并发下增长非常快一天几个 G 不夸张。默认的日志配置如果不做轮转磁盘很快会被吃满。我的做法是双管齐下一方面在log4j2.xml里配置按大小和时间双触发的滚动策略保留最近若干天的文件另一方面在系统层面用日志轮转工具做兜底防止配置文件被误改之后日志失控。这两层不冲突反而互补。日志级别上生产环境用 INFO 就够DEBUG 只在排特定问题时临时打开而且要有计划地关回去。我见过有环境开着 DEBUG 跑了半个月日志把磁盘写满最后是数据库连接异常才被发现排查方向一开始全跑偏了。5. 配置落地用一张订单表验证分片是否真的生效5.1 server.xml里定义用户和逻辑库先定义一个应用能用的账号和它可见的逻辑库。示例结构如下user nameapp_user defaultAccounttrue property namepasswordStrongPass_2024/property property nameschemasORDER_DB/property property namereadOnlyfalse/property /user这里schemas写的是逻辑库名不是真实库名。多个逻辑库用逗号分隔。readOnly设为 true 的话这个账号只能执行查询写操作会被中间件拦下来。同一个文件里还有几个全局参数值得关注。sequnceHandlerType决定全局序列的实现方式取值 0 是本地文件、1 是数据库、2 是时间戳、3 是分布式协调服务。做订单号这种全局唯一 ID我一般选 1也就是在某个物理库里建一张序列表可靠性和性能比较平衡。5.2 schema.xml里绑定逻辑表和物理节点这一段是配置的核心。先定义逻辑库和逻辑表schema nameORDER_DB checkSQLschemafalse sqlMaxLimit100 table namet_order dataNodedn1,dn2 rulemod-long / table namet_order_item dataNodedn1,dn2 rulemod-long / /schemasqlMaxLimit这个属性很关键它会给没有 limit 的查询自动加上一个上限防止应用一个select *把几百万行全拉回来。100 只是个示例值按业务调整。接着定义 dataNodedataNode namedn1 dataHosthost1 databaseorder_db_1 / dataNode namedn2 dataHosthost1 databaseorder_db_2 /最后定义 dataHostdataHost namehost1 maxCon500 minCon10 balance0 writeType0 dbTypemysql dbDrivernative switchType1 slaveThreshold100 heartbeatselect user()/heartbeat writeHost hosthostM1 url10.0.0.11:3306 userdb_user passworddb_pass / /dataHostbalance0表示不开启读写分离所有请求走写节点switchType1表示写节点挂掉后自动切换到备用写节点需要配writeHost里的从节点。这两个属性的取值组合很多配之前一定要确认清楚业务能接受什么样的数据一致性。maxCon是单个 dataHost 的最大连接数这个值要和后端 MySQL 的max_connections对齐。比如后端 MySQL 设了 1000你有两个 dataHost 指向同一台机器每个 500加起来正好 1000再算上其他应用的连接就会超。5.3 rule.xml里落地取模分片tableRule namemod-long rule columnsorder_id/columns algorithmmod-long/algorithm /rule /tableRule function namemod-long classio.mycat.route.function.PartitionByMod property namecount2/property /functioncount的值必须跟这个表绑定的 dataNode 数量完全一致。绑了dn1,dn2两个节点count就写 2写成 4 会出现路由到不存在的节点报cant find datanode。取模分片的优点是数据分布均匀、写入无热点缺点也很明显范围查询会打到所有分片扩容时数据迁移成本高。订单表用户量大的场景下我通常用用户 ID 取模这样同一个用户的订单落在同一个分片查我的订单这类高频 SQL 只打一个节点。5.4 从连接验证到分片验证配置改完启动之后先验证连通性用 MySQL 客户端连 8066 端口账号密码就是server.xml里定义的。连上之后show databases能看到逻辑库名说明第一层通了。第二步验证表映射use ORDER_DB然后show tables能看到t_order说明 schema.xml 的 table 配置被加载了。第三步才是验证分片是否真的生效。往t_order里插几条order_id不同的数据然后去两个物理库分别查一遍看数据是不是按取模规则分开了。这一步最容易暴露问题比如分片键类型不匹配、字段名拼错、count 数量不对。还有一个更直接的验证方式登录管理端口 9066执行show datanode看节点状态执行explain后面跟你的查询语句会输出这条 SQL 被路由到了哪些 dataNode。这个命令在调试分片规则时非常好用比猜要快得多。6. 排错链路从启动闪退到路由异常的完整排查顺序6.1 进程起来了但端口不通这种表现是mycat status显示运行中但应用连 8066 要么超时要么拒绝。原因通常有三类防火墙没放行、监听地址绑到了本地回环、端口被配置改掉了。先看监听状态ss -lntp | grep 8066如果只看到127.0.0.1:8066说明绑的是本地地址外部连不进来。这种情况要检查启动参数里有没有wrapper.java.additional把绑定地址写死了或者配置文件里有没有相关设置。再看防火墙。这一步经常被跳过尤其是云主机安全组和系统防火墙是两层两个都要放行。我吃过一次亏系统防火墙关了但安全组没加规则排查了半天以为是配置问题。6.2 配置语法错误导致的启动失败XML 配置最坑的地方是错误提示不精确。少一个闭合标签或者某个属性名拼错报错信息可能只说解析失败不告诉你在哪一行。排查方法是分段验证。先把schema.xml里不相关的 table 注释掉只留一个最小配置启动看能不能过。能过就说明问题在被注释掉的那部分逐步放开二分定位。这个方法笨但有效比盯着几千行 XML 找错字快得多。还有一个高频错误是属性值用了中文标点。从文档里复制配置的时候引号、逗号经常被词法分析成全角字符肉眼看不出来解析器直接报错。建议改完配置在编辑器里开一下不可见字符显示或者用工具做一次校验。6.3 分片键选错引发的后续麻烦分片键的选择是一次性决策改起来代价极大。我见过几个典型失误。第一个是用自增主键做分片键。自增 ID 本身是连续递增的取模之后分布虽然均匀但业务上高频的查询往往是按用户维度而不是按 ID 维度导致每次查询都要扫所有分片。第二个是用时间字段做分片键。按天或按月分片热点会集中在新分片上写入压力全压在一个节点其他节点闲着。第三个是用可空字段做分片键。值为 null 的时候路由行为不确定可能落到默认分片也可能报错排查起来非常隐蔽。我的经验是分片键要选业务查询里出现频率最高、且值分布足够离散的字段。订单表用用户 ID日志表用设备 ID 或租户 ID这些字段的特征是查询必带、值域大、不随时间聚集。6.4 全局自增序列的三种实现取舍分库分表之后数据库自增主键就不可靠了——两个物理库各自从 1 开始会撞。这时候要用 MyCAT 的全局序列。本地文件方式的原理是在配置文件里维护一个当前值和步长启动时读进内存用完再写回。优点是配置简单缺点是多个 MyCAT 实例之间不共享做主备或者多活的时候会重复。数据库方式的原理是在某个物理库里建一张序列表每次取号都去更新那一行。优点是全局唯一缺点是多一次数据库交互高并发下那张表会成为瓶颈所以一般会批量取号一次取一段缓存在内存里。时间戳方式就是拼时间加机器标识加序列不依赖存储但有单机时钟回拨的风险。实际选型上如果只有一个 MyCAT 实例本地文件够用多实例部署数据库方式最稳对 ID 连续性没要求的场景时间戳方式最省事。6.5 跨分片查询的能力边界这是使用 MyCAT 时最需要提前建立心理预期的地方。单表按分片键查询性能很好带上分片键的 join如果是 ER 分片子表跟父表用同一个分片键和规则也能路由到单节点但跨分片的 join、子查询、聚合排序行为就不那么理想了。比如order by加limit的跨分片分页MyCAT 的做法是把每个分片的 Top N 拉回来在内存里归并再取最终的 N 条。数据量小没问题数据量大时内存和网络开销都很可观。count(*)会下发到每个分片再求和这个还好。但group by之后的having过滤以及多表关联的复杂查询兼容性就要打个问号。我的处理原则是能在应用层拆的 SQL 就在应用层拆不要指望中间件把复杂查询优化得很好。比如报表类的跨维度统计直接走单独的汇总表或者离线计算不要压在业务库的中间件上。7. 版本与下载怎么确认手里的包是能用且该用的那个7.1 校验包的完整性比找下载地址更重要拿到一个中间件的发布包第一件事不是急着解压启动是校验。校验分两层文件层面和内容层面。文件层面看压缩包大小是否与发布说明一致解压过程有没有报错。内容层面看lib目录下的主 jar 版本号是否和目录名一致有时候包名写的是 1.6.7.6里面的 jar 是别的版本这种包一定要扔掉。如果是团队协作我建议把基线包放到内部制品库记录哈希值每次部署从这个源取不要每个人各自去网上下载。这样能保证所有环境跑的是同一个东西出问题的时候排除掉版本不一致这个变量。7.2 版本选择要跟驱动和数据库对齐MyCAT 1.6.x 对 MySQL 8 的支持是有限的。MySQL 8 默认的密码插件变了老版本的 JDBC 驱动连不上需要在 MySQL 侧调整认证插件或者升级lib目录里的驱动 jar。但升级驱动又有类冲突风险是个两难。如果你的后端是 MySQL 5.71.6.7.6 用起来基本没有兼容性问题。如果是 MySQL 8部署前一定要在测试环境把连接打通别等到上线当天才发现连不上。应用侧的 JDBC 驱动版本也要注意驱动版本太低会不认中间件返回的一些元数据。7.3 保留回滚能力中间件这层一旦出问题影响面是所有连它的应用。所以每次改配置或者换版本之前把整个conf目录打包备份一份命名带上日期和变更说明。回滚的时候直接换回备份目录重启比逐行改回去快得多。我自己的习惯是在conf同级建一个conf_bak目录每次变更前执行一次带时间戳的拷贝。这个习惯在两次紧急回滚里救过场都是因为某个配置项改错导致部分 SQL 路由异常换回备份五分钟恢复。最后分享一个我在实际部署里体会最深的小技巧不要把 MyCAT 当成一个透明的数据库来用。它的透明是有边界的分片键、SQL 写法、事务范围这三件事应用侧必须心里有数。部署的时候多花半天时间把explain和show datanode这两个命令用熟后面排查路由问题的效率会完全不一样。