def _maybe_connect(self, node_id): """Idempotent non-blocking connection attempt to the given node id.""" with self._lock: conn = self._conns.get(node_id) if conn is None: broker = self.cluster.broker_metadata(node_id) assert broker, 'Broker id %s not in current metadata' % (node_id,) log.debug("Initiating connection to node %s at %s:%s", node_id, broker.host, broker.port) host, port, afi = get_ip_port_afi(broker.host) cb = functools.partial(WeakMethod(self._conn_state_change), node_id) conn = BrokerConnection(host, broker.port, afi, state_change_callback=cb, node_id=node_id, **self.config) self._conns[node_id] = conn # Check if existing connection should be recreated because host/port changed elif self._should_recycle_connection(conn): self._conns.pop(node_id) return False elif conn.connected(): return True conn.connect() return conn.connected()
def test_init(conn): cli = KafkaClient() coordinator = ConsumerCoordinator(cli, SubscriptionState()) # metadata update on init assert cli.cluster._need_update is True assert WeakMethod(coordinator._handle_metadata_update) in cli.cluster._listeners
def _bootstrap(self, hosts): log.info('Bootstrapping cluster metadata from %s', hosts) # Exponential backoff if bootstrap fails backoff_ms = self.config[ 'reconnect_backoff_ms'] * 2**self._bootstrap_fails next_at = self._last_bootstrap + backoff_ms / 1000.0 self._refresh_on_disconnects = False now = time.time() if next_at > now: log.debug("Sleeping %0.4f before bootstrapping again", next_at - now) time.sleep(next_at - now) self._last_bootstrap = time.time() if self.config['api_version'] is None or self.config['api_version'] < ( 0, 10): metadata_request = MetadataRequest[0]([]) else: metadata_request = MetadataRequest[1](None) for host, port, afi in hosts: log.debug("Attempting to bootstrap via node at %s:%s", host, port) cb = functools.partial(WeakMethod(self._conn_state_change), 'bootstrap') bootstrap = BrokerConnection(host, port, afi, state_change_callback=cb, node_id='bootstrap', **self.config) if not bootstrap.connect_blocking(): bootstrap.close() continue future = bootstrap.send(metadata_request) while not future.is_done: self._selector.select(1) for r, f in bootstrap.recv(): f.success(r) if future.failed(): bootstrap.close() continue self.cluster.update_metadata(future.value) log.info('Bootstrap succeeded: found %d brokers and %d topics.', len(self.cluster.brokers()), len(self.cluster.topics())) # A cluster with no topics can return no broker metadata # in that case, we should keep the bootstrap connection if not len(self.cluster.brokers()): self._conns['bootstrap'] = bootstrap else: bootstrap.close() self._bootstrap_fails = 0 break # No bootstrap found... else: log.error('Unable to bootstrap from %s', hosts) # Max exponential backoff is 2^12, x4000 (50ms -> 200s) self._bootstrap_fails = min(self._bootstrap_fails + 1, 12) self._refresh_on_disconnects = True
def __init__(self, *args, **kwargs): if len(args) == len(self.SCHEMA.fields): for i, name in enumerate(self.SCHEMA.names): self.__dict__[name] = args[i] elif len(args) > 0: raise ValueError('Args must be empty or mirror schema') else: for name in self.SCHEMA.names: self.__dict__[name] = kwargs.pop(name, None) if kwargs: raise ValueError('Keyword(s) not in schema %s: %s' % (list(self.SCHEMA.names), ', '.join(kwargs.keys()))) # overloading encode() to support both class and instance # Without WeakMethod() this creates circular ref, which # causes instances to "leak" to garbage self.encode = WeakMethod(self._encode_self)
def _maybe_connect(self, node_id): """Idempotent non-blocking connection attempt to the given node id.""" with self._lock: broker = self.cluster.broker_metadata(node_id) conn = self._conns.get(node_id) if conn is None: assert broker, 'Broker id %s not in current metadata' % ( node_id, ) log.debug("Initiating connection to node %s at %s:%s", node_id, broker.host, broker.port) host, port, afi = get_ip_port_afi(broker.host) cb = functools.partial(WeakMethod(self._conn_state_change), node_id) conn = BrokerConnection(host, broker.port, afi, state_change_callback=cb, node_id=node_id, **self.config) self._conns[node_id] = conn # Check if existing connection should be recreated because host/port changed elif conn.disconnected() and broker is not None: host, _, __ = get_ip_port_afi(broker.host) if conn.host != host or conn.port != broker.port: log.info( "Broker metadata change detected for node %s" " from %s:%s to %s:%s", node_id, conn.host, conn.port, broker.host, broker.port) # Drop old connection object. # It will be recreated on next _maybe_connect self._conns.pop(node_id) return False elif conn.connected(): return True conn.connect() return conn.connected()
def __init__(self, value, key=None, magic=0, attributes=0, crc=0, timestamp=None): assert value is None or isinstance(value, bytes), 'value must be bytes' assert key is None or isinstance(key, bytes), 'key must be bytes' assert magic > 0 or timestamp is None, 'timestamp not supported in v0' # Default timestamp to now for v1 messages if magic > 0 and timestamp is None: timestamp = int(time.time() * 1000) self.timestamp = timestamp self.crc = crc self._validated_crc = None self.magic = magic self.attributes = attributes self.key = key self.value = value self.encode = WeakMethod(self._encode_self)
def _maybe_connect(self, node_id): """Idempotent non-blocking connection attempt to the given node id.""" with self._lock: conn = self._conns.get(node_id) if conn is None: # Note that when bootstrapping, each call to broker_metadata may # return a different host/port. So we need to be careful to only # call when necessary to avoid skipping some possible bootstrap # source. broker = self.cluster.broker_metadata(node_id) assert broker, 'Broker id %s not in current metadata' % ( node_id, ) log.debug("Initiating connection to node %s at %s:%s", node_id, broker.host, broker.port) host, port, afi = get_ip_port_afi(broker.host) cb = functools.partial(WeakMethod(self._conn_state_change), node_id) conn = BrokerConnection(host, broker.port, afi, state_change_callback=cb, node_id=node_id, **self.config) self._conns[node_id] = conn # Check if existing connection should be recreated because host/port changed elif self._should_recycle_connection(conn): self._conns.pop(node_id) return False elif conn.connected(): return True conn.connect() return conn.connected()
def __init__(self, client, subscription, metrics, **configs): """Initialize the coordination manager. Keyword Arguments: group_id (str): name of the consumer group to join for dynamic partition assignment (if enabled), and to use for fetching and committing offsets. Default: 'kafka-python-default-group' enable_auto_commit (bool): If true the consumer's offset will be periodically committed in the background. Default: True. auto_commit_interval_ms (int): milliseconds between automatic offset commits, if enable_auto_commit is True. Default: 5000. default_offset_commit_callback (callable): called as callback(offsets, exception) response will be either an Exception or None. This callback can be used to trigger custom actions when a commit request completes. assignors (list): List of objects to use to distribute partition ownership amongst consumer instances when group management is used. Default: [RangePartitionAssignor, RoundRobinPartitionAssignor] heartbeat_interval_ms (int): The expected time in milliseconds between heartbeats to the consumer coordinator when using Kafka's group management feature. Heartbeats are used to ensure that the consumer's session stays active and to facilitate rebalancing when new consumers join or leave the group. The value must be set lower than session_timeout_ms, but typically should be set no higher than 1/3 of that value. It can be adjusted even lower to control the expected time for normal rebalances. Default: 3000 session_timeout_ms (int): The timeout used to detect failures when using Kafka's group management facilities. Default: 30000 retry_backoff_ms (int): Milliseconds to backoff when retrying on errors. Default: 100. exclude_internal_topics (bool): Whether records from internal topics (such as offsets) should be exposed to the consumer. If set to True the only way to receive records from an internal topic is subscribing to it. Requires 0.10+. Default: True """ super(ConsumerCoordinator, self).__init__(client, metrics, **configs) self.config = copy.copy(self.DEFAULT_CONFIG) for key in self.config: if key in configs: self.config[key] = configs[key] self._subscription = subscription self._is_leader = False self._joined_subscription = set() self._metadata_snapshot = self._build_metadata_snapshot( subscription, client.cluster) self._assignment_snapshot = None self._cluster = client.cluster self.auto_commit_interval = self.config[ "auto_commit_interval_ms"] / 1000 self.next_auto_commit_deadline = None self.completed_offset_commits = collections.deque() if self.config["default_offset_commit_callback"] is None: self.config[ "default_offset_commit_callback"] = self._default_offset_commit_callback if self.config["group_id"] is not None: if self.config["api_version"] >= (0, 9): if not self.config["assignors"]: raise Errors.KafkaConfigurationError( "Coordinator requires assignors") if self.config["api_version"] < (0, 10, 1): if (self.config["max_poll_interval_ms"] != self.config["session_timeout_ms"]): raise Errors.KafkaConfigurationError( "Broker version %s does not support " "different values for max_poll_interval_ms " "and session_timeout_ms") if self.config["enable_auto_commit"]: if self.config["api_version"] < (0, 8, 1): log.warning( "Broker version (%s) does not support offset" " commits; disabling auto-commit.", self.config["api_version"], ) self.config["enable_auto_commit"] = False elif self.config["group_id"] is None: log.warning("group_id is None: disabling auto-commit.") self.config["enable_auto_commit"] = False else: self.next_auto_commit_deadline = time.time( ) + self.auto_commit_interval self.consumer_sensors = ConsumerCoordinatorMetrics( metrics, self.config["metric_group_prefix"], self._subscription) self._cluster.request_update() self._cluster.add_listener(WeakMethod(self._handle_metadata_update))
def __del__(self): if hasattr(self, "_cluster") and self._cluster: self._cluster.remove_listener( WeakMethod(self._handle_metadata_update)) super(ConsumerCoordinator, self).__del__()
def test_init(client, coordinator): # metadata update on init assert client.cluster._need_update is True assert WeakMethod(coordinator._handle_metadata_update) in client.cluster._listeners