Follower Read

本章将介绍使用 Follower Read 在特定情况下加速查询的方法。

简介

在 TiDB 当中,数据是以 Region 为单位,分散在集群中所有的节点上进行存储的。一个 Region 可以存在多个副本,副本又分为一个 leader 和多个 follower。当 leader 上的数据发生变化时,TiDB 会将数据同步更新到 follower。

默认情况下,TiDB 只会在同一个 Region 的 leader 上读写数据。当系统中存在读取热点 Region 导致 leader 资源紧张成为整个系统读取瓶颈时,启用 Follower Read 功能可明显降低 leader 的负担,并且通过在多个 follower 之间均衡负载,显著地提升整体系统的吞吐能力。

何时使用

优化读热点

你可以在 TiDB Dashboard 流量可视化页面当中通过可视化的方法分析你的应用程序是否存在热点 Region。你可以通过将「指标选择框」选择到 Read (bytes)Read (keys) 查看是否存在读取热点 Region。

如果发现确实存在热点问题,你可以通过阅读 TiDB 热点问题处理章节进行逐一排查,以便从应用程序层面上避免热点的产生。

如果读取热点的确无法避免或者改动的成本很大,你可以尝试通过 Follower Read 功能将读取请求更好的负载均衡到 follower region。

优化跨数据中心部署的延迟

如果 TiDB 集群是跨地区或跨数据中心部署的,一个 Region 的不同副本分布在不同的地区或数据中心,此时可以通过配置 Follower Read 为 closest-adaptiveclosest-replicas 让 TiDB 优先从当前的数据中心执行读操作,这样可以大幅降低读操作的延迟和流量开销。具体原理可参考 Follower Read

开启 Follower Read

  • SQL
  • Java

在 SQL 中,你可以将变量 tidb_replica_read 的值(默认为 leader)设置为 followerleader-and-followerprefer-leaderclosest-replicasclosest-adaptive 开启 TiDB 的 Follower Read 功能:

  1. SET [GLOBAL] tidb_replica_read = 'follower';

你可以通过 Follower Read 使用方式 了解该变量的更多细节。

在 Java 语言当中,可以定义一个 FollowerReadHelper 类用于开启 Follower Read 功能:

  1. public enum FollowReadMode {
  2. LEADER("leader"),
  3. FOLLOWER("follower"),
  4. LEADER_AND_FOLLOWER("leader-and-follower"),
  5. CLOSEST_REPLICA("closest-replica"),
  6. CLOSEST_ADAPTIVE("closest-adaptive"),
  7. PREFER_LEADER("prefer-leader");
  8. private final String mode;
  9. FollowReadMode(String mode) {
  10. this.mode = mode;
  11. }
  12. public String getMode() {
  13. return mode;
  14. }
  15. }
  16. public class FollowerReadHelper {
  17. public static void setSessionReplicaRead(Connection conn, FollowReadMode mode) throws SQLException {
  18. if (mode == null) mode = FollowReadMode.LEADER;
  19. PreparedStatement stmt = conn.prepareStatement(
  20. "SET @@tidb_replica_read = ?;"
  21. );
  22. stmt.setString(1, mode.getMode());
  23. stmt.execute();
  24. }
  25. public static void setGlobalReplicaRead(Connection conn, FollowReadMode mode) throws SQLException {
  26. if (mode == null) mode = FollowReadMode.LEADER;
  27. PreparedStatement stmt = conn.prepareStatement(
  28. "SET GLOBAL @@tidb_replica_read = ?;"
  29. );
  30. stmt.setString(1, mode.getMode());
  31. stmt.execute();
  32. }
  33. }

在需要使用从 Follower 节点读取数据时,通过 setSessionReplicaRead(conn, FollowReadMode.LEADER_AND_FOLLOWER) 方法在当前 Session 开启能够在 Leader 节点和 Follower 节点进行负载均衡的 Follower Read 功能,当连接断开时,会恢复到原来的模式。

  1. public static class AuthorDAO {
  2. // Omit initialization of instance variables...
  3. public void getAuthorsByFollowerRead() throws SQLException {
  4. try (Connection conn = ds.getConnection()) {
  5. // Enable the follower read feature.
  6. FollowerReadHelper.setSessionReplicaRead(conn, FollowReadMode.LEADER_AND_FOLLOWER);
  7. // Read the authors list for 100000 times.
  8. Random random = new Random();
  9. for (int i = 0; i < 100000; i++) {
  10. Integer birthYear = 1920 + random.nextInt(100);
  11. List<Author> authors = this.getAuthorsByBirthYear(birthYear);
  12. System.out.println(authors.size());
  13. }
  14. }
  15. }
  16. public List<Author> getAuthorsByBirthYear(Integer birthYear) throws SQLException {
  17. List<Author> authors = new ArrayList<>();
  18. try (Connection conn = ds.getConnection()) {
  19. PreparedStatement stmt = conn.prepareStatement("SELECT id, name FROM authors WHERE birth_year = ?");
  20. stmt.setInt(1, birthYear);
  21. ResultSet rs = stmt.executeQuery();
  22. while (rs.next()) {
  23. Author author = new Author();
  24. author.setId( rs.getLong("id"));
  25. author.setName(rs.getString("name"));
  26. authors.add(author);
  27. }
  28. }
  29. return authors;
  30. }
  31. }

扩展阅读