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入门_怎么就重名了的博客-优快云博客_memcached入门

3.4 linux安装


原文链接:https://blog.youkuaiyun.com/qq_43147136/article/details/85067986

memcached入门_怎么就重名了的博客-优快云博客_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 缓存框架介绍使用_小毕超的博客-优快云博客

 原文参考:阿里开源的缓存框架JetCache,实现spring二级缓存_Young丶的博客-优快云博客_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

这个错误是由于无法连接到本地主机的10248端口导致的。这个端口通常是kubelet进程监听的端口,用于健康检查。出现这个错误可能是由于kubelet进程没有正确启动或者配置错误导致的。 解决这个问题的方法是检查kubelet进程的状态和配置。你可以按照以下步骤进行操作: 1. 检查kubelet进程是否正在运行。你可以使用以下命令检查kubelet进程的状态: ```shell systemctl status kubelet ``` 如果kubelet进程没有运行,你可以使用以下命令启动它: ```shell systemctl start kubelet ``` 2. 检查kubelet的配置文件。你可以使用以下命令查看kubelet的配置文件路径: ```shell kubelet --kubeconfig /etc/kubernetes/kubelet.conf --config /var/lib/kubelet/config.yaml --bootstrap-kubeconfig /etc/kubernetes/bootstrap-kubelet.conf config view ``` 确保配置文件中的端口号和地址正确,并且与你的环境相匹配。 3. 检查网络连接。你可以使用以下命令检查是否可以连接到localhost10248端口: ```shell curl -sSL http://localhost:10248/healthz ``` 如果无法连接,请确保端口没有被防火墙或其他网络配置阻止。 4. 检查docker的配置。有时候,kubelet进程依赖于docker进程。你可以按照以下步骤检查docker的配置: - 创建/etc/docker目录: ```shell sudo mkdir /etc/docker ``` - 编辑/etc/docker/daemon.json文件,并添加以下内容: ```json { "exec-opts": ["native.cgroupdriver=systemd"], "log-driver": "json-file", "log-opts": { "max-size": "100m" }, "storage-driver": "overlay2", "storage-opts": [ "overlay2.override_kernel_check=true" ], "registry-mirrors": ["https://tdhp06eh.mirror.aliyuncs.com"] } ``` - 重启docker进程: ```shell systemctl restart docker ``` 请注意,以上步骤是一种常见的解决方法,但具体解决方法可能因环境而异。如果以上步骤无法解决问题,请提供更多的错误信息和环境配置,以便我们能够更好地帮助你。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值