[源码分析] Dynomite 分布式存储引擎 之 DynoJedisClient(1)

0x00 摘要

前面我们有文章介绍了Amazon Dynamo系统架构 和 NetFlix Dynomite。

我们今天来看看 NetFlix Dynomite 的 Java 客户端 DynoJedisClient 如何实现。分析客户端是因为,此客户端的作用很类似于集群master,其思路是:java驱动提供多个策略接口,可以用来驱动程序行为调优。包括负载均衡,重试请求,管理节点连接等等。

因为 Dynomite 对于本文来说,过于庞大&底层,而且 DynoJedisClient 与 Dynomite 耦合过于紧密, 所以我们从最简单的功能点出发看看 DynoJedisClient,于是我们可以想到的功能点是:

  • 如何提供基本功能,即提供数据库连接池;
  • 如何管理节点连接;
  • 如何拓扑感知;
  • 如何负载均衡;
  • 如何故障转移;
  • 故障转移;

所以我们接下来就围绕这些基本功能点进行分析。

0x01 背景概念

1.1 Amazon Dynamo

亚马逊在业务发展期间面临一些问题,主要受限于关系型数据库的可扩展性和高可用性,因此研发了一套新的、基于 KV 存储模型的数据库,将之命名为 Dynamo,其主要采取完全的分布式、去中心化的架构。

相较于传统的关系型数据库 MySQLDynamo 的功能目标与之有一些细小的差别,例如: Amazon 的业务场景多数情况并不需要支持复杂查询,却要求必要的单节点故障容错性、数据最终一致性(即牺牲数据强一致优先保障可用性)、较强的可扩展性等。

1.2 NetFlix Dynomite

Dynomite 是 NetFlix 对亚马逊分布式存储引擎 Dynamo 的一个开源通用实现,它不仅支持基于内存的 K/V 数据库,还支持持久化的 Mysql、BerkeleyDb、LevelDb 等数据库,并具有简单、高效、支持跨数据中心的数据复制等优点。

Dynomite 的最终目标是提供数据库存储引擎不能提供的简单、高效、跨数据中心的数据复制功能。目前,Dynomite 已经实现了对 Redis 和 Memcached 的支持。

0x02 Netflix选型思路

Netflix选择Dynomite,是因为:

  • 其具有性能,多数据中心复制和高可用性的特点;

  • Dynomite提供分片和可插拔的数据存储引擎,允许在数据需求增加垂直和水平扩展;

  • Dynomite在Redis之上提供了高可用性、对等复制以及一致性等特性,用于构建分布式集群队列。

  • Dyno为持久连接提供连接池;

  • Dyno可以为连接池配置为拓扑感知;

  • 故障转移:Dyno为应用程序提供特定的本地机架,us-east-1a的客户端将连接到相同区域的Dynomite/Redis节点,除非该节点不可用,在这种情况下该客户端将进行故障转移。这个属性被用于通过区域划分队列。

Dynomite对于本文来说,过于底层。

所以我们重点就看看 DynoJedisClient 如何实现后面两点,当然,这两点其实也无法脱离Dynomite,我们只是力争剥离出来

0x03 基础知识

3.1 Data Center

Data Center 是由多个Rack组成的逻辑集合。

Data Center 可以是一个机房或者一个区域的设备组合。

3.2 Rack

这是一个逻辑集合,有多个彼此临近node的组成。比如一个机架上的所有物理机器。可简单的理解为存放服务器的机柜。

数据中心与机架是什么关系呢?N:1,1:N,M:N。

  • 如果只需要几台服务器就能满足业务需求,这些服务器至少有2个数据中心,那这种情况下多个数据中心可以放在1个机架上,不过这种情况对数据灾备来说是不太保险的。
  • 第2种情况是1个数据中心相当于1个机房,那机房里会有多个机架。
  • 第3种情况M:N为多个机房的多个数据中心置于多个机架上。

3.2 Rings and Tokens

由集群管理的数据就是一个环。环中的每个节点被分配一个或多个由token描述的数据范围,确定在环中的位置。

Token是用于标识每个分区的64位整数ID,范围是-2^63 -- 2^63-1。通过hash算法计算partition key的hash值,以此确定存放在哪个节点。

Token也决定了每个节点存储的数据的分布范围,每个节点保存的数据的key在(前一个节点Token,本节点Token]的半开半闭区间内,所有的节点形成一个首尾相接的环。

0x04 需求 & 思路

因为要为上层屏蔽信息,所以 DynoJedisClient 就需要应对各种复杂信息,需要对系统有深刻的了解,比如:

  • 如何维护连接,为持久连接提供连接池;
  • 如何维护拓扑;
  • 如何负载均衡;
  • 如何故障转移;
  • 如何自动重试及发现,比如自动重试挂掉的主机。自动发现集群中的其他主机。
  • 如何监控底层机架状态;

因此,DynoJedisClient 的思路是:java驱动提供多个策略接口,可以用来驱动程序行为调优。包括负载均衡,重试请求,管理节点连接等等

0x05 使用

示例代码如下:

  1. public static void main(String[] args) throws IOException {
  2. final String clusterName = args[0];
  3. int version = Integer.parseInt(args[1]);
  4. final DynoQueueDemo demo = new DynoQueueDemo(clusterName, "us-east-1e");
  5. Properties props = new Properties();
  6. props.load(DynoQueueDemo.class.getResourceAsStream("/demo.properties"));
  7. for (String name : props.stringPropertyNames()) {
  8. System.setProperty(name, props.getProperty(name));
  9. }
  10. try {
  11. demo.initWithRemoteClusterFromEurekaUrl(args[0], 8102, false);
  12. if (version == 1) {
  13. demo.runSimpleV1Demo(demo.client);
  14. } else if (version == 2) {
  15. demo.runSimpleV2QueueDemo(demo.client);
  16. }
  17. Thread.sleep(10000);
  18. } catch (Exception ex) {
  19. ex.printStackTrace();
  20. } finally {
  21. demo.stop();
  22. logger.info("Done");
  23. }
  24. }

以及辅助函数:

  1. public void initWithRemoteClusterFromEurekaUrl(final String clusterName, final int port, boolean lock) throws Exception {
  2. initWithRemoteCluster(clusterName, getHostsFromDiscovery(clusterName), port, lock);
  3. }
  4. private void initWithRemoteCluster(String clusterName, final List<Host> hosts, final int port, boolean lock) throws Exception {
  5. final HostSupplier clusterHostSupplier = () -> hosts;
  6. if (lock)
  7. initDynoLockClient(clusterHostSupplier, null, "test", clusterName);
  8. else
  9. init(clusterHostSupplier, port, null);
  10. }
  11. public void initDynoLockClient(HostSupplier hostSupplier, TokenMapSupplier tokenMapSupplier, String appName,
  12. String clusterName) {
  13. dynoLockClient = new DynoLockClient.Builder().withApplicationName(appName)
  14. .withDynomiteClusterName(clusterName)
  15. .withTimeoutUnit(TimeUnit.MILLISECONDS)
  16. .withTimeout(10000)
  17. .withHostSupplier(hostSupplier)
  18. .withTokenMapSupplier(tokenMapSupplier).build();
  19. }

0x06 配置

在 DynoJedisClient 之中,有如下重要配置类。

6.1 缺省配置

ConnectionPoolConfigurationImpl主要是提供缺省配置。

  1. public class ConnectionPoolConfigurationImpl implements ConnectionPoolConfiguration {
  2. // DEFAULTS
  3. private static final LoadBalancingStrategy DEFAULT_LB_STRATEGY = LoadBalancingStrategy.TokenAware;
  4. private static final CompressionStrategy DEFAULT_COMPRESSION_STRATEGY = CompressionStrategy.NONE;
  5. private HostSupplier hostSupplier;
  6. private TokenMapSupplier tokenSupplier;
  7. private HostConnectionPoolFactory hostConnectionPoolFactory;
  8. private HashPartitioner hashPartitioner;
  9. private LoadBalancingStrategy lbStrategy = DEFAULT_LB_STRATEGY;
  10. private CompressionStrategy compressionStrategy = DEFAULT_COMPRESSION_STRATEGY;
  11. }

6.2 策略配置

ArchaiusConnectionPoolConfiguration最主要是提供了若干策略,包括负载,压缩,重试:

  • LoadBalancingStrategy parseLBStrategy(String propertyPrefix) 是负载策略;
  • CompressionStrategy parseCompressionStrategy(String propertyPrefix) 是压缩策略;
  • RetryPolicyFactory parseRetryPolicyFactory(String propertyPrefix) 是重试策略;

具体如下:

  1. public class ArchaiusConnectionPoolConfiguration extends ConnectionPoolConfigurationImpl {
  2. ......
  3. private final LoadBalancingStrategy loadBalanceStrategy;
  4. private final CompressionStrategy compressionStrategy;
  5. private final ErrorRateMonitorConfig errorRateConfig;
  6. private final RetryPolicyFactory retryPolicyFactory;
  7. private final DynamicBooleanProperty failOnStartupIfNoHosts;
  8. private final DynamicIntProperty lockVotingSize;
  9. ......
  10. }

0x07 定义

DynoJedisClient 定义如下,我们可以看到最重要的成员变量就是连接池ConnectionPool。

  1. public class DynoJedisClient implements JedisCommands, BinaryJedisCommands, MultiKeyCommands,ScriptingCommands, MultiKeyBinaryCommands, DynoJedisCommands {
  2. private final String appName;
  3. private final String clusterName;
  4. private final ConnectionPool<Jedis> connPool;
  5. private final AtomicReference<DynoJedisPipelineMonitor> pipelineMonitor = new AtomicReference<DynoJedisPipelineMonitor>();
  6. protected final DynoOPMonitor opMonitor;
  7. protected final ConnectionPoolMonitor cpMonitor;
  8. }

0x08 逻辑连接池

因为 DynoJedisClient 最主要是管理连接池,所以我们首先介绍 逻辑连接池 ConnectionPoolImpl。

连接池层为应用程序抽象所有连接管理。在这里,我们可以配置所有内容,例如指定池选项,负载平衡策略,重试策略或默认一致性级别。

ConnectionPoolImpl 是核心类,其主要功能是:

  • 对于从HostSupplier获得的各种HostConnectionPool进行维护,形成一个HostConnectionPool集合;
  • 对于HostSupplier检测到的hosts,进行添加删除;
  • 从HostConnectionPool提取Connection,进行Operation的执行;
  • 在执行Operation时,采用HostSelectionStrategy,比如:basically Round Robin 或者 TokenAware策略;
  • 使用health check monitor来进行错误率跟踪。health check monitor可以决定重用HostConnectionPool,以及fallback到remote数据中心的HostConnectionPools执行;
  • 使用RetryPolicy来执行operation;

具体定义如下:

  1. public class ConnectionPoolImpl<CL> implements ConnectionPool<CL>, TopologyView {
  2. private final ConcurrentHashMap<Host, HostConnectionPool<CL>> cpMap = new ConcurrentHashMap<Host, HostConnectionPool<CL>>();
  3. private final ConnectionPoolHealthTracker<CL> cpHealthTracker;
  4. private final HostConnectionPoolFactory<CL> hostConnPoolFactory;
  5. private final ConnectionFactory<CL> connFactory;
  6. private final ConnectionPoolConfiguration cpConfiguration;
  7. private final ConnectionPoolMonitor cpMonitor;
  8. private final ScheduledExecutorService idleThreadPool = Executors.newSingleThreadScheduledExecutor();
  9. private final HostsUpdater hostsUpdater;
  10. private final ScheduledExecutorService connPoolThreadPool = Executors.newScheduledThreadPool(1);
  11. private HostSelectionWithFallback<CL> selectionStrategy;
  12. private Type poolType;
  13. }

此时逻辑如下:

  1. +------------------------+
  2. |DynoJedisClient |
  3. | |
  4. | | +------------------------+
  5. | | | |
  6. | connPool +--------------> | ConnectionPoolImpl |
  7. | | | |
  8. | | +------------------------+
  9. +------------------------+

8.1 启动

连接池 启动逻辑是:

  • 利用hostsUpdater来获取到的host进行配置添加;
  • 启用health check monitor来进行错误率跟踪;

具体如下:

  1. @Override
  2. public Future<Boolean> start() throws DynoException {
  3. HostSupplier hostSupplier = cpConfiguration.getHostSupplier();
  4. HostStatusTracker hostStatus = hostsUpdater.refreshHosts();
  5. cpMonitor.setHostCount(hostStatus.getHostCount());
  6. Collection<Host> hostsUp = hostStatus.getActiveHosts();
  7. final ExecutorService threadPool = Executors.newFixedThreadPool(Math.max(10, hostsUp.size()));
  8. final List<Future<Void>> futures = new ArrayList<Future<Void>>();
  9. // 利用hostsUpdater来获取到的host进行配置添加
  10. for (final Host host : hostsUp) {
  11. // Add host connection pool, but don't init the load balancer yet
  12. futures.add(threadPool.submit(new Callable<Void>() {
  13. @Override
  14. public Void call() throws Exception {
  15. addHost(host, false);
  16. return null;
  17. }
  18. }));
  19. }
  20. // 启用health check monitor来进行错误率跟踪
  21. boolean success = started.compareAndSet(false, true);
  22. if (success) {
  23. selectionStrategy = initSelectionStrategy();
  24. cpHealthTracker.start();
  25. connPoolThreadPool.scheduleWithFixedDelay(new Runnable() {
  26. @Override
  27. public void run() {
  28. HostStatusTracker hostStatus = hostsUpdater.refreshHosts();
  29. cpMonitor.setHostCount(hostStatus.getHostCount());
  30. Logger.debug(hostStatus.toString());
  31. updateHosts(hostStatus.getActiveHosts(), hostStatus.getInactiveHosts());
  32. }
  33. }, 15 * 1000, 30 * 1000, TimeUnit.MILLISECONDS);
  34. MonitorConsole.getInstance().registerConnectionPool(this);
  35. registerMonitorConsoleMBean(MonitorConsole.getInstance());
  36. }
  37. return getEmptyFutureTask(true);
  38. }

8.2 配置Host

启动过程中,添加host逻辑如下:

  • 依据host获取HostConnectionPool;
  • 把HostConnectionPool加入到集合;
  • 把 host,HostConnectionPool加入到选择策略selectionStrategy;
  • 依据host设置health check monitor;

具体如下:

  1. public boolean addHost(Host host, boolean refreshLoadBalancer) {
  2. HostConnectionPool<CL> connPool = cpMap.get(host);
  3. final HostConnectionPool<CL> hostPool = hostConnPoolFactory.createHostConnectionPool(host, this);
  4. HostConnectionPool<CL> prevPool = cpMap.putIfAbsent(host, hostPool);
  5. if (prevPool == null) {
  6. // This is the first time we are adding this pool.
  7. try {
  8. int primed = hostPool.primeConnections();
  9. if (hostPool.isActive()) {
  10. if (refreshLoadBalancer) {
  11. selectionStrategy.addHost(host, hostPool);
  12. }
  13. cpHealthTracker.initializePingHealthchecksForPool(hostPool);
  14. cpMonitor.hostAdded(host, hostPool);
  15. } else {
  16. cpMap.remove(host);
  17. }
  18. return primed > 0;
  19. } catch (DynoException e) {
  20. cpMap.remove(host);
  21. return false;
  22. }
  23. }
  24. }

8.3 获取HostConnectionPool

关于获取HostConnectionPool,有同步和异步 两种实现方式,具体如下。

  1. private class SyncHostConnectionPoolFactory implements HostConnectionPoolFactory<CL> {
  2. @Override
  3. public HostConnectionPool<CL> createHostConnectionPool(Host host, ConnectionPoolImpl<CL> parentPoolImpl) {
  4. return new HostConnectionPoolImpl<CL>(host, connFactory, cpConfiguration, cpMonitor);
  5. }
  6. }
  7. private class AsyncHostConnectionPoolFactory implements HostConnectionPoolFactory<CL> {
  8. @Override
  9. public HostConnectionPool<CL> createHostConnectionPool(Host host, ConnectionPoolImpl<CL> parentPoolImpl) {
  10. return new SimpleAsyncConnectionPoolImpl<CL>(host, connFactory, cpConfiguration, cpMonitor);
  11. }
  12. }

8.4 执行

逻辑连接池 有两种执行方式:executeWithRing 与 executeWithFailover。

  • executeWithRing使用较少,所以不详细介绍。

  • executeWithFailover 是 利用selectionStrategy获取Connection,在此Connection之上进行执行。如果失败就各种重试。

  1. public <R> OperationResult<R> executeWithFailover(Operation<CL, R> op) throws DynoException {
  2. RetryPolicy retry = cpConfiguration.getRetryPolicyFactory().getRetryPolicy();
  3. retry.begin();
  4. do {
  5. Connection<CL> connection = null;
  6. try {
  7. connection = selectionStrategy.getConnectionUsingRetryPolicy(op,
  8. cpConfiguration.getMaxTimeoutWhenExhausted(), TimeUnit.MILLISECONDS, retry);
  9. updateConnectionContext(connection.getContext(), connection.getHost());
  10. OperationResult<R> result = connection.execute(op);
  11. // Add context to the result from the successful execution
  12. result.setNode(connection.getHost()).addMetadata(connection.getContext().getAll());
  13. retry.success();
  14. cpMonitor.incOperationSuccess(connection.getHost(), System.currentTimeMillis() - startTime);
  15. return result;
  16. } finally {
  17. if (connection != null) {
  18. if (connection.getLastException() != null
  19. && connection.getLastException() instanceof FatalConnectionException) {
  20. connection.getParentConnectionPool().recycleConnection(connection);
  21. // note - don't increment connection closed metric here;
  22. // it's done in closeConnection
  23. } else {
  24. connection.getContext().reset();
  25. connection.getParentConnectionPool().returnConnection(connection);
  26. }
  27. }
  28. }
  29. } while (retry.allowRetry());
  30. throw lastException;
  31. }

此时逻辑如下:

  1. +----------------------+
  2. +-------------------+ |ConnectionPoolImpl |
  3. |DynoJedisClient | | |
  4. | | | | +--------------+
  5. | | | hostsUpdater +--------> | HostSupplier |
  6. | | | | +--------------+
  7. | connPool +---------> | |
  8. | | | | +--------------------------+
  9. | | | cpMap +--------> |[Host, HostConnectionPool]|
  10. +-------------------+ | | | + |
  11. +----------------------+ | | |
  12. +--------------------------+
  13. |
  14. |
  15. |
  16. v
  17. +---------------+-----+
  18. | |
  19. | HostConnectionPool |
  20. | |
  21. +---------------------+

0x09 具体连接池

HostConnectionPool 是具体连接池实现,此类为每一个Host节点维护一个有效连接池

具体是:

  • HostConnectionPool 使用 LinkedBlockingQueue availableConnections 来维护所有有效连接,当client需要一个连接,需要从queue中提取。
  • 所以,availableConnections 就是有效连接池。
  • availableConnections 之中每一个 连接就是一个 Connection;
  • 这个 Connection (JedisConnection)是通过 JedisConnectionFactory 建立的;
  • 另外,每一个 JedisConnection 里面有:
    • HostConnectionPool hostPool;
    • Jedis jedisClient;

具体如下:

  1. public class HostConnectionPoolImpl<CL> implements HostConnectionPool<CL> {
  2. // The connections available for this connection pool
  3. private final LinkedBlockingQueue<Connection<CL>> availableConnections = new LinkedBlockingQueue<Connection<CL>>();
  4. // Private members required by this class
  5. private final Host host;
  6. private final ConnectionFactory<CL> connFactory;
  7. private final ConnectionPoolConfiguration cpConfig;
  8. private final ConnectionPoolMonitor monitor;
  9. // states that dictate the behavior of the pool
  10. // cp not inited is the starting state of the pool. The pool will not allow connections to be borrowed in this state
  11. private final ConnectionPoolState<CL> cpNotInited = new ConnectionPoolNotInited();
  12. // cp active is where connections of the pool can be borrowed and returned
  13. private final ConnectionPoolState<CL> cpActive = new ConnectionPoolActive(this);
  14. // cp reconnecting is where connections cannot be borrowed and all returning connections will be shutdown
  15. private final ConnectionPoolState<CL> cpReconnecting = new ConnectionPoolReconnectingOrDown();
  16. // similar to reconnecting
  17. private final ConnectionPoolState<CL> cpDown = new ConnectionPoolReconnectingOrDown();
  18. // The thread safe reference to the pool state
  19. private final AtomicReference<ConnectionPoolState<CL>> cpState = new AtomicReference<ConnectionPoolState<CL>>(cpNotInited);
  20. }

9.1 生成Connection

首先我们要看看 如何生成 Connection,大致就是从 connFactory 中直接获取,然后执行监控等相应操作。

  1. @Override
  2. public Connection<CL> createConnection() {
  3. try {
  4. Connection<CL> connection;
  5. if (cpConfig.isConnectToDatastore()) {
  6. // 具体建立连接操作
  7. connection = connFactory.createConnectionWithDataStore(pool);
  8. } else if (cpConfig.isConnectionPoolConsistencyProvided()) {
  9. connection = connFactory.createConnectionWithConsistencyLevel(pool, cpConfig.getConnectionPoolConsistency());
  10. } else {
  11. connection = connFactory.createConnection(pool);
  12. }
  13. connection.open();
  14. availableConnections.add(connection);
  15. monitor.incConnectionCreated(host);
  16. numActiveConnections.incrementAndGet();
  17. return connection;
  18. }
  19. }

9.2 JedisConnectionFactory

JedisConnectionFactory 的 createConnectionWithDataStore 函数执行了具体 建立连接操作,涉及到 Jedis 很多朋友应该都很熟悉。

简略版代码如下:

  1. public class JedisConnectionFactory implements ConnectionFactory<Jedis> {
  2. private final OperationMonitor opMonitor;
  3. private final SSLSocketFactory sslSocketFactory;
  4. public JedisConnectionFactory(OperationMonitor monitor, SSLSocketFactory sslSocketFactory) {
  5. this.opMonitor = monitor;
  6. this.sslSocketFactory = sslSocketFactory;
  7. }
  8. @Override
  9. public Connection<Jedis> createConnectionWithDataStore(HostConnectionPool<Jedis> pool) {
  10. return new JedisConnection(pool, true);
  11. }
  12. // TODO: raghu compose redisconnection with jedisconnection in it
  13. public class JedisConnection implements Connection<Jedis> {
  14. private final HostConnectionPool<Jedis> hostPool;
  15. private final Jedis jedisClient;
  16. public JedisConnection(HostConnectionPool<Jedis> hostPool, boolean connectDataStore) {
  17. this.hostPool = hostPool;
  18. Host host = hostPool.getHost();
  19. int port = connectDataStore ? host.getDatastorePort() : host.getPort();
  20. if (sslSocketFactory == null) {
  21. JedisShardInfo shardInfo = new JedisShardInfo(host.getHostAddress(), port,
  22. hostPool.getConnectionTimeout(), hostPool.getSocketTimeout(), Sharded.DEFAULT_WEIGHT);
  23. jedisClient = new Jedis(shardInfo);
  24. } else {
  25. JedisShardInfo shardInfo = new JedisShardInfo(host.getHostAddress(), port,
  26. hostPool.getConnectionTimeout(), hostPool.getSocketTimeout(), Sharded.DEFAULT_WEIGHT,
  27. true, sslSocketFactory, new SSLParameters(), null);
  28. jedisClient = new Jedis(shardInfo);
  29. }
  30. }
  31. @Override
  32. public HostConnectionPool<Jedis> getParentConnectionPool() {
  33. return hostPool;
  34. }
  35. public Jedis getClient() {
  36. return jedisClient;
  37. }
  38. }
  39. }

此时逻辑如下:

  1. +----------------------+
  2. +-------------------+ |ConnectionPoolImpl |
  3. |DynoJedisClient | | |
  4. | | | | +--------------+
  5. | | | hostsUpdater +--------> | HostSupplier |
  6. | | | | +--------------+
  7. | connPool +---------> | |
  8. | | | | +--------------------------+
  9. | | | cpMap +--------> |[Host, HostConnectionPool]|
  10. +-------------------+ | | | + |
  11. +----------------------+ | | |
  12. +--------------------------+
  13. |
  14. |
  15. +-----------------------------+ |
  16. | JedisConnectionFactory | v
  17. | | +---------------+-------------------------------------------+
  18. | | createConnectionWithDataStore | HostConnectionPool |
  19. | | | |
  20. | sslSocketFactory | <------------------------------------------------+ connFactory Host |
  21. | | | |
  22. | | | LinkedBlockingQueue<Connection<CL<> availableConnections |
  23. +-----------------------------+ | |
  24. +------------------------------+----------------------------+
  25. + ^
  26. | +----------------------------------------+ |
  27. | |JedisConnection | |
  28. | | | |
  29. | return | | return |
  30. | | HostConnectionPool<Jedis> hostPool | |
  31. +---------------> | | +--------------------------------+
  32. | Jedis(shardInfo) jedisClient |
  33. | |
  34. +----------------------------------------+

手机上如下:

9.3 获取Connection

用户使用 borrowConnection 来得到 连接,并且做监控。

  1. @Override
  2. public Connection<CL> borrowConnection(int duration, TimeUnit unit) {
  3. // Start recording how long it takes to get the connection - for insight/metrics
  4. long startTime = System.nanoTime() / 1000;
  5. Connection<CL> conn = null;
  6. // wait on the connection pool with a timeout
  7. conn = availableConnections.poll(duration, unit);
  8. long delay = System.nanoTime() / 1000 - startTime;
  9. monitor.incConnectionBorrowed(host, delay);
  10. }

0x10 拓扑

这里拓扑主要指的是token环,我们再复习下概念。

在 Dynomite 之中,由集群管理的数据就是一个环。环中的每个节点被分配一个或多个由token描述的数据范围,toekn 可以确定在环中的位置。

Token是用于标识每个分区的64位整数ID,范围是-2^63 -- 2^63-1。通过hash算法计算partition key的hash值,以此确定存放在哪个节点。

Token决定了每个节点存储的数据的分布范围,每个节点保存的数据的key在(前一个节点Token,本节点Token]的半开半闭区间内,所有的节点形成一个首尾相接的环。

10.1 只读视图

TopologyView代表了服务器拓扑的只读视图。

  1. public interface TopologyView {
  2. /**
  3. * Retrieves a read-only view of the server topology
  4. *
  5. * @return An unmodifiable map of server-id to list of token status
  6. */
  7. Map<String, List<TokenPoolTopology.TokenStatus>> getTopologySnapshot();
  8. /**
  9. * Returns the token for the given key.
  10. *
  11. * @param key The key of the record stored in dynomite
  12. * @return Long The token that owns the given key
  13. */
  14. Long getTokenForKey(String key);
  15. }

ConnectionPoolImpl 实现了 TopologyView,即 implements TopologyView

所以 ConnectionPoolImpl 本身就是一个 TopologyView。

  1. public class ConnectionPoolImpl<CL> implements ConnectionPool<CL>, TopologyView {
  2. public TokenPoolTopology getTopology() {
  3. return selectionStrategy.getTokenPoolTopology();
  4. }
  5. @Override
  6. public Map<String, List<TokenPoolTopology.TokenStatus>> getTopologySnapshot() {
  7. return Collections.unmodifiableMap(selectionStrategy.getTokenPoolTopology().getAllTokens());
  8. }
  9. @Override
  10. public Long getTokenForKey(String key) {
  11. if (cpConfiguration
  12. .getLoadBalancingStrategy() == ConnectionPoolConfiguration.LoadBalancingStrategy.TokenAware) {
  13. return selectionStrategy.getTokenForKey(key);
  14. }
  15. return null;
  16. }
  17. }

在 DynoJedisClient 中获取 TopologyView 就是直接 获取了 ConnectionPoolImpl。

  1. public TopologyView getTopologyView() {
  2. return this.getConnPool();
  3. }

所以此时逻辑图上加入了 TopologyView 。

  1. +----------------------+
  2. +-------------------+ |ConnectionPoolImpl |
  3. |DynoJedisClient | | |
  4. | | | | +--------------+
  5. | | | hostsUpdater +--------> | HostSupplier |
  6. | | | | +--------------+
  7. | connPool +---------> | |
  8. | | | | +--------------------------+
  9. | TopologyView +------> | cpMap +--------> |[Host, HostConnectionPool]|
  10. | | | | | + |
  11. +-------------------+ +----------------------+ | | |
  12. +--------------------------+
  13. |
  14. |
  15. +-----------------------------+ |
  16. | JedisConnectionFactory | v
  17. | | +---------------+-------------------------------------------+
  18. | | createConnectionWithDataStore | HostConnectionPool |
  19. | | | |
  20. | sslSocketFactory | <------------------------------------------------+ connFactory Host |
  21. | | | |
  22. | | | LinkedBlockingQueue<Connection<CL<> availableConnections |
  23. +-----------------------------+ | |
  24. +------------------------------+----------------------------+
  25. + ^
  26. | +----------------------------------------+ |
  27. | |JedisConnection | |
  28. | | | |
  29. | return | | return |
  30. | | HostConnectionPool<Jedis> hostPool | |
  31. +---------------> | | +--------------------------------+
  32. | Jedis(shardInfo) jedisClient |
  33. | |
  34. +----------------------------------------+

手机如下:

10.2 具体实现

TokenPoolTopology 属于 拓扑 的具体实现。

getTopologySnapshot就是return map。就是得到对应了所有 rack 的 TokenStatus,这就是拓扑。

其实大家仔细想想就可以理解,拓扑不就是 “当前所有机架上分别有哪些东西,这些东西是什么状态" 的一个逻辑集合嘛

具体定义如下,其核心成员是两个:

  • map 可以理解为 rack 作为key,value 是一个list,即 "该 rack 上对应的 token status 被整理成 list";
  • rackTokenHostMap 可以理解为 rack 作为 key,value 是一个map,即 "该 rack 上的 token status <---> host 之间的关系被整理成一个 map";

这样就有两个不同维度可以分别处理这些 token了。

  1. public class TokenPoolTopology {
  2. private final ConcurrentHashMap<String, List<TokenStatus>> map = new ConcurrentHashMap<String, List<TokenStatus>>();
  3. private final ConcurrentHashMap<String, Map<Long, Host>> rackTokenHostMap = new ConcurrentHashMap<String, Map<Long, Host>>();
  4. public ConcurrentHashMap<String, List<TokenStatus>> getAllTokens() {
  5. return map;
  6. }
  7. public void addToken(String rack, Long token, HostConnectionPool<?> hostPool) {
  8. List<TokenStatus> list = map.get(rack);
  9. if (list == null) {
  10. list = new ArrayList<TokenStatus>();
  11. map.put(rack, list);
  12. }
  13. list.add(new TokenStatus(token, hostPool));
  14. }
  15. public void addHostToken(String rack, Long token, Host host) {
  16. Map<Long, Host> tokenHostMap = rackTokenHostMap.get(rack);
  17. if (tokenHostMap == null) {
  18. tokenHostMap = new HashMap<>();
  19. rackTokenHostMap.put(rack, tokenHostMap);
  20. }
  21. tokenHostMap.put(token, host);
  22. }
  23. }

10.3 如何使用

TokenPoolTopology 具体在 ConnectionPoolImpl 和 HostSelectionWithFallback 都有使用

10.3.1 ConnectionPoolImpl

ConnectionPoolImpl中如下处理,或者直接返回由上层再处理,或者就是直接返回 TokenPoolTopology 之中的所有 token 给上层:

  1. public TokenPoolTopology getTopology() {
  2. return selectionStrategy.getTokenPoolTopology();
  3. }
  4. public Map<String, List<TokenPoolTopology.TokenStatus>> getTopologySnapshot() {
  5. return Collections.unmodifiableMap(selectionStrategy.getTokenPoolTopology().getAllTokens());
  6. }

10.3.2 HostSelectionWithFallback

HostSelectionWithFallback中也有TokenPoolTopology的使用,只是用来 failover/fallback使用。

  1. public class HostSelectionWithFallback<CL> {
  2. // Represents the *initial* topology from the token supplier. This does not affect selection of a host connection
  3. // pool for traffic. It only affects metrics such as failover/fallback
  4. private final AtomicReference<TokenPoolTopology> topology = new AtomicReference<>(null);
  5. }

HostSelectionWithFallback中 也利用 host tokens 来建立或者更新已有的 TokenPoolTopology。

  1. /**
  2. * Create token pool topology from the host tokens
  3. *
  4. * @param allHostTokens
  5. * @return tokenPoolTopology with the host information
  6. */
  7. public TokenPoolTopology createTokenPoolTopology(List<HostToken> allHostTokens) {
  8. TokenPoolTopology topology = new TokenPoolTopology(replicationFactor.get());
  9. for (HostToken hostToken : allHostTokens) {
  10. String rack = hostToken.getHost().getRack();
  11. topology.addHostToken(rack, hostToken.getToken(), hostToken.getHost());
  12. }
  13. updateTokenPoolTopology(topology);
  14. return topology;
  15. }

至此,连接管理和拓扑感知部分已经分析完毕,下文将继续分析自动发现和故障转移。

0xFF 参考

Cassandra系列(二):系统流程

Cassandra JAVA客户端是如何做到高性能高并发的

Cassandra之Token

http://www.ningoo.net/html/2010/cassandra_token.html

cassandra权威指南读书笔记--客户端

关于cassandra集群的数据一致性问题

[源码分析] Dynomite 分布式存储引擎 之 DynoJedisClient(1)的更多相关文章

  1. [源码分析] Dynomite 分布式存储引擎 之 DynoJedisClient(2)

    [源码分析] Dynomite 分布式存储引擎 之 DynoJedisClient(2) 目录 [源码分析] Dynomite 分布式存储引擎 之 DynoJedisClient(2) 0x00 摘要 ...

  2. Nmap源码分析(脚本引擎)

    Nmap提供了强大的脚本引擎(NSE),以支持通过Lua编程来扩展Nmap的功能.目前脚本库已经包含300多个常用的Lua脚本,辅助完成Nmap的主机发现.端口扫描.服务侦测.操作系统侦测四个基本功能 ...

  3. tair源码分析——leveldb存储引擎使用

    分析完leveldb以后,接下来的时间准备队tair的源码进行阅读和分析.我们刚刚分析完了leveldb而在tair中leveldb是其几大存储引擎之一,所以我们这里首先从tair对leveldb的使 ...

  4. Mysql源码分析--csv存储引擎

    一直想分析下mysql的源码,开始的时候不知道从哪下手,先从csv的文件存储开始吧,这个还是比较简单的.我是用的是mysql5.7.16版本的源码. csv源码文件在mysql源码的mysql-5.7 ...

  5. phpcms 源码分析七: 模板引擎实现

    这次是逆雪寒对模板引擎实现的分析: 1 /* 函数 template函数是在global.func.php 里面定义的. 在前面的phpcms 的首页 index.php 里就见到了. 用法: inc ...

  6. sqlalchemy 源码分析之create_engine引擎的创建

    引擎是sqlalchemy的核心,不管是 sql core 还是orm的使用都需要依赖引擎的创建,为此我们研究下,引擎是如何创建的. from sqlalchemy import create_eng ...

  7. jQuery 2.0.3 源码分析Sizzle引擎解析原理

    jQuery 2.0.3 源码分析Sizzle引擎 - 解析原理 声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 先来回答博友的提问: 如何解析 div > p + ...

  8. quartz源码分析——执行引擎和线程模型

    title: quartz源码分析--执行引擎和线程模型 date: 2017-09-09 23:14:48 categories: quartz tags: [quartz, 源码分析] --- - ...

  9. 分布式存储Seaweedfs源码分析

    基于源码版本号 0.67 , [Seaweedfs以前旧版叫Weedfs]. Seaweedfs 是一个非常优秀的由 golang 开发的分布式存储开源项目, 虽然在我刚开始关注的时候它在 githu ...

随机推荐

  1. element-ui使用后手记

    一.路由模式el-menu中使用路由模式 在el-meun中设置:router="true" 在el-menu-item中设置index="路由地址"

  2. exchangeNetwork

    泛洪(Flooding) 转发(Forwarding) 丢弃(Discarding) 交换机中有一个MAC地址表,里面存放了MAC地址与交换机的映射关系.MAC地址表也称为CAM(Content Ad ...

  3. NOIP初赛篇——09原码、反码和补码

    一.数的原码.补码和反码表示 机器数和真值 ​ 在计算机中,表示数值的数字符号只有0和1两个数码,我们规定最高位为符号位,并用0表示正符号,用1表示负符号.这样,机器中的数值和符号全"数码化 ...

  4. M43 第一阶段考试

    一.解答题 1.统计当前主机的TCP协议网络各种连接状态出现的次数 netstat -an | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a ...

  5. 如何实现一个简易版的 Spring - 如何实现 Constructor 注入

    前言 本文是「如何实现一个简易版的 Spring」系列的第二篇,在 第一篇 介绍了如何实现一个基于 XML 的简单 Setter 注入,这篇来看看要如何去实现一个简单的 Constructor 注入功 ...

  6. windows下使用mingw和msvc静态编译Qt5.15.xx

    windows下使用mingw和msvc静态编译Qt5.15.xx 下载并安装相关依赖软件 Python version 2.7 https://www.python.org/downloads/ ( ...

  7. 【Oracle】迁移表到其他的表空间

    有些时候需要将表迁移到其他的表空间,在将表空间做相关的操作 下面是命令如何迁移表空间 SQL> alter table 表名 move tablespace 表空间名; 如果有很多的表想要迁移的 ...

  8. 性能测试WAS内存使用的探索和分析

    性能测试中,CPU和内存是关注最多的两个性能指标.以我行应用最多的系统架构(WAS+Oracle)来说,CPU使用率高的问题多发生于数据库,比如索引不当引发的表扫描.绑定变量使用不当引发的硬解析.连接 ...

  9. 深度学习论文翻译解析(十七):MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications

    论文标题:MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications 论文作者:Andrew ...

  10. (12)-Python3之--openpyxl模块

    1.安装 pip install openpyxl 2.Excel操作的流程 1.打开excel,进入工作薄 workbook2.选择表单 Sheet 3.单元格  Cell4.读写操作 3.Exce ...