#connection-pool #dns-resolver #pool #service #traits #cueball #joyent

cueball-dns-resolver

这是Joyent的node-cueball DNS解析器的Rust实现

3个版本

0.3.2 2020年5月19日
0.3.1 2020年5月12日
0.3.0 2020年1月14日

#33 in #dns-resolver

MPL-2.0 许可证

115KB
2.5K SLoC

cueball

关于

多节点服务连接池

Cueball是一个用于“玩扑克”的库——管理到多节点服务的连接池。这个Cueball的实现受到了Joyent许多服务和软件组件使用的cueball的Node.js原始实现的启发。Rust实现依赖于两个主要的特质来管理节点提供的服务集合中的连接。这些是Resolver特质和Connection特质。

解析器

解析器负责在一个逻辑服务中定位所有可用的节点或后端,获取它们的IP地址和端口信息(或连接到它们所需的任何信息),并跟踪它们。这通常是某种形式的服务发现客户端。一个例子是一个基于DNS的解析器实现,它使用DNS SRV记录作为服务发现机制的一种形式来查找后端。

连接

在Cueball中,连接不一定只是一个TCP套接字。它可以提供某种逻辑连接到服务的东西,只要它遵循与套接字相似的接口。

这允许API用户将“连接”表示为应用层或会话层概念。例如,构建一个连接到LDAP服务器并执行绑定操作(验证)的连接池可能是有用的。

除了ResolverConnection实现外,Cueball用户还向Cueball连接池提供一个建立到所需服务的连接的函数。Cueball连接池为此函数建立的特质界限如下

FnMut(&Backend) -> C + Send + 'static
where C: Connection

要求是一个函数,它接受解析器中一个Backend的引用,并返回Connection的某个实例。

该函数的目的是提供一种方式来捕获建立服务连接所需的、应用级别的配置信息。例如,建立数据库连接可能需要特定于应用程序的配置,例如数据库名称或用户名,以便建立连接。

重新平衡

随着服务后端的出现和消失,连接池将在可用的后端集中重新平衡配置的连接数(max_connections)。当Resolver通知连接池已添加新的后端或已删除现有后端时,就会发生重新平衡。连接池响应这些事件之一重新平衡连接,以保持连接在可用的后端之间均匀分布。

当从Resolver接收到消息后,在执行实际重新平衡之前,连接池使用可配置的延迟。这种延迟是为了考虑到在非常短的时间内Resolver可能会发送多个消息的情况,并且允许连接池在重新平衡连接时更加高效。默认的重新平衡延迟时间为100毫秒。

重新平衡可能导致连接池暂时超过池配置的最大连接数。如果Resolver通知连接池删除后端,但该后端的连接仍在使用中,则连接数可能会超过最大值,直到这些连接返回连接池并被丢弃。

不协调

在不协调球中,不协调是指连接池中连接顺序的周期性随机洗牌。不协调的目标是避免在连接池的生命周期中可能出现的不希望的模式。例如,假设一个服务有三个后端ABC,并且连接池的最大连接数为九。初始连接分布可能如下所示

1 2 3 4 5 6 7 8 9
A B C A B C A B C

不协调连接池使用队列内部存储连接。鉴于从池中取出的连接可能被占用非均匀的时间段,队列可能从其初始状态变为以下状态

1 4 7 2 5 8 3 6 9
A A A B B B C C C

这种情况并不理想,因为同一后端必须处理多个连续的请求,而其他后端则空闲。对于不协调球来说,理想的状况是在后端之间均匀分配工作,不仅限于连接数,而且还涉及随时间推移的请求分布。诚然,上面的例子是一个极端情况,模式可能会根据工作负载迅速解决,但没有保证这种情况一定会发生。不协调球中周期性洗牌的目标是扰乱可能持续一段时间的这种模式。

有一个与不协调相关的配置选项:decoherence_interval。该decoherence_interval表示不协调洗牌周期的长度,以秒为单位。如果在ConnectionPoolOptions结构中未指定此值,则默认值为300秒。

示例

使用cueball进行连接管理需要同时实现Resolver特性和实现Connection特性。实现Resolver特性的实现者向连接池提供有关可提供特定服务的节点信息。Connection特性定义了建立和关闭到特定服务的连接的行为。

以下是一个使用假设的ResolverConnection实现来创建cueball连接池的示例。

use std::thread;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::{Arc, Barrier, Mutex};
use std::sync::mpsc::Sender;
use std::{thread, time};

use slog::{Drain, Logger, info, o};

use cueball::backend;
use cueball::backend::{Backend, BackendAddress, BackendPort};
use cueball::connection::Connection;
use cueball::connection_pool::ConnectionPool;
use cueball::connection_pool::types::ConnectionPoolOptions;
use cueball::error::Error;
use cueball::resolver::{BackendAddedMsg, BackendMsg, Resolver};

fn main() {
    let plain = slog_term::PlainSyncDecorator::new(std::io::stdout());
    let log = Logger::root(
        Mutex::new(
            slog_term::FullFormat::new(plain).build()
        ).fuse(),
        o!("build-id" => "0.1.0")
    );

    let be1 = (IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 55555);
    let be2 = (IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 55556);
    let be3 = (IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 55557);

    let resolver = FakeResolver::new(vec![be1, be2, be3]);

    let pool_opts = ConnectionPoolOptions::<FakeResolver> {
        max_connections: 15,
        claim_timeout: Some(1000)
        resolver: resolver,
        log: log.clone(),
        decoherence_interval: None,
    };

    let pool = ConnectionPool::<DummyConnection, FakeResolver>::new(pool_opts);

    for _ in 0..10 {
        let pool = pool.clone();
        thread::spawn(move || {
            let conn = pool.claim()?;
            // Do stuff here
            // The connection is returned to the pool when it falls out of scope.
        })
    }
}

有几种ResolverConnection特性的实现,可能对任何想要开始使用cueball的人来说都很有用。

Resolver特性实现者

Connection特性实现者

最低支持的Rust版本

当前最低支持的Rust版本是1.39。

依赖项

~18MB
~324K SLoC