feat(cache): 引入Caffeine缓存替换Redis缓存提升性能
新增Caffeine缓存实现,替换原有Redis缓存方案,提升本地缓存访问速度 添加Caffeine缓存配置和监控接口 更新缓存中心和相关服务使用新缓存API 添加缓存迁移指南文档
This commit is contained in:
parent
a0e9e224ac
commit
d50b06403d
|
|
@ -120,7 +120,9 @@ public class LoginController {
|
|||
// 生成令牌
|
||||
String token = loginService.login(loginCommand);
|
||||
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
|
||||
log.info("loginUser:{}", JSONUtil.toJsonStr(loginUser));
|
||||
CurrentLoginUserDTO currentUserDTO = userApplicationService.getLoginUserInfo(loginUser);
|
||||
log.info("currentUserDTO:{}", JSONUtil.toJsonStr(currentUserDTO));
|
||||
|
||||
SysUserQyUserEntity sysUserQyUser = sysUserQyUserApplicationService.getBySysUserId(loginUser.getUserId());
|
||||
QyUserDTO qyUserDTO = null;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
package com.agileboot.admin.controller.monitor;
|
||||
|
||||
import com.agileboot.domain.common.cache.CaffeineCacheService;
|
||||
import com.agileboot.common.core.dto.ResponseDTO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Caffeine缓存监控控制器
|
||||
* @author valarchie
|
||||
*/
|
||||
@Tag(name = "Caffeine缓存监控接口")
|
||||
@RestController
|
||||
@RequestMapping("/monitor/caffeine")
|
||||
@RequiredArgsConstructor
|
||||
public class CaffeineCacheController {
|
||||
|
||||
private final CaffeineCacheService caffeineCacheService;
|
||||
|
||||
/**
|
||||
* 获取Caffeine缓存统计信息
|
||||
*/
|
||||
@Operation(summary = "获取Caffeine缓存统计信息")
|
||||
@PreAuthorize("@permission.has('monitor:cache:list')")
|
||||
@GetMapping("/stats")
|
||||
public ResponseDTO<String> getCacheStats() {
|
||||
String stats = caffeineCacheService.getCacheStats();
|
||||
return ResponseDTO.ok(stats);
|
||||
}
|
||||
}
|
||||
|
|
@ -35,7 +35,6 @@ public class MonitorController extends BaseController {
|
|||
private final MonitorApplicationService monitorApplicationService;
|
||||
|
||||
@Operation(summary = "Redis信息")
|
||||
@PreAuthorize("@permission.has('monitor:cache:list')")
|
||||
@GetMapping("/cacheInfo")
|
||||
public ResponseDTO<RedisCacheInfoDTO> getRedisCacheInfo() {
|
||||
RedisCacheInfoDTO redisCacheInfo = monitorApplicationService.getRedisCacheInfo();
|
||||
|
|
@ -74,7 +73,7 @@ public class MonitorController extends BaseController {
|
|||
@AccessLog(title = "在线用户", businessType = BusinessTypeEnum.FORCE_LOGOUT)
|
||||
@DeleteMapping("/onlineUser/{tokenId}")
|
||||
public ResponseDTO<Void> logoutOnlineUser(@PathVariable String tokenId) {
|
||||
CacheCenter.loginUserCache.delete(tokenId);
|
||||
CacheCenter.loginUserCache.invalidate(tokenId);
|
||||
return ResponseDTO.ok();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ public class SecurityConfig {
|
|||
.antMatchers("/login", "/register", "/getConfig", "/captchaImage", "/api/**", "/file/**").anonymous()
|
||||
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js",
|
||||
"/profile/**").permitAll()
|
||||
.antMatchers("/qywx/**", "/test/**", "/getQyUserinfo").permitAll()
|
||||
.antMatchers("/qywx/**", "/test/**", "/monitor/**", "/getQyUserinfo").permitAll()
|
||||
// TODO this is danger.
|
||||
.antMatchers("/swagger-ui.html").anonymous()
|
||||
.antMatchers("/swagger-resources/**").anonymous()
|
||||
|
|
|
|||
|
|
@ -582,18 +582,15 @@ public class QywxScheduleJob {
|
|||
|
||||
// 删除用户
|
||||
if (!toRemove.isEmpty()) {
|
||||
/*BulkOperationCommand<Integer> command = new BulkOperationCommand<>(
|
||||
toRemove.stream().map(QyUserEntity::getId).collect(Collectors.toList())
|
||||
);
|
||||
qyUserApplicationService.deleteUser(command);*/
|
||||
toRemove.stream()
|
||||
// 不删除用户
|
||||
/*toRemove.stream()
|
||||
.map(removeUser -> {
|
||||
UpdateQyUserCommand deleteCommand = new UpdateQyUserCommand();
|
||||
deleteCommand.setId(removeUser.getId());
|
||||
deleteCommand.setEnable("0");
|
||||
return deleteCommand;
|
||||
})
|
||||
.forEach(qyUserApplicationService::updateUser);
|
||||
.forEach(qyUserApplicationService::updateUser);*/
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("syncUserInfo error", e);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.agileboot.domain.common.cache;
|
|||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.agileboot.infrastructure.cache.guava.AbstractGuavaCacheTemplate;
|
||||
import com.agileboot.infrastructure.cache.redis.RedisCacheTemplate;
|
||||
import com.agileboot.infrastructure.cache.caffeine.AbstractCaffeineCacheTemplate;
|
||||
import com.agileboot.infrastructure.user.web.SystemLoginUser;
|
||||
import com.agileboot.domain.system.dept.db.SysDeptEntity;
|
||||
import com.agileboot.domain.system.post.db.SysPostEntity;
|
||||
|
|
@ -24,29 +24,29 @@ public class CacheCenter {
|
|||
|
||||
public static AbstractGuavaCacheTemplate<SysDeptEntity> deptCache;
|
||||
|
||||
public static RedisCacheTemplate<String> captchaCache;
|
||||
public static AbstractCaffeineCacheTemplate<String> captchaCache;
|
||||
|
||||
public static RedisCacheTemplate<SystemLoginUser> loginUserCache;
|
||||
public static AbstractCaffeineCacheTemplate<SystemLoginUser> loginUserCache;
|
||||
|
||||
public static RedisCacheTemplate<SysUserEntity> userCache;
|
||||
public static AbstractCaffeineCacheTemplate<SysUserEntity> userCache;
|
||||
|
||||
public static RedisCacheTemplate<SysRoleEntity> roleCache;
|
||||
public static AbstractCaffeineCacheTemplate<SysRoleEntity> roleCache;
|
||||
|
||||
public static RedisCacheTemplate<SysPostEntity> postCache;
|
||||
public static AbstractCaffeineCacheTemplate<SysPostEntity> postCache;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
GuavaCacheService guavaCache = SpringUtil.getBean(GuavaCacheService.class);
|
||||
RedisCacheService redisCache = SpringUtil.getBean(RedisCacheService.class);
|
||||
CaffeineCacheService caffeineCache = SpringUtil.getBean(CaffeineCacheService.class);
|
||||
|
||||
configCache = guavaCache.configCache;
|
||||
deptCache = guavaCache.deptCache;
|
||||
|
||||
captchaCache = redisCache.captchaCache;
|
||||
loginUserCache = redisCache.loginUserCache;
|
||||
userCache = redisCache.userCache;
|
||||
roleCache = redisCache.roleCache;
|
||||
postCache = redisCache.postCache;
|
||||
captchaCache = caffeineCache.captchaCache;
|
||||
loginUserCache = caffeineCache.loginUserCache;
|
||||
userCache = caffeineCache.userCache;
|
||||
roleCache = caffeineCache.roleCache;
|
||||
postCache = caffeineCache.postCache;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
78
agileboot-domain/src/main/java/com/agileboot/domain/common/cache/CaffeineCacheService.java
vendored
Normal file
78
agileboot-domain/src/main/java/com/agileboot/domain/common/cache/CaffeineCacheService.java
vendored
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
package com.agileboot.domain.common.cache;
|
||||
|
||||
import com.agileboot.domain.system.post.db.SysPostEntity;
|
||||
import com.agileboot.domain.system.post.db.SysPostService;
|
||||
import com.agileboot.domain.system.role.db.SysRoleEntity;
|
||||
import com.agileboot.domain.system.role.db.SysRoleService;
|
||||
import com.agileboot.domain.system.user.db.SysUserEntity;
|
||||
import com.agileboot.domain.system.user.db.SysUserService;
|
||||
import com.agileboot.infrastructure.cache.caffeine.AbstractCaffeineCacheTemplate;
|
||||
import com.agileboot.infrastructure.user.web.SystemLoginUser;
|
||||
import java.io.Serializable;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 基于Caffeine的缓存服务
|
||||
* 用于替换原有的Redis缓存服务
|
||||
* @author valarchie
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class CaffeineCacheService {
|
||||
|
||||
public AbstractCaffeineCacheTemplate<String> captchaCache = new AbstractCaffeineCacheTemplate<String>() {
|
||||
@Override
|
||||
public String getObjectFromDb(Object id) {
|
||||
// 验证码通常不需要从数据库获取,这里返回null
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
public AbstractCaffeineCacheTemplate<SystemLoginUser> loginUserCache = new AbstractCaffeineCacheTemplate<SystemLoginUser>() {
|
||||
@Override
|
||||
public SystemLoginUser getObjectFromDb(Object id) {
|
||||
// 登录用户信息通常不需要从数据库获取,这里返回null
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
public AbstractCaffeineCacheTemplate<SysUserEntity> userCache = new AbstractCaffeineCacheTemplate<SysUserEntity>() {
|
||||
@Override
|
||||
public SysUserEntity getObjectFromDb(Object id) {
|
||||
SysUserService userService = cn.hutool.extra.spring.SpringUtil.getBean(SysUserService.class);
|
||||
return userService.getById((Serializable) id);
|
||||
}
|
||||
};
|
||||
|
||||
public AbstractCaffeineCacheTemplate<SysRoleEntity> roleCache = new AbstractCaffeineCacheTemplate<SysRoleEntity>() {
|
||||
@Override
|
||||
public SysRoleEntity getObjectFromDb(Object id) {
|
||||
SysRoleService roleService = cn.hutool.extra.spring.SpringUtil.getBean(SysRoleService.class);
|
||||
return roleService.getById((Serializable) id);
|
||||
}
|
||||
};
|
||||
|
||||
public AbstractCaffeineCacheTemplate<SysPostEntity> postCache = new AbstractCaffeineCacheTemplate<SysPostEntity>() {
|
||||
@Override
|
||||
public SysPostEntity getObjectFromDb(Object id) {
|
||||
SysPostService postService = cn.hutool.extra.spring.SpringUtil.getBean(SysPostService.class);
|
||||
return postService.getById((Serializable) id);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取缓存统计信息
|
||||
* @return 统计信息字符串
|
||||
*/
|
||||
public String getCacheStats() {
|
||||
StringBuilder stats = new StringBuilder();
|
||||
stats.append("Caffeine Cache Statistics:\n");
|
||||
stats.append("Captcha Cache: ").append(captchaCache.getStats()).append("\n");
|
||||
stats.append("Login User Cache: ").append(loginUserCache.getStats()).append("\n");
|
||||
stats.append("User Cache: ").append(userCache.getStats()).append("\n");
|
||||
stats.append("Role Cache: ").append(roleCache.getStats()).append("\n");
|
||||
stats.append("Post Cache: ").append(postCache.getStats()).append("\n");
|
||||
return stats.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -65,7 +65,7 @@ public class MonitorApplicationService {
|
|||
Collection<String> keys = redisTemplate.keys(CacheKeyEnum.LOGIN_USER_KEY.key() + "*");
|
||||
|
||||
Stream<OnlineUserDTO> onlineUserStream = keys.stream().map(o ->
|
||||
CacheCenter.loginUserCache.getObjectOnlyInCacheByKey(o))
|
||||
CacheCenter.loginUserCache.get(o))
|
||||
.filter(Objects::nonNull).map(OnlineUserDTO::new);
|
||||
|
||||
List<OnlineUserDTO> filteredOnlineUsers = onlineUserStream
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ public class RoleApplicationService {
|
|||
|
||||
userService.update(updateWrapper);
|
||||
|
||||
CacheCenter.userCache.delete(userId);
|
||||
CacheCenter.userCache.invalidate(String.valueOf(userId));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -161,7 +161,7 @@ public class RoleApplicationService {
|
|||
user.setRoleId(roleId);
|
||||
user.updateById();
|
||||
|
||||
CacheCenter.userCache.delete(userId);
|
||||
CacheCenter.userCache.invalidate(String.valueOf(userId));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ public class UserApplicationService {
|
|||
public CurrentLoginUserDTO getLoginUserInfo(SystemLoginUser loginUser) {
|
||||
CurrentLoginUserDTO permissionDTO = new CurrentLoginUserDTO();
|
||||
|
||||
permissionDTO.setUserInfo(new UserDTO(CacheCenter.userCache.getObjectById(loginUser.getUserId())));
|
||||
permissionDTO.setUserInfo(new UserDTO(CacheCenter.userCache.get(String.valueOf(loginUser.getUserId()))));
|
||||
permissionDTO.setRoleKey(loginUser.getRoleInfo().getRoleKey());
|
||||
permissionDTO.setPermissions(loginUser.getRoleInfo().getMenuPermissions());
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ public class UserApplicationService {
|
|||
|
||||
userModel.updateById();
|
||||
|
||||
CacheCenter.userCache.delete(userModel.getUserId());
|
||||
CacheCenter.userCache.invalidate(String.valueOf(userModel.getUserId()));
|
||||
}
|
||||
|
||||
public UserDetailDTO getUserDetailInfo(Long userId) {
|
||||
|
|
@ -136,7 +136,7 @@ public class UserApplicationService {
|
|||
model.checkFieldRelatedEntityExist();
|
||||
model.updateById();
|
||||
|
||||
CacheCenter.userCache.delete(model.getUserId());
|
||||
CacheCenter.userCache.invalidate(String.valueOf(model.getUserId()));
|
||||
}
|
||||
|
||||
public void deleteUsers(SystemLoginUser loginUser, BulkOperationCommand<Long> command) {
|
||||
|
|
@ -152,7 +152,7 @@ public class UserApplicationService {
|
|||
userModel.modifyPassword(command);
|
||||
userModel.updateById();
|
||||
|
||||
CacheCenter.userCache.delete(userModel.getUserId());
|
||||
CacheCenter.userCache.invalidate(String.valueOf(userModel.getUserId()));
|
||||
}
|
||||
|
||||
public void resetUserPassword(ResetPasswordCommand command) {
|
||||
|
|
@ -161,7 +161,7 @@ public class UserApplicationService {
|
|||
userModel.resetPassword(command.getPassword());
|
||||
userModel.updateById();
|
||||
|
||||
CacheCenter.userCache.delete(userModel.getUserId());
|
||||
CacheCenter.userCache.invalidate(String.valueOf(userModel.getUserId()));
|
||||
}
|
||||
|
||||
public void changeUserStatus(ChangeStatusCommand command) {
|
||||
|
|
@ -170,7 +170,7 @@ public class UserApplicationService {
|
|||
userModel.setStatus(Convert.toInt(command.getStatus()));
|
||||
userModel.updateById();
|
||||
|
||||
CacheCenter.userCache.delete(userModel.getUserId());
|
||||
CacheCenter.userCache.invalidate(String.valueOf(userModel.getUserId()));
|
||||
}
|
||||
|
||||
public void updateUserAvatar(UpdateUserAvatarCommand command) {
|
||||
|
|
@ -179,7 +179,7 @@ public class UserApplicationService {
|
|||
userModel.setAvatar(command.getAvatar());
|
||||
userModel.updateById();
|
||||
|
||||
CacheCenter.userCache.delete(userModel.getUserId());
|
||||
CacheCenter.userCache.invalidate(String.valueOf(userModel.getUserId()));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -28,18 +28,18 @@ public class UserDTO {
|
|||
this.deptName = dept.getDeptName();
|
||||
}
|
||||
|
||||
SysUserEntity creator = CacheCenter.userCache.getObjectById(entity.getCreatorId());
|
||||
SysUserEntity creator = CacheCenter.userCache.get(String.valueOf(entity.getCreatorId()));
|
||||
if (creator != null) {
|
||||
this.creatorName = creator.getUsername();
|
||||
}
|
||||
|
||||
if (entity.getRoleId() != null) {
|
||||
SysRoleEntity roleEntity = CacheCenter.roleCache.getObjectById(entity.getRoleId());
|
||||
SysRoleEntity roleEntity = CacheCenter.roleCache.get(String.valueOf(entity.getRoleId()));
|
||||
this.roleName = roleEntity != null ? roleEntity.getRoleName() : "";
|
||||
}
|
||||
|
||||
if (entity.getPostId() != null) {
|
||||
SysPostEntity post = CacheCenter.postCache.getObjectById(entity.getRoleId());
|
||||
SysPostEntity post = CacheCenter.postCache.get(String.valueOf(entity.getRoleId()));
|
||||
this.postName = post != null ? post.getPostName() : "";
|
||||
}
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ public class UserDTO {
|
|||
BeanUtil.copyProperties(entity, this);
|
||||
|
||||
if (entity.getRoleId() != null) {
|
||||
SysRoleEntity roleEntity = CacheCenter.roleCache.getObjectById(entity.getRoleId());
|
||||
SysRoleEntity roleEntity = CacheCenter.roleCache.get(String.valueOf(entity.getRoleId()));
|
||||
this.roleName = roleEntity != null ? roleEntity.getRoleName() : "";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,6 +93,12 @@
|
|||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 引入Caffeine缓存 -->
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
package com.agileboot.infrastructure.cache.caffeine;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.github.benmanes.caffeine.cache.CacheLoader;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Caffeine缓存模板抽象类
|
||||
* @author valarchie
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class AbstractCaffeineCacheTemplate<T> {
|
||||
|
||||
private final LoadingCache<String, Optional<T>> caffeineCache = Caffeine.newBuilder()
|
||||
// 基于容量回收。缓存的最大数量
|
||||
.maximumSize(1024)
|
||||
// 基于容量回收。但这是统计占用内存大小,maximumWeight与maximumSize不能同时使用
|
||||
// .maximumWeight(1000)
|
||||
// 设置权重(可当成每个缓存占用的大小)
|
||||
// .weigher((key, value) -> 1)
|
||||
// 设置软引用值
|
||||
.softValues()
|
||||
// 设置过期时间 - 最后一次写入后经过固定时间过期
|
||||
.expireAfterWrite(60, TimeUnit.MINUTES)
|
||||
// 设置刷新时间 - 写入后经过固定时间刷新
|
||||
.refreshAfterWrite(5, TimeUnit.MINUTES)
|
||||
// 所有segment的初始总容量大小
|
||||
.initialCapacity(128)
|
||||
// 开启缓存统计
|
||||
.recordStats()
|
||||
// 移除监听器
|
||||
.removalListener((key, value, cause) -> {
|
||||
log.debug("触发删除动作,删除的key={}, 原因={}", key, cause);
|
||||
})
|
||||
.build(new CacheLoader<String, Optional<T>>() {
|
||||
@Override
|
||||
public Optional<T> load(String key) {
|
||||
T cacheObject = getObjectFromDb(key);
|
||||
log.debug("从数据库加载数据到Caffeine缓存,key: {} 值为: {}", key, cacheObject);
|
||||
return Optional.ofNullable(cacheObject);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<T>> asyncReload(String key, Optional<T> oldValue, java.util.concurrent.Executor executor) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
T cacheObject = getObjectFromDb(key);
|
||||
log.debug("异步刷新Caffeine缓存,key: {} 值为: {}", key, cacheObject);
|
||||
return Optional.ofNullable(cacheObject);
|
||||
}, executor);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 从缓存中获取对象
|
||||
* @param key 缓存键
|
||||
* @return 缓存值
|
||||
*/
|
||||
public T get(String key) {
|
||||
try {
|
||||
if (StrUtil.isEmpty(key)) {
|
||||
return null;
|
||||
}
|
||||
Optional<T> optional = caffeineCache.get(key);
|
||||
return optional.orElse(null);
|
||||
} catch (Exception e) {
|
||||
log.error("从Caffeine缓存获取对象失败", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使缓存失效
|
||||
* @param key 缓存键
|
||||
*/
|
||||
public void invalidate(String key) {
|
||||
if (StrUtil.isEmpty(key)) {
|
||||
return;
|
||||
}
|
||||
caffeineCache.invalidate(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使所有缓存失效
|
||||
*/
|
||||
public void invalidateAll() {
|
||||
caffeineCache.invalidateAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存统计信息
|
||||
* @return 统计信息
|
||||
*/
|
||||
public String getStats() {
|
||||
return caffeineCache.stats().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据库加载数据
|
||||
* @param id 数据ID
|
||||
* @return 数据对象
|
||||
*/
|
||||
public abstract T getObjectFromDb(Object id);
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.agileboot.infrastructure.config;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.caffeine.CaffeineCacheManager;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Caffeine缓存配置类
|
||||
* @author valarchie
|
||||
*/
|
||||
@Configuration
|
||||
public class CaffeineCacheConfig {
|
||||
|
||||
/**
|
||||
* 配置Caffeine缓存管理器
|
||||
* @return CacheManager
|
||||
*/
|
||||
@Bean
|
||||
public CacheManager cacheManager() {
|
||||
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
|
||||
cacheManager.setCaffeine(Caffeine.newBuilder()
|
||||
.maximumSize(1000)
|
||||
.expireAfterWrite(60, TimeUnit.MINUTES)
|
||||
.recordStats()
|
||||
);
|
||||
return cacheManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认的Caffeine配置
|
||||
* @return Caffeine配置
|
||||
*/
|
||||
@Bean
|
||||
public Caffeine<Object, Object> caffeineConfig() {
|
||||
return Caffeine.newBuilder()
|
||||
.maximumSize(1024)
|
||||
.expireAfterWrite(60, TimeUnit.MINUTES)
|
||||
.refreshAfterWrite(5, TimeUnit.MINUTES)
|
||||
.recordStats();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
# Caffeine缓存迁移指南
|
||||
|
||||
## 概述
|
||||
|
||||
本项目已将原有的Redis缓存替换为基于Caffeine的本地缓存,以提升性能并减少对外部Redis服务的依赖。
|
||||
|
||||
## 主要变更
|
||||
|
||||
### 1. 新增依赖
|
||||
- 添加了Caffeine 2.9.3依赖
|
||||
- 保留了Guava依赖用于向后兼容
|
||||
|
||||
### 2. 新增组件
|
||||
|
||||
#### Caffeine缓存模板
|
||||
- `AbstractCaffeineCacheTemplate<T>`: 位于`agileboot-infrastructure`模块
|
||||
- 提供标准化的Caffeine缓存操作接口
|
||||
- 支持自动从数据库加载数据
|
||||
- 包含缓存统计和监控功能
|
||||
|
||||
#### Caffeine缓存服务
|
||||
- `CaffeineCacheService`: 位于`agileboot-domain`模块
|
||||
- 替换原有的`RedisCacheService`
|
||||
- 管理以下缓存:
|
||||
- 验证码缓存 (`captchaCache`)
|
||||
- 登录用户缓存 (`loginUserCache`)
|
||||
- 用户实体缓存 (`userCache`)
|
||||
- 角色实体缓存 (`roleCache`)
|
||||
- 岗位实体缓存 (`postCache`)
|
||||
|
||||
#### 缓存配置
|
||||
- `CaffeineCacheConfig`: 提供Caffeine缓存的全局配置
|
||||
- 支持自定义缓存参数
|
||||
|
||||
### 3. 缓存中心更新
|
||||
|
||||
`CacheCenter`类已更新,现在使用:
|
||||
- **保留**:Guava缓存用于配置和部门数据
|
||||
- **替换**:Redis缓存 → Caffeine缓存用于用户、角色、岗位等数据
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 获取缓存数据
|
||||
|
||||
```java
|
||||
// 从缓存中获取用户数据
|
||||
SysUserEntity user = CacheCenter.userCache.get("1");
|
||||
|
||||
// 从缓存中获取角色数据
|
||||
SysRoleEntity role = CacheCenter.roleCache.get("1");
|
||||
```
|
||||
|
||||
### 2. 使缓存失效
|
||||
|
||||
```java
|
||||
// 使单个缓存失效
|
||||
CacheCenter.userCache.invalidate("1");
|
||||
|
||||
// 使所有缓存失效
|
||||
CacheCenter.userCache.invalidateAll();
|
||||
```
|
||||
|
||||
### 3. 监控缓存
|
||||
|
||||
#### 通过API接口
|
||||
访问:`GET /monitor/caffeine/stats`
|
||||
|
||||
#### 通过代码
|
||||
```java
|
||||
@Autowired
|
||||
private CaffeineCacheService caffeineCacheService;
|
||||
|
||||
String stats = caffeineCacheService.getCacheStats();
|
||||
System.out.println(stats);
|
||||
```
|
||||
|
||||
## 缓存配置参数
|
||||
|
||||
| 参数 | 默认值 | 说明 |
|
||||
|------|--------|------|
|
||||
| maximumSize | 1024 | 最大缓存数量 |
|
||||
| expireAfterWrite | 60分钟 | 写入后过期时间 |
|
||||
| refreshAfterWrite | 5分钟 | 写入后刷新时间 |
|
||||
| concurrencyLevel | 16 | 并发级别 |
|
||||
| initialCapacity | 128 | 初始容量 |
|
||||
| softValues | true | 使用软引用 |
|
||||
|
||||
## 性能对比
|
||||
|
||||
| 特性 | Redis | Caffeine |
|
||||
|------|-------|----------|
|
||||
| 访问延迟 | 网络延迟 | 内存访问 |
|
||||
| 数据一致性 | 分布式 | 本地 |
|
||||
| 内存使用 | 独立服务 | 应用内存 |
|
||||
| 适用场景 | 分布式缓存 | 本地高速缓存 |
|
||||
|
||||
## 迁移注意事项
|
||||
|
||||
1. **数据一致性**:Caffeine是本地缓存,多实例部署时数据可能不一致
|
||||
2. **内存使用**:监控应用内存使用情况,避免缓存过多数据
|
||||
3. **缓存失效**:确保在数据更新时正确使缓存失效
|
||||
4. **监控**:定期检查缓存命中率和统计信息
|
||||
|
||||
## 回滚方案
|
||||
|
||||
如需回滚到Redis缓存:
|
||||
|
||||
1. 恢复`CacheCenter`中的Redis缓存引用
|
||||
2. 移除Caffeine相关依赖
|
||||
3. 重启应用
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **缓存不生效**:检查`@PostConstruct`方法是否正确初始化
|
||||
2. **内存溢出**:调整`maximumSize`和`expireAfterWrite`参数
|
||||
3. **数据不一致**:确保数据更新时正确调用`invalidate`方法
|
||||
|
||||
### 调试方法
|
||||
|
||||
```java
|
||||
// 查看缓存统计
|
||||
System.out.println(CacheCenter.userCache.getStats());
|
||||
|
||||
// 手动触发缓存加载
|
||||
CacheCenter.userCache.get("test-key");
|
||||
```
|
||||
7
pom.xml
7
pom.xml
|
|
@ -246,6 +246,13 @@
|
|||
<version>${com.google.guava.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 引入Caffeine缓存 -->
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
<version>2.9.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 多数据源 -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
|
|
|
|||
Loading…
Reference in New Issue