This is an automated email from the ASF dual-hosted git repository.
ptupitsyn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new c5486a37c1c IGNITE-25868 Add client name configuration (#6645)
c5486a37c1c is described below
commit c5486a37c1c4c85efebebc944dba411cfdfb7cb7
Author: Pavel Tupitsyn <[email protected]>
AuthorDate: Fri Sep 26 07:23:28 2025 +0300
IGNITE-25868 Add client name configuration (#6645)
* Add `IgniteClientConfiguration#name`
* Propagate to `Ignite#name` and JMX bean name
* Generate with `AtomicLong` if not specified by the user, for example:
"client_1", "jdbc_client_2"
* Fixes JMX conflict when multiple clients exist in one JVM
---
.../org/apache/ignite/client/IgniteClient.java | 22 +++++++-
.../ignite/client/IgniteClientConfiguration.java | 12 ++++
.../client/IgniteClientConfigurationImpl.java | 22 ++++++--
.../ignite/internal/client/TcpIgniteClient.java | 25 +++++++--
.../apache/ignite/client/ClientMetricsTest.java | 65 +++++++++++++++++++++-
.../apache/ignite/client/ConfigurationTest.java | 1 +
.../org/apache/ignite/client/RetryPolicyTest.java | 2 +-
.../apache/ignite/client/TestLoggerFactory.java | 5 ++
.../ignite/internal/jdbc/JdbcConnection.java | 5 +-
.../ignite/internal/metrics/MetricManagerImpl.java | 8 ++-
10 files changed, 147 insertions(+), 20 deletions(-)
diff --git
a/modules/client/src/main/java/org/apache/ignite/client/IgniteClient.java
b/modules/client/src/main/java/org/apache/ignite/client/IgniteClient.java
index 92a0e254464..099833a59f1 100644
--- a/modules/client/src/main/java/org/apache/ignite/client/IgniteClient.java
+++ b/modules/client/src/main/java/org/apache/ignite/client/IgniteClient.java
@@ -113,6 +113,8 @@ public interface IgniteClient extends Ignite, AutoCloseable
{
private int sqlPartitionAwarenessMetadataCacheSize =
DFLT_SQL_PARTITION_AWARENESS_METADATA_CACHE_SIZE;
+ private @Nullable String name;
+
/**
* Sets the addresses of Ignite server nodes within a cluster. An
address can be an IP address or a hostname, with or without port.
* If port is not set then Ignite will use the default one - see
{@link IgniteClientConfiguration#DFLT_PORT}.
@@ -387,6 +389,23 @@ public interface IgniteClient extends Ignite,
AutoCloseable {
return this;
}
+ /**
+ * Sets the client name. Default is {@code null}, which means that
Ignite will generate a unique name automatically.
+ *
+ * <p>Client name is used for identifying clients in JMX metrics. The
name is only used locally and is not sent to the server.
+ *
+ * <p>If multiple clients with the same exist in the same JVM, JMX
metrics will be exposed only for one of them.
+ * Others will log an error.
+ *
+ * @param name Client name.
+ * @return This instance.
+ */
+ public Builder name(@Nullable String name) {
+ this.name = name;
+
+ return this;
+ }
+
/**
* Builds the client.
*
@@ -416,7 +435,8 @@ public interface IgniteClient extends Ignite, AutoCloseable
{
metricsEnabled,
authenticator,
operationTimeout,
- sqlPartitionAwarenessMetadataCacheSize
+ sqlPartitionAwarenessMetadataCacheSize,
+ name
);
return TcpIgniteClient.startAsync(cfg);
diff --git
a/modules/client/src/main/java/org/apache/ignite/client/IgniteClientConfiguration.java
b/modules/client/src/main/java/org/apache/ignite/client/IgniteClientConfiguration.java
index 98e48cf60ad..ef708867caa 100644
---
a/modules/client/src/main/java/org/apache/ignite/client/IgniteClientConfiguration.java
+++
b/modules/client/src/main/java/org/apache/ignite/client/IgniteClientConfiguration.java
@@ -210,4 +210,16 @@ public interface IgniteClientConfiguration {
* @return Cache size, in number of entries.
*/
int sqlPartitionAwarenessMetadataCacheSize();
+
+ /**
+ * Gets the client name. Default is {@code null}, which means that Ignite
will generate a unique name automatically.
+ *
+ * <p>Client name is used for identifying clients in JMX metrics. The name
is only used locally and is not sent to the server.
+ *
+ * <p>If multiple clients with the same exist in the same JVM, JMX metrics
will be exposed only for one of them.
+ * Others will log an error.
+ *
+ * @return Client name.
+ */
+ @Nullable String name();
}
diff --git
a/modules/client/src/main/java/org/apache/ignite/internal/client/IgniteClientConfigurationImpl.java
b/modules/client/src/main/java/org/apache/ignite/internal/client/IgniteClientConfigurationImpl.java
index 9c95a95af05..2be085ee1ea 100644
---
a/modules/client/src/main/java/org/apache/ignite/internal/client/IgniteClientConfigurationImpl.java
+++
b/modules/client/src/main/java/org/apache/ignite/internal/client/IgniteClientConfigurationImpl.java
@@ -31,7 +31,7 @@ import org.jetbrains.annotations.Nullable;
*/
public final class IgniteClientConfigurationImpl implements
IgniteClientConfiguration {
/** Address finder. */
- private final IgniteClientAddressFinder addressFinder;
+ private final @Nullable IgniteClientAddressFinder addressFinder;
/** Addresses. */
private final String[] addresses;
@@ -43,7 +43,7 @@ public final class IgniteClientConfigurationImpl implements
IgniteClientConfigur
private final long backgroundReconnectInterval;
/** Async continuation executor. */
- private final Executor asyncContinuationExecutor;
+ private final @Nullable Executor asyncContinuationExecutor;
/** Heartbeat interval. */
private final long heartbeatInterval;
@@ -66,6 +66,8 @@ public final class IgniteClientConfigurationImpl implements
IgniteClientConfigur
private final int sqlPartitionAwarenessMetadataCacheSize;
+ private final @Nullable String name;
+
/**
* Constructor.
*
@@ -83,13 +85,14 @@ public final class IgniteClientConfigurationImpl implements
IgniteClientConfigur
* @param authenticator Authenticator.
* @param operationTimeout Operation timeout.
* @param sqlPartitionAwarenessMetadataCacheSize Size of the cache to
store partition awareness metadata.
+ * @param name Client name.
*/
public IgniteClientConfigurationImpl(
- IgniteClientAddressFinder addressFinder,
+ @Nullable IgniteClientAddressFinder addressFinder,
String[] addresses,
long connectTimeout,
long backgroundReconnectInterval,
- Executor asyncContinuationExecutor,
+ @Nullable Executor asyncContinuationExecutor,
long heartbeatInterval,
long heartbeatTimeout,
@Nullable RetryPolicy retryPolicy,
@@ -98,7 +101,8 @@ public final class IgniteClientConfigurationImpl implements
IgniteClientConfigur
boolean metricsEnabled,
@Nullable IgniteClientAuthenticator authenticator,
long operationTimeout,
- int sqlPartitionAwarenessMetadataCacheSize
+ int sqlPartitionAwarenessMetadataCacheSize,
+ @Nullable String name
) {
this.addressFinder = addressFinder;
@@ -117,11 +121,12 @@ public final class IgniteClientConfigurationImpl
implements IgniteClientConfigur
this.authenticator = authenticator;
this.operationTimeout = operationTimeout;
this.sqlPartitionAwarenessMetadataCacheSize =
sqlPartitionAwarenessMetadataCacheSize;
+ this.name = name;
}
/** {@inheritDoc} */
@Override
- public IgniteClientAddressFinder addressesFinder() {
+ public @Nullable IgniteClientAddressFinder addressesFinder() {
return addressFinder;
}
@@ -200,4 +205,9 @@ public final class IgniteClientConfigurationImpl implements
IgniteClientConfigur
public int sqlPartitionAwarenessMetadataCacheSize() {
return sqlPartitionAwarenessMetadataCacheSize;
}
+
+ @Override
+ public @Nullable String name() {
+ return name;
+ }
}
diff --git
a/modules/client/src/main/java/org/apache/ignite/internal/client/TcpIgniteClient.java
b/modules/client/src/main/java/org/apache/ignite/internal/client/TcpIgniteClient.java
index a46dbe2ebce..8b1c206fde5 100644
---
a/modules/client/src/main/java/org/apache/ignite/internal/client/TcpIgniteClient.java
+++
b/modules/client/src/main/java/org/apache/ignite/internal/client/TcpIgniteClient.java
@@ -22,6 +22,7 @@ import static
org.apache.ignite.lang.ErrorGroups.Client.CONNECTION_ERR;
import java.util.List;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicLong;
import org.apache.ignite.catalog.IgniteCatalog;
import org.apache.ignite.client.IgniteClient;
import org.apache.ignite.client.IgniteClientConfiguration;
@@ -41,6 +42,7 @@ import
org.apache.ignite.internal.marshaller.ReflectionMarshallersProvider;
import org.apache.ignite.internal.metrics.MetricManager;
import org.apache.ignite.internal.metrics.MetricManagerImpl;
import org.apache.ignite.internal.metrics.exporters.jmx.JmxExporter;
+import org.apache.ignite.internal.tostring.S;
import org.apache.ignite.lang.ErrorGroups;
import org.apache.ignite.lang.IgniteException;
import org.apache.ignite.network.ClusterNode;
@@ -56,6 +58,8 @@ import org.jetbrains.annotations.TestOnly;
* Implementation of {@link IgniteClient} over TCP protocol.
*/
public class TcpIgniteClient implements IgniteClient {
+ private static final AtomicLong GLOBAL_CONN_ID_GEN = new AtomicLong();
+
/** Configuration. */
private final IgniteClientConfiguration cfg;
@@ -86,11 +90,11 @@ public class TcpIgniteClient implements IgniteClient {
/** Cluster. */
private final ClientCluster cluster;
- /**
- * Cluster name.
- */
+ /** Cluster name. */
private String clusterName;
+ private final String clientName;
+
/**
* Constructor.
*
@@ -114,6 +118,11 @@ public class TcpIgniteClient implements IgniteClient {
this.cfg = cfg;
+ String cfgName = cfg.name();
+ clientName = cfgName != null
+ ? cfgName
+ : "client_" + GLOBAL_CONN_ID_GEN.incrementAndGet(); // Use
underscores for JMX compat.
+
metrics = new ClientMetricSource();
ch = new ReliableChannel(chFactory, cfg, metrics,
observableTimeTracker);
tables = new ClientTables(ch, marshallers,
cfg.sqlPartitionAwarenessMetadataCacheSize());
@@ -130,7 +139,7 @@ public class TcpIgniteClient implements IgniteClient {
return null;
}
- var metricManager = new MetricManagerImpl(ClientUtils.logger(cfg,
MetricManagerImpl.class));
+ var metricManager = new MetricManagerImpl(ClientUtils.logger(cfg,
MetricManagerImpl.class), clientName);
metricManager.registerSource(metrics);
metricManager.enable(metrics);
@@ -234,7 +243,7 @@ public class TcpIgniteClient implements IgniteClient {
/** {@inheritDoc} */
@Override
public String name() {
- return "thin-client";
+ return clientName;
}
/** {@inheritDoc} */
@@ -249,6 +258,12 @@ public class TcpIgniteClient implements IgniteClient {
return ch.connections();
}
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return S.toString(TcpIgniteClient.class.getSimpleName(), "name",
clientName, "clusterName", clusterName);
+ }
+
/**
* Returns the name of the cluster to which this client is connected to.
*
diff --git
a/modules/client/src/test/java/org/apache/ignite/client/ClientMetricsTest.java
b/modules/client/src/test/java/org/apache/ignite/client/ClientMetricsTest.java
index 5e7b125f7b7..6eeb24bda12 100644
---
a/modules/client/src/test/java/org/apache/ignite/client/ClientMetricsTest.java
+++
b/modules/client/src/test/java/org/apache/ignite/client/ClientMetricsTest.java
@@ -67,10 +67,11 @@ import org.junit.jupiter.params.provider.ValueSource;
public class ClientMetricsTest extends BaseIgniteAbstractTest {
private TestServer server;
private IgniteClient client;
+ private IgniteClient client2;
@AfterEach
public void afterEach() throws Exception {
- closeAll(client, server);
+ closeAll(client2, client, server);
}
@Test
@@ -165,6 +166,7 @@ public class ClientMetricsTest extends
BaseIgniteAbstractTest {
() -> "handshakesFailedTimeout: " +
metrics().handshakesFailedTimeout());
}
+ @SuppressWarnings("resource")
@Test
public void testRequestsMetrics() throws InterruptedException {
Function<Integer, Boolean> shouldDropConnection = requestIdx ->
requestIdx == 5;
@@ -304,10 +306,10 @@ public class ClientMetricsTest extends
BaseIgniteAbstractTest {
@ValueSource(booleans = {true, false})
public void testJmxExport(boolean metricsEnabled) throws Exception {
server = AbstractClientTest.startServer(1000, new FakeIgnite());
- client = clientBuilder().metricsEnabled(metricsEnabled).build();
+ client =
clientBuilder().metricsEnabled(metricsEnabled).name("testJmxExport").build();
client.tables().tables();
- String beanName = "org.apache.ignite:type=metrics,name=client";
+ String beanName =
"org.apache.ignite:nodeName=testJmxExport,type=metrics,name=client";
MBeanServer mbeanSrv = ManagementFactory.getPlatformMBeanServer();
ObjectName objName = new ObjectName(beanName);
@@ -331,6 +333,63 @@ public class ClientMetricsTest extends
BaseIgniteAbstractTest {
assertEquals("java.lang.Long", attribute.getType());
}
+ @Test
+ public void testJmxExportTwoClients() throws Exception {
+ server = AbstractClientTest.startServer(1000, new FakeIgnite());
+
+ // Client names are auto-generated, unless explicitly specified.
+ client = clientBuilder().metricsEnabled(true).build();
+ client2 = clientBuilder().metricsEnabled(true).build();
+
+ client.tables().tables();
+ client2.tables().tables();
+
+ for (var clientName : new String[]{client.name(), client2.name()}) {
+ String beanName = "org.apache.ignite:nodeName=" + clientName +
",type=metrics,name=client";
+ MBeanServer mbeanSrv = ManagementFactory.getPlatformMBeanServer();
+
+ ObjectName objName = new ObjectName(beanName);
+ boolean registered = mbeanSrv.isRegistered(objName);
+
+ assertTrue(registered, "Unexpected MBean state: [name=" + beanName
+ ", registered=" + registered + ']');
+
+ DynamicMBean bean =
MBeanServerInvocationHandler.newProxyInstance(mbeanSrv, objName,
DynamicMBean.class, false);
+ assertEquals(1L, bean.getAttribute("ConnectionsActive"));
+ assertEquals(1L, bean.getAttribute("ConnectionsEstablished"));
+ }
+ }
+
+ @Test
+ public void testJmxExportTwoClientsSameName() throws Exception {
+ server = AbstractClientTest.startServer(1000, new FakeIgnite());
+
+ var loggerFactory1 = new TestLoggerFactory("client1");
+ var loggerFactory2 = new TestLoggerFactory("client2");
+
+ String clientName = "testJmxExportTwoClientsSameName";
+ client =
clientBuilder().metricsEnabled(true).name(clientName).loggerFactory(loggerFactory1).build();
+ client2 =
clientBuilder().metricsEnabled(true).name(clientName).loggerFactory(loggerFactory2).build();
+
+ client.tables().tables();
+ client2.tables().tables();
+
+ String beanName = "org.apache.ignite:nodeName=" + clientName +
",type=metrics,name=client";
+ MBeanServer mbeanSrv = ManagementFactory.getPlatformMBeanServer();
+
+ ObjectName objName = new ObjectName(beanName);
+ boolean registered = mbeanSrv.isRegistered(objName);
+
+ assertTrue(registered, "Unexpected MBean state: [name=" + beanName +
", registered=" + registered + ']');
+
+ DynamicMBean bean =
MBeanServerInvocationHandler.newProxyInstance(mbeanSrv, objName,
DynamicMBean.class, false);
+ assertEquals(1L, bean.getAttribute("ConnectionsActive"));
+ assertEquals(1L, bean.getAttribute("ConnectionsEstablished"));
+
+ // Error is logged, but the client is functional.
+ loggerFactory2.waitForLogContains("MBean for metric set can't be
created [name=client].", 3_000);
+ loggerFactory1.assertLogDoesNotContain("MBean for metric set can't be
created");
+ }
+
private Table oneColumnTable() {
if (server.ignite().tables().table(TABLE_ONE_COLUMN) == null) {
((FakeIgniteTables)
server.ignite().tables()).createTable(TABLE_ONE_COLUMN);
diff --git
a/modules/client/src/test/java/org/apache/ignite/client/ConfigurationTest.java
b/modules/client/src/test/java/org/apache/ignite/client/ConfigurationTest.java
index b306462360f..f37d7c848d8 100644
---
a/modules/client/src/test/java/org/apache/ignite/client/ConfigurationTest.java
+++
b/modules/client/src/test/java/org/apache/ignite/client/ConfigurationTest.java
@@ -87,6 +87,7 @@ public class ConfigurationTest extends AbstractClientTest {
.addresses(addr)
.connectTimeout(1234)
.addressFinder(() -> new String[]{addr})
+ .name("thin-client")
.build();
// Builder can be reused, and it won't affect already created clients.
diff --git
a/modules/client/src/test/java/org/apache/ignite/client/RetryPolicyTest.java
b/modules/client/src/test/java/org/apache/ignite/client/RetryPolicyTest.java
index 56db449b17e..573f0915b02 100644
--- a/modules/client/src/test/java/org/apache/ignite/client/RetryPolicyTest.java
+++ b/modules/client/src/test/java/org/apache/ignite/client/RetryPolicyTest.java
@@ -242,7 +242,7 @@ public class RetryPolicyTest extends BaseIgniteAbstractTest
{
@Test
public void testRetryReadPolicyAllOperationsSupported() {
var plc = new RetryReadPolicy();
- var cfg = new IgniteClientConfigurationImpl(null, null, 0, 0, null, 0,
0, null, null, null, false, null, 0, 1024);
+ var cfg = new IgniteClientConfigurationImpl(null, null, 0, 0, null, 0,
0, null, null, null, false, null, 0, 1024, null);
for (var op : ClientOperationType.values()) {
var ctx = new RetryPolicyContextImpl(cfg, op, 0, null);
diff --git
a/modules/client/src/test/java/org/apache/ignite/client/TestLoggerFactory.java
b/modules/client/src/test/java/org/apache/ignite/client/TestLoggerFactory.java
index a8f3f138667..ff2fc9f5934 100644
---
a/modules/client/src/test/java/org/apache/ignite/client/TestLoggerFactory.java
+++
b/modules/client/src/test/java/org/apache/ignite/client/TestLoggerFactory.java
@@ -17,6 +17,7 @@
package org.apache.ignite.client;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.lang.System.Logger;
@@ -46,6 +47,10 @@ public class TestLoggerFactory implements LoggerFactory {
assertTrue(logContains(msg), this::log);
}
+ void assertLogDoesNotContain(String msg) {
+ assertFalse(logContains(msg), this::log);
+ }
+
void waitForLogContains(String msg, long timeoutMillis) throws
InterruptedException {
assertTrue(
IgniteTestUtils.waitForCondition(() -> logContains(msg),
timeoutMillis),
diff --git
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
index f87e0faa4db..ade01948936 100644
---
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
+++
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
@@ -79,6 +79,8 @@ public class JdbcConnection implements Connection {
/** Network timeout permission. */
private static final String SET_NETWORK_TIMEOUT_PERM = "setNetworkTimeout";
+ private static final AtomicLong GLOBAL_CONN_ID_GEN = new AtomicLong();
+
/** Statements modification mutex. */
private final Object stmtsMux = new Object();
@@ -187,7 +189,8 @@ public class JdbcConnection implements Connection {
false,
extractAuthenticationConfiguration(connProps),
IgniteClientConfiguration.DFLT_OPERATION_TIMEOUT,
-
IgniteClientConfiguration.DFLT_SQL_PARTITION_AWARENESS_METADATA_CACHE_SIZE
+
IgniteClientConfiguration.DFLT_SQL_PARTITION_AWARENESS_METADATA_CACHE_SIZE,
+ "jdbc_client_" + GLOBAL_CONN_ID_GEN.getAndIncrement() // Use
underscores for JMX compat.
);
return (TcpIgniteClient) sync(TcpIgniteClient.startAsync(cfg,
observableTimeTracker));
diff --git
a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/MetricManagerImpl.java
b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/MetricManagerImpl.java
index f681c0b900e..7232da5a606 100644
---
a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/MetricManagerImpl.java
+++
b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/MetricManagerImpl.java
@@ -55,6 +55,7 @@ import
org.apache.ignite.internal.metrics.exporters.configuration.LogPushExporte
import
org.apache.ignite.internal.metrics.exporters.configuration.LogPushExporterView;
import org.apache.ignite.internal.metrics.exporters.log.LogPushExporter;
import org.apache.ignite.internal.util.IgniteSpinBusyLock;
+import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;
/**
@@ -80,13 +81,13 @@ public class MetricManagerImpl implements MetricManager {
private volatile Supplier<UUID> clusterIdSupplier;
- private volatile String nodeName;
+ private volatile @Nullable String nodeName;
/**
* Constructor.
*/
public MetricManagerImpl() {
- this(Loggers.forClass(MetricManagerImpl.class));
+ this(Loggers.forClass(MetricManagerImpl.class), null);
}
/**
@@ -94,9 +95,10 @@ public class MetricManagerImpl implements MetricManager {
*
* @param log Logger.
*/
- public MetricManagerImpl(IgniteLogger log) {
+ public MetricManagerImpl(IgniteLogger log, @Nullable String nodeName) {
registry = new MetricRegistry();
this.log = log;
+ this.nodeName = nodeName;
}
@Override