Search code examples
spring-boothibernatehazelcastspring-sessionspring-cache

Using Hazelcast for both Spring Session and 2 Level Cache (LC2) with Hibernate


So I want to use Hazelcast in my web application for both 2 level caching (hibernate layer) and spring session, the setup is very simple I want to be able to use NearCache configurations with a server running on the network.

I first ran into a compatibility problem recent version of Hazelcast 4.* is not yet supported in spring session, so I was happy to use the supported version: 3.12.6... below is my hazelcast-client.xml and I have properly configured it to be used by spring.hazelcast.config=file://... when I start my application the Hazelcast instance is not created, so I decided well I should create the ClientConfig and HazelcastInstance beans myself using the code below:

@Bean()
ClientConfig clientConfig() throws IOException{
    try(final InputStream stream = Files.newInputStream(Paths.get("proper-path/hazelcast-client.xml"))){
      com.hazelcast.client.config.XmlClientConfigBuilder builder = new com.hazelcast.client.config.XmlClientConfigBuilder(stream);
      return builder.build();
    }
  }
@Bean()
HazelcastInstance hazelcastInstance(final ClientConfig clientConfig){
    return HazelcastClient.newHazelcastClient(clientConfig);
}

Now i have a problem, I can't add the code below so I can use it with sessions:

final MapAttributeConfig attributeConfig = new MapAttributeConfig()
            .setName(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
            .setExtractor(PrincipalNameExtractor.class.getName());

hazelcastInstance.getConfig().getMapConfig(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME)
        .addMapAttributeConfig(attributeConfig)
        .addMapIndexConfig(new MapIndexConfig(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false));  

hazelcast-client.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <hazelcast-client xmlns="http://www.hazelcast.com/schema/client-config"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xsi:schemaLocation="http://www.hazelcast.com/schema/client-config
                  http://www.hazelcast.com/schema/client-config/hazelcast-client-config-3.12.xsd">
    <group>
        <name>GroupName</name>
        <password>pass</password>
    </group>
    <instance-name>${application.container.name}</instance-name>
    <properties>
        <property name="hazelcast.client.shuffle.member.list">true</property>
        <property name="hazelcast.client.heartbeat.timeout">60000</property>
        <property name="hazelcast.client.heartbeat.interval">5000</property>
        <property name="hazelcast.client.event.thread.count">5</property>
        <property name="hazelcast.client.event.queue.capacity">1000000</property>
        <property name="hazelcast.client.invocation.timeout.seconds">120</property>
    </properties>
    <client-labels>
        <label>web-app</label>
        <label>${application.container.name}</label>
    </client-labels>
    <network>
        <cluster-members>
            <address>127.0.0.1</address>
        </cluster-members>
        <outbound-ports>
            <ports>5801</ports>
        </outbound-ports>
        <smart-routing>true</smart-routing>
        <redo-operation>true</redo-operation>
        <connection-timeout>60000</connection-timeout>
        <connection-attempt-period>3000</connection-attempt-period>
        <connection-attempt-limit>2</connection-attempt-limit>
        <socket-options>
            <tcp-no-delay>false</tcp-no-delay>
            <keep-alive>true</keep-alive>
            <reuse-address>true</reuse-address>
            <linger-seconds>3</linger-seconds>
            <buffer-size>128</buffer-size>
        </socket-options>
    </network>
    <executor-pool-size>40</executor-pool-size>
    <native-memory enabled="false" allocator-type="POOLED">
        <size unit="MEGABYTES" value="128"/>
        <min-block-size>1</min-block-size>
        <page-size>1</page-size>
        <metadata-space-percentage>40.5</metadata-space-percentage>
    </native-memory>
    <load-balancer type="random"/>
    <flake-id-generator name="default">
        <prefetch-count>100</prefetch-count>
        <prefetch-validity-millis>600000</prefetch-validity-millis>
    </flake-id-generator>
    <connection-strategy async-start="true" reconnect-mode="ASYNC">
        <connection-retry enabled="true">
            <initial-backoff-millis>2000</initial-backoff-millis>
            <max-backoff-millis>60000</max-backoff-millis>
            <multiplier>3</multiplier>
            <fail-on-max-backoff>true</fail-on-max-backoff>
            <jitter>0.5</jitter>
        </connection-retry>
    </connection-strategy>

   </hazelcast-client>

Solution

  • You need to configure the map on the server side, which means you need to have some of the Spring classes on member's classpath. But keep in mind that you need this configuration only when HazelcastIndexedSessionRepository#findByIndexNameAndIndexValue is used.

    Also in client mode, do not forget to deploy necessary classes and enable user code deployment for members. Otherwise session updates will fail:

    // member
    config.getUserCodeDeploymentConfig().setEnabled(true)
          .setClassCacheMode(UserCodeDeploymentConfig.ClassCacheMode.ETERNAL);
    
    // client
    clientConfig.getUserCodeDeploymentConfig().setEnabled(true).addClass(Session.class)
                .addClass(MapSession.class).addClass(SessionUpdateEntryProcessor.class);
    

    But I recommend to include spring-session-hazelcast on member's cp rather than user code deployment. This will satisfy both of the needs above.

    Finally, if hazelcast-client.xml exists in one of the known paths in your project (e.g. under resources/), the client will be created with this configuration. You do not need to create ClientConfig bean in that case.