ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

eleme项目上线实战:LVS+Mycat+MySQL高并发架构设计与部署

2026/9/26 15:07:00 拓冰建站 浏览量
eleme项目上线实战:LVS+Mycat+MySQL高并发架构设计与部署 1. 从零到一eleme 项目上线的整体架构设计1.1 为什么选择这套技术组合eleme 这类外卖平台的项目上线本质上要解决三个核心问题高并发请求的分发、数据库的读写压力、以及业务代码的稳定运行。我拿到这个项目的时候第一反应不是急着装环境而是先把架构图在脑子里过一遍。因为上线一个项目最怕的就是“边做边想”到最后发现某个环节的选型跟整体不搭返工的成本远比前期规划高得多。这套组合里LVS 负责四层负载均衡它工作在传输层性能极高单台机器扛几十万并发连接不是问题。为什么不用 Nginx 做四层因为 Nginx 虽然也能做四层转发但在极端高并发场景下LVS 的内核态转发效率明显更优。LVS 把流量分发到后端的 Java 应用服务器集群Java 层处理业务逻辑再往下走就是 MySQL 数据库层。当单库单表扛不住的时候Mycat 作为数据库中间件介入做分库分表和数据路由。这个分层思路很清晰流量层用 LVS 扛业务层用 Java 集群扛数据层用 Mycat MySQL 扛。每一层都可以独立扩展不会因为某一层成为瓶颈就拖垮整个系统。1.2 各组件在 eleme 项目中的角色定位我习惯把每个组件比作一个岗位。LVS 是“前台接待”所有请求先到它这里它负责把客人分配到不同的服务窗口。Java 应用是“业务员”真正处理订单、用户、商家这些逻辑。Mycat 是“档案管理员”它知道哪份数据存在哪个柜子里你查数据的时候它帮你找到对应的库。MySQL 则是“仓库”数据最终落在这里。具体到 eleme 的业务场景用户下单是一个写操作会经过 LVS 转发到某台 Java 服务器Java 服务器通过 Mycat 路由到对应的 MySQL 分片用户查询订单列表是读操作同样走这条链路但可能命中不同的分片。商家端查看订单、骑手端更新配送状态这些请求也走同一套架构。所以这套架构的通用性很强不是只为某一个功能设计的。1.3 上线前的容量估算与资源规划上线之前必须做容量估算否则你根本不知道要准备几台机器。我的经验是按峰值 QPS 倒推。假设 eleme 项目高峰期每秒有 5000 个请求每台 Java 服务器能处理 500 QPS那至少需要 10 台 Java 服务器。LVS 单台能扛 10 万并发所以两台做主备就够了。MySQL 这边假设每天新增 100 万条订单单表超过 500 万行查询就会明显变慢那就要考虑分表比如按用户 ID 哈希分成 16 张表分布在 4 个库上。注意容量估算不是拍脑袋要结合压测数据。我一般会用 JMeter 先压单台 Java 服务拿到真实的 QPS 和响应时间再乘以机器数量。MySQL 的写入能力也要压用 sysbench 跑一遍心里才有底。资源规划还包括网络带宽。LVS 到 Java 服务器之间是内网通信带宽一般够用但 Java 到 MySQL 的查询如果返回大量数据内网带宽也可能成为瓶颈。我遇到过因为一个慢查询返回了几万行数据把内网带宽打满的情况所以 SQL 优化和分页是必须的。2. 环境准备MySQL、Java、LVS 的安装与配置2.1 MySQL 安装与关键参数调优MySQL 的安装方式有很多种我推荐用官方 YUM 源或者二进制包安装不建议用系统自带的版本因为版本太老很多新特性用不上。以 CentOS 为例先下载 MySQL 8.0 的 YUM 源包安装后直接yum install mysql-server。安装完成后第一件事是运行mysqld --initialize初始化数据目录然后启动服务。# 下载并安装 MySQL YUM 源 wget https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm rpm -ivh mysql80-community-release-el7-3.noarch.rpm yum install mysql-community-server -y # 启动 MySQL systemctl start mysqld systemctl enable mysqld # 查看初始密码 grep temporary password /var/log/mysqld.log安装完之后必须调优的几个参数参数名推荐值说明innodb_buffer_pool_size物理内存的 60%-70%这是 InnoDB 最重要的参数缓存数据和索引max_connections1000-2000根据 Java 连接池大小调整别设太大innodb_flush_log_at_trx_commit2兼顾性能和安全1 最安全但慢sync_binlog100减少磁盘同步频率提升写入性能character_set_serverutf8mb4支持 emojieleme 的商家名称可能有特殊字符我踩过的一个坑innodb_buffer_pool_size 设得太大导致系统 OOM。有一次在一台 16G 内存的机器上设了 14G结果 MySQL 进程被系统 kill 了。后来改成 10G留出足够内存给操作系统和其他进程就稳定了。2.2 Java 环境搭建与版本选择Java 环境搭建看起来简单但版本选择很关键。eleme 项目我用的JDK 17因为它是 LTS 版本长期支持而且性能比 JDK 8 有明显提升。安装方式推荐用包管理器比如yum install java-17-openjdk-devel这样环境变量都不用自己配。# 安装 JDK 17 yum install java-17-openjdk-devel -y # 验证版本 java -version javac -version如果你遇到警告: 源发行版 17 需要目标发行版 17这个报错说明编译时的 source 和 target 版本不一致。解决办法是在 Maven 的pom.xml里明确指定properties maven.compiler.source17/maven.compiler.source maven.compiler.target17/maven.compiler.target /properties或者用--release 17参数编译。这个坑我在多个项目里都遇到过尤其是团队里有人用 JDK 8 有人用 JDK 17 的时候统一版本是第一步。2.3 LVS 负载均衡的部署与模式选择LVS 有三种模式NAT、DR、TUN。eleme 项目我用的是DR 模式因为它的性能最好LVS 只修改 MAC 地址不修改 IP 包响应数据直接从 Real Server 返回给客户端不经过 LVS。NAT 模式适合小规模TUN 模式适合跨机房但配置复杂。DR 模式的配置步骤# 在 LVS 服务器上安装 ipvsadm yum install ipvsadm -y # 添加虚拟服务 ipvsadm -A -t 192.168.1.100:80 -s rr # 添加 Real Server ipvsadm -a -t 192.168.1.100:80 -r 192.168.1.101:80 -g ipvsadm -a -t 192.168.1.100:80 -r 192.168.1.102:80 -g # 查看配置 ipvsadm -L -nReal Server 上需要抑制 ARP 响应否则会出现 IP 冲突。具体做法是修改/etc/sysctl.confnet.ipv4.conf.all.arp_ignore 1 net.ipv4.conf.all.arp_announce 2 net.ipv4.conf.lo.arp_ignore 1 net.ipv4.conf.lo.arp_announce 2然后在 lo 接口上绑定 VIPifconfig lo:0 192.168.1.100 netmask 255.255.255.255 up提示LVS 的调度算法我选的是rr轮询简单够用。如果后端服务器性能不一致可以用wrr加权轮询。lc最少连接适合长连接场景比如 WebSocket。3. Mycat 分库分表与数据路由实战3.1 为什么需要 Mycat 以及分片策略设计单库单表在数据量达到千万级别时查询性能会急剧下降。eleme 的订单表每天新增百万级数据一个月就是三千万三个月就上亿了。这时候必须分库分表。Mycat 的核心价值在于对应用透明Java 代码不需要知道数据存在哪个库哪张表只需要像操作单库一样写 SQLMycat 帮你路由。分片策略我选的是按用户 ID 哈希取模。为什么不用范围分片因为范围分片容易导致热点比如按时间分片最新的数据总是写在同一张表上。哈希分片能把写入均匀分散到各个分片。具体配置在schema.xml里table nameorders dataNodedn1,dn2,dn3,dn4 rulemod-long /rulemod-long表示按分片字段取模。分片字段在rule.xml里定义function namemod-long classio.mycat.route.function.PartitionByMod property namecount4/property /function这里count4表示分成 4 个分片。如果数据量继续增长可以改成 8 或 16但扩容是个麻烦事需要数据迁移。所以我建议一开始就预估好宁可多分几个片。3.2 Mycat 安装与核心配置文件详解Mycat 的安装很简单下载解压就能用依赖 Java 环境。关键是三个配置文件server.xml、schema.xml、rule.xml。server.xml配置 Mycat 的用户和权限user nameroot defaultAccounttrue property namepassword123456/property property nameschemaseleme_db/property /userschema.xml配置逻辑库、逻辑表、数据节点schema nameeleme_db checkSQLschemafalse sqlMaxLimit100 table nameorders dataNodedn1,dn2,dn3,dn4 rulemod-long / table nameusers dataNodedn1,dn2,dn3,dn4 rulemod-long / /schema dataNode namedn1 dataHosthost1 databaseeleme_db1 / dataNode namedn2 dataHosthost1 databaseeleme_db2 / dataNode namedn3 dataHosthost2 databaseeleme_db3 / dataNode namedn4 dataHosthost2 databaseeleme_db4 /dataHost配置后端 MySQL 的连接信息dataHost namehost1 maxCon1000 minCon10 balance1 writeType0 dbTypemysql dbDrivernative heartbeatselect user()/heartbeat writeHost hosthostM1 url192.168.1.201:3306 userroot password123456 readHost hosthostS1 url192.168.1.202:3306 userroot password123456 / /writeHost /dataHost注意balance1表示读操作随机分发到 readHost写操作走 writeHost。这是读写分离的基本配置。如果读压力不大可以设balance0所有操作都走 writeHost。3.3 分片后的查询优化与注意事项分片之后跨分片的查询会变慢。比如select * from orders order by create_time desc limit 10Mycat 需要去每个分片查 10 条然后在内存里排序取前 10 条。如果分片多这个开销很大。解决办法是尽量带上分片字段比如where user_id 123这样 Mycat 能直接定位到某个分片。另一个坑是全局表。像城市、分类这种小表每个分片都需要可以配置成全局表Mycat 会自动同步到所有分片table namecities dataNodedn1,dn2,dn3,dn4 typeglobal /还有ER 分片比如订单和订单详情可以配置成父子表保证同一订单的数据落在同一分片table nameorders dataNodedn1,dn2,dn3,dn4 rulemod-long childTable nameorder_items joinKeyorder_id parentKeyid / /table这样查询订单详情时Mycat 知道去哪个分片找不用广播。4. Java 应用层开发与上线部署4.1 数据库连接池配置与 JDBC 优化Java 连接 MySQL 用 JDBC但直接使用 JDBC 效率低必须用连接池。我推荐HikariCP它的性能最好配置也简单。Spring Boot 项目里默认就是 HikariCP。spring: datasource: url: jdbc:mysql://192.168.1.200:8066/eleme_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 username: root password: 123456 hikari: maximum-pool-size: 50 minimum-idle: 10 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000注意 URL 里的useSSLfalse因为内网通信不需要 SSL开了反而增加开销。serverTimezone必须设否则时间字段会差 8 小时。characterEncodingutf8保证中文不乱码。提示连接池大小不是越大越好。我见过有人设 200结果 MySQL 的max_connections只有 100直接报错。一般按CPU 核数 * 2 磁盘数来估算50 左右够用了。4.2 业务代码中的分片键处理用了 Mycat 之后Java 代码里查询必须带分片键否则 Mycat 会广播到所有分片性能极差。比如查订单必须传user_idSelect(select * from orders where user_id #{userId} and status #{status}) ListOrder selectByUserAndStatus(Param(userId) Long userId, Param(status) Integer status);如果业务上确实需要按其他字段查比如按商家 ID 查订单那就要考虑冗余分片键或者建索引表。我的做法是建一张order_index表按商家 ID 分片里面存订单 ID 和用户 ID 的映射先查索引表拿到用户 ID再查订单表。4.3 上线部署流程与回滚方案上线部署我走的是蓝绿部署。先部署一套新版本绿验证没问题后把 LVS 的流量切到绿旧版本蓝保留一段时间万一有问题可以快速切回。具体操作在绿环境部署新版本 Java 应用启动后做健康检查。用ipvsadm把绿环境的 Real Server 权重调高蓝环境调低观察流量和错误率。确认无误后把蓝环境从 LVS 摘除完成上线。如果出问题把蓝环境权重调回绿环境摘除完成回滚。# 调整权重 ipvsadm -e -t 192.168.1.100:80 -r 192.168.1.103:80 -g -w 10 ipvsadm -e -t 192.168.1.100:80 -r 192.168.1.101:80 -g -w 1注意回滚方案必须提前演练别等到出事了才翻文档。我一般会在上线前把回滚命令写成脚本放在跳板机上一键执行。5. 常见问题排查与避坑经验实录5.1 MySQL 连接失败与权限问题ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock这个报错我遇到太多次了。原因通常是 MySQL 没启动或者 socket 文件路径不对。先检查服务状态systemctl status mysqld如果服务正常检查/etc/my.cnf里的socket配置确保客户端和服务端一致。还有一种情况是/tmp目录被清理了socket 文件丢失重启 MySQL 即可。权限问题也很常见。Java 连接 MySQL 报Access denied for user root192.168.1.103说明 root 用户没有从那个 IP 连接的权限。解决办法是授权CREATE USER root192.168.1.% IDENTIFIED BY 123456; GRANT ALL PRIVILEGES ON *.* TO root192.168.1.%; FLUSH PRIVILEGES;5.2 LVS 转发异常与 ARP 冲突排查LVS 配置好了但请求不通先看ipvsadm -L -n的统计信息有没有流量进来。如果InActConn一直是 0说明请求没到 LVS。检查防火墙和 SELinuxsystemctl stop firewalld setenforce 0如果 LVS 有流量但 Real Server 没响应大概率是ARP 冲突。Real Server 上必须抑制 ARP否则它会响应 VIP 的 ARP 请求导致客户端直接连到 Real Server绕过 LVS。检查sysctl -a | grep arp_ignore确保值是 1。5.3 Mycat 分片查询慢与数据倾斜Mycat 查询慢先看是不是广播查询。在 Mycat 的日志里搜route如果看到route to all nodes说明没带分片键。解决办法是优化 SQL带上分片字段。数据倾斜是另一个问题。哈希分片理论上均匀但如果分片键选择不当比如用性别做分片键那只有两个分片肯定倾斜。我一般用用户 ID 或订单 ID这种离散度高的字段。如果已经倾斜了可以调整分片规则比如用一致性哈希但迁移数据很麻烦所以前期设计要慎重。问题现象可能原因排查命令解决方案MySQL 连接超时连接池耗尽show processlist增大连接池或优化慢查询LVS 请求不通ARP 冲突arp -an配置 arp_ignore 和 arp_announceMycat 查询慢广播查询查看 Mycat 日志SQL 带上分片键Java 应用 OOM内存泄漏jmap -heap分析堆转储修复泄漏点数据不一致主从延迟show slave status读走主库或加缓存5.4 上线后的监控与告警配置上线不是终点监控才是。我一般会配三个层面的监控LVS 的连接数和流量、Java 应用的 QPS 和响应时间、MySQL 的慢查询和主从延迟。工具用 Prometheus GrafanaJava 应用暴露 Micrometer 指标MySQL 用 mysqld_exporter。告警规则我设了这几条LVS 活跃连接数超过 5 万告警。Java 应用 P99 响应时间超过 500ms告警。MySQL 慢查询超过 10 条/分钟告警。主从延迟超过 5 秒告警。提示告警阈值别设太敏感否则天天被骚扰最后就麻木了。我一般先观察一周的基线再根据基线设阈值。6. 性能调优与扩展性思考6.1 MySQL 读写分离与主从复制eleme 的读请求远多于写请求所以读写分离是必须的。MySQL 主从复制配置# 主库配置 [mysqld] log-binmysql-bin server-id1 # 从库配置 [mysqld] server-id2 relay-logrelay-bin主库创建复制用户CREATE USER repl% IDENTIFIED BY 123456; GRANT REPLICATION SLAVE ON *.* TO repl%;从库执行CHANGE MASTER TO指向主库然后START SLAVE。Mycat 的balance1会自动把读请求分发到从库。6.2 LVS 与 Java 集群的水平扩展当流量增长时加机器是最简单的扩展方式。LVS 后面加一台 Java 服务器只需要在 LVS 上执行ipvsadm -a添加 Real Server然后在 Real Server 上配置 VIP 和 ARP 抑制。整个过程不影响现有服务。Java 应用本身要无状态session 不能存在本地要放到 Redis 里。这样任何一台 Java 服务器都能处理任何用户的请求扩展才方便。6.3 缓存层引入与热点数据处理数据库扛不住的时候加缓存。Redis 是首选把热点数据比如商家信息、用户信息缓存起来减少 MySQL 查询。缓存更新策略我用的是Cache Aside先更新数据库再删除缓存。为什么不是更新缓存因为并发更新时容易产生脏数据删除更安全。public void updateUser(User user) { userMapper.update(user); redisTemplate.delete(user: user.getId()); }热点数据比如秒杀商品可以用本地缓存 Redis 二级缓存本地缓存扛住绝大部分请求Redis 做兜底。7. 个人实操体会与后续扩展方向这套架构我从头到尾搭了一遍踩了不少坑也积累了一些经验。最大的体会是上线前的压测比什么都重要。我见过太多团队直接上生产结果一压就垮。压测不仅能发现性能瓶颈还能验证架构的可靠性。另一个体会是文档和脚本要同步维护。上线过程中改了什么配置、执行了什么命令都要记下来。我习惯用 Ansible 把部署步骤写成 playbook下次上线直接跑减少人为失误。后续如果要扩展我会考虑这几个方向引入消息队列解耦订单和配送用 Elasticsearch 做订单搜索用 Kubernetes 做容器编排。但这些都是后话先把当前这套跑稳再说。毕竟架构不是越复杂越好适合业务阶段的才是最好的。