Working with Spring Boot and Hazelcast(Distributed Cache)
Hazelcast provides central, predictable scaling of applications through in-memory access to frequently used data and across an elastically scalable data grid. These techniques reduce the query load on databases and improve speed.
Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”
Let us create simple Spring Boot application with Hazelcast as Distrubuted Cache.
Below is an implementation of a local cache for better performance.
Here when a requested is made from application, if that data is available in cache then we don’t have to make a call to database or if data is not available in cache then requested is made to database.
We deploy the application as a distributed system as below for better load balancing.
Here is the implementation of a simple spring boot application.
First we make changes in pom file by adding hazelcast dependency as below.
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
</dependency>
We also add a plugin where the entry point to the application is provided in distributed system. Since DoctorApplication is the main class it is added in plugin as below.
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.rbitcs.DoctorApplication</mainClass>
</configuration>
</plugin>
POM file should look like below
pom.xml
<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.rbitcs</groupId>
<artifactId>hazelcast-springboot</artifactId>
<version>0.0.1-SNAPSHOT</version><parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent><properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
</dependency>
</dependencies><build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.RBITCS.DoctorApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
We will be using IMAP provided by hazelmap as the cache. IMAP implements map interface. Below is the spring boot application where beans use IMAP for caching in configuring hazelcast instance.
package com.rbitcs;import java.util.HashMap;
import java.util.Map;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.hazelcast.config.Config;
import com.hazelcast.config.ManagementCenterConfig;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
import com.codeusingjava.model.UserAccount;import com.rbitcs.model.Doctors;@SpringBootApplication
public class HospitalApplication {public static void main(String[] args) {
SpringApplication.run(HospitalApplication.class, args);
}@Bean
public Config hazelCastConfig() {
return new Config().setManagementCenterConfig(
new ManagementCenterConfig().setEnabled(true).setUrl("http://localhost:8080/hazelcast-mancenter"));}@Bean
public HazelcastInstance hazelcastInstance(Config hazelCastConfig) {
return Hazelcast.newHazelcastInstance(hazelCastConfig);
}@Bean
public Map<String, Doctor> doctorMap(HazelcastInstance hazelcastInstance) {
return hazelcastInstance.getMap("doctorMap");
}
}
Since we are making use of IMAP we need to serialize the object. Here Doctor object should be serializable.
package com.rbitcs.model;import java.io.Serializable;import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;@Entity
@Table(name = "doctor")
public class Doctor implements Serializable {private static final long serialVersionUID = 1L;@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;@Column(name = "doctorNumber")
private String doctorNumber;@Column(name = "name")
private String name;@Column(name = "address")
private String address;public String getName() {
return name;
}public void setName(String name) {
this.name = name;
}public String getAddress() {
return address;
}public void setAddress(String address) {
this.address = address;
}public String getDoctorNumber() {
return doctorNumber;
}public void setDoctorNumber(String doctorNumber) {
this.doctorNumber = doctorNumber;
}
}
Let us create controller and autowire doctormap
package com.rbitcs.controller;import java.util.List;
import java.util.Map;
import java.util.Optional;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import com.rbitcs.db.DoctorRepository;
import com.rbitcs.model.Doctor;@RestController
@RequestMapping(path = "doctors")
public class DoctorController {@Autowired
private DoctorRepository doctorRepository;@Autowired
private Map<String, Doctor> doctorMap;@GetMapping(path = { "/get/{doctorNumber}" })
public Doctor getDoctor(@PathVariable("doctorNumber") String doctorNumber) {
/First call is to check if doctormap has doctor details if yes, return the value otherwise call database.
Doctor doctor = (doctortMap.get(doctorNumber) != null) ? doctorMap.get(doctorNumber)
: DoctorRepository.findByDoctorNumber(doctorNumber);
return doctor;
}@PostMapping("/add")
public void createDoctor(@RequestBody Doctor doctor) {
//save doctor details in cache
doctorMap.put(doctor.getDoctorNumber(), doctor);
doctorRepository.save(doctor);
}@DeleteMapping(path = { "/delete/{doctorNumber}" })
public Doctor deleteDoctor(@PathVariable("doctorNumber") String doctorNumber) {
//remove doctor details from both cache and database
doctorMap.remove(doctorNumber);
return doctorRepository.deleteByDoctorNumber(doctorNumber);
}
}
Nice! We have made all the necessary changes.
Let us start application with below command.
java -Dserver.port='8081' -jar boot-hazel-0.0.1-SNAPSHOT.jarjava -Dserver.port='8082' -jar boot-hazel-0.0.1-SNAPSHOT.jarjava -Dserver.port='8083' -jar boot-hazel-0.0.1-SNAPSHOT.jar
Let say we have started 3 instances at 3 different ports(8081, 8082, 8083)
When a doctor details are stored via 8081 port application, then the same object can be retrieved from 8083 port or 8082 port application from the cache and it does not need to make a call to database.
Take a look at the Hazelcast Management Center
After downloading Hazelcast Management Center start the process and after successful start you will see below.
C:\hazelcast-management-center-3.12.14>start.bat
2021-02-23 10:09:15 [main] INFO c.h.webmonitor.config.BuildInfo - Management Center 3.12.14
2021-02-23 10:09:19 [main] INFO c.h.w.storage.DiskUsageMonitor - Monitoring C:\Users\diraj.pandey\hazelcast-mc [mode=purge, interval=1000ms, limit=512 MB]
2021-02-23 10:09:19 [main] INFO c.h.webmonitor.config.SqlDbConfig - Checking DB for required migrations.
2021-02-23 10:09:20 [main] INFO c.h.webmonitor.config.SqlDbConfig - Number of applied DB migrations: 0.
2021-02-23 10:09:20 [main] INFO c.h.w.s.s.impl.DisableLoginStrategy - Login will be disabled for 5 seconds after 3 failed login attempts. For every 3 consecutive failed login attempts, disable period will be multiplied by 10.
2021-02-23 10:09:20 [main] INFO c.h.webmonitor.config.AppConfig - Creating cache with maxSize=768
2021-02-23 10:09:21 [main] INFO Launcher - Hazelcast Management Center successfully started at http://localhost:8080/hazelcast-mancenter
Management center can be open with http://localhost:8080/hazelcast-mancenter url and enter login credentials.
After successful login you will be landed to the below screen.
All the information of the cluster will be available. Here we can on node with ip addtess 192.168.0.191, You can also see the health of the node like heap size, distribution, cpu utilization, cluster state.
After above implementation the architecture looks like below.
Here all the nodes are synchronized and same date will be retrived from when requested from any of the jvm with better performance and better load balance.
Please take at look at github for complete code.