spring-boottcpkubernetesehcachejgroups

Setup ehcache with jgroups replication on kubernetes


I'm trying to setup a small springboot application using ehcache with jgroups replication in kubernetes, but somehow not able to discover the other members to form a cluster. The bootstrap request to find other nodes is not working, as the message is sent synchronously but not received by the other pod residing on another node.

maven pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.test.k8s</groupId>
    <artifactId>ehcache-jgroups-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>ehcache-jgroups-demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>11</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>${ehcache.version}</version>
        </dependency>
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache-jgroupsreplication</artifactId>
            <version>1.7</version>
            <exclusions>
                <exclusion>
                    <groupId>net.sf.ehcache</groupId>
                    <artifactId>ehcache-core</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.jgroups</groupId>
                    <artifactId>jgroups</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.jgroups</groupId>
            <artifactId>jgroups</artifactId>
            <version>3.6.17.Final</version>
        </dependency>
        <dependency>
            <groupId>org.jgroups.kubernetes</groupId>
            <artifactId>kubernetes</artifactId>
            <version>0.9.2</version>
            <exclusions>
                <exclusion>
                    <groupId>io.undertow</groupId>
                    <artifactId>undertow-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Springboot application

@SpringBootApplication
public class EhcacheJgroupsDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(EhcacheJgroupsDemoApplication.class, args);
    }

}

@EnableCaching
@Configuration
class EHCacheConfig {

    @Bean
    public CacheManager cacheManager(){
        return new EhCacheCacheManager(ehCacheManagerFactory().getObject());
    }

    @Bean
    public EhCacheManagerFactoryBean ehCacheManagerFactory(){
        EhCacheManagerFactoryBean ehCacheBean = new EhCacheManagerFactoryBean();
        ehCacheBean.setConfigLocation(new ClassPathResource("ehcache.xml"));
        ehCacheBean.setShared(true);
        return ehCacheBean;
    }

}

@Component
@Slf4j
class CacheManagerCheck implements CommandLineRunner {


    private final CacheManager cacheManager;

    public CacheManagerCheck(CacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }

    @Override
    public void run(String... strings) {
        log.info("\n\n" + "=========================================================\n"
                + "Using cache manager: " + this.cacheManager.getClass().getName() + "\n"
                + "=========================================================\n\n");
    }

}

@RestController
class CountryController {

    @Autowired
    CountryRepository countryRepository;

    @GetMapping(value = "/country/{code}")
    public ResponseEntity<Country> getCountryByCode(@PathVariable("code") String code) {
        Country country = countryRepository.findByCode(code);
        return ResponseEntity.ok(country);
    }

    @DeleteMapping(value = "/country/{code}")
    public ResponseEntity deleteCountryByCode(@PathVariable("code") String code) {
        countryRepository.deleteCode(code);
        return ResponseEntity.ok().build();
    }
}

@Slf4j
@Component
class CountryRepository {

    @Cacheable(value="countries", key="#code")
    public Country findByCode(String code) {
        log.info("---> Loading country with code={}", code);
        return new Country(code);
    }

    @CacheEvict(value="countries",key = "#code")
    public int deleteCode(String code){
        log.info("---> Deleting country with code={}", code);
        return 0;
    }
}

@Data
class Country implements Serializable {

    private final String code;
}
<?xml version="1.0" encoding="UTF-8"?>
<ehcache
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
        updateCheck="false">

    <cacheManagerPeerProviderFactory
            class="x.x.x.x.JGroupsCacheManagerPeerProviderFactory"
            properties="file=jgroups/tcp.xml" />

    <defaultCache maxElementsInMemory="50" eternal="false"
                  overflowToDisk="false" memoryStoreEvictionPolicy="LFU">
        <cacheEventListenerFactory
                class="net.sf.ehcache.distribution.jgroups.JGroupsCacheReplicatorFactory"
                properties="replicateAsynchronously=false, replicatePuts=true,
            replicateUpdates=true, replicateUpdatesViaCopy=false,
            replicateRemovals=true"/>

        <bootstrapCacheLoaderFactory
                class="net.sf.ehcache.distribution.jgroups.JGroupsBootstrapCacheLoaderFactory"
                properties="bootstrapAsynchronously=false"/>
    </defaultCache>

    <cache name="countries" eternal="false" maxElementsInMemory="100"
           overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="0"
           timeToLiveSeconds="60" memoryStoreEvictionPolicy="LRU">
        <cacheEventListenerFactory
                class="net.sf.ehcache.distribution.jgroups.JGroupsCacheReplicatorFactory"
                properties="replicateAsynchronously=false, replicatePuts=true,
            replicateUpdates=true, replicateUpdatesViaCopy=false,
            replicateRemovals=true"/>

        <bootstrapCacheLoaderFactory
                class="net.sf.ehcache.distribution.jgroups.JGroupsBootstrapCacheLoaderFactory"
                properties="bootstrapAsynchronously=false"/>
    </cache>

</ehcache>

tcp.xml

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="urn:org:jgroups"
        xsi:schemaLocation="urn:org:jgroups http://www.jgroups.org/schema/jgroups.xsd">
    <TCP
            bind_addr="${jgroups.tcp.address:match-interface:en.*}"
            bind_port="7800"
            recv_buf_size="5M"
            send_buf_size="1M"
            max_bundle_size="64K"
            enable_diagnostics="true"
            thread_naming_pattern="cl"

            thread_pool.min_threads="0"
            thread_pool.max_threads="500"
            thread_pool.keep_alive_time="30000" />

    <org.jgroups.protocols.kubernetes.KUBE_PING
            namespace="${KUBE_NAMESPACE:ehcache-demo}"/>

    <MERGE3 max_interval="30000"
            min_interval="10000"/>

    <VERIFY_SUSPECT timeout="1500"/>

    <BARRIER />
    <pbcast.NAKACK2 xmit_interval="500"
                    xmit_table_num_rows="100"
                    xmit_table_msgs_per_row="2000"
                    xmit_table_max_compaction_time="30000"
                    use_mcast_xmit="false"
                    discard_delivered_msgs="true" />
    <UNICAST3
            xmit_table_num_rows="100"
            xmit_table_msgs_per_row="1000"
            xmit_table_max_compaction_time="30000"/>
    <pbcast.STABLE stability_delay="1000" desired_avg_gossip="50000"
                   max_bytes="8m"/>
    <pbcast.GMS print_local_addr="true" join_timeout="3000"
                view_bundling="true"/>
    <MFC max_credits="2M"
         min_threshold="0.4"/>
    <FRAG2 frag_size="60K"  />
    <pbcast.STATE_TRANSFER  />
    <CENTRAL_LOCK />
    <COUNTER/>
</config>

Solution

  • Solved it exposing container port for undertow server and forcing to use ipv4 stack - https://github.com/kunal-bhatia/ehcache-jgroups-demo