淘宝返利app多数据源设计:基于MyCat的分库分表与读写分离
大家好,我是省赚客APP研发者阿宝!
在省赚客这类高并发返利应用中,用户订单、佣金记录、推广关系等核心数据量增长迅猛。单库单表在日均百万级订单下已出现性能瓶颈。为保障系统稳定性和扩展性,我们采用MyCat作为中间件,实现分库分表 + 读写分离的多数据源架构,支撑当前千万级用户规模下的高效数据访问。
整体架构设计
系统部署4个MySQL实例(2主2从),通过MyCat逻辑库rebate_db对外提供统一入口:
- 写操作 :路由至主库(
dn1_master,dn2_master); - 读操作 :负载均衡至从库(
dn1_slave,dn2_slave); - 分片规则:按用户ID哈希分片,确保同一用户数据落在同一库。
MyCat配置文件关键部分如下:
xml
<!-- schema.xml -->
<schema name="rebate_db" checkSQLschema="false" sqlMaxLimit="100">
<table name="user_order" dataNode="dn1,dn2" rule="user_id_mod"/>
<table name="commission_record" dataNode="dn1,dn2" rule="user_id_mod"/>
</schema>
<dataNode name="dn1" dataHost="host1" database="rebate_01"/>
<dataNode name="dn2" dataHost="host2" database="rebate_02"/>
<dataHost name="host1" maxCon="1000" minCon="10" balance="1">
<writeHost host="master" url="192.168.1.10:3306" user="root" password="xxx">
<readHost host="slave" url="192.168.1.11:3306" user="root" password="xxx"/>
</writeHost>
</dataHost>
分片规则定义在rule.xml:
xml
<tableRule name="user_id_mod">
<rule>
<columns>user_id</columns>
<algorithm>mod-long</algorithm>
</rule>
</tableRule>
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<property name="count">2</property>
</function>

Java应用连接配置
Spring Boot项目通过JDBC URL直连MyCat(端口8066),无需感知底层分片:
yaml
# application.yml
spring:
datasource:
url: jdbc:mysql://mycat.juwatech.cn:8066/rebate_db?useUnicode=true&characterEncoding=utf8
username: app_user
password: secure_password
driver-class-name: com.mysql.cj.jdbc.Driver
业务代码保持透明,例如订单创建:
java
package juwatech.cn.order.mapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
@Mapper
public interface UserOrderMapper {
@Insert("INSERT INTO user_order (user_id, order_no, amount, status) VALUES (#{userId}, #{orderNo}, #{amount}, #{status})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(UserOrder order);
}
MyCat根据user_id自动路由到dn1或dn2,开发者无需编写分库逻辑。
强制走主库场景处理
对于"下单后立即查询"等强一致性场景,需强制读主库。MyCat支持注解式路由:
java
package juwatech.cn.order.service;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class OrderQueryService {
@Transactional(readOnly = false)
public UserOrder createAndQuery(String userId, String orderNo) {
// 插入订单(写主库)
orderMapper.insert(new UserOrder(userId, orderNo, 100L, "PENDING"));
// 强制读主库:MyCat注解 /*balance*/ 或 /*master*/
return orderMapper.selectByOrderNoWithHint(orderNo);
}
}
对应Mapper方法:
java
@Select("/*master*/ SELECT * FROM user_order WHERE order_no = #{orderNo}")
UserOrder selectByOrderNoWithHint(String orderNo);
该注释将绕过读写分离,直接查询主库,避免主从延迟导致查不到数据。
全局自增ID生成
由于分库后MySQL自增主键不再全局唯一,我们采用Snowflake算法生成分布式ID:
java
package juwatech.cn.common.id;
@Component
public class SnowflakeIdGenerator {
private final Snowflake snowflake = IdUtil.createSnowflake(1, 1);
public long nextId() {
return snowflake.nextId();
}
}
在实体插入前赋值:
java
UserOrder order = new UserOrder();
order.setId(idGenerator.nextId());
order.setUserId(userId);
orderMapper.insert(order);
跨分片查询优化
对于运营后台的全局统计需求(如"昨日总佣金"),避免全表扫描。我们采用以下策略:
- 冗余汇总表 :每日凌晨通过Flink聚合写入
daily_commission_summary(不分片); - 异步导出:大数据量查询走离线数仓,不压在线库。
示例汇总任务:
java
@Scheduled(cron = "0 0 2 * * ?")
public void aggregateDailyCommission() {
List<CommissionSummary> summaries = commissionMapper.sumByDate(LocalDate.now().minusDays(1));
summaryMapper.batchInsert(summaries); // 写入非分片表
}
监控与故障切换
MyCat提供JDBC连接池监控和心跳检测。当主库宕机时,自动切换至备用主库(需配合MHA或Orchestrator)。我们还通过Prometheus采集MyCat指标:
yaml
# mycat_exporter配置
metrics_path: /actuator/prometheus
static_configs:
- targets: ['mycat-metrics:9104']
关键告警项包括:连接池耗尽、SQL执行超时、主从延迟>5s。
上线后,系统写入TPS提升3倍,复杂查询响应时间从2s降至200ms以内,有效支撑大促期间流量洪峰。
本文著作权归聚娃科技省赚客app开发者团队,转载请注明出处!