在当今互联网应用中,MySQL作为最流行的关系型数据库之一,承担着核心数据存储的重任。但随着业务规模扩大,单一MySQL实例往往难以应对高并发读写请求。我曾经参与过一个电商项目,在促销活动期间,MySQL数据库的QPS(每秒查询率)峰值达到8000+,导致响应延迟明显增加,部分页面加载时间超过5秒。
这种情况下,实时数据同步方案的价值就凸显出来了。通过将MySQL中的数据实时同步到Redis等高性能存储中,我们可以:
Canal的核心设计非常巧妙,它通过模拟MySQL从库的行为来获取数据变更。这种设计有以下几个关键优势:
具体工作流程如下:
重要提示:MySQL必须配置为ROW模式(binlog-format=ROW),因为STATEMENT或MIXED模式无法提供完整的数据变更前/后内容。
在my.cnf中,以下配置项至关重要:
ini复制[mysqld]
log-bin=mysql-bin
binlog-format=ROW
server-id=1
binlog-do-db=canaldb # 只同步指定数据库
配置说明:
log-bin:启用二进制日志binlog-format=ROW:必须设置为ROW模式server-id:在MySQL集群中必须唯一binlog-do-db:生产环境建议明确指定需要同步的数据库推荐使用以下docker-compose.yml配置:
yaml复制version: "3.8"
services:
mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: root
volumes:
- ./mysql/conf.d:/etc/mysql/conf.d
- ./mysql/data:/var/lib/mysql
ports:
- "3306:3306"
canal-server:
image: canal/canal-server:v1.1.7
environment:
- canal.instance.mysql.slaveId=100
- canal.instance.master.address=mysql:3306
- canal.instance.dbUsername=root
- canal.instance.dbPassword=root
- canal.instance.filter.regex=canaldb\\..*
ports:
- "11111:11111"
depends_on:
- mysql
关键参数说明:
slaveId:必须与MySQL集群中其他节点不同filter.regex:使用双反斜杠进行正则转义建议使用以下POM依赖:
xml复制<dependencies>
<dependency>
<groupId>top.javatool</groupId>
<artifactId>canal-spring-boot-starter</artifactId>
<version>1.2.1-RELEASE</version>
</dependency>
<!-- 根据实际需要添加Redis等客户端 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
完整的事件处理示例:
java复制@CanalTable("t_user")
@Component
public class UserHandler implements EntryHandler<User> {
private final RedisTemplate<String, Object> redisTemplate;
// 构造器注入
public UserHandler(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
public void insert(User user) {
String key = "user:" + user.getId();
redisTemplate.opsForValue().set(key, user);
log.info("新增用户缓存: {}", key);
}
@Override
public void update(User before, User after) {
String key = "user:" + after.getId();
redisTemplate.opsForValue().set(key, after);
log.info("更新用户缓存: {}", key);
}
@Override
public void delete(User user) {
String key = "user:" + user.getId();
redisTemplate.delete(key);
log.info("删除用户缓存: {}", key);
}
}
application.yml配置建议:
yaml复制canal:
server: canal-server:11111
destination: jobs
userName: admin
password: password
batch-size: 1000 # 批量获取大小
filter: canaldb.t_user,canaldb.t_order # 多表过滤
建议监控以下指标:
show slave status查看)Prometheus监控示例配置:
yaml复制metrics:
canal:
enabled: true
endpoints:
- id: canal_metrics
url: http://canal-server:11111/metrics
问题1:Canal连接不上MySQL
问题2:数据同步延迟高
问题3:部分事件丢失
可以通过实现多个Handler来同步到不同存储:
java复制@CanalTable("t_product")
@Component
@RequiredArgsConstructor
public class ProductMultiHandler {
private final RedisTemplate<String, Object> redisTemplate;
private final MongoTemplate mongoTemplate;
@CanalEventListener
public void onEvent(Product product) {
// 同步到Redis
redisTemplate.opsForValue().set(
"product:" + product.getId(),
product
);
// 同步到MongoDB
mongoTemplate.save(product);
}
}
对于分库分表场景,可以通过正则匹配:
yaml复制canal:
filter: order_db_\\d+.t_order_\\d+
在Handler中解析实际表名:
java复制@CanalEventListener
public void onEvent(
@CanalTableName String tableName,
Order order) {
// 解析分表编号
int tableNo = extractTableNo(tableName);
// 按分表规则处理...
}
可以在Handler中添加业务逻辑:
java复制@Override
public void update(Product before, Product after) {
if (after.getStatus() == Status.OFFLINE) {
// 下架商品时同步清理缓存
redisTemplate.delete("product:" + after.getId());
esClient.delete("products", after.getId());
} else {
// 正常更新流程...
}
}
在实际项目中,我们通过Canal实现了MySQL到Elasticsearch的商品搜索数据同步,同步延迟控制在500ms以内,成功支撑了日均百万级的商品搜索请求。关键是要合理设计索引结构和批量处理策略,避免频繁的ES索引操作影响整体性能。