Ngoài Redis thì Hazelcast cũng là một trong những nền tảng cache tương đối tốt, mã nguồn mở. Trước đây mình cũng đã từng sử dụng hazelcast cho các dự án phức tạp vì nó cũng được viết bằng java, đồng nhất ngôn ngữ và công nghệ với dự án nên rất tiện, ngoài ra cơ chế MapStore của nó cũng cho phép tái sử dụng cơ sở dữ liệu thay vì phụ thuộc vào phần lưu trữ của Redis. Trong bài này Dũng sẽ cùng các bạn tìm hiểu về Hazelcast.

Mô hình hoạt động

Đầu tiên chúng ta hãy tìm hiểu một chút về mô hình hoạt động của Hazelcast.
Nội dung này chỉ được hỗ trợ trong Lark Docs

Các thành phần chúng ta nhìn thấy bao gồm:

  1. Client: Chính là các kết nối từ các ứng dụng của chúng ta muốn đưa dữ liệu vào Hazelcast hoặc lấy ra.
  2. Hazelcast: Có thể là một server hoặc một cluster tuỳ theo chúng ta cài đặt. Hazelcast cung nhiều cơ chế khác nhau để phân bổ dữ liệu cho các node server trong cluster, trong một Hazelcast cũng có cách thành phần:
  3. Các Map: Lưu các dữ liệu dạng key-value trên RAM để truy cập nhanh.
  4. Các MapStore: Lưu dữ liệu dạng key-value trên RAM, ngoài ra cho phép lấy và lưu dữ liệu vào cơ sở dữ liệu.
  5. Cơ sở dữ liệu: Là bất kỳ cơ sở dữ liệu nào bạn có thể sử dụng, ví dụ như MySQL, MongoDB.

Khởi tạo module

Chúng ta sẽ khởi tạo module có tên spring-boot-hazelcast.

Chuẩn bị

Chúng ta sẽ lưu dữ liệu từ hazelcast cache xuống cơ sở dữ liệu là mysql nên chúng ta sẽ cần tạo một cơ sở dữ liệu với một bảng như sau:

CREATE SCHEMA `test` DEFAULT CHARACTER SET utf8 COLLATE utf8_bin ;

CREATE TABLE `persons` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8_bin;

Bạn có thể cài hazelcast management-center để theo dõi dữ liệu:

docker pull hazelcast/management-center
docker run --rm -p 8080:8080 hazelcast/management-center:latest

Cấu hình dự án

Chúng ta sẽ cần thay đổi tập tin spring-boot-hazelcast/pom.xml với mã nguồn như sau:

<?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>vn.techmaster</groupId>
        <artifactId>mastering-spring-boot</artifactId>
        <version>1.0.0</version>
    </parent>

    <artifactId>spring-boot-hazelcast</artifactId>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring.boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.hazelcast</groupId>
            <artifactId>hazelcast-spring</artifactId>
            <version>${hazelcast.version}</version>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>${mysql.version}</version>
        </dependency>
    </dependencies>

</project>

Ở đây chúng bổ sung thư viện spring-boot-starter-hazelcast để có thể sử dụng các thành phần liên quan đến hazelcast. Ngoài ra chúng ta cũng cần bổ sung spring-boot-starter-data-jpa để lưu trữ dữ liệu từ cache vào cơ sở dữ liệu mysql thông qua MapStore.

Cấu hình

Chúng ta sẽ có tập tin cấu hình application.properties với nội dung như sau:

spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=12345678
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect

# Hazelcast cache configuration
spring.cache.type=hazelcast

Ở đây chúng ta có cấu hình cho jpa kết nối đến cơ sở dữ liệu chúng ta đã tạo và cấu hình chỉ định loại cache là hazelcast.
Tiếp theo chúng ta sẽ cần tạo một lớp cấu hình HazelcastConfiguration với nội dung như sau:

package vn.techmaster.hazelcast.config;

import com.hazelcast.config.Config;
import com.hazelcast.config.JoinConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class HazelcastConfiguration {

    @Bean
    public Config hazelcastConfig() {
        Config config = new Config();
        config.setInstanceName("hazelcast-instance");
        JoinConfig joinConfig = config.getNetworkConfig().getJoin();
        joinConfig.getMulticastConfig().setEnabled(false);
        joinConfig.getTcpIpConfig().setEnabled(true).addMember("127.0.0.1");
        return config;
    }
}

Ở đây chúng ta đang khởi tạo một hazelcast server chạy cùng với ứng dụng của chúng ta luôn, bạn thấy hơi kỳ lạ và khác biệt so với redis đúng không? Đây vừa là điểm hay cũng là điểm dở của hazelcast. Hay là nó rất linh hoạt trong việc chúng ta sử dụng loại hệ quản trị cơ sở dữ liệu nào để lưu dữ liệu từ cache xuống, dở là nó hơi khác mô hình client server chúng ta vẫn dùng gây nên cảm giác khó hiểu và ứng dụng cũng thể trở nên nặng nề hơn. Tuy nhiên bạn cũng có thể tách phần máy chủ hazelcast ra thành một module và triển khai trên 1 máy chủ riêng biệt và ứng dụng của chúng ta lúc này đóng vai trò là client cũng là một cách được khuyến khích.
Trong phần cấu hình chúng ta cũng đang để ở dạng multicast tức là cho phép hazelcast tự tìm trong mạng LAN các hazelcast server khác để tự kết nối thành một cluster.

Lớp dữ liệu

Chúng ta có thể khai báo một lớp dữ liệu Person thế này:

package vn.techmaster.hazelcast.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.io.Serializable;

@Getter
@Setter
@ToString
@Entity
@Table(name = "persons")
public class Person implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    private String name;
}

Lớp này chính là dạng Entity lưu vào cơ sở dữ liệu.

Lớp Repository

Chúng ta vẫn sẽ khái báo một lớp PersonRepository để lưu dữ liệu vào mysql:

package vn.techmaster.hazelcast.repo;


import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import vn.techmaster.hazelcast.entity.Person;

@Repository
public interface PersonRepository extends JpaRepository<Person, Long> {
}

Lớp Service

Bây giờ mới là lúc chúng ta động chạm đến Hazelcast thông qua lớp PersonService như sau:

package vn.techmaster.hazelcast.service;

import lombok.AllArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import vn.techmaster.hazelcast.entity.Person;
import vn.techmaster.hazelcast.repo.PersonRepository;

@Service
@AllArgsConstructor
public class PersonService {

    private final PersonRepository personRepository;

    @Cacheable("persons")
    public Person savePerson(Person person) {
        return personRepository.save(person);
    }

    @Cacheable("persons")
    public Person getPersonById(Long id) {
        return personRepository.findById(id).orElse(null);
    }
}

Ở đây chúng ta sử dụng Cacheable annotation để nói spring biết rằng chúng ta sẽ sử dụng cache cho các hàm được đánh dấu. Về bản chất bên trong thì đây là việc ứng dụng proxy design pattern của spring để cài đặt lại các hàm được đánh dấu để ưu tiên lấy từ cache hazelcast trước, nếu không có thì lấy từ cơ sở dữ liệu.

Khởi chạy chương trình

Để khởi chạy chương trình chúng ta sẽ cần tạo ra lớp SpringBootHazelcastStartUp với mã nguồn như sau:

package vn.techmaster.hazelcast;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.ApplicationContext;
import vn.techmaster.hazelcast.entity.Person;
import vn.techmaster.hazelcast.service.PersonService;

@EnableCaching
@SpringBootApplication
public class SpringBootHazelcastStartUp {

    public static void main(String[] args) {
        ApplicationContext applicationContext = SpringApplication
            .run(SpringBootHazelcastStartUp.class);
        PersonService personService = applicationContext
            .getBean(PersonService.class);
        Person person = new Person();
        person.setName("Techmaster");
        person = personService.savePerson(person);
        System.out.println("save person: " + person);

        Person fetchedPerson1 = personService.getPersonById(
            person.getId()
        );
        System.out.println("fetched person1: " + fetchedPerson1);
        Person fetchedPerson2 = personService.getPersonById(
            person.getId()
        );
        System.out.println("fetched person2: " + fetchedPerson2);
    }
}

Chúng ta lưu một đối tượng Person vào cơ sử dữ liệu và rồi gọi để lấy ra từ cache, kết quả chúng ta nhận được là:

// bỏ qua các log trước đó
save person: Person(id=12, name=Techmaster)
fetched person1: Person(id=12, name=Techmaster)
fetched person2: Person(id=12, name=Techmaster)

Chúng ta có thể truy cập vào trang quản trị http://localhost:8080/cluster-connections. Khởi tạo một cluster với Member Addresses là địa chỉ mạng LAN của máy tính, ví dụ thế này:

Sau đó nhấn connect.
Tiếp theo hãy truy cập vào cluster vừa tạo, chọn đến phần Storage, Maps bạn sẽ thấy các thông số về map persons kiểu thế này:

Tổng kết

Như vậy chúng ta đã cùng nhau tìm hiểu spring boot Hazelcast, hiểu cách sử dụng sử dụng cách lưu trữ, lấy ra từ cache và cơ sở dữ liệu.


Cám ơn bạn đã quan tâm đến bài viết|video này. Để nhận được thêm các kiến thức bổ ích bạn có thể:

  1. Đọc các bài viết của TechMaster trên facebook: https://www.facebook.com/techmastervn
  2. Xem các video của TechMaster qua Youtube: https://www.youtube.com/@TechMasterVietnam nếu bạn thấy video/bài viết hay bạn có thể theo dõi kênh của TechMaster để nhận được thông báo về các video mới nhất nhé.
  3. Chat với techmaster qua Discord: https://discord.gg/yQjRTFXb7a