从零开始:Spring Boot项目中如何集成并使用Infinispan

一、介绍

Infinispan 其实就是一个分布式缓存数据网格平台,提供了高度可扩展和高性能数据缓存解决方案。Infinispan可以作为本地缓存分布式缓存使用,支持事务、查询、处理大数据等功能。简单地说,Infinispan 可以理解为是 MySQL 的内存版本。

与 Redis 对比

Infinispan 与 Redis 都是一种缓存解决方案或工具,但两者之间存在着许多不同的地方。

  • 设计目标:Infinispan 是一种数据网格和分布式计算平台,提供丰富的数据管理功能;而 Redis 主要设计为高性能的键值存储。
  • 数据模型:Infinispan 主要提供键值存储模型,支持复杂的查询和事务,支持 JAVA 数据类型存储;Redis支持丰富的数据结构,如列表、集合等。
  • 分布式特性:Infinispan 天生设计为分布式数据网格,提供数据分片、负载均衡等分布式特性;Redis 虽然也支持分布式部署,但需要通过 Redis 集群、哨兵模式等方式实现。
  • 事务和查询:Infinispan 支持事务和基于 Lucene 的全文搜索、基于 SQL 的查询;Redis 的事务支持较为基础,查询能力主要限于基本的数据结构操作。
  • 使用场景:Infinispan 适合需要复杂数据处理、分布式计算的场景,如大数据处理、实时分析等;Redis 适用于需要快速读写、高效缓存、简单消息队列的场景。

二、缓存模式

在 Infinispan 中,有一个缓存管理器叫做 CacheManager,能够用于创建和控制使用不同的缓存模式,比如本地缓存、分布式缓存、失效缓存等。

缓存模式分类
  • Local(本地)
    • Infinispan 作为单一节点运行
    • 适用场景:不需要跨多个节点共享数据
  • Replicated(复制)
    • Infinispan 在集群中的所有节点上复制所有的缓存数据,并且只执行本地读取操作,即集群中每个节点的数据都是一致的,从任何一个节点都能读取到所需要的数据
    • 适用场景:读密集型
  • Distributed(分布式)
    • Infinispan 在集群中的部分节点上复制缓存数据,并将数据分配给固定的大多数节点,即集群中的任意节点都不会有全部的数据,但读取的时候会请求拥有该数据的节点进行读取
    • 适用场景:读/写操作均衡,并且需要数据在部分节点之间共享的场景
  • Invalidation(失效)
    • 每当有操作修改缓存数据时,Infinispan 会从所有节点中清理过期的数据,只进行本地读取操作(不会跨节点操作数据)
    • 适用场景:一致性需求较高,但不需要分布式读取
  • Scattered(分散)
    • Infinispan 将缓存数据存储在集群中的部分节点上。默认情况下,Infinispan 为分散缓存分配一个主要拥有节点和一个备份拥有节点,主要拥有节点的分配方式与分布式缓存相同,而备份拥有节点始终是发起写入操作的节点。Infinispan 从至少一个拥有节点请求读取操作以确保返回正确的值。
    • 适用场景:高并发读写,并且数据能够在多个节点之间进行备份和恢复。
缓存模式比较

缓存模式比较

三、Spring Boot 集成

项目demo地址:https://gitee.com/regexpei/infinispan-demo

1. 创建项目,添加依赖
<dependency>
  <groupId>org.infinispan</groupId>
  <artifactId>infinispan-spring-boot-starter-embedded</artifactId>
</dependency>
<dependency>
    <groupId>org.infinispan</groupId>
    <artifactId>infinispan-query</artifactId>
</dependency>
2.缓存配置
@Configuration
public class InfinispanCacheConfig {
    @Bean
    public InfinispanGlobalConfigurationCustomizer globalCustomizer() {
        return builder -> builder.clusteredDefault().transport()
                .clusterName("MyCluster")
                .machineId("myMachine")
                .rackId("Rack").siteId("China").build();
    }


    @Bean
    public InfinispanCacheConfigurer cacheConfigurer() {
        return manager -> {
            final org.infinispan.configuration.cache.Configuration ispnConfig = new ConfigurationBuilder()
                    .clustering()
                    .cacheMode(CacheMode.LOCAL)     // 设置缓存模式
                    .persistence()                  // 开启持久化
                    .passivation(false)          // 缓存数据时持久化数据
                    .addSoftIndexFileStore()        // 使用软索引文件存储方式 
                    .dataLocation("cache-data")     // 持久化数据存储位置
                    .indexLocation("cache-index")   // 持久化索引存储位置
                    .shared(false)               // 不共享持久化存储 
                    .memory().maxCount(1000000L)    // 设置缓存最大数量, 1000000L 表示存储一百万条数据
                    .build();

            manager.defineConfiguration("local-sync-config", ispnConfig);
        };
    }
    @Bean(name = "large-cache")
    public org.infinispan.configuration.cache.Configuration largeCache() {
        return new ConfigurationBuilder()
                .clustering().cacheMode(CacheMode.LOCAL)
                .indexing().enable().storage(IndexStorage.FILESYSTEM).path("my-fs-cache-inf")
                .addIndexedEntities(UserInfoEntity.class)
                .memory().maxCount(1000000L)
                .build();
    }
}
3.实体类
@Data
@Table(value = "user_info")
@Indexed
public class UserInfoEntity implements Serializable {
    @Id(keyType = KeyType.Auto)
    private Long id;
    @KeywordField
    private String name;
    private String idCard;
    private String phoneNumber;
    @FullTextField
    private String desc;
    private String email;
    private String bankCard;
    @GenericField
    private LocalDateTime updateTime;
    private LocalDateTime createTime;
}
  1. 实体类需要实现序列化
  2. 实体类需要添加@Indexed注解
  3. 字段注解
    • @KeywordField:关键词索引,建议用于内容只有数字和字母的字段上,比如id、taskNo,数据类型只能是String
    • @FullTextField:全文索引,建议用在需要模糊匹配、文本检索的字段上,比如taskName,对于纯数字字母内容的字段,只能全匹配,模糊匹配会失效,数据类型只能是String
    • @GenericField:普通索引,建议除了添加@KeywordField和@FullTextField注解之外的字段都加上这个索引,因为SQL中出现使用@FullTextField注解的字段不能和没有添加注解的字段起使用
4. Controller
@Slf4j
@RestController
@RequestMapping("users")
public class UserController {
    @Autowired
    UserInfoMapper userInfoMapper;
    @Autowired
    UserInfoService userInfoService;
    @Autowired
    EmbeddedCacheManager cacheManager;
    @GetMapping("{id}")
    public UserInfoEntity getOne(@PathVariable Long id){
        return userInfoMapper.selectOneByCondition(USER_INFO_ENTITY.ID.eq(id));
    }
    @GetMapping("add-cache")
    public String addCache(@RequestParam long page, @RequestParam(defaultValue = "10000") long pageSize){
        long count = userInfoMapper.selectCountByCondition(QueryCondition.createEmpty());
        long total = count / pageSize + 1;
        log.info("分页总数:{}", total);
        int coreSize = Runtime.getRuntime().availableProcessors() * 2 + 1;
        ThreadPoolExecutor executor = ThreadUtil.newExecutor(coreSize, coreSize + 5);
        for (long i = 1; i <= total; i++) {
            long finalI = i;
            executor.execute(() -> userInfoService.addToCache(finalI, pageSize));
        }
        log.info("缓存完成!");
        return "ok";
    }

    @PostMapping("search")
    public List<UserInfoEntity> query(@RequestBody Map<String, Object> parameters){
        String sql = (String) parameters.get("sql");
        Integer page = (Integer) parameters.get("page");
        Integer pageSize = (Integer) parameters.get("pageSize");
        return userInfoService.search(sql, page, pageSize);
    }
    @DeleteMapping("{id}")
    public int del(@PathVariable Long id){
        return userInfoMapper.deleteByCondition(USER_INFO_ENTITY.ID.eq(id));
    }
    @GetMapping("count")
    public long count() {
        Cache<String, UserInfoEntity> cache = cacheManager.getCache("large-cache");
        OptionalLong optionalLong = Search.getQueryFactory(cache)
                .create("from cn.regexp.infinispan.entity.UserInfoEntity")
                .execute().hitCount();
        return optionalLong.orElse(0L);
    }
    @GetMapping("clear")
    public String clear() {
        Cache<String, UserInfoEntity> cache = cacheManager.getCache("large-cache");
        cache.clear();
        return "ok";
    }
}

四、问题记录

  1. ISPN000343: Must have a transport set in the global configuration in order to define a clustered cache

    infinispan.xml配置文件添加以下内容:

    <infinispan xmlns="urn:infinispan:config:13.0">
        <cache-container>
            <transport cluster="MyCluster"
                       machine="LinuxServer01"
                       rack="Rack01"
                       site="US-WestCoast"/>
        </cache-container>
    </infinispan>
    
  2. ISPN000403: No indexable classes were defined for this indexed cache. The configuration must contain classes or protobuf message types annotated with ‘@Indexed’

    infinispan.xml配置文件添加以下内容:

    <infinispan xmlns="urn:infinispan:config:13.0">
        <cache-container>
            <replicated-cache segments="256"
                              mode="SYNC"
                              statistics="true" name="replicatedCache">
                
                <indexing enabled="true"
                          storage="local-heap">
                    <index-reader refresh-interval="1000"/>
                    <indexed-entities>
                        <indexed-entity>cn.regexp.infinispan.entity.UserInfoEntity</indexed-entity>
                    </indexed-entities>
                </indexing>
            </replicated-cache>
        </cache-container>
    </infinispan>
    
  • 16
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

正则表达式1951

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值