Hot Rod is a binary TCP client/server protocol that gives fast access to data in remote Infinispan caches. The Hot Rod Java client provides automatic load balancing and failover along with intelligence capabilities that use consistent hash techniques to efficiently locate cache entries.
1. Hot Rod Java Clients
Access Infinispan remotely through the Hot Rod Java client API.
1.1. Hot Rod Protocol
Hot Rod is a binary TCP protocol that Infinispan offers high-performance client-server interactions with the following capabilities:
-
Load balancing. Hot Rod clients can send requests across Infinispan clusters using different strategies.
-
Failover. Hot Rod clients can monitor Infinispan cluster topology changes and automatically switch to available nodes.
-
Efficient data location. Hot Rod clients can find key owners and make requests directly to those nodes, which reduces latency.
1.2. Client Intelligence
Hot Rod clients use intelligence mechanisms to efficiently send requests to Infinispan Server clusters.
By default, the Hot Rod protocol has the HASH_DISTRIBUTION_AWARE
intelligence mechanism enabled.
BASIC
intelligenceClients do not receive topology change events for Infinispan clusters, such as nodes joining or leaving, and use only the list of Infinispan Server network locations that you add to the client configuration.
Enable |
TOPOLOGY_AWARE
intelligenceClients receive and store topology change events for Infinispan clusters to dynamically keep track of Infinispan Servers on the network.
To receive cluster topology, clients need the network location, either IP address or host name, of at least one Hot Rod server at startup. After the client connects, Infinispan Server transmits the topology to the client. When Infinispan Server nodes join or leave the cluster, Infinispan transmits an updated topology to the client.
HASH_DISTRIBUTION_AWARE
intelligenceClients receive and store topology change events for Infinispan clusters in addition to hashing information that enables clients to identify which nodes store specific keys.
For example, consider a put(k,v)
operation. The client calculates the hash value for the key so it can locate the exact Infinispan Server node on which the data resides.
Clients can then connect directly to that node to perform read and write operations.
The benefit of HASH_DISTRIBUTION_AWARE
intelligence is that Infinispan Server does not need to look up values based on key hashes, which uses less server-side resources.
Another benefit is that Infinispan Server responds to client requests more quickly because they do not need to make additional network roundtrips.
Configuration
By default, Hot Rod client uses the intelligence that you configure globally for all the Infinispan clusters.
ConfigurationBuilder builder = new ConfigurationBuilder();
builder.clientIntelligence(ClientIntelligence.BASIC);
infinispan.client.hotrod.client_intelligence=BASIC
When you configure Hot Rod client to use multiple Infinispan clusters you can use different intelligence for each of the clusters.
ConfigurationBuilder builder = new ConfigurationBuilder();
builder.addCluster("NYC").clusterClientIntelligence(ClientIntelligence.BASIC);
infinispan.client.hotrod.cluster.intelligence.NYC=BASIC
1.3. Request Balancing
Hot Rod Java clients balance requests to Infinispan Server clusters so that read and write operations are spread across nodes.
Clients that use BASIC
or TOPOLOGY_AWARE
intelligence use request balancing for all requests.
Clients that use HASH_DISTRIBUTION_AWARE
intelligence send requests directly to the node that stores the desired key.
If the node does not respond, the clients then fall back to request balancing.
The default balancing strategy is round-robin, so Hot Rod clients perform request balancing as in the following example where s1
, s2
, s3
are nodes in a Infinispan cluster:
// Connect to the Infinispan cluster
RemoteCacheManager cacheManager = new RemoteCacheManager(builder.build());
// Obtain the remote cache
RemoteCache<String, String> cache = cacheManager.getCache("test");
//Hot Rod client sends a request to the "s1" node
cache.put("key1", "aValue");
//Hot Rod client sends a request to the "s2" node
cache.put("key2", "aValue");
//Hot Rod client sends a request to the "s3" node
String value = cache.get("key1");
//Hot Rod client sends the next request to the "s1" node again
cache.remove("key2");
You can use custom FailoverRequestBalancingStrategy
implementations if you add your class in the Hot Rod client configuration.
ConfigurationBuilder builder = new ConfigurationBuilder();
builder.addServer()
.host("127.0.0.1")
.port(11222)
.balancingStrategy(new MyCustomBalancingStrategy());
infinispan.client.hotrod.request_balancing_strategy=my.package.MyCustomBalancingStrategy
1.4. Client Failover
Hot Rod clients can automatically failover when Infinispan cluster topologies change. For instance, Hot Rod clients that are topology-aware can detect when one or more Infinispan servers fail.
In addition to failover between clustered Infinispan servers, Hot Rod clients can failover between Infinispan clusters.
For example, you have a Infinispan cluster running in New York (NYC) and another cluster running in London (LON). Clients sending requests to NYC detect that no nodes are available so they switch to the cluster in LON. Clients then maintain connections to LON until you manually switch clusters or failover happens again.
Conditional operations, such as putIfAbsent()
, replace()
, remove()
, have
strict method return guarantees. Likewise, some operations can require previous
values to be returned.
Even though Hot Rod clients can failover, you should use transactional caches to ensure that operations do not partially complete and leave conflicting entries on different nodes.
1.5. Hot Rod client compatibility with Infinispan Server
Infinispan Server allows you to connect Hot Rod clients with different versions. For instance during a migration or upgrade to your Infinispan cluster, the Hot Rod client version might be a lower Infinispan version than Infinispan Server.
Infinispan recommends using the latest Hot Rod client version to benefit from the most recent capabilities and security enhancements. |
Hot Rod protocol version 3.x automatically negotiates the highest version possible for clients with Infinispan Server.
Clients that use a Hot Rod protocol version that is higher than the Infinispan Server version must set the infinispan.client.hotrod.protocol_version
property.
2. Adding Infinispan to your Maven repository
Infinispan Java distributions are available from Maven.
Infinispan artifacts are available from Maven central. See the org.infinispan group for available Infinispan artifacts.
2.1. Configuring your project POM
Configure Project Object Model (POM) files in your project to use Infinispan dependencies for embedded caches, Hot Rod clients, and other capabilities.
-
Open your project
pom.xml
for editing. -
Define the
version.infinispan
property with the correct Infinispan version. -
Include the
infinispan-bom
in adependencyManagement
section.The Bill Of Materials (BOM) controls dependency versions, which avoids version conflicts and means you do not need to set the version for each Infinispan artifact you add as a dependency to your project.
-
Save and close
pom.xml
.
The following example shows the Infinispan version and BOM:
<properties>
<version.infinispan>14.0.33.Final</version.infinispan>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-bom</artifactId>
<version>${version.infinispan}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Add Infinispan artifacts as dependencies to your pom.xml
as required.
3. Hot Rod Java Client Configuration
Infinispan provides a Hot Rod Java client configuration API that exposes configuration properties.
3.1. Adding Hot Rod Java Client Dependencies
Add Hot Rod Java client dependencies to include it in your project.
-
Java 11 or greater.
-
Add the
infinispan-client-hotrod
artifact as a dependency in yourpom.xml
as follows:
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-client-hotrod</artifactId>
</dependency>
3.2. Configuring Hot Rod Client Connections
Configure Hot Rod Java client connections to Infinispan Server.
-
Use the
ConfigurationBuilder
class to generate immutable configuration objects that you can pass toRemoteCacheManager
or use ahotrod-client.properties
file on the application classpath.
ConfigurationBuilder builder = new ConfigurationBuilder();
builder.addServer()
.host("127.0.0.1")
.port(ConfigurationProperties.DEFAULT_HOTROD_PORT)
.addServer()
.host("192.0.2.0")
.port(ConfigurationProperties.DEFAULT_HOTROD_PORT)
.security().authentication()
.username("username")
.password("changeme")
.realm("default")
.saslMechanism("SCRAM-SHA-512");
RemoteCacheManager cacheManager = new RemoteCacheManager(builder.build());
infinispan.client.hotrod.server_list = 127.0.0.1:11222,192.0.2.0:11222
infinispan.client.hotrod.auth_username = username
infinispan.client.hotrod.auth_password = changeme
infinispan.client.hotrod.auth_realm = default
infinispan.client.hotrod.sasl_mechanism = SCRAM-SHA-512
Configuring Hot Rod URIs
You can also configure Hot Rod client connections with URIs as follows:
ConfigurationBuilder builder = new ConfigurationBuilder();
builder.uri("hotrod://username:changeme@127.0.0.1:11222,192.0.2.0:11222?auth_realm=default&sasl_mechanism=SCRAM-SHA-512");
RemoteCacheManager cacheManager = new RemoteCacheManager(builder.build());
infinispan.client.hotrod.uri = hotrod://username:changeme@127.0.0.1:11222,192.0.2.0:11222?auth_realm=default&sasl_mechanism=SCRAM-SHA-512
Adding properties outside the classpath
If the hotrod-client.properties
file is not on the application classpath then you need to specify the location, as in the following example:
ConfigurationBuilder builder = new ConfigurationBuilder();
Properties p = new Properties();
try(Reader r = new FileReader("/path/to/hotrod-client.properties")) {
p.load(r);
builder.withProperties(p);
}
RemoteCacheManager cacheManager = new RemoteCacheManager(builder.build());
3.2.1. Defining Infinispan Clusters in Client Configuration
Provide the locations of Infinispan clusters in Hot Rod client configuration.
-
Provide at least one Infinispan cluster name along with a host name and port for at least one node with the
ClusterConfigurationBuilder
class.If you want to define a cluster as default, so that clients always attempt to connect to it first, then define a server list with the
addServers("<host_name>:<port>; <host_name>:<port>")
method.
ConfigurationBuilder clientBuilder = new ConfigurationBuilder();
clientBuilder.addCluster("siteA")
.addClusterNode("hostA1", 11222)
.addClusterNode("hostA2", 11222)
.addCluster("siteB")
.addClusterNodes("hostB1:11222; hostB2:11222");
RemoteCacheManager remoteCacheManager = new RemoteCacheManager(clientBuilder.build());
ConfigurationBuilder clientBuilder = new ConfigurationBuilder();
clientBuilder.addServers("hostA1:11222; hostA2:11222")
.addCluster("siteB")
.addClusterNodes("hostB1:11222; hostB2:11223");
RemoteCacheManager remoteCacheManager = new RemoteCacheManager(clientBuilder.build());
3.2.2. Manually Switching Infinispan Clusters
Manually switch Hot Rod Java client connections between Infinispan clusters.
-
Call one of the following methods in the
RemoteCacheManager
class:switchToCluster(clusterName)
switches to a specific cluster defined in the client configuration.switchToDefaultCluster()
switches to the default cluster in the client configuration, which is defined as a list of Infinispan servers.
3.2.3. Configuring Connection Pools
Hot Rod Java clients keep pools of persistent connections to Infinispan servers to reuse TCP connections instead of creating them on each request.
-
Configure Hot Rod client connection pool settings as in the following examples:
ConfigurationBuilder clientBuilder = new ConfigurationBuilder();
clientBuilder.addServer()
.host("127.0.0.1")
.port(11222)
.connectionPool()
.maxActive(10)
exhaustedAction(ExhaustedAction.valueOf("WAIT"))
.maxWait(1)
.minIdle(20)
.minEvictableIdleTime(300000)
.maxPendingRequests(20);
RemoteCacheManager remoteCacheManager = new RemoteCacheManager(clientBuilder.build());
infinispan.client.hotrod.server_list = 127.0.0.1:11222
infinispan.client.hotrod.connection_pool.max_active = 10
infinispan.client.hotrod.connection_pool.exhausted_action = WAIT
infinispan.client.hotrod.connection_pool.max_wait = 1
infinispan.client.hotrod.connection_pool.min_idle = 20
infinispan.client.hotrod.connection_pool.min_evictable_idle_time = 300000
infinispan.client.hotrod.connection_pool.max_pending_requests = 20
3.3. Configuring Authentication Mechanisms for Hot Rod Clients
Infinispan Server uses different mechanisms to authenticate Hot Rod client connections.
-
Specify authentication mechanisms with the
saslMechanism()
method from theAuthenticationConfigurationBuilder
class or with theinfinispan.client.hotrod.sasl_mechanism
property.
ConfigurationBuilder clientBuilder = new ConfigurationBuilder();
clientBuilder.addServer()
.host("127.0.0.1")
.port(11222)
.security()
.authentication()
.saslMechanism("SCRAM-SHA-512")
.username("myuser")
.password("qwer1234!");
ConfigurationBuilder clientBuilder = new ConfigurationBuilder();
clientBuilder.addServer()
.host("127.0.0.1")
.port(11222)
.security()
.authentication()
.saslMechanism("DIGEST-MD5")
.username("myuser")
.password("qwer1234!");
ConfigurationBuilder clientBuilder = new ConfigurationBuilder();
clientBuilder.addServer()
.host("127.0.0.1")
.port(11222)
.security()
.authentication()
.saslMechanism("PLAIN")
.username("myuser")
.password("qwer1234!");
String token = "..."; // Obtain the token from your OAuth2 provider
ConfigurationBuilder clientBuilder = new ConfigurationBuilder();
clientBuilder.addServer()
.host("127.0.0.1")
.port(11222)
.security()
.authentication()
.saslMechanism("OAUTHBEARER")
.token(token);
ConfigurationBuilder clientBuilder = new ConfigurationBuilder();
clientBuilder
.addServer()
.host("127.0.0.1")
.port(11222)
.security()
.ssl()
// TrustStore stores trusted CA certificates for the server.
.trustStoreFileName("/path/to/truststore")
.trustStorePassword("truststorepassword".toCharArray())
.trustStoreType("PCKS12")
// KeyStore stores valid client certificates.
.keyStoreFileName("/path/to/keystore")
.keyStorePassword("keystorepassword".toCharArray())
.keyStoreType("PCKS12")
.authentication()
.saslMechanism("EXTERNAL");
remoteCacheManager = new RemoteCacheManager(clientBuilder.build());
RemoteCache<String, String> cache = remoteCacheManager.getCache("secured");
LoginContext lc = new LoginContext("GssExample", new BasicCallbackHandler("krb_user", "krb_password".toCharArray()));
lc.login();
Subject clientSubject = lc.getSubject();
ConfigurationBuilder clientBuilder = new ConfigurationBuilder();
clientBuilder.addServer()
.host("127.0.0.1")
.port(11222)
.security()
.authentication()
.saslMechanism("GSSAPI")
.clientSubject(clientSubject)
.callbackHandler(new BasicCallbackHandler());
Basic Callback Handler
The BasicCallbackHandler
, as shown in the GSSAPI example, invokes the following callbacks:
-
NameCallback
andPasswordCallback
construct the client subject. -
AuthorizeCallback
is called during SASL authentication.
OAUTHBEARER with Token Callback Handler
Use a TokenCallbackHandler
to refresh OAuth2 tokens before they expire, as in the following example:
String token = "..."; // Obtain the token from your OAuth2 provider
TokenCallbackHandler tokenHandler = new TokenCallbackHandler(token);
ConfigurationBuilder clientBuilder = new ConfigurationBuilder();
clientBuilder.addServer()
.host("127.0.0.1")
.port(11222)
.security()
.authentication()
.saslMechanism("OAUTHBEARER")
.callbackHandler(tokenHandler);
remoteCacheManager = new RemoteCacheManager(clientBuilder.build());
RemoteCache<String, String> cache = remoteCacheManager.getCache("secured");
// Refresh the token
tokenHandler.setToken("newToken");
Custom CallbackHandler
Hot Rod clients set up a default CallbackHandler
to pass credentials to SASL
mechanisms. In some cases you might need to provide a custom CallbackHandler
, as in the following example:
public class MyCallbackHandler implements CallbackHandler {
final private String username;
final private char[] password;
final private String realm;
public MyCallbackHandler(String username, String realm, char[] password) {
this.username = username;
this.password = password;
this.realm = realm;
}
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (Callback callback : callbacks) {
if (callback instanceof NameCallback) {
NameCallback nameCallback = (NameCallback) callback;
nameCallback.setName(username);
} else if (callback instanceof PasswordCallback) {
PasswordCallback passwordCallback = (PasswordCallback) callback;
passwordCallback.setPassword(password);
} else if (callback instanceof AuthorizeCallback) {
AuthorizeCallback authorizeCallback = (AuthorizeCallback) callback;
authorizeCallback.setAuthorized(authorizeCallback.getAuthenticationID().equals(
authorizeCallback.getAuthorizationID()));
} else if (callback instanceof RealmCallback) {
RealmCallback realmCallback = (RealmCallback) callback;
realmCallback.setText(realm);
} else {
throw new UnsupportedCallbackException(callback);
}
}
}
}
ConfigurationBuilder clientBuilder = new ConfigurationBuilder();
clientBuilder.addServer()
.host("127.0.0.1")
.port(11222)
.security().authentication()
.serverName("myhotrodserver")
.saslMechanism("DIGEST-MD5")
.callbackHandler(new MyCallbackHandler("myuser","default","qwer1234!".toCharArray()));
A custom |
3.3.1. Creating GSSAPI Login Contexts
To use the GSSAPI mechanism, you must create a LoginContext so your Hot Rod client can obtain a Ticket Granting Ticket (TGT).
-
Define a login module in a login configuration file.
gss.confGssExample { com.sun.security.auth.module.Krb5LoginModule required client=TRUE; };
For the IBM JDK:
gss-ibm.confGssExample { com.ibm.security.auth.module.Krb5LoginModule required client=TRUE; };
-
Set the following system properties:
java.security.auth.login.config=gss.conf java.security.krb5.conf=/etc/krb5.conf
krb5.conf
provides the location of your KDC. Use the kinit command to authenticate with Kerberos and verifykrb5.conf
.
3.3.2. SASL authentication mechanisms
Infinispan Server supports the following SASL authentications mechanisms with Hot Rod endpoints:
Authentication mechanism | Description | Security realm type | Related details |
---|---|---|---|
|
Uses credentials in plain-text format. You should use |
Property realms and LDAP realms |
Similar to the |
|
Uses hashing algorithms and nonce values. Hot Rod connectors support |
Property realms and LDAP realms |
Similar to the |
|
Uses salt values in addition to hashing algorithms and nonce values. Hot Rod connectors support |
Property realms and LDAP realms |
Similar to the |
|
Uses Kerberos tickets and requires a Kerberos Domain Controller. You must add a corresponding |
Kerberos realms |
Similar to the |
|
Uses Kerberos tickets and requires a Kerberos Domain Controller. You must add a corresponding |
Kerberos realms |
Similar to the |
|
Uses client certificates. |
Trust store realms |
Similar to the |
|
Uses OAuth tokens and requires a |
Token realms |
Similar to the |
3.4. Configuring Hot Rod client encryption
Infinispan Server can enforce SSL/TLS encryption and present Hot Rod clients with certificates to establish trust and negotiate secure connections.
To verify certificates issued to Infinispan Server, Hot Rod clients require either the full certificate chain or a partial chain that starts with the Root CA. You provide server certificates to Hot Rod clients as trust stores.
Alternatively to providing trust stores you can use shared system certificates. |
-
Create a trust store that Hot Rod clients can use to verify Infinispan Server identities.
-
If you configure Infinispan Server to validate or authenticate client certificates, create a keystore as appropriate.
-
Add the trust store to the client configuration with the
trustStoreFileName()
andtrustStorePassword()
methods or corresponding properties. -
If you configure client certificate authentication, do the following:
-
Add the keystore to the client configuration with the
keyStoreFileName()
andkeyStorePassword()
methods or corresponding properties. -
Configure clients to use the
EXTERNAL
authentication mechanism.
-
ConfigurationBuilder clientBuilder = new ConfigurationBuilder();
clientBuilder
.addServer()
.host("127.0.0.1")
.port(11222)
.security()
.ssl()
// Server SNI hostname.
.sniHostName("myservername")
// Keystore that contains the public keys for Infinispan Server.
// Clients use the trust store to verify Infinispan Server identities.
.trustStoreFileName("/path/to/server/truststore")
.trustStorePassword("truststorepassword".toCharArray())
.trustStoreType("PCKS12")
// Keystore that contains client certificates.
// Clients present these certificates to Infinispan Server.
.keyStoreFileName("/path/to/client/keystore")
.keyStorePassword("keystorepassword".toCharArray())
.keyStoreType("PCKS12")
.authentication()
// Clients must use the EXTERNAL mechanism for certificate authentication.
.saslMechanism("EXTERNAL");
infinispan.client.hotrod.server_list = 127.0.0.1:11222
infinispan.client.hotrod.use_ssl = true
infinispan.client.hotrod.sni_host_name = myservername
# Keystore that contains the public keys for Infinispan Server.
# Clients use the trust store to verify Infinispan Server identities.
infinispan.client.hotrod.trust_store_file_name = server_truststore.pkcs12
infinispan.client.hotrod.trust_store_password = changeme
infinispan.client.hotrod.trust_store_type = PCKS12
# Keystore that contains client certificates.
# Clients present these certificates to Infinispan Server.
infinispan.client.hotrod.key_store_file_name = client_keystore.pkcs12
infinispan.client.hotrod.key_store_password = changeme
infinispan.client.hotrod.key_store_type = PCKS12
# Clients must use the EXTERNAL mechanism for certificate authentication.
infinispan.client.hotrod.sasl_mechanism = EXTERNAL
Add a client trust store to the $ISPN_HOME/server/conf
directory and configure Infinispan Server to use it, if necessary.
-
Using Shared System Certificates (Red Hat Enterprise Linux 7 Security Guide)
3.5. Enabling Hot Rod client statistics
Hot Rod Java clients can provide statistics that include remote cache and near-cache hits and misses as well as connection pool usage.
-
Open your Hot Rod Java client configuration for editing.
-
Set
true
as the value for thestatistics
property or invoke thestatistics().enable()
methods. -
Export JMX MBeans for your Hot Rod client with the
jmx
andjmx_domain
properties or invoke thejmxEnable()
andjmxDomain()
methods. -
Save and close your client configuration.
Hot Rod Java client statistics
ConfigurationBuilder builder = new ConfigurationBuilder();
builder.statistics().enable()
.jmxEnable()
.jmxDomain("my.domain.org")
.addServer()
.host("127.0.0.1")
.port(11222);
RemoteCacheManager remoteCacheManager = new RemoteCacheManager(builder.build());
infinispan.client.hotrod.statistics = true
infinispan.client.hotrod.jmx = true
infinispan.client.hotrod.jmx_domain = my.domain.org
3.6. Hot Rod client tracing propagation
When you configure OpenTelemetry tracing on both the client VM and the Infinispan Server, the Hot Rod client enables automatic correlation of tracing spans between the client application and the Infinispan Server.
Disabling tracing propagation from the client to the Infinispan Server
-
Have OpenTelemetry tracing enabled on The Infinispan Server and the client side.
-
Use the
disableTracingPropagation()
method to disable OpenTelemetry tracing propagation.import org.infinispan.client.hotrod.configuration.ConfigurationBuilder; ConfigurationBuilder builder = new ConfigurationBuilder(); builder.addServer() .host("127.0.0.1") .port(ConfigurationProperties.DEFAULT_HOTROD_PORT) .disableTracingPropagation();
The Hot Rod client stops sending tracing to the Infinispan Server.
3.7. Near Caches
Near caches are local to Hot Rod clients and store recently used data so that every read operation does not need to traverse the network, which significantly increases performance.
Near caches:
-
Are populated with read operations, calls to
get()
orgetVersioned()
methods.
In the following example theput()
call does not populate the near cache and only has the effect of invalidating the entry if it already exists:cache.put("k1", "v1"); cache.get("k1");
-
Register a client listener to invalidate entries when they are updated or removed in remote caches on Infinispan Server.
If entries are requested after they are invalidated, clients must retrieve them from the remote caches again. -
Are cleared when clients fail over to different servers.
You should always use bounded near caches by specifying the maximum number of entries they can contain. When near caches reach the maximum number of entries, eviction automatically takes place to remove older entries. This means you do not need to manually keep the cache size within the boundaries of the client JVM.
Do not use maximum idle expiration with near caches because near-cache reads do not propagate the last access time for entries. |
Bloom filters optimize performance for write operations by reducing the total number of invalidation messages.
Bloom filters:
-
Reside on Infinispan Server and keep track of the entries that the client has requested.
-
Require a connection pool configuration that has a maximum of one active connection per server and uses the
WAIT
exhausted action. -
Cannot be used with unbounded near caches.
3.7.1. Configuring Near Caches
Configure Hot Rod Java clients with near caches to store recently used data locally in the client JVM.
-
Open your Hot Rod Java client configuration.
-
Configure each cache to perform near caching with the
nearCacheMode(NearCacheMode.INVALIDATED)
method.Infinispan provides global near cache configuration properties. However, those properties are deprecated and you should not use them but configure near caching on a per-cache basis instead.
-
Specify the maximum number of entries that the near cache can hold before eviction occurs with the
nearCacheMaxEntries()
method. -
Enable bloom filters for near caches with the
nearCacheUseBloomFilter()
method.
import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
import org.infinispan.client.hotrod.configuration.NearCacheMode;
import org.infinispan.client.hotrod.configuration.ExhaustedAction;
ConfigurationBuilder builder = new ConfigurationBuilder();
builder.addServer()
.host("127.0.0.1")
.port(ConfigurationProperties.DEFAULT_HOTROD_PORT)
.security().authentication()
.username("username")
.password("password")
.realm("default")
.saslMechanism("SCRAM-SHA-512")
// Configure the connection pool for bloom filters.
.connectionPool()
.maxActive(1)
.exhaustedAction(ExhaustedAction.WAIT);
// Configure near caching for specific caches
builder.remoteCache("books")
.nearCacheMode(NearCacheMode.INVALIDATED)
.nearCacheMaxEntries(100)
.nearCacheUseBloomFilter(false);
builder.remoteCache("authors")
.nearCacheMode(NearCacheMode.INVALIDATED)
.nearCacheMaxEntries(200)
.nearCacheUseBloomFilter(true);
3.8. Forcing Return Values
To avoid sending data unnecessarily, write operations on remote caches return null
instead of previous values.
For example, the following method calls do not return previous values for keys:
V remove(Object key);
V put(K key, V value);
You can, however, change the default behavior so your invocations return previous values for keys.
-
Configure Hot Rod clients so method calls return previous values for keys in one of the following ways:
cache.withFlags(Flag.FORCE_RETURN_VALUE).put("aKey", "newValue")
ConfigurationBuilder builder = new ConfigurationBuilder();
// Return previous values for keys for invocations for a specific cache.
builder.remoteCache("mycache")
.forceReturnValues(true);
# Use the "*" wildcard in the cache name to return previous values
# for all caches that start with the "somecaches" string.
infinispan.client.hotrod.cache.somecaches*.force_return_values = true
3.9. Creating remote caches from Hot Rod clients
Use the Infinispan Hot Rod API to create remote caches on Infinispan Server from Java, C++, .NET/C#, JS clients and more.
This procedure shows you how to use Hot Rod Java clients that create remote caches on first access. You can find code examples for other Hot Rod clients in the Infinispan Tutorials.
-
Create a Infinispan user with
admin
permissions. -
Start at least one Infinispan Server instance.
-
Have a Infinispan cache configuration.
-
Invoke the
remoteCache()
method as part of your theConfigurationBuilder
. -
Set the
configuration
orconfiguration_uri
properties in thehotrod-client.properties
file on your classpath.
File file = new File("path/to/infinispan.xml")
ConfigurationBuilder builder = new ConfigurationBuilder();
builder.remoteCache("another-cache")
.configuration("<distributed-cache name=\"another-cache\"/>");
builder.remoteCache("my.other.cache")
.configurationURI(file.toURI());
infinispan.client.hotrod.cache.another-cache.configuration=<distributed-cache name=\"another-cache\"/>
infinispan.client.hotrod.cache.[my.other.cache].configuration_uri=file:///path/to/infinispan.xml
If the name of your remote cache contains the |
4. Hot Rod Client API
Infinispan Hot Rod client API provides interfaces for creating caches remotely, manipulating data, monitoring the topology of clustered caches, and more.
4.1. RemoteCache API
The collection methods keySet
, entrySet
and values
are backed by the
remote cache. That is that every method is called back into the RemoteCache
.
This is useful as it allows for the various keys, entries or values to be
retrieved lazily, and not requiring them all be stored in the client memory at
once if the user does not want.
These collections adhere to the Map
specification being that add
and
addAll
are not supported but all other methods are supported.
One thing to note is the Iterator.remove
and Set.remove
or
Collection.remove
methods require more than 1 round trip to the server to
operate. You can check out the
RemoteCache
Javadoc to see more details about these and the other methods.
The iterator method of these collections uses retrieveEntries
internally,
which is described below. If you notice retrieveEntries
takes an argument for
the batch size. There is no way to provide this to the iterator. As such the
batch size can be configured via system property
infinispan.client.hotrod.batch_size
or through the ConfigurationBuilder when configuring the
RemoteCacheManager
.
Also the retrieveEntries
iterator returned is Closeable
as such the
iterators from keySet
, entrySet
and values
return an AutoCloseable
variant. Therefore you should always close these `Iterator`s when you are done
with them.
try (CloseableIterator<Map.Entry<K, V>> iterator = remoteCache.entrySet().iterator()) {
}
What if I want a deep copy and not a backing collection?
Previous version of RemoteCache
allowed for the retrieval of a deep copy
of the keySet
. This is still possible with the new backing map, you just
have to copy the contents yourself. Also you can do this with entrySet
and
values
, which we didn’t support before.
Set<K> keysCopy = remoteCache.keySet().stream().collect(Collectors.toSet());
4.1.1. Unsupported Methods
The Infinispan RemoteCache
API does not support all methods available in the
Cache
API and throws UnsupportedOperationException
when unsupported methods
are invoked.
Most of these methods do not make sense on the remote cache (e.g. listener management operations), or correspond to methods that are not supported by local cache as well (e.g. containsValue).
Certain atomic operations inherited from ConcurrentMap
are also not supported
with the RemoteCache
API, for example:
boolean remove(Object key, Object value);
boolean replace(Object key, Object value);
boolean replace(Object key, Object oldValue, Object value);
However, RemoteCache
offers alternative versioned methods for these atomic
operations that send version identifiers over the network instead of whole
value objects.
4.2. Remote Iterator API
Infinispan provides a remote iterator API to retrieve entries where memory resources are constrained or if you plan to do server-side filtering or conversion.
// Retrieve all entries in batches of 1000
int batchSize = 1000;
try (CloseableIterator<Entry<Object, Object>> iterator = remoteCache.retrieveEntries(null, batchSize)) {
while(iterator.hasNext()) {
// Do something
}
}
// Filter by segment
Set<Integer> segments = ...
try (CloseableIterator<Entry<Object, Object>> iterator = remoteCache.retrieveEntries(null, segments, batchSize)) {
while(iterator.hasNext()) {
// Do something
}
}
// Filter by custom filter
try (CloseableIterator<Entry<Object, Object>> iterator = remoteCache.retrieveEntries("myFilterConverterFactory", segments, batchSize)) {
while(iterator.hasNext()) {
// Do something
}
}
4.2.1. Deploying Custom Filters to Infinispan Server
Deploy custom filters to Infinispan server instances.
-
Create a factory that extends
KeyValueFilterConverterFactory
.import java.io.Serializable; import org.infinispan.filter.AbstractKeyValueFilterConverter; import org.infinispan.filter.KeyValueFilterConverter; import org.infinispan.filter.KeyValueFilterConverterFactory; import org.infinispan.filter.NamedFactory; import org.infinispan.metadata.Metadata; //@NamedFactory annotation defines the factory name @NamedFactory(name = "myFilterConverterFactory") public class MyKeyValueFilterConverterFactory implements KeyValueFilterConverterFactory { @Override public KeyValueFilterConverter<String, SampleEntity1, SampleEntity2> getFilterConverter() { return new MyKeyValueFilterConverter(); } // Filter implementation. Should be serializable or externalizable for DIST caches static class MyKeyValueFilterConverter extends AbstractKeyValueFilterConverter<String, SampleEntity1, SampleEntity2> implements Serializable { @Override public SampleEntity2 filterAndConvert(String key, SampleEntity1 entity, Metadata metadata) { // returning null will case the entry to be filtered out // return SampleEntity2 will convert from the cache type SampleEntity1 } @Override public MediaType format() { // returns the MediaType that data should be presented to this converter. // When omitted, the server will use "application/x-java-object". // Returning null will cause the filter/converter to be done in the storage format. } } }
-
Create a JAR that contains a
META-INF/services/org.infinispan.filter.KeyValueFilterConverterFactory
file. This file should include the fully qualified class name of the filter factory class implementation.If the filter uses custom key/value classes, you must include them in your JAR file so that the filter can correctly unmarshall key and/or value instances.
-
Add the JAR file to the
server/lib
directory of your Infinispan server installation directory.
4.3. MetadataValue API
Use the MetadataValue
interface for versioned operations.
The following example shows a remove operation that occurs only if the version of the value for the entry is unchanged:
RemoteCacheManager remoteCacheManager = new RemoteCacheManager();
RemoteCache<String, String> remoteCache = remoteCacheManager.getCache();
remoteCache.put("car", "ferrari");
VersionedValue valueBinary = remoteCache.getWithMetadata("car");
assert remoteCache.remove("car", valueBinary.getVersion());
assert !remoteCache.containsKey("car");
4.4. Streaming API
Infinispan provides a Streaming API that implements methods that return
instances of InputStream
and OutputStream
so you can stream large objects
between Hot Rod clients and Infinispan servers.
Consider the following example of a large object:
StreamingRemoteCache<String> streamingCache = remoteCache.streaming();
OutputStream os = streamingCache.put("a_large_object");
os.write(...);
os.close();
You could read the object through streaming as follows:
StreamingRemoteCache<String> streamingCache = remoteCache.streaming();
InputStream is = streamingCache.get("a_large_object");
for(int b = is.read(); b >= 0; b = is.read()) {
// iterate
}
is.close();
The Streaming API does not marshall values, which means you cannot access the same entries using both the Streaming and Non-Streaming API at the same time. You can, however, implement a custom marshaller to handle this case. |
The InputStream
returned by the RemoteStreamingCache.get(K key)
method
implements the VersionedMetadata
interface, so you can retrieve version and
expiration information as follows:
StreamingRemoteCache<String> streamingCache = remoteCache.streaming();
InputStream is = streamingCache.get("a_large_object");
long version = ((VersionedMetadata) is).getVersion();
for(int b = is.read(); b >= 0; b = is.read()) {
// iterate
}
is.close();
Conditional write methods ( |
4.5. Counter API
The CounterManager
interface is the entry point to define, retrieve and remove counters.
Hot Rod clients can retrieve the CounterManager
interface as in the following example:
// create or obtain your RemoteCacheManager
RemoteCacheManager manager = ...;
// retrieve the CounterManager
CounterManager counterManager = RemoteCounterManagerFactory.asCounterManager(manager);
4.6. Creating Event Listeners
Java Hot Rod clients can register listeners to receive cache-entry level events. Cache entry created, modified and removed events are supported.
Creating a client listener is very similar to embedded listeners, except that different annotations and event classes are used. Here’s an example of a client listener that prints out each event received:
import org.infinispan.client.hotrod.annotation.*;
import org.infinispan.client.hotrod.event.*;
@ClientListener(converterFactoryName = "static-converter")
public class EventPrintListener {
@ClientCacheEntryCreated
public void handleCreatedEvent(ClientCacheEntryCreatedEvent e) {
System.out.println(e);
}
@ClientCacheEntryModified
public void handleModifiedEvent(ClientCacheEntryModifiedEvent e) {
System.out.println(e);
}
@ClientCacheEntryRemoved
public void handleRemovedEvent(ClientCacheEntryRemovedEvent e) {
System.out.println(e);
}
}
ClientCacheEntryCreatedEvent
and ClientCacheEntryModifiedEvent
instances
provide information on the affected key, and the version of the entry. This
version can be used to invoke conditional operations on the server, such as
replaceWithVersion
or removeWithVersion
.
ClientCacheEntryRemovedEvent
events are only sent when the remove operation
succeeds. In other words, if a remove operation is invoked but no entry is
found or no entry should be removed, no event is generated. Users interested
in removed events, even when no entry was removed, can develop event
customization logic to generate such events. More information can be found
in the customizing client events section.
All ClientCacheEntryCreatedEvent
, ClientCacheEntryModifiedEvent
and
ClientCacheEntryRemovedEvent
event instances also provide a boolean isCommandRetried()
method that will return true if the write command that caused this had to be retried
again due to a topology change. This could be a sign that this event
has been duplicated or another event was dropped and replaced
(eg: ClientCacheEntryModifiedEvent replaced ClientCacheEntryCreatedEvent).
Once the client listener implementation has been created, it needs to be registered with the server. To do so, execute:
RemoteCache<?, ?> cache = ...
cache.addClientListener(new EventPrintListener());
4.6.1. Removing Event Listeners
When an client event listener is not needed any more, it can be removed:
EventPrintListener listener = ...
cache.removeClientListener(listener);
4.6.2. Filtering Events
In order to avoid inundating clients with events, users can provide filtering functionality to limit the number of events fired by the server for a particular client listener. To enable filtering, a cache event filter factory needs to be created that produces filter instances:
import org.infinispan.notifications.cachelistener.filter.CacheEventFilterFactory;
import org.infinispan.filter.NamedFactory;
@NamedFactory(name = "static-filter")
public static class StaticCacheEventFilterFactory implements CacheEventFilterFactory {
@Override
public StaticCacheEventFilter getFilter(Object[] params) {
return new StaticCacheEventFilter();
}
}
// Serializable, Externalizable or marshallable with Infinispan Externalizers
// needed when running in a cluster
class StaticCacheEventFilter implements CacheEventFilter<Integer, String>, Serializable {
@Override
public boolean accept(Integer key, String oldValue, Metadata oldMetadata,
String newValue, Metadata newMetadata, EventType eventType) {
if (key.equals(1)) // static key
return true;
return false;
}
}
The cache event filter factory instance defined above creates filter instances
which statically filter out all entries except the one whose key is 1
.
To be able to register a listener with this cache event filter factory, the factory has to be given a unique name, and the Hot Rod server needs to be plugged with the name and the cache event filter factory instance.
-
Create a JAR file that contains the filter implementation.
If the cache uses custom key/value classes, these must be included in the JAR so that the callbacks can be executed with the correctly unmarshalled key and/or value instances. If the client listener has
useRawData
enabled, this is not necessary since the callback key/value instances will be provided in binary format. -
Create a
META-INF/services/org.infinispan.notifications.cachelistener.filter.CacheEventFilterFactory
file within the JAR file and within it, write the fully qualified class name of the filter class implementation. -
Add the JAR file to the
server/lib
directory of your Infinispan server installation directory. -
Link the client listener with this cache event filter factory by adding the factory name to the
@ClientListener
annotation:@ClientListener(filterFactoryName = "static-filter") public class EventPrintListener { ... }
-
Register the listener with the server:
RemoteCache<?, ?> cache = ... cache.addClientListener(new EventPrintListener());
You can also register dynamic filter instances that filter based on parameters provided when the listener is registered are also possible. Filters use the parameters received by the filter factories to enable this option, for example:
import org.infinispan.notifications.cachelistener.filter.CacheEventFilterFactory;
import org.infinispan.notifications.cachelistener.filter.CacheEventFilter;
class DynamicCacheEventFilterFactory implements CacheEventFilterFactory {
@Override
public CacheEventFilter<Integer, String> getFilter(Object[] params) {
return new DynamicCacheEventFilter(params);
}
}
// Serializable, Externalizable or marshallable with Infinispan Externalizers
// needed when running in a cluster
class DynamicCacheEventFilter implements CacheEventFilter<Integer, String>, Serializable {
final Object[] params;
DynamicCacheEventFilter(Object[] params) {
this.params = params;
}
@Override
public boolean accept(Integer key, String oldValue, Metadata oldMetadata,
String newValue, Metadata newMetadata, EventType eventType) {
if (key.equals(params[0])) // dynamic key
return true;
return false;
}
}
The dynamic parameters required to do the filtering are provided when the listener is registered:
RemoteCache<?, ?> cache = ...
cache.addClientListener(new EventPrintListener(), new Object[]{1}, null);
Filter instances have to marshallable when they are deployed in a
cluster so that the filtering can happen right where the event is generated,
even if the even is generated in a different node to where the listener is
registered. To make them marshallable, either make them extend Serializable ,
Externalizable , or provide a custom Externalizer for them.
|
4.6.3. Skipping Notifications
Include the SKIP_LISTENER_NOTIFICATION
flag when calling remote API methods to
perform operations without getting event notifications from the server.
For example, to prevent listener notifications when creating or modifying values,
set the flag as follows:
remoteCache.withFlags(Flag.SKIP_LISTENER_NOTIFICATION).put(1, "one");
4.6.4. Customizing Events
The events generated by default contain just enough information to make the
event relevant but they avoid cramming too much information in order to reduce
the cost of sending them. Optionally, the information shipped in the events
can be customised in order to contain more information, such as values, or to
contain even less information. This customization is done with CacheEventConverter
instances generated by a CacheEventConverterFactory
:
import org.infinispan.notifications.cachelistener.filter.CacheEventConverterFactory;
import org.infinispan.notifications.cachelistener.filter.CacheEventConverter;
import org.infinispan.filter.NamedFactory;
@NamedFactory(name = "static-converter")
class StaticConverterFactory implements CacheEventConverterFactory {
final CacheEventConverter<Integer, String, CustomEvent> staticConverter = new StaticCacheEventConverter();
public CacheEventConverter<Integer, String, CustomEvent> getConverter(final Object[] params) {
return staticConverter;
}
}
// Serializable, Externalizable or marshallable with Infinispan Externalizers
// needed when running in a cluster
class StaticCacheEventConverter implements CacheEventConverter<Integer, String, CustomEvent>, Serializable {
public CustomEvent convert(Integer key, String oldValue, Metadata oldMetadata, String newValue, Metadata newMetadata, EventType eventType) {
return new CustomEvent(key, newValue);
}
}
// Needs to be Serializable, Externalizable or marshallable with Infinispan Externalizers
// regardless of cluster or local caches
static class CustomEvent implements Serializable {
final Integer key;
final String value;
CustomEvent(Integer key, String value) {
this.key = key;
this.value = value;
}
}
In the example above, the converter generates a new custom event which includes the value as well as the key in the event. This will result in bigger event payloads compared with default events, but if combined with filtering, it can reduce its network bandwidth cost.
The target type of the converter must be either Serializable or
Externalizable . In this particular case of converters, providing an
Externalizer will not work by default since the default Hot Rod client
marshaller does not support them.
|
Handling custom events requires a slightly different client listener
implementation to the one demonstrated previously. To be more precise, it
needs to handle ClientCacheEntryCustomEvent
instances:
import org.infinispan.client.hotrod.annotation.*;
import org.infinispan.client.hotrod.event.*;
@ClientListener
public class CustomEventPrintListener {
@ClientCacheEntryCreated
@ClientCacheEntryModified
@ClientCacheEntryRemoved
public void handleCustomEvent(ClientCacheEntryCustomEvent<CustomEvent> e) {
System.out.println(e);
}
}
The ClientCacheEntryCustomEvent
received in the callback exposes the custom
event via getEventData
method, and the getType
method provides information
on whether the event generated was as a result of cache entry creation,
modification or removal.
Similar to filtering, to be able to register a listener with this converter factory, the factory has to be given a unique name, and the Hot Rod server needs to be plugged with the name and the cache event converter factory instance.
-
Create a JAR file with the converter implementation within it.
If the cache uses custom key/value classes, these must be included in the JAR so that the callbacks can be executed with the correctly unmarshalled key and/or value instances. If the client listener has
useRawData
enabled, this is not necessary since the callback key/value instances will be provided in binary format. -
Create a
META-INF/services/org.infinispan.notifications.cachelistener.filter.CacheEventConverterFactory
file within the JAR file and within it, write the fully qualified class name of the converter class implementation. -
Add the JAR file to the
server/lib
directory of your Infinispan server installation directory. -
Link the client listener with this converter factory by adding the factory name to the
@ClientListener
annotation:@ClientListener(converterFactoryName = "static-converter") public class CustomEventPrintListener { ... }
-
Register the listener with the server:
RemoteCache<?, ?> cache = ... cache.addClientListener(new CustomEventPrintListener());
Dynamic converter instances that convert based on parameters provided when the listener is registered are also possible. Converters use the parameters received by the converter factories to enable this option. For example:
import org.infinispan.notifications.cachelistener.filter.CacheEventConverterFactory;
import org.infinispan.notifications.cachelistener.filter.CacheEventConverter;
@NamedFactory(name = "dynamic-converter")
class DynamicCacheEventConverterFactory implements CacheEventConverterFactory {
public CacheEventConverter<Integer, String, CustomEvent> getConverter(final Object[] params) {
return new DynamicCacheEventConverter(params);
}
}
// Serializable, Externalizable or marshallable with Infinispan Externalizers needed when running in a cluster
class DynamicCacheEventConverter implements CacheEventConverter<Integer, String, CustomEvent>, Serializable {
final Object[] params;
DynamicCacheEventConverter(Object[] params) {
this.params = params;
}
public CustomEvent convert(Integer key, String oldValue, Metadata oldMetadata,
String newValue, Metadata newMetadata, EventType eventType) {
// If the key matches a key given via parameter, only send the key information
if (params[0].equals(key))
return new CustomEvent(key, null);
return new CustomEvent(key, newValue);
}
}
The dynamic parameters required to do the conversion are provided when the listener is registered:
RemoteCache<?, ?> cache = ...
cache.addClientListener(new EventPrintListener(), null, new Object[]{1});
Converter instances have to marshallable when they are deployed in a
cluster, so that the conversion can happen right where the event is generated,
even if the event is generated in a different node to where the listener is
registered. To make them marshallable, either make them extend Serializable ,
Externalizable , or provide a custom Externalizer for them.
|
4.6.5. Filter and Custom Events
If you want to do both event filtering and customization, it’s easier to
implement org.infinispan.notifications.cachelistener.filter.CacheEventFilterConverter
which allows both filter and customization to happen in a single step.
For convenience, it’s recommended to extend
org.infinispan.notifications.cachelistener.filter.AbstractCacheEventFilterConverter
instead of implementing org.infinispan.notifications.cachelistener.filter.CacheEventFilterConverter
directly. For example:
import org.infinispan.notifications.cachelistener.filter.CacheEventConverterFactory;
import org.infinispan.notifications.cachelistener.filter.CacheEventConverter;
@NamedFactory(name = "dynamic-filter-converter")
class DynamicCacheEventFilterConverterFactory implements CacheEventFilterConverterFactory {
public CacheEventFilterConverter<Integer, String, CustomEvent> getFilterConverter(final Object[] params) {
return new DynamicCacheEventFilterConverter(params);
}
}
// Serializable, Externalizable or marshallable with Infinispan Externalizers needed when running in a cluster
//
class DynamicCacheEventFilterConverter extends AbstractCacheEventFilterConverter<Integer, String, CustomEvent>, Serializable {
final Object[] params;
DynamicCacheEventFilterConverter(Object[] params) {
this.params = params;
}
public CustomEvent filterAndConvert(Integer key, String oldValue, Metadata oldMetadata,
String newValue, Metadata newMetadata, EventType eventType) {
// If the key matches a key given via parameter, only send the key information
if (params[0].equals(key))
return new CustomEvent(key, null);
return new CustomEvent(key, newValue);
}
}
Similar to filters and converters, to be able to register a listener with this
combined filter/converter factory, the factory has to be given a unique name via the
@NamedFactory
annotation, and the Hot Rod server needs to be plugged with the
name and the cache event converter factory instance.
-
Create a JAR file with the converter implementation within it.
If the cache uses custom key/value classes, these must be included in the JAR so that the callbacks can be executed with the correctly unmarshalled key and/or value instances. If the client listener has
useRawData
enabled, this is not necessary since the callback key/value instances will be provided in binary format. -
Create a
META-INF/services/org.infinispan.notifications.cachelistener.filter.CacheEventFilterConverterFactory
file within the JAR file and within it, write the fully qualified class name of the converter class implementation. -
Add the JAR file to the
server/lib
directory of your Infinispan server installation directory.
From a client perspective, to be able to use the combined filter and converter class, the client listener must define the same filter factory and converter factory names, e.g.:
@ClientListener(filterFactoryName = "dynamic-filter-converter", converterFactoryName = "dynamic-filter-converter")
public class CustomEventPrintListener { ... }
The dynamic parameters required in the example above are provided when the listener is registered via either filter or converter parameters. If filter parameters are non-empty, those are used, otherwise, the converter parameters:
RemoteCache<?, ?> cache = ...
cache.addClientListener(new CustomEventPrintListener(), new Object[]{1}, null);
4.6.6. Event Marshalling
Hot Rod servers can store data in different formats, but in spite of that,
Java Hot Rod client users can still develop CacheEventConverter
or CacheEventFilter
instances that work on typed objects. By default, filters and converter will use data as POJO
(application/x-java-object) but it is possible to override the desired format by overriding the
method format()
from the filter/converter. If the format returns null
, the filter/converter will receive
data as it’s stored.
Hot Rod Java clients can be configured to use different org.infinispan.commons.marshall.Marshaller
instances.
If doing this and deploying CacheEventConverter
or CacheEventFilter
instances,
to be able to present filters/converter with Java Objects rather than marshalled content,
the server needs to be able to convert between objects and the binary format produced
by the marshaller.
To deploy a Marshaller instance server-side, follow a similar method to the one
used to deploy CacheEventConverter
or CacheEventFilter
instances:
-
Create a JAR file with the converter implementation within it.
-
Create a
META-INF/services/org.infinispan.commons.marshall.Marshaller
file within the JAR file and within it, write the fully qualified class name of the marshaller class implementation. -
Add the JAR file to the
server/lib
directory of your Infinispan server installation directory.
Note that the Marshaller could be deployed in either a separate jar, or in the
same jar as the CacheEventConverter
and/or CacheEventFilter
instances.
Deploying Protostream Marshallers
If a cache stores Protobuf content, as it happens when using ProtoStream marshaller in the Hot Rod client, it’s not necessary to deploy a custom marshaller since the format is already support by the server: there are transcoders from Protobuf format to most common formats like JSON and POJO.
When using filters/converters with those caches, and it’s desirable to use filter/converters with Java Objects rather
binary Protobuf data, it’s necessary to configure the extra ProtoStream marshallers so that the server can unmarshall
the data before filtering/converting. To do so, you must configure the required SerializationContextInitializer(s)
as part of the Infinispan server configuration.
See Cache Encoding and Marshalling for more information.
4.6.7. Listener State Handling
Client listener annotation has an optional includeCurrentState
attribute
that specifies whether state will be sent to the client when the listener is
added or when there’s a failover of the listener.
By default, includeCurrentState
is false, but if set to true and a client
listener is added in a cache already containing data, the server iterates over
the cache contents and sends an event for each entry to the client as a
ClientCacheEntryCreated
(or custom event if configured). This allows clients
to build some local data structures based on the existing content. Once the
content has been iterated over, events are received as normal, as cache
updates are received. If the cache is clustered, the entire cluster wide
contents are iterated over.
4.6.8. Listener Failure Handling
When a Hot Rod client registers a client listener, it does so in a single node in a cluster. If that node fails, the Java Hot Rod client detects that transparently and fails over all listeners registered in the node that failed to another node.
During this fail over the client might miss some events. To avoid missing
these events, the client listener annotation contains an optional parameter
called includeCurrentState
which if set to true, when the failover happens,
the cache contents can iterated over and ClientCacheEntryCreated
events
(or custom events if configured) are generated. By default,
includeCurrentState
is set to false.
Use callbacks to handle failover events:
@ClientCacheFailover
public void handleFailover(ClientCacheFailoverEvent e) {
...
}
This is very useful in use cases where the client has cached some data, and as a result of the fail over, taking in account that some events could be missed, it could decide to clear any locally cached data when the fail over event is received, with the knowledge that after the fail over event, it will receive events for the contents of the entire cache.
4.7. Hot Rod Java Client Transactions
You can configure and use Hot Rod clients in JTA Transactions.
To participate in a transaction, the Hot Rod client requires the TransactionManager with which it interacts and whether it participates in the transaction through the Synchronization or XAResource interface.
Transactions are optimistic in that clients acquire write locks on entries during the prepare phase. To avoid data inconsistency, be sure to read about Detecting Conflicts with Transactions. |
4.7.1. Configuring the Server
Caches in the server must also be transactional for clients to participate in JTA Transactions.
The following server configuration is required, otherwise transactions rollback only:
-
Isolation level must be
REPEATABLE_READ
. -
PESSIMISTIC
locking mode is recommended butOPTIMISTIC
can be used. -
Transaction mode should be
NON_XA
orNON_DURABLE_XA
. Hot Rod transactions should not useFULL_XA
because it degrades performance.
For example:
<replicated-cache name="hotrodReplTx">
<locking isolation="REPEATABLE_READ"/>
<transaction mode="NON_XA" locking="PESSIMISTIC"/>
</replicated-cache>
Hot Rod transactions have their own recovery mechanism.
4.7.2. Configuring Hot Rod Clients
Transactional RemoteCache are configured per-cache basis.
The exception is the transaction’s timeout
which is global, because a single transaction can interact with multiple RemoteCaches.
The following example shows how to configure a transactional RemoteCache for cache my-cache
:
org.infinispan.client.hotrod.configuration.ConfigurationBuilder cb = new org.infinispan.client.hotrod.configuration.ConfigurationBuilder();
//other client configuration parameters
cb.transactionTimeout(1, TimeUnit.MINUTES);
cb.remoteCache("my-cache")
.transactionManagerLookup(GenericTransactionManagerLookup.getInstance())
.transactionMode(TransactionMode.NON_XA);
See ConfigurationBuilder and RemoteCacheConfigurationBuilder Javadoc for documentation on configuration parameters.
You can also configure the Java Hot Rod client with a properties file, as in the following example:
infinispan.client.hotrod.cache.my-cache.transaction.transaction_manager_lookup = org.infinispan.client.hotrod.transaction.lookup.GenericTransactionManagerLookup
infinispan.client.hotrod.cache.my-cache.transaction.transaction_mode = NON_XA
infinispan.client.hotrod.transaction.timeout = 60000
TransactionManagerLookup Interface
TransactionManagerLookup
provides an entry point to fetch a TransactionManager.
Available implementations of TransactionManagerLookup
:
- GenericTransactionManagerLookup
-
A lookup class that locates TransactionManagers running in Java EE application servers. Defaults to the RemoteTransactionManager if it cannot find a TransactionManager. This is the default for Hot Rod Java clients.
In most cases, GenericTransactionManagerLookup is suitable.
However, you can implement the |
- RemoteTransactionManagerLookup
-
A basic, and volatile, TransactionManager if no other implementation is available. Note that this implementation has significant limitations when handling concurrent transactions and recovery.
4.7.3. Transaction Modes
TransactionMode controls how a RemoteCache interacts with the TransactionManager.
Configure transaction modes on both the Infinispan server and your client application. If clients attempt to perform transactional operations on non-transactional caches, runtime exceptions can occur. |
Transaction modes are the same in both the Infinispan configuration and client settings. Use the following modes with your client, see the Infinispan configuration schema for the server:
NONE
-
The RemoteCache does not interact with the TransactionManager. This is the default mode and is non-transactional.
NON_XA
-
The RemoteCache interacts with the TransactionManager via Synchronization.
NON_DURABLE_XA
-
The RemoteCache interacts with the TransactionManager via XAResource. Recovery capabilities are disabled.
FULL_XA
-
The RemoteCache interacts with the TransactionManager via XAResource. Recovery capabilities are enabled. Invoke the
XaResource.recover()
method to retrieve transactions to recover.
4.7.4. Detecting Conflicts with Transactions
Transactions use the initial values of keys to detect conflicts.
For example, "k" has a value of "v" when a transaction begins. During the prepare phase, the transaction fetches "k" from the server to read the value. If the value has changed, the transaction rolls back to avoid a conflict.
Transactions use versions to detect changes instead of checking value equality. |
The forceReturnValue
parameter controls write operations to the RemoteCache and helps avoid conflicts.
It has the following values:
-
If
true
, the TransactionManager fetches the most recent value from the server before performing write operations. However, theforceReturnValue
parameter applies only to write operations that access the key for the first time. -
If
false
, the TransactionManager does not fetch the most recent value from the server before performing write operations.
This parameter does not affect conditional write operations such as |
The following transactions provide an example where the forceReturnValue
parameter can prevent conflicting write operations:
RemoteCache<String, String> cache = ...
TransactionManager tm = ...
tm.begin();
cache.put("k", "v1");
tm.commit();
RemoteCache<String, String> cache = ...
TransactionManager tm = ...
tm.begin();
cache.put("k", "v2");
tm.commit();
In this example, TX1 and TX2 are executed in parallel. The initial value of "k" is "v".
-
If
forceReturnValue = true
, thecache.put()
operation fetches the value for "k" from the server in both TX1 and TX2. The transaction that acquires the lock for "k" first then commits. The other transaction rolls back during the commit phase because the transaction can detect that "k" has a value other than "v". -
If
forceReturnValue = false
, thecache.put()
operation does not fetch the value for "k" from the server and returns null. Both TX1 and TX2 can successfully commit, which results in a conflict. This occurs because neither transaction can detect that the initial value of "k" changed.
The following transactions include cache.get()
operations to read the value for "k" before doing the cache.put()
operations:
RemoteCache<String, String> cache = ...
TransactionManager tm = ...
tm.begin();
cache.get("k");
cache.put("k", "v1");
tm.commit();
RemoteCache<String, String> cache = ...
TransactionManager tm = ...
tm.begin();
cache.get("k");
cache.put("k", "v2");
tm.commit();
In the preceding examples, TX1 and TX2 both read the key so the forceReturnValue
parameter does not take effect.
One transaction commits, the other rolls back.
However, the cache.get()
operation requires an additional server request.
If you do not need the return value for the cache.put()
operation that server request is inefficient.
4.7.5. Using the Configured Transaction Manager and Transaction Mode
The following example shows how to use the TransactionManager
and TransactionMode
that you configure in the RemoteCacheManager
:
//Configure the transaction manager and transaction mode.
org.infinispan.client.hotrod.configuration.ConfigurationBuilder cb = new org.infinispan.client.hotrod.configuration.ConfigurationBuilder();
cb.remoteCache("my-cache")
.transactionManagerLookup(RemoteTransactionManagerLookup.getInstance())
.transactionMode(TransactionMode.NON_XA);
RemoteCacheManager rcm = new RemoteCacheManager(cb.build());
//The my-cache instance uses the RemoteCacheManager configuration.
RemoteCache<String, String> cache = rcm.getCache("my-cache");
//Return the transaction manager that the cache uses.
TransactionManager tm = cache.getTransactionManager();
//Perform a simple transaction.
tm.begin();
cache.put("k1", "v1");
System.out.println("K1 value is " + cache.get("k1"));
tm.commit();
4.8. Counter API
The MultimapCacheManager
interface is the entry point to get a RemoteMultimapCache
.
Hot Rod clients can retrieve the MultimapCacheManager
interface as in the following example:
// create or obtain your RemoteCacheManager
RemoteCacheManager manager = ...;
// retrieve the MultimapCacheManager
MultimapCacheManager multimapCacheManager = RemoteMultimapCacheManagerFactory.from(manager);
// retrieve the RemoteMultimapCache
RemoteMultimapCache<Integer, String> people = multimapCacheManager.get("people");
// add key - values
people.put("coders", "Will");
people.put("coders", "Auri");
people.put("coders", "Pedro");
// retrieve single key with multiple values
Collection<String> coders = people.get("coders").join();
5. Using the JCache API
Infinispan provides an implementation of the JCache (JSR-107) API that specifies a standard Java API for caching temporary Java objects in memory. Caching Java objects can help get around bottlenecks arising from using data that is expensive to retrieve or data that is hard to calculate. Caching these type of objects in memory can help speed up application performance by retrieving the data directly from memory instead of doing an expensive roundtrip or recalculation.
5.1. Creating remote caches
-
Ensure that
cache-api
is on your classpath. -
Add the following dependency to your
pom.xml
:<dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-jcache-remote</artifactId> </dependency>
-
Create caches on remote Infinispan servers and use the default JCache API configuration as follows:
import javax.cache.*;
import javax.cache.configuration.*;
// Retrieve the system wide Cache Manager via org.infinispan.jcache.remote.JCachingProvider
CacheManager cacheManager = Caching.getCachingProvider("org.infinispan.jcache.remote.JCachingProvider").getCacheManager();
// Define a named cache with default JCache configuration
Cache<String, String> cache = cacheManager.createCache("remoteNamedCache",
new MutableConfiguration<String, String>());
5.1.1. Configuring remote caches
Hot Rod configuration files include infinispan.client.hotrod.cache.*
properties that you can use to customize remote caches.
-
Pass the URI for your
hotrod-client.properties
file to theCachingProvider.getCacheManager(URI)
call as follows:
import javax.cache.*;
import javax.cache.configuration.*;
// Load configuration from an absolute filesystem path
URI uri = URI.create("file:///path/to/hotrod-client.properties");
// Load configuration from a classpath resource
// URI uri = this.getClass().getClassLoader().getResource("hotrod-client.properties").toURI();
// Retrieve the system wide Cache Manager via org.infinispan.jcache.remote.JCachingProvider
CacheManager cacheManager = Caching.getCachingProvider("org.infinispan.jcache.remote.JCachingProvider")
.getCacheManager(uri, this.getClass().getClassLoader(), null);
5.2. Store and retrieve data
Even though JCache API does not extend neither java.util.Map not java.util.concurrent.ConcurrentMap, it providers a key/value API to store and retrieve data:
import javax.cache.*;
import javax.cache.configuration.*;
CacheManager cacheManager = Caching.getCachingProvider().getCacheManager();
Cache<String, String> cache = cacheManager.createCache("namedCache",
new MutableConfiguration<String, String>());
cache.put("hello", "world"); // Notice that javax.cache.Cache.put(K) returns void!
String value = cache.get("hello"); // Returns "world"
Contrary to standard java.util.Map,
javax.cache.Cache
comes with two basic put methods called put and getAndPut. The former returns
void
whereas the latter returns the previous value associated with the key.
So, the equivalent of java.util.Map.put(K)
in JCache is javax.cache.Cache.getAndPut(K).
Even though JCache API only covers standalone caching, it can be plugged with a persistence store, and has been designed with clustering or distribution in mind. The reason why javax.cache.Cache offers two put methods is because standard java.util.Map put call forces implementors to calculate the previous value. When a persistent store is in use, or the cache is distributed, returning the previous value could be an expensive operation, and often users call standard java.util.Map.put(K) without using the return value. Hence, JCache users need to think about whether the return value is relevant to them, in which case they need to call javax.cache.Cache.getAndPut(K) , otherwise they can call java.util.Map.put(K, V) which avoids returning the potentially expensive operation of returning the previous value. |
5.3. Comparing java.util.concurrent.ConcurrentMap and javax.cache.Cache APIs
Here’s a brief comparison of the data manipulation APIs provided by java.util.concurrent.ConcurrentMap and javax.cache.Cache APIs.
Operation | java.util.concurrent.ConcurrentMap<K, V> | javax.cache.Cache<K, V> |
---|---|---|
store and no return |
N/A |
|
store and return previous value |
|
|
store if not present |
|
|
retrieve |
|
|
delete if present |
|
|
delete and return previous value |
|
|
delete conditional |
|
|
replace if present |
|
|
replace and return previous value |
|
|
replace conditional |
|
|
Comparing the two APIs, it’s obvious to see that, where possible, JCache avoids returning the previous value to avoid operations doing expensive network or IO operations. This is an overriding principle in the design of JCache API. In fact, there’s a set of operations that are present in java.util.concurrent.ConcurrentMap , but are not present in the javax.cache.Cache because they could be expensive to compute in a distributed cache. The only exception is iterating over the contents of the cache:
Operation | java.util.concurrent.ConcurrentMap<K, V> | javax.cache.Cache<K, V> |
---|---|---|
calculate size of cache |
|
N/A |
return all keys in the cache |
|
N/A |
return all values in the cache |
|
N/A |
return all entries in the cache |
|
N/A |
iterate over the cache |
use |
|
5.4. Clustering JCache instances
Infinispan JCache implementation goes beyond the specification in order to provide the possibility to cluster caches using the standard API. Given a Infinispan configuration file configured to replicate caches like this:
<infinispan>
<cache-container default-cache="namedCache">
<transport cluster="jcache-cluster" />
<replicated-cache name="namedCache" />
</cache-container>
</infinispan>
You can create a cluster of caches using this code:
import javax.cache.*;
import java.net.URI;
// For multiple Cache Managers to be constructed with the standard JCache API
// and live in the same JVM, either their names, or their classloaders, must
// be different.
// This example shows how to force their classloaders to be different.
// An alternative method would have been to duplicate the XML file and give
// it a different name, but this results in unnecessary file duplication.
ClassLoader tccl = Thread.currentThread().getContextClassLoader();
CacheManager cacheManager1 = Caching.getCachingProvider().getCacheManager(
URI.create("infinispan-jcache-cluster.xml"), new TestClassLoader(tccl));
CacheManager cacheManager2 = Caching.getCachingProvider().getCacheManager(
URI.create("infinispan-jcache-cluster.xml"), new TestClassLoader(tccl));
Cache<String, String> cache1 = cacheManager1.getCache("namedCache");
Cache<String, String> cache2 = cacheManager2.getCache("namedCache");
cache1.put("hello", "world");
String value = cache2.get("hello"); // Returns "world" if clustering is working
// --
public static class TestClassLoader extends ClassLoader {
public TestClassLoader(ClassLoader parent) {
super(parent);
}
}
6. JUnit testing
Run JUnit tests for Infinispan.
6.1. Testing Infinispan with test containers
Run Infinispan Server instances using Testcontainers for faster, simpler JUnit tests.
The Infinispan Server test container is based on the official container image provided by Infinispan.
6.1.1. Starting the Infinispan test container
You can start the Infinispan Server container instance from any Java application as follows:
try (InfinispanContainer container = new InfinispanContainer()) {
container.start();
try (RemoteCacheManager cacheManager = container.getRemoteCacheManager()) {
// Use the RemoteCacheManager
}
}
6.1.2. Changing credentials
Infinispan Server is configured with the following default credentials:
-
Username:
admin
-
Password:
secret
Add custom credentials with the withUser()
and withPassword()
methods before starting the container:
InfinispanContainer container = new InfinispanContainer();
container.withUser("admin").withPassword("secret");
container.start();