Connecting Spring Boot to an Infinispan Cluster
Infinispan, or its supported product which is Red Hat DataGrid, is a very strong in-memory data grid product which offers flexible deployment options and robust capabilities for storing, managing, and processing data. And to maintain high availability and fault tolerance, Infinispan provides a clustering mechanism which have a multiple members.
For this article, im trying to create a cluster which consist of 3 Infinispan instances and all instances are being installed by using docker images. First we need to pull a specific Infinispan image,
$ docker pull infinispan/server:14.0.2.Final
And run 3 different instances of Infinispan,
$ docker run -p 11222:11222 -e USER=admin -e PASS=password \ --add-host=HOST:192.168.56.1 \ infinispan/server:14.0.2.Final $ docker run -p 11223:11222 -e USER=admin -e PASS=password \ --add-host=HOST:192.168.56.1 \ infinispan/server:14.0.2.Final $ docker run -p 11224:11222 -e USER=admin -e PASS=password \ --add-host=HOST:192.168.56.1 \ infinispan/server:14.0.2.Final
Next is login to one of Infinispan instances which is located in localhost:11222, and login with credential of “admin” and “password”. A successfully cluster will give this display,
Once every Infinispan instances are started, we can now focus on our Java project. Lets start with 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> <groupId>com.edw</groupId> <artifactId>spring-boot-and-clustered-infinispan</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <version.infinispan>14.0.7.Final</version.infinispan> <version.protostream>4.6.2.Final</version.protostream> <version.spring.boot3>3.0.4</version.spring.boot3> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-bom</artifactId> <version>${version.infinispan}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>${version.spring.boot3}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- infinispan --> <dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-spring-boot3-starter-remote</artifactId> </dependency> <dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-query</artifactId> </dependency> <dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-remote-query-client</artifactId> </dependency> <dependency> <groupId>org.infinispan.protostream</groupId> <artifactId>protostream-processor</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-client-hotrod</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
And an application.properties, in here we can define how many instances of Infinispan that we are connecting to. For this sample, im putting 3 instances which are each having their own ip.
### server port server.port=8080 spring.application.name=Spring Boot and Clustered Infinispan ## logging logging.level.root=INFO logging.pattern.console=%d{dd-MM-yyyy HH:mm:ss} %magenta([%thread]) %highlight(%-5level) %logger.%M - %msg%n # infinispan infinispan.remote.server-list=172.17.0.2:11222;172.17.0.3:11222;172.17.0.4:11222 infinispan.remote.auth-username=admin infinispan.remote.auth-password=password infinispan.remote.marshaller=org.infinispan.commons.marshall.ProtoStreamMarshaller
And create several Spring Boot’s Java classes, such as main class, controllers, beans, and configs.
@EnableCaching @SpringBootApplication public class Main { public static void main(String[] args) { SpringApplication.run(Main.class, args); } }
@RestController public class IndexController { @Autowired private RemoteCacheManager cacheManager; @GetMapping(path = "/") public HashMap index() { return new HashMap(){{ put("hello", "world"); }}; } @GetMapping(path = "/get-user") public User getUsers(@RequestParam String name) { return (User) cacheManager.getCache("user-cache").getOrDefault(name, new User()); } @GetMapping(path = "/add-user") public User addUsers(@RequestParam String name, @RequestParam Integer age, @RequestParam String address) { cacheManager.getCache("user-cache").put(name, new User(name, age, address)); return (User) cacheManager.getCache("user-cache").getOrDefault(name, new User()); } }
public class User implements Serializable { private String name; private Integer age; private String address; public User() { } public User(String name, Integer age, String address) { this.name = name; this.age = age; this.address = address; } @ProtoField(number = 1, required = true) public String getName() { return name; } public void setName(String name) { this.name = name; } @ProtoField(number = 2) public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @ProtoField(number = 3) public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } }
@Configuration public class InfinispanConfiguration { @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public InfinispanRemoteCacheCustomizer remoteCacheCustomizer() { return b -> { b.remoteCache("user-cache").marshaller(ProtoStreamMarshaller.class); }; } }
@Component public class InfinispanInitializer implements CommandLineRunner { @Autowired private RemoteCacheManager cacheManager; @Override public void run(String...args) throws Exception { SerializationContext ctx = MarshallerUtil.getSerializationContext(cacheManager); RemoteCache<String, String> protoMetadataCache = cacheManager.getCache(ProtobufMetadataManagerConstants.PROTOBUF_METADATA_CACHE_NAME); String msgSchemaFile = null; try { ProtoSchemaBuilder protoSchemaBuilder = new ProtoSchemaBuilder(); msgSchemaFile = protoSchemaBuilder.fileName("user.proto").packageName("user").addClass(User.class).build(ctx); protoMetadataCache.put("user.proto", msgSchemaFile); } catch (Exception e) { throw new RuntimeException("Failed to build protobuf definition from 'User class'", e); } String errors = protoMetadataCache.get(ProtobufMetadataManagerConstants.ERRORS_KEY_SUFFIX); if (errors != null) { throw new IllegalStateException("Some Protobuf schema files contain errors: " + errors + "\nSchema :\n" + msgSchemaFile); } } }
Run the code and try do some curl to add and retrieve data from cache,
$ curl -kv http://localhost:8080/add-user?name=lele&age=14&address=Jogja {"name":"lele","age":14,"address":"Jogja"} $ curl -kv http://localhost:8080/get-user?name=lele {"name":"lele","age":14,"address":"Jogja"}
And we can check the content of our cache from our dashboard,
Have fun with Infinispan.
https://github.com/edwin/spring-boot-and-clustered-infinispan