Hazelcast原理及使用

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 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
相关文章
|
7月前
|
存储 缓存 Java
【Spring原理高级进阶】有Redis为啥不用?深入剖析 Spring Cache:缓存的工作原理、缓存注解的使用方法与最佳实践
【Spring原理高级进阶】有Redis为啥不用?深入剖析 Spring Cache:缓存的工作原理、缓存注解的使用方法与最佳实践
|
4月前
|
存储 缓存 Java
Java本地高性能缓存实践问题之定义Caffeine的缓存的问题如何解决
Java本地高性能缓存实践问题之定义Caffeine的缓存的问题如何解决
|
缓存 NoSQL Java
分布式系列教程(01) -Ehcache缓存架构
分布式系列教程(01) -Ehcache缓存架构
319 0
|
7月前
|
缓存 NoSQL Java
SpringBoot:第五篇 集成Guava(本地缓存+分布式缓存)
SpringBoot:第五篇 集成Guava(本地缓存+分布式缓存)
367 0
|
人工智能 NoSQL Java
SpringBoot实战(十七):Redis Pipeline 轻松实现百倍性能提升(续)
SpringBoot实战(十七):Redis Pipeline 轻松实现百倍性能提升(续)
340 0
|
存储 缓存 监控
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache抽象详解的核心原理探索
缓存的工作机制是先从缓存中读取数据,如果没有再从慢速设备上读取实际数据,并将数据存入缓存中。通常情况下,我们会将那些经常读取且不经常修改的数据或昂贵(CPU/IO)的且对于相同请求有相同计算结果的数据存储到缓存中。
197 1
|
存储 监控 算法
Sentinel源码剖析之核心组件作用和介绍
Sentinel 是分布式系统的防御系统。以流量为切入点,通过动态设置的流量控制、服务熔断降级、系统负载保护等多个维度保护服务的稳定性,通过服务降级增强服务被拒后用户的体验。
140 0
|
缓存 监控 NoSQL
SpringBoot 填坑 | Shiro 与 Redis 多级缓存问题
来自不愿意透露姓名的小师弟的第三篇投稿。这篇主要讲了,项目中配置了多缓存遇到的坑,以及解决办法。
SpringBoot 填坑 | Shiro 与 Redis 多级缓存问题
|
存储 缓存 算法
Caffeine 本地缓存框架原理及用法总结
Caffeine 本地缓存框架原理及用法总结
2513 1
Caffeine 本地缓存框架原理及用法总结
|
缓存 监控 Java
Ehcache缓存设计原理
Ehcache缓存设计原理
282 0
Ehcache缓存设计原理