在Kubernetes上使用Spring Boot实现Hazelcast分布式缓存 – Piotr


Hazelcast是领先的内存数据网格(IMDG)解决方案。IMDG的主要思想是在群集内的许多节点之间分布数据。因此,它似乎是在Kubernetes等云平台上运行的理想解决方案,在该平台上,您可以轻松扩展或缩减多个正在运行的实例。由于Hazelcast是用Java编写的,因此您可以使用标准库轻松地将其与Java应用程序集成。Spring Boot可以简化Hazelcast的入门。您也可以使用非官方的库来为Hazelcast实现Spring Repositories模式-Spring Data Hazelcast。本文的主要目的是演示如何将Hazelcast嵌入到Spring Boot应用程序中以及如何在Kubernetes上将其作为多实例集群运行。多亏了Spring Data Hazelcast,我们不必再去研究Hazelcast数据类型的细节了。尽管Spring Data Hazelcast并没有提供许多高级功能,但对于入门来说还是非常好的。
带有示例应用程序的源代码通常可以在GitHub上获得。它可以在这里https://github.com/piomin/sample-hazelcast-spring-datagrid.git。您应该访问模块employee-kubernetes-service。

架构
我们正在Kubernetes上运行单个Spring Boot应用程序的多个实例。每个应用程序公开用于HTTP API访问的端口8080和用于Hazelcast群集成员发现的端口5701。Hazelcast实例被嵌入到Spring Boot应用程序中。我们正在Kubernetes上创建两个服务。它们中的第一个专用于HTTP API访问,而第二个则负责启用Hazelcast实例之间的发现。HTTP API将用于发出一些测试请求,这些请求会将数据添加到群集并在其中查找数据。让我们继续执行。

依赖关系
hazelcast-spring库提供了Spring和Hazelcast之间的集成。Hazelcast库的版本通过依赖管理与Spring Boot相关,因此我们只需要将Spring Boot的版本定义为最新的stable即可2.2.4.RELEASE。与该版本的Spring Boot相关的Hazelcast的当前版本为3.12.5。为了在Kubernetes上启用Hazelcast成员发现,我们还需要包括hazelcast-kubernetes依赖项。其版本与核心库无关。最新的版本2.0是因为我们仍然在使用Hazelcast 3我们宣布版本专用于Hazelcast 4 1.5.2中hazelcast-kubernetes。为了简化,我们还包括Spring Data Hazelcast和可选的Lombok。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.4.RELEASE</version>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.hazelcast</groupId>
        <artifactId>spring-data-hazelcast</artifactId>
        <version>2.2.2</version>
    </dependency>
    <dependency>
        <groupId>com.hazelcast</groupId>
        <artifactId>hazelcast-spring</artifactId>
    </dependency>
    <dependency>
        <groupId>com.hazelcast</groupId>
        <artifactId>hazelcast-client</artifactId>
    </dependency>
    <dependency>
        <groupId>com.hazelcast</groupId>
        <artifactId>hazelcast-kubernetes</artifactId>
        <version>1.5.2</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

启用KUBERNETES发现
包括必需的依赖项后,已为我们的应用程序启用了Hazelcast。我们唯一需要做的就是通过Kubernetes进行发现。该HazelcastInstancebean在上下文中已经可用,因此我们可以通过定义com.hazelcast.config.Configbean 来更改其配置。我们需要禁用默认情况下启用的多播发现,并在网络配置中启用Kubernetes发现,如下所示。Kubernetes配置需要设置Hazelcast部署的目标名称空间及其服务名称。

@Bean
Config config() {
    Config config = new Config();
    config.getNetworkConfig().getJoin().getTcpIpConfig().setEnabled(false);
    config.getNetworkConfig().getJoin().getMulticastConfig().setEnabled(false);
    config.getNetworkConfig().getJoin().getKubernetesConfig().setEnabled(true)
            .setProperty("namespace", "default")
            .setProperty(
"service-name", "hazelcast-service");
    return config;
}

我们还必须hazelcast-service在port上定义Kubernetes服务5701。它指的是employee-service部署。

apiVersion: v1
kind: Service
metadata:
  name: hazelcast-service
spec:
  selector:
    app: employee-service
  ports:
    - name: hazelcast
      port: 5701
  type: LoadBalancer


这是我们的示例应用程序的Kubernetes部署和服务定义。我们为部署设置了三个副本。我们还将在容器外部公开两个端口。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: employee-service
  labels:
    app: employee-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: employee-service
  template:
    metadata:
      labels:
        app: employee-service
    spec:
      containers:
        - name: employee-service
          image: piomin/employee-service
          ports:
            - name: http
              containerPort: 8080
            - name: multicast
              containerPort: 5701
---
apiVersion: v1
kind: Service
metadata:
  name: employee-service
  labels:
    app: employee-service
spec:
  ports:
    - port: 8080
      protocol: TCP
  selector:
    app: employee-service
  type: NodePort

实际上,这是在Kubernetes上成功运行Hazelcast集群所需要做的全部工作。在进行部署之前,让我们看一下应用程序实现的详细信息。

实体
我们的应用非常简单。它定义了一个模型对象,该对象存储在Hazelcast群集中。这样的类需要具有id –一个用Spring Data注释的字段@Id,并且应该实现Seriazable接口。

@Getter
@Setter
@EqualsAndHashCode
@ToString
public class Employee implements Serializable {
 
    @Id
    private Long id;
    @EqualsAndHashCode.Exclude
    private Integer personId;
    @EqualsAndHashCode.Exclude
    private String company;
    @EqualsAndHashCode.Exclude
    private String position;
    @EqualsAndHashCode.Exclude
    private int salary;
 
}

使用Spring Data Hazelcast,我们可以定义存储库,而无需使用任何查询或特定于Hazelcast的API进行查询。我们使用Spring Data定义的众所周知的方法命名模式来构建find方法,如下所示。我们的存储库接口应该扩展HazelcastRepository。

public interface EmployeeRepository extends HazelcastRepository<Employee, Long> {
 
    Employee findByPersonId(Integer personId);
    List<Employee> findByCompany(String company);
    List<Employee> findByCompanyAndPosition(String company, String position);
    List<Employee> findBySalaryGreaterThan(int salary);
 
}

要启用Spring Data Hazelcast存储库,我们应该使用注释主类或配置类@EnableHazelcastRepositories。

@SpringBootApplication
@EnableHazelcastRepositories
public class EmployeeApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(EmployeeApplication.class, args);
    }
     
}

最后,这是Spring控制器的实现。它允许我们调用存储库中定义的所有find方法,将新Employee对象添加到Hazelcast中并删除现有对象。

@RestController
@RequestMapping("/employees")
public class EmployeeController {
 
    private static final Logger logger = LoggerFactory.getLogger(EmployeeController.class);
 
    private EmployeeRepository repository;
 
    EmployeeController(EmployeeRepository repository) {
        this.repository = repository;
    }
 
    @GetMapping(
"/person/{id}")
    public Employee findByPersonId(@PathVariable(
"id") Integer personId) {
        logger.info(
"findByPersonId({})", personId);
        return repository.findByPersonId(personId);
    }
     
    @GetMapping(
"/company/{company}")
    public List<Employee> findByCompany(@PathVariable(
"company") String company) {
        logger.info(String.format(
"findByCompany({})", company));
        return repository.findByCompany(company);
    }
 
    @GetMapping(
"/company/{company}/position/{position}")
    public List<Employee> findByCompanyAndPosition(@PathVariable(
"company") String company, @PathVariable("position") String position) {
        logger.info(String.format(
"findByCompany({}, {})", company, position));
        return repository.findByCompanyAndPosition(company, position);
    }
     
    @GetMapping(
"/{id}")
    public Employee findById(@PathVariable(
"id") Long id) {
        logger.info(
"findById({})", id);
        return repository.findById(id).get();
    }
 
    @GetMapping(
"/salary/{salary}")
    public List<Employee> findBySalaryGreaterThan(@PathVariable(
"salary") int salary) {
        logger.info(String.format(
"findBySalaryGreaterThan({})", salary));
        return repository.findBySalaryGreaterThan(salary);
    }
     
    @PostMapping
    public Employee add(@RequestBody Employee emp) {
        logger.info(
"add({})", emp);
        return repository.save(emp);
    }
 
    @DeleteMapping(
"/{id}")
    public void delete(@PathVariable(
"id") Long id) {
        logger.info(
"delete({})", id);
        repository.deleteById(id);
    }
 
}

在MINIKUBE上运行
我们将在Minikube上测试示例应用程序。

$ minikube start --vm-driver=virtualbox

该应用程序配置为可以与Skaffold和Jib Maven插件一起运行。它们简化了Minikube上的构建和部署过程。假设我们位于应用程序的根目录中,我们只需要运行以下命令。Skaffold使用Maven自动构建我们的应用程序,基于Maven设置创建Docker映像,从k8s目录中应用部署文件,最后在Kubernetes上运行该应用程序。

$ skaffold dev

从那以后,我们在deployment.yaml启动的三个pod中声明了我们的应用程序的三个实例。如果成功完成Hazelcast发现,您应该会看到Skaffold打印的Pod日志片段。

详细调试图片点击标题见原文。