SpringBoot整合缓存框架(jetcache、memcached、mykit-cache)

目录

1.缓存简介

2.应用场景

3.memcached

3.1 简介

3.2 特征

3.3 docker安装

3.3.1 拉取镜像

3.4 linux安装

4.jetcache

4.1 简介

4.1.2 引入依赖

4.1.3 jetcacje配置

4.1.4 测试项目结构

4.1.5 启动类开启缓存

4.1.6 编写测试用例

4.1.7 缓存同步

4.1.8 缓存刷新

5.mykit-cache


1.缓存简介

2.应用场景

3.memcached

3.1 简介

        Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态、数据库驱动网站的速度。Memcached基于一个存储键/值对的hashmap。其守护进程(daemon )是用C写的,但是客户端可以用任何语言来编写,并通过memcached协议与守护进程通信。

3.2 特征

memcached作为高速运行的分布式缓存服务器,具有以下的特点。

1、协议简单
2、基于libevent的事件处理
3、内置内存存储方式
4、memcached不互相通信的分布式

3.3 docker安装

3.3.1 拉取镜像

docker pull memcached

拉取后查看docker镜像

后台运行容器

docker run -itd -p 9091:11211 --name memcache memcached

进入容器

[root@hw-cloud-02 memcached]# docker exec -it memcache bash
memcache@9490fbd7c0e1:/$
memcache@9490fbd7c0e1:/$
memcache@9490fbd7c0e1:/$ memcached -h  # 查看帮助信息

原文参考:memcached入门_怎么就重名了的博客-CSDN博客_memcached入门

3.4 linux安装


原文链接:https://blog.csdn.net/qq_43147136/article/details/85067986

memcached入门_怎么就重名了的博客-CSDN博客_memcached入门

4.jetcache

4.1 简介

        JetCache是一个基于Java的缓存系统封装,提供统一的API和注解来简化缓存的使用。 JetCache提供了比SpringCache更加强大的注解,可以原生的支持TTL、两级缓存、分布式自动刷新,还提供了Cache接口用于手工缓存操作。 当前有四个实现,RedisCache、TairCache(此部分未在github开源)、CaffeineCache(in memory)和一个简易的LinkedHashMapCache(in memory),要添加新的实现也是非常简单的。

4.1.2 引入依赖


# spring-boot-starter-parent  2.6.4
# jetcache-starter-redis      2.6.2
<dependency>
    <groupId>com.alicp.jetcache</groupId>
    <artifactId>jetcache-starter-redis</artifactId>
</dependency>

4.1.3 jetcacje配置

jetcache:
  statIntervalMinutes: 15
  areaInCacheName: false
  hidePackages: com.huachun
  local:
    default:
      type: caffeine
      limit: 100
      keyConvertor: fastjson
      expireAfterWriteInMillis: 100000
    otherArea:
      type: linkedhashmap
      limit: 100
      keyConvertor: none
      expireAfterWriteInMillis: 100000
  remote:
    default:
      type: redis
      keyConvertor: fastjson
      valueEncoder: java
      valueDecoder: java
      poolConfig:
        minIdle: 5
        maxIdle: 20
        maxTotal: 50
      host: ${spring.redis.host}
      port: ${spring.redis.port}
      password: ${spring.redis.password}
    otherArea:
      type: redis
      keyConvertor: fastjson
      valueEncoder: kryo
      valueDecoder: kryo
      poolConfig:
        minIdle: 5
        maxIdle: 20
        maxTotal: 50
      host: ${spring.redis.host}
      port: ${spring.redis.port}
      password: ${spring.redis.password}

配置参数说明:

属性默认值说明
jetcache.statIntervalMinutes0统计间隔,0表示不统计
jetcache.areaInCacheNametruejetcache-anno把cacheName作为远程缓存key前缀,2.4.3以前的版本总是把areaName加在cacheName中,因此areaName也出现在key前缀中。2.4.4以后可以配置,为了保持远程key兼容默认值为true,但是新项目的话false更合理些。
jetcache.hiddenPackages@Cached和@CreateCache自动生成name的时候,为了不让name太长,hiddenPackages指定的包名前缀被截掉
jetcache.[local|remote].${area}.type缓存类型。tair、redis为当前支持的远程缓存;linkedhashmap、caffeine为当前支持的本地缓存类型
jetcache.[local|remote].${area}.keyConvertorkey转换器的全局配置,当前只有一个已经实现的keyConvertor:fastjson。仅当使用@CreateCache且缓存类型为LOCAL时可以指定为none,此时通过equals方法来识别key。方法缓存必须指定keyConvertor
jetcache.[local|remote].${area}.valueEncoderjava序列化器的全局配置。仅remote类型的缓存需要指定,可选java和kryo
jetcache.[local|remote].${area}.valueDecoderjava序列化器的全局配置。仅remote类型的缓存需要指定,可选java和kryo
jetcache.[local|remote].${area}.limit100每个缓存实例的最大元素的全局配置,仅local类型的缓存需要指定。注意是每个缓存实例的限制,而不是全部,比如这里指定100,然后用@CreateCache创建了两个缓存实例(并且注解上没有设置localLimit属性),那么每个缓存实例的限制都是100
jetcache.[local|remote].${area}.expireAfterWriteInMillis无穷大以毫秒为单位指定超时时间的全局配置(以前为defaultExpireInMillis)
jetcache.local.${area}.expireAfterAccessInMillis0需要jetcache2.2以上,以毫秒为单位,指定多长时间没有访问,就让缓存失效,当前只有本地缓存支持。0表示不使用这个功能

上表中${area}对应@Cached和@CreateCache的area属性。注意如果注解上没有指定area,默认值是"default"。 

关于缓存的超时时间,有多个地方指定,澄清说明一下:

1.put等方法上指定了超时时间,则以此时间为准
2.put等方法上未指定超时时间,使用Cache实例的默认超时时间
3.Cache实例的默认超时时间,通过在@CreateCache和@Cached上的expire属性指定,如果没有指定,使用yml中定义的全局配置,例如@Cached(cacheType=local)使用jetcache.local.default.expireAfterWriteInMillis,如果仍未指定则是无穷大

4.1.4 测试项目结构

4.1.5 启动类开启缓存

package com.huachun;

import com.alicp.jetcache.anno.config.EnableCreateCacheAnnotation;
import com.alicp.jetcache.anno.config.EnableMethodCache;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableMethodCache(basePackages = "com.huachun")
@EnableCreateCacheAnnotation
public class JetCacheApplication {

    public static void main(String[] args) {
        SpringApplication.run(JetCacheApplication.class, args);
    }

}

说明:

启动类需要添加两个注解

@EnableMethodCache(basePackages = "com.huachun") 
用于激活@Cached注解的使用,basePackages指定包路径

@EnableCreateCacheAnnotation
用于激活@CreateCache注解的使用

4.1.6 编写测试用例

1.添加实体类

package com.huachun.model;

import lombok.Data;

import java.io.Serializable;

/**
 * 测试表(HcTest)实体类
 *
 * @author makejava
 * @since 2022-03-12 14:51:40
 */
@Data
public class HcTest implements Serializable {
    private static final long serialVersionUID = -73106959549361546L;
    private String id;
    private String name;
    private String description;
}

2. 添加业务处理

package com.huachun.service.impl;

import com.alicp.jetcache.Cache;
import com.alicp.jetcache.anno.CreateCache;
import com.huachun.dao.HcTestDao;
import com.huachun.model.HcTest;
import com.huachun.service.JetCacheService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class JetCacheServiceImpl implements JetCacheService {

    @Autowired
    private HcTestDao hcTestDao;

    @CreateCache(expire = 300)
    private Cache<String, HcTest> cache;

    @Override
    public HcTest getCache(String str) {

        HcTest hcTest = cache.get(str);
        if (null == hcTest) {
            hcTest = hcTestDao.queryById(str);
            cache.put(str, hcTest);
        }
        return hcTest;
    }
}

3. 编写控制器

package com.huachun.controller;

import com.huachun.model.HcTest;
import com.huachun.service.JetCacheService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/jet")
public class JetCacheController {

    @Autowired
    private JetCacheService jetCacheService;

    @GetMapping("/cache/{str}")
    public HcTest getCache(@PathVariable("str") String str) {
        return jetCacheService.getCache(str);
    }

}

4. 启动服务测试

 控制台打印信息

再次访问接口,控制台不再打印访问数据库信息,并且返回正常数据,此时说明缓存已生效

并且数据已经存入redis

此时重启服务,虽然内存中没有,但是会从redis中获取 ,仍然可以正常获取

问题:redis中存储的key名字不是我想要的,能否按照指定格式生成key?

答案是当然可以的。

分析一下redis是由jetcache控制的,是否可以从配置文件或者注解入手?如果配置文件的话应该会有规则,而注解的方式明显会比较灵活

点进@CreateCache注解,发现有一个name属性

尝试设置@CreateCache注解name属性值

@CreateCache(expire = 10, name = "JetCacheService.getCache.")
private Cache<String, HcTest> cache;

再次重启后访问,搜索redis服务key果然有值,再次测试也是可用的

设置本地内存和远程都缓存数据

@CreateCache(expire = 20, name = "JetCacheService.getCache.",localExpire = 100,cacheType = CacheType.BOTH,
            localLimit = 100,serialPolicy = SerialPolicy.KRYO,keyConvertor = KeyConvertor.FASTJSON)
expire:远程缓存时间
name:缓存名称
localExpire:本地缓存时间
cacheType:缓存类型 REMOTE,LOCAL,BOTH;
localLimit:
serialPolicy:
keyConvertor:

L1:本地缓存->L2:远程缓存->L3:数据库

 问题:jetcache是怎样保证缓存读写一致性?怎样保证本地和远程缓存服务数据一致性?

并发处理

package com.huachun.service.impl;

import com.alicp.jetcache.Cache;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.CreateCache;
import com.alicp.jetcache.anno.KeyConvertor;
import com.alicp.jetcache.anno.SerialPolicy;
import com.huachun.dao.HcTestDao;
import com.huachun.model.HcTest;
import com.huachun.service.JetCacheService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@Service
public class JetCacheServiceImpl implements JetCacheService {

    @Autowired
    private HcTestDao hcTestDao;

    @CreateCache(expire = 100, name = "JetCacheService-", localExpire = 100, cacheType = CacheType.BOTH)
    private Cache<String, HcTest> cache;

    @Resource(description = "myRedisson")
    private RedissonClient redissonClient;

    @Override
    public HcTest getCache(String str) {

        RLock rLock = redissonClient.getLock("jetcache-lock");
        HcTest hcTest = cache.get(str);

        try {
            if (rLock.tryLock(0, 10, TimeUnit.SECONDS) && null == hcTest) {
                hcTest = hcTestDao.queryById(str);
                if (null == hcTest) {
                    hcTest = new HcTest();
                    hcTest.setId(str);
                }
                cache.put(str, hcTest);

            } else {
                if (rLock.isLocked()) {
                    if (rLock.isHeldByCurrentThread()) {
                        rLock.unlock();
                    }
                }
            }
        } catch (Exception exception) {
            System.out.println(exception);
        } finally {
            if (rLock.isLocked()) {
                if (rLock.isHeldByCurrentThread()) {
                    rLock.unlock();
                }
            }
        }
        return hcTest;
    }
}

4.1.7 缓存同步

1 手动方式

package com.huachun.service.impl;
 
import com.alicp.jetcache.Cache;
import com.alicp.jetcache.anno.*;
import com.huachun.dao.HcTestDao;
import com.huachun.model.HcTest;
import com.huachun.service.JetCacheService;
import com.huachun.utils.RlockUtils;
import com.huachun.utils.SnowFlakeGenerateIdWorker;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
 
@Service
@Slf4j
public class JetCacheServiceImpl implements JetCacheService {
 
    /**
     * 预计插入的数据
     */
    private static Integer expectedInsertions = 10000;
    /**
     * 误判率
     */
    private static Double fpp = 0.01;
 
    @Autowired
    private HcTestDao hcTestDao;
 
    @Autowired
    private RlockUtils rlockUtils;
 
    @CreateCache(expire = 100, name = "JetCacheService-", localExpire = 100, cacheType = CacheType.BOTH)
    private Cache<String, HcTest> cache;
 
    @Resource(description = "myRedisson")
    private RedissonClient redissonClient;
 
    @Override
    public HcTest getData(String str) {
        String logBy = this.getClass().getName() + ".getCache:{}";
 
        RBloomFilter<Object> bloomFilter = redissonClient.getBloomFilter("JetCacheService");
 
        if (!bloomFilter.contains(str)) {
            return null;
        }
 
        RLock rLock = redissonClient.getLock("jetcache-lock");
        HcTest hcTest = cache.get(str);
 
        rLock.lock(10, TimeUnit.SECONDS);
        try {
            if (rLock.tryLock(0, 10, TimeUnit.SECONDS) && null == hcTest) {
                hcTest = hcTestDao.queryById(str);
                if (null == hcTest) {
                    hcTest = new HcTest();
                    hcTest.setId(str);
                    cache.put(str, hcTest);
                }
            }
        } catch (Exception exception) {
            log.error(logBy, exception);
        } finally {
            rlockUtils.unLock(rLock);
        }
        return hcTest;
    }
 
    @Override
    public void addData(HcTest hcTest) {
        String logBy = this.getClass().getName() + ".addData:{}";
 
        try {
            RBloomFilter<Object> bloomFilter = redissonClient.getBloomFilter("JetCacheService");
            bloomFilter.tryInit(expectedInsertions, fpp);
            SnowFlakeGenerateIdWorker snowFlakeGenerateIdWorker = new SnowFlakeGenerateIdWorker(1, 3);
            String id = snowFlakeGenerateIdWorker.generateNextId();
            bloomFilter.add(id);
            hcTest.setId(id);
            hcTestDao.insert(hcTest);
        } catch (Exception exception) {
            log.error(logBy, exception);
        }
    }
 
    @Override
    public void updateData(HcTest hcTest) {
        cache.put(hcTest.getId(), hcTest);
        hcTestDao.update(hcTest);
    }
 
    @Override
    public void removeData(String str) {
        cache.remove(str);
        hcTestDao.deleteById(str);
    }
 
 
}
// 创建缓存
@CreateCache(expire = 100, name = "JetCacheService-", localExpire = 100, cacheType = CacheType.BOTH)
    private Cache<String, HcTest> cache;

// 添加缓存
cache.put(str, hcTest);
// 缓存更新
cache.put(hcTest.getId(), hcTest);
// 缓存删除
cache.remove(str);
同步测试正常

2 注解方式   

    @Autowired
    private HcTestDao hcTestDao;
 
    @Cached(name = "JetCacheServiceAnno.", key = "#str")
    @Override
    public HcTest getData(String str) {
        return hcTestDao.queryById(str);
    }
 
    @Override
    public void addData(HcTest hcTest) {
        hcTestDao.insert(hcTest);
    }
 
    @CacheUpdate(name = "JetCacheServiceAnno.", value = "#hcTest", key = "#hcTest.id")
    @Override
    public void updateData(HcTest hcTest) {
        hcTestDao.update(hcTest);
    }
 
    @CacheInvalidate(name = "JetCacheServiceAnno.", key = "#str")
    @Override
    public void removeData(String str) {
        hcTestDao.deleteById(str);
    }

4.1.8 缓存刷新

需求说明:在接口调用的过程中,缓存过期失效后还是会直接查询数据库,数据库的查询还是会降低接口响应时间,所以需要使用到缓存刷新。缓存刷新原理是在查询当前数据后,定时刷新数据库数据缓存起来,让下一次请求直接从缓存中获取数据,提升接口响应能力,降低响应时间

注解:@CacheRefresh

使用示例

@Cached(name = "getPlanInfo.", key = "#adxPlanQuery.mediaId+'_'+#adxPlanQuery.seatId+'_'+#adxPlanQuery.displayType+'_'+#adxPlanQuery.displayRule", cacheType = CacheType.BOTH, expire = 10, cacheNullValue = true)
    @CacheRefresh(refresh = 3, refreshLockTimeout = 1)
    @Override
    public List<HhAdxPlanInfoVo> getPlanInfo(AdxPlanQuery adxPlanQuery) {
        return hhAdxPlanInfoMapper.selectPlanInfoByAdxPlanQuery(adxPlanQuery);
    }

注意:添加了缓存刷新并不会在启动时候刷新,需要在第一次调用后才会根据请求参数刷新

 原文参考:阿里开源 JetCache 缓存框架介绍使用_小毕超的博客-CSDN博客

 原文参考:阿里开源的缓存框架JetCache,实现spring二级缓存_Young丶的博客-CSDN博客_spring二级缓存

问题:如何删除缓存集合?

    @CacheInvalidate(name = ServiceMode.SERVICE_PARAMS, key = "#ids", multi = true)
    @Override
    public int deleteHhChainServiceParamsByIds(Long[] ids) {
        return hhChainServiceParamsMapper.deleteHhChainServiceParamsByIds(ids);
    }

属性设置 mutil = true

 jetcache常用配置:https://www.cnblogs.com/lifullmoon/p/13854158.html

5.mykit-cache

常见问题

1.整合jetcache循环依赖问题

问题详细:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  redisAutoInit (field protected com.alicp.jetcache.anno.support.ConfigProvider com.alicp.jetcache.autoconfigure.AbstractCacheAutoInit.configProvider)
↑     ↓
|  springConfigProvider
└─────┘


Action:

Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.

解决方法:application.yml文件中添加 allow-circular-references: true

spring:
  main:
    allow-circular-references: true

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值