Hazelcast原理及使用

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,倚天版 1GB 1个月
简介: Hazelcast原理及使用

大家在平常写代码用缓存的时候是不是要么是内存缓存,要么是redis的缓存?像内存缓存那种,最大的好处是不用去搭建什么缓存中间件,引入个jar包就能直接用了,缺点也很明显,分布式环境下内存只是单机的不能多个实例共享。redis那种好处就是解决了内存的不共享的问题,缺点也很明显,配置复杂还需要服务器啥的比较复杂。


有没有一个中间件能既可以用于分布式缓存,又不用搭建服务器就可以使用的呢?当然有,最近小编接触到了一个分布式缓存工具,结合了上面说的两大特性,感觉很牛逼,接下来给大家介绍一下:


Redis和Hazelcast进行了效率的对比,红色是Redis,蓝色是Hazelcast,根据结果。Redis在低数据负载的时候响应比 Hazelcast 表现更好,而在数据负载和并发请求增加时则表现相反。在不常见的大环境(如我们在脚本4)我们可以看 Redis的平均响应时间剧烈的增长。Hazelcast 响应时间虽然也随着线程数增加而增长,但是这种增长要稳定得多,而且不像 Redis 表现的那样是指数级。


1. Hazelcast原理

Hazelcast 提供了 Map、Queue、MultiMap、Set、List、Semaphore、Atomic 等常用接口的分布式实现。

2. Hazelcast存储数据的实现过程

2.1 Hazelcast分区

由于Hazelcast 服务之间是端对端的,没有主从之分,集群中所有的节点都存储等量的数据以及进行等量的计算。

Hazelcast 默认情况下把数据存储在 271 个区上,这个值可以通过系统属性 hazelcast.partition.count来配置。

2.2 Hazelcast分区存储原理

对于一个给定的键,在经过序列化、哈希并对分区总数取模之后能得到此键对应的分区号,所有的分区等量的分布与集群中所有的节点中,每个分区对应的备份也同样分布在集群中。

也就是说 Hazelcast 会使用哈希算法对数据进行分区,比如对于一个给定的map中的键,或者topic和list中的对象名称,分区存储的过程如下:

  • 先序列化此键或对象名称,得到一个byte数组;
  • 然后对上面得到的byte数组进行哈希运算;
  • 再进行取模后的值即为分区号;
  • 最后每个节点维护一个分区表,存储着分区号与节点之间的对应关系,这样每个节点都知道如何获取数据。

2.3 Hazelcast集群实现原理

Hazelcast通过分片来存储和管理所有进入集群的数据,采用分片的方案目标是保证数据可以快速被读写、通过冗余保证数据不会因节点退出而丢失、节点可线性扩展存储能力。下面将从理论上说明Hazelcast是如何进行分片管理的。

2.3.1 分片

Hazelcast的每个数据分片(shards)被称为一个分区(Partitions)。分区是一些内存段,根据系统内存容量的不同,每个这样的内存段都包含了几百到几千项数据条目,默认情况下,Hazelcast会把数据划分为271个分区,并且每个分区都有一个备份副本。当启动一个集群成员时,这271个分区将会一起被启动。下图展示了集群只有一个节点时的分区情况。

从一个节点的分区情况可以看出,当只启动一个节点时,所有的271个分区都存放在一个节点中。然后我们启动第二个节点,会出现下面这样的集群分区方式。

其中黑色的字体表示分区,蓝色的字体表示备份。节点1存储了标号为1到135的分区,这些分区会同时备份到节点2中。而节点2则存储了136到271的分区,并备份到了节点1中。

此时如果再添加2个新的节点到集群中,Hazelcast会一个一个的移动分区和备份到新的节点中,使得集群数据分布平衡。

注意:
实际中分区并不是有序的分布,而是随机分布,上面的示例只是为了方便理解,重要的是理解 Hazelcast 的平均分布分区以及备份。

2.4 重分区

集群中最老的节点(或者说最先启动)负责定时发送分区表到其他节点,这样如果有其他节点加入或者离开集群,所有的节点也能更新分区表。

这个定时任务时间间隔可以通过系统属性 hazelcast.partition.table.send.interval来配置,缺省值为15秒。重分区会发生在如下时间:

  • 节点加入集群时;
  • 节点离开集群时。

此时最老节点会更新分区表,然后分发,再接着集群开始移动分区,或者从备份恢复分区。

注意:

如果最老的节点挂了,次老节点会接手这个任务。

3. Hazelcast的使用方式

有两种方式,嵌入式和客户端服务器。

  • 嵌入式: Hazelcast 服务器的 jar 包被导入到宿主应用程序中,服务器启动后缓存数据会被存在于各个宿主应用中,优点是可以更低延迟的数据访问。

  • 客户端服务器: Hazelcast 客户端的 jar 包被导入宿主应用程序中,服务器 jar 包独立运行于 JVM 中。优点是更容易调试以及有更可靠的性能,最重要的是有更好的扩展性。



使用:


第一步:引入jar包

<dependency>
    <groupId>com.hazelcast</groupId>
    <artifactId>hazelcast-all</artifactId>
    <version>4.2.7</version>
</dependency>


第二步:创建一个工具类

package com.example.team6.util;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.map.IMap;
import java.util.concurrent.TimeUnit;
public class HazelCastUtils {
    private final HazelcastInstance hazelcastInstance;
    private static final String MAP_NAME = "test:cache";
    public HazelCastUtils(HazelcastInstance hazelcastInstance) {
        this.hazelcastInstance = hazelcastInstance;
    }
    public Object get(String key) {
        IMap<Object, Object> map = hazelcastInstance.getMap(MAP_NAME);
        return map.get(key);
    }
    public void set(String key, String value, Integer ttl, TimeUnit timeUnit) {
        IMap<Object, Object> map = hazelcastInstance.getMap(MAP_NAME);
        map.put(key, value, ttl, timeUnit);
    }
    public void del(String key) {
        IMap<Object, Object> map = hazelcastInstance.getMap(MAP_NAME);
        map.remove(key);
    }
    public void delAllCache() {
        IMap<Object, Object> map = hazelcastInstance.getMap(MAP_NAME);
        map.clear();
    }
}


第三步:配置一个配置类

package com.example.team6.config;
import com.example.team6.util.HazelCastUtils;
import com.hazelcast.config.*;
import com.hazelcast.instance.impl.HazelcastInstanceFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
public class HazelcastConfiguration {
    @Bean
    @Primary
    public Config config() {
        EvictionConfig evictionConfig = new EvictionConfig();
        //数据释放策略[NONE|LRU|LFU]。这是Map作为缓存的一个参数,用于指定数据的回收算法,默认为NONE。
        //
        //NONE:当设置为NONE时,不会发生数据回收,同时max-size会失效。但是任然可以使用time-to-live-seconds和max-idle-seconds参数来控制数据留存时间。
        //
        //LRU:“最近最少使用“策略。
        //
        //LFU:“最不常用的使用”策略。
        evictionConfig.setEvictionPolicy(EvictionPolicy.LRU);
        evictionConfig.setMaxSizePolicy(MaxSizePolicy.FREE_HEAP_SIZE);
        Config config = new Config();
        config.setInstanceName("testInstanceName")
                .setClusterName("testClusterName")
                .addMapConfig(new MapConfig()
                        .setName("testName")
                        .setEvictionConfig(evictionConfig)
                        //数据留存时间[0~Integer.MAX_VALUE]。缓存相关参数,单位秒,
                        // 默认为0。这个参数决定了一条数据在map中的停留时间。
                        // 当数据在Map中留存超过这个时间并且没有被更新时,它会根据指定的回收策略从Map中移除。值为0时,意味着无求大。
                        .setTimeToLiveSeconds(10));
        return config;
    }
    @Bean
    public HazelCastUtils hazelCastUtils(Config config) {
        return new HazelCastUtils(HazelcastInstanceFactory.getOrCreateHazelcastInstance(config));
    }
}


第四步:创建一个controller来测试(只测试类String其他类型没有测)

package com.example.team6.controller;
import com.alibaba.fastjson.JSONObject;
import com.example.team6.util.HazelCastUtils;
import com.example.team6.util.UserDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.concurrent.TimeUnit;
@Slf4j
@RestController
@RequestMapping("/hazelcast")
public class HazelcastController {
    @Autowired
    private HazelCastUtils hazelCastUtils;
    @PostMapping(value = "/save")
    public String saveMapData(@RequestParam String key, @RequestParam String value) {
        UserDTO userDTO=UserDTO.builder().userId(1).userName(value).build();
        hazelCastUtils.set(key, JSONObject.toJSONString(userDTO), 60, TimeUnit.SECONDS);
        return "success";
    }
    @GetMapping(value = "/get")
    public Object getMapData(@RequestParam String key) {
        return hazelCastUtils.get(key);
    }
    @GetMapping(value = "/del")
    public String del(@RequestParam String key) {
        hazelCastUtils.del(key);
        return "success";
    }
    @GetMapping(value = "/clear")
    public String clear() {
        hazelCastUtils.delAllCache();
        return "success";
    }
}



最后,大家想获取更多知识的,可以继续关注公众号,不定时推送。分享了这么牛逼的知识,还不请小编喝个水吗,哈哈哈,欢迎土豪直接赏赞,谢谢,您的支持就是小编最大的动力。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
3月前
|
缓存 NoSQL Java
Spring Cache 缓存原理与 Redis 实践
Spring Cache 缓存原理与 Redis 实践
288 0
|
2月前
|
存储 NoSQL Java
教程:Spring Boot与RocksDB本地存储的整合方法
教程:Spring Boot与RocksDB本地存储的整合方法
|
2月前
|
缓存 Java Spring
Spring Boot中如何集成Hazelcast实现分布式缓存
Spring Boot中如何集成Hazelcast实现分布式缓存
|
9月前
|
缓存 NoSQL Java
分布式系列教程(01) -Ehcache缓存架构
分布式系列教程(01) -Ehcache缓存架构
162 0
|
3月前
|
缓存 NoSQL Java
SpringBoot:第五篇 集成Guava(本地缓存+分布式缓存)
SpringBoot:第五篇 集成Guava(本地缓存+分布式缓存)
212 0
|
存储 缓存 监控
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache抽象详解的核心原理探索
缓存的工作机制是先从缓存中读取数据,如果没有再从慢速设备上读取实际数据,并将数据存入缓存中。通常情况下,我们会将那些经常读取且不经常修改的数据或昂贵(CPU/IO)的且对于相同请求有相同计算结果的数据存储到缓存中。
170 1
|
存储 缓存 NoSQL
深入理解分布式缓存——使用Spring Boot+Redis实现分布式缓存解决方案
在微服务飞速发展的今天,在高并发的分布式的系统中,缓存是提升系统性能的重要手段。没有缓存对后端请求的拦截,大量的请求将直接落到系统的底层数据库。系统是很难撑住高并发的冲击,下面就以Redis为例来聊聊分布式系统中关于缓存的设计以及过程中遇到的一些问题。
深入理解分布式缓存——使用Spring Boot+Redis实现分布式缓存解决方案
|
存储 Java 容器
基于Spring Boot的Environment源码理解实现分散配置详解
基于Spring Boot的Environment源码理解实现分散配置详解
基于Spring Boot的Environment源码理解实现分散配置详解
|
存储 缓存 算法
Caffeine 本地缓存框架原理及用法总结
Caffeine 本地缓存框架原理及用法总结
2392 1
Caffeine 本地缓存框架原理及用法总结
|
缓存 NoSQL Java
Spring Boot中的缓存支持(二)使用Redis做集中式缓存
Spring Boot中的缓存支持(二)使用Redis做集中式缓存
149 0