ProxySQL解决MySQL复制延迟: 应用与ProxySQL方案
2025-01-03 11:08:59
使用ProxySQL处理MySQL复制延迟问题
数据复制延迟是使用读写分离架构时常遇到的一个问题,特别是当应用需要立即读取刚写入的数据时。在基于 Spring Boot, MySQL InnoDB 和 ProxySQL 的环境中,如何解决这一问题值得深入探讨。 这种架构中,通常写操作发送至主库,读操作分配到从库。但当从库存在延迟,就可能出现应用读取不到刚写入数据的情况。
问题分析
数据在主库写入后,需要一段时间才能复制到从库。 这个延迟,称为复制延迟(Replication Lag)。 当应用程序在写入后立刻从从库读取数据时,由于从库尚未同步新数据,读取操作会失败。 直接原因是从库还未收到主库的更新,根本没有需要读取的数据。
解决方法:从主库读取,基于异常捕获
解决延迟问题的一个办法,就是当从库无法查询到数据时,尝试从主库查询。 Spring Data JPA 提供了便利的操作数据库的方式。在数据访问层进行异常处理,捕获“row not found”的异常,并回退到主库执行查询。这种办法需要在应用程序代码中额外进行处理。
步骤:
- 定义一个数据读取的方法。
- 在方法中尝试从库读取数据。
- 如果出现诸如
javax.persistence.NoResultException
(JPA 标准),或者 Spring Data JPA 抛出的org.springframework.dao.EmptyResultDataAccessException
这类异常,就表示从库没有对应的数据。 - 此时,将数据源切换到主库,重新执行查询操作。
代码示例(Spring Data JPA):
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Query;
import com.example.demo.domain.entity.User; //假设的实体类
@Service
public class UserService {
@PersistenceContext
private EntityManager entityManager;
@Transactional(readOnly = true)
public User getUserById(Long id) {
Query slaveQuery = entityManager.createQuery("SELECT u FROM User u WHERE u.id = :id", User.class);
slaveQuery.setParameter("id", id);
try{
return (User) slaveQuery.getSingleResult();
} catch(Exception e) {
Query masterQuery = entityManager.createQuery("SELECT u FROM User u WHERE u.id = :id", User.class);
masterQuery.setParameter("id", id);
return (User) masterQuery.getSingleResult();
}
}
}
此方法定义了一个查找用户的方法,它先在从库查询,发生EmptyResultDataAccessException
时会在回退到主库进行查询,并返回结果。 这里为了更好的示例和逻辑清晰,异常处理采用了通用的 Exception,真实场景中需要根据具体的JPA Provider进行异常捕获,确保安全性和鲁棒性。 在生产环境下,异常日志也需要仔细地收集并处理,以确保能够定位错误和进行有效的性能分析。此外,你可以在代码里对查询结果做一个缓存,从而减轻数据库压力。
使用 ProxySQL Query Rules 实现主库查询重定向
另外一种办法是使用ProxySQL的查询规则。 我们可以创建一个规则,判断某个查询返回的结果集为空时,ProxySQL将此查询重新发送到主库。这种方式可以在数据库代理层解决复制延迟,不需要更改应用代码。
步骤:
- 配置 ProxySQL 连接主从服务器,将他们分为不同的主机组(
hostgroups
)。 例如,hostgroup 0
表示主服务器,hostgroup 1
表示从服务器。 - 定义 ProxySQL 的查询规则。 该规则检查查询结果,判断是否为空,如果结果为空,就把这个查询发送到主服务器(
hostgroup 0
)。
ProxySQL配置:
-- 连接到ProxySQL控制台
mysql -u admin -padmin -h127.0.0.1 -P6032
-- 添加主库信息
INSERT INTO mysql_servers (hostgroup_id, hostname, port, status) VALUES (0, 'master-ip', 3306, 'ONLINE');
-- 添加从库信息
INSERT INTO mysql_servers (hostgroup_id, hostname, port, status) VALUES (1, 'slave-ip', 3306, 'ONLINE');
-- 配置查询路由规则
INSERT INTO mysql_query_rules (rule_id, active, match_digest,destination_hostgroup, apply) VALUES
(1001, 1, '^SELECT .* FROM your_table .*WHERE id=\\?',0, 1 );
-- 配置一个监控规则来重新尝试
INSERT into mysql_query_rules (rule_id, active, match_digest, destination_hostgroup, apply, if_no_resultset_found, reconnect, cache_ttl ) values(1002, 1, '^SELECT .* FROM your_table .*WHERE id=\\?', 0 ,1,1,1,1000);
-- 加载配置
LOAD MYSQL VARIABLES TO RUNTIME;
SAVE MYSQL VARIABLES TO DISK;
LOAD MYSQL QUERY RULES TO RUNTIME;
SAVE MYSQL QUERY RULES TO DISK;
以上代码首先定义了两个数据库实例,分别为主库(hostgroup 0)和从库(hostgroup 1)。 其中^SELECT .* FROM your_table .*WHERE id=\\?
表示查询规则仅作用于查询指定表单且使用了 id 的场景; if_no_resultset_found
设置为 1 表示若结果集为空则会跳转 destination_hostgroup
配置的 hostgroup
,reconnect
代表强制 ProxySQL 在跳转 hostgroup 后断开并重连数据库,保证每次都会去主库获取数据;cache_ttl
代表的是查询规则结果集的缓存有效时间。这些设置旨在当初始查询未返回结果集的时候,尝试主库查询。 你可以使用其他 match_digest
来进行匹配,例如前缀匹配。
这种方式的优势:
- 对应用透明:无需更改应用代码。
- 更加集中化处理:在ProxySQL层实现逻辑,应用专注于业务。
使用 ProxySQL 规则时要注意:需要根据具体的查询模式设置match_digest
,并且对数据库的压力做好评估和监控。 过多地执行查询回滚到主库查询可能对数据库造成较大压力,需要谨慎实施,做好压测,切忌贸然上线。
以上讨论了两种解决MySQL复制延迟问题的策略:应用代码捕获异常重试查询以及使用ProxySQL 查询规则重定向。在实际应用中,需要根据具体的应用场景和技术架构进行权衡选择。 适当的缓存机制配合这些措施,效果更佳。