返回

ProxySQL解决MySQL复制延迟: 应用与ProxySQL方案

mysql

使用ProxySQL处理MySQL复制延迟问题

数据复制延迟是使用读写分离架构时常遇到的一个问题,特别是当应用需要立即读取刚写入的数据时。在基于 Spring Boot, MySQL InnoDB 和 ProxySQL 的环境中,如何解决这一问题值得深入探讨。 这种架构中,通常写操作发送至主库,读操作分配到从库。但当从库存在延迟,就可能出现应用读取不到刚写入数据的情况。

问题分析

数据在主库写入后,需要一段时间才能复制到从库。 这个延迟,称为复制延迟(Replication Lag)。 当应用程序在写入后立刻从从库读取数据时,由于从库尚未同步新数据,读取操作会失败。 直接原因是从库还未收到主库的更新,根本没有需要读取的数据。

解决方法:从主库读取,基于异常捕获

解决延迟问题的一个办法,就是当从库无法查询到数据时,尝试从主库查询。 Spring Data JPA 提供了便利的操作数据库的方式。在数据访问层进行异常处理,捕获“row not found”的异常,并回退到主库执行查询。这种办法需要在应用程序代码中额外进行处理。

步骤:

  1. 定义一个数据读取的方法。
  2. 在方法中尝试从库读取数据。
  3. 如果出现诸如 javax.persistence.NoResultException(JPA 标准),或者 Spring Data JPA 抛出的 org.springframework.dao.EmptyResultDataAccessException 这类异常,就表示从库没有对应的数据。
  4. 此时,将数据源切换到主库,重新执行查询操作。

代码示例(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将此查询重新发送到主库。这种方式可以在数据库代理层解决复制延迟,不需要更改应用代码。

步骤:

  1. 配置 ProxySQL 连接主从服务器,将他们分为不同的主机组(hostgroups)。 例如,hostgroup 0 表示主服务器, hostgroup 1 表示从服务器。
  2. 定义 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 查询规则重定向。在实际应用中,需要根据具体的应用场景和技术架构进行权衡选择。 适当的缓存机制配合这些措施,效果更佳。