OkHttp源码学习之RouteDatabase

136 阅读4分钟

OkHttp 中,RouteDatabase 是一个内部组件,用于管理和记录连接路由(Route)的历史信息。它在连接复用和故障恢复中扮演着重要角色,帮助 OkHttp 在遇到网络问题时快速切换到可用的路由。


1. 什么是 Route 和 RouteDatabase

Route

  • 定义Route 表示一个具体的网络连接路径,包括目标地址、代理、Socket 配置等信息。

  • 组成

    • Address:目标服务器的地址(主机名、端口号、SSL 配置等)。
    • Proxy:使用的代理服务器(如果有)。
    • InetSocketAddress:实际连接的目标 IP 地址和端口号。

RouteDatabase

  • 定义RouteDatabase 是一个内部数据结构,用于记录和管理 Route 的成功或失败状态。

  • 作用

    1. 跟踪路由的使用情况。
    2. 标记不可用或失败的路由。
    3. 提供备选路由,支持故障切换。

2. RouteDatabase 的核心作用

  1. 路由选择优化

    • 通过记录成功和失败的路由信息,帮助 OkHttp 优先选择已知的成功路由。
  2. 故障恢复

    • 当某个路由失败时,RouteDatabase 提供备选路由,避免多次尝试失败的路径。
  3. 连接重试

    • 在网络不稳定或服务器不可达时,允许快速切换到其他路由,提高连接成功率。

3. RouteDatabase 的工作机制

1. 记录失败路由

  • 当连接某个路由失败时,RouteDatabase 会将该路由标记为失败。
  • 方法:RouteDatabase.failed(route)

2. 检查路由是否失败

  • 在尝试连接之前,检查该路由是否被标记为失败。
  • 方法:RouteDatabase.shouldPostpone(route) 返回 true 表示需要跳过该路由。

3. 提供备选路由

  • 如果一个路由失败,OkHttp 会从 RouteSelector 获取其他可用的路由,并避免选择已知的失败路由。

4. RouteDatabase 的主要方法

以下是 RouteDatabase 的核心方法及其作用:

1. failed(route: Route)

  • 作用:将某个路由标记为失败。
  • 参数route,表示需要标记的失败路由。

2. connected(route: Route)

  • 作用:当某个路由成功连接时,从失败列表中移除该路由。
  • 参数route,表示成功的路由。

3. shouldPostpone(route: Route): Boolean

  • 作用:检查某个路由是否需要推迟尝试。
  • 返回值true 表示该路由已被标记为失败,应跳过。

4. failedRoutesCount(): Int

  • 作用:返回当前标记为失败的路由数量,用于调试或统计。

5. RouteDatabase 在 OkHttp 中的作用

1. 提升路由选择效率

  • OkHttp 使用 RouteSelector 提供候选路由列表,通过 RouteDatabase 过滤掉失败的路由,优先选择成功的路由。

2. 故障恢复

  • 在网络不稳定或路由配置错误时,避免重复尝试不可用的路由,提升连接成功率。

3. 多路由支持

  • 支持一个目标服务器有多个路由的场景,例如通过不同的代理或 IP 地址连接同一主机。

4. 配合连接池

  • RouteDatabase 与连接池(ConnectionPool)协作,确保复用连接时选择有效的路由。

6. 工作流程示例

以下是 RouteDatabase 在 OkHttp 工作中的典型流程:

  1. 初始化连接

    • OkHttp 获取目标地址的所有候选路由。
  2. 过滤失败路由

    • 使用 RouteDatabase 跳过已知失败的路由。
  3. 尝试连接

    • 如果连接成功,调用 RouteDatabase.connected(route)
    • 如果连接失败,调用 RouteDatabase.failed(route)
  4. 故障切换

    • 若当前路由失败,从备选路由中获取新的路由并尝试。

7. 代码示例

以下是一个模拟 RouteDatabase 使用的伪代码:

val routeDatabase = RouteDatabase()

// 模拟多个路由
val route1 = Route(address, proxy, socketAddress)
val route2 = Route(address, Proxy.NO_PROXY, socketAddress)

// 标记 route1 失败
routeDatabase.failed(route1)

// 检查是否需要跳过 route1
if (routeDatabase.shouldPostpone(route1)) {
    println("Route1 is marked as failed, skipping...")
}

// 连接成功后移除失败标记
routeDatabase.connected(route1)

8. RouteDatabase 的典型使用场景

1. 多路由切换

  • 在通过多个代理或 DNS 解析结果连接同一服务器时,RouteDatabase 可以管理路由的成功与失败状态。

2. 网络不稳定场景

  • 当某些路由因网络原因无法访问时,快速切换到其他路由。

3. 高可用系统

  • 在需要高可用性和快速故障恢复的场景下,例如连接多个数据中心或 CDN。