目录
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.statIntervalMinutes | 0 | 统计间隔,0表示不统计 |
jetcache.areaInCacheName | true | jetcache-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}.keyConvertor | 无 | key转换器的全局配置,当前只有一个已经实现的keyConvertor:fastjson 。仅当使用@CreateCache且缓存类型为LOCAL时可以指定为none ,此时通过equals方法来识别key。方法缓存必须指定keyConvertor |
jetcache.[local|remote].${area}.valueEncoder | java | 序列化器的全局配置。仅remote类型的缓存需要指定,可选java和kryo |
jetcache.[local|remote].${area}.valueDecoder | java | 序列化器的全局配置。仅remote类型的缓存需要指定,可选java和kryo |
jetcache.[local|remote].${area}.limit | 100 | 每个缓存实例的最大元素的全局配置,仅local类型的缓存需要指定。注意是每个缓存实例的限制,而不是全部,比如这里指定100,然后用@CreateCache创建了两个缓存实例(并且注解上没有设置localLimit属性),那么每个缓存实例的限制都是100 |
jetcache.[local|remote].${area}.expireAfterWriteInMillis | 无穷大 | 以毫秒为单位指定超时时间的全局配置(以前为defaultExpireInMillis) |
jetcache.local.${area}.expireAfterAccessInMillis | 0 | 需要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