文章目录
- 前言
- 正文
- 1、Lock4j的代码仓库
- 2、pine-manage-common-redis的项目结构
- 3、pine-manage-common-redis 的完整代码
- 3.1 maven依赖:pom.xml
- 3.2 redis连接参数:application.yaml
- 3.3 RedisCache.java
- 3.4 CacheConfig.java
- 3.5 RedissonClientUtil.java
- 3.6 SpringBeanUtil.java
- 3.7 RedisLock.java
- 3.8 RedisLockJsonKeyFunction.java
- 4、pine-manage-common-redis的使用
前言
在平时使用SpringBoot框架时,有些业务场景会需要使用到Redis。但是,常见的Redis使用方式有好几种:
-
spring-boot-starter-data-redis
:这是Spring Boot官方提供的Redis starter,它基于Spring Data Redis,提供了对Redis的集成支持。它默认使用Lettuce作为Redis客户端,但也支持Jedis客户端。通过这个starter,可以快速地在Spring Boot中整合、使用Redis,进行数据缓存、会话管理等操作。 -
spring-boot-starter-integration
:这个starter提供了Spring Integration的支持,可以与Redis结合使用,实现分布式锁等功能。 -
spring-integration-redis
:这是Spring Integration项目的一部分,提供了与Redis的集成支持,可以用于实现分布式锁等高级功能。 -
redisson-spring-boot-starter
:Redisson是一个在Java环境下的Redis客户端,它提供了一系列的分布式数据结构和服务,如分布式锁、原子变量、集合等。这个starter简化了Redisson在Spring Boot中的配置和使用。 -
spring-boot-starter-data-redis-reactive:基于Webflux的响应式redis组件。
一般而言,我们会选择其中的一个。但是最近笔者发现了小黑鸟https://baomidou.com/resources/eco-system/ 的一个开源组件,就试着用了下。最后得到的结果是:真香!!!
这个Lock4j 本身是主要用于做分布式锁的。但是我们加入缓存组件,就能很容易得到一个具备缓存功能,同时支持分布式锁的一个小工具。
正文
1、Lock4j的代码仓库
https://gitee.com/baomidou/lock4j
Lock4j 本身支持集成多种Redis的连接器,我这次主要使用Redisson的集成。
我的集成项目实例仓库地址:https://gitee.com/fengsoshuai/pine-manage-system.git
进入pine-manage-system项目后,pine-manage-common-redis
模块是本文的重点。
注意:我的项目使用了java17版本,SpringBoot版本是3.3.4,如果想直接复用,需要自己调整。
2、pine-manage-common-redis的项目结构
com.pine.common.redis.cache
:缓存包,实现了redis做缓存时的基本方法。com.pine.common.redis.config
:配置包,配置了缓存参数,redisson的相关配置,还有Spring获取Bean的工具类。com.pine.common.redis.lock
:redis做分布式锁的包装,实现了常用的工具方法。并自定义了redis key的拼接方法。
3、pine-manage-common-redis 的完整代码
3.1 maven依赖:pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.pine</groupId>
<artifactId>pine-manage-common</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>pine-manage-common-redis</artifactId>
<packaging>jar</packaging>
<name>pine-manage-common-redis</name>
<url>http://maven.apache.org</url>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<lock4j.version>2.2.7</lock4j.version>
<redisson.boot.version>3.25.2</redisson.boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 使用lock4j实现分布式锁 https://gitee.com/baomidou/lock4j/-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>lock4j-core</artifactId>
<version>${lock4j.version}</version>
</dependency>
<!-- 引入redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>${redisson.boot.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
<version>${lock4j.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<!-- Spring Boot Starter Cache for integration -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
</dependencies>
</project>
3.2 redis连接参数:application.yaml
# redis配置 -使用时需要配置自己的redis
spring:
data:
redis:
host: localhost
port: 6379
3.3 RedisCache.java
package com.pine.common.redis.cache;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import java.util.Objects;
/**
* redis缓存
*
* @author pine manage
* @since 2024-08-13
*/
@Slf4j
public class RedisCache {
@Resource(name = "redissonCacheManager")
private CacheManager redissonCacheManager;
/**
* 获取缓存
*
* @param cacheName 缓存名称
* @param key 缓存key
* @return 缓存数据
*/
public Object get(String cacheName, String key) {
log.info("RedisCache获取缓存,cacheName={}, key={}", cacheName, key);
Cache cache = redissonCacheManager.getCache(cacheName);
if (Objects.isNull(cache)) {
return null;
}
// 从缓存中获取数据
return cache.get(key);
}
/**
* 设置缓存
*
* @param cacheName 缓存名称
* @param key 缓存key
* @param value 缓存数据
*/
public void set(String cacheName, String key, Object value) {
log.info("RedisCache设置缓存,cacheName={}, key={}, value={}", cacheName, key, value);
Cache cache = redissonCacheManager.getCache(cacheName);
if (Objects.isNull(cache)) {
return;
}
// 将数据放入缓存
cache.put(key, value);
}
/**
* 移除缓存
*
* @param cacheName 缓存名称
* @param key 缓存key
*/
public void remove(String cacheName, String key) {
log.info("RedisCache移除缓存,cacheName={}, key={}", cacheName, key);
Cache cache = redissonCacheManager.getCache(cacheName);
if (Objects.isNull(cache)) {
return;
}
// 从缓存中移除数据
cache.evict(key);
}
/**
* 移除所有缓存
*
* @param cacheName 缓存名称
*/
public void removeAll(String cacheName) {
log.info("RedisCache移除所有缓存,cacheName={}", cacheName);
Cache cache = redissonCacheManager.getCache(cacheName);
if (Objects.isNull(cache)) {
return;
}
// 从缓存中移除数据
cache.clear();
}
}
3.4 CacheConfig.java
package com.pine.common.redis.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.pine.common.redis.cache.RedisCache;
import org.redisson.api.RedissonClient;
import org.redisson.codec.JsonJacksonCodec;
import org.redisson.spring.cache.RedissonSpringCacheManager;
import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 缓存配置
*
* @author pine manage
* @since 2024-08-13
*/
@Configuration
public class CacheConfig {
@Bean(name = "redissonCacheManager")
@ConditionalOnMissingBean
public CacheManager redissonCacheManager(@Autowired RedissonClient redissonClient) {
// 可以自定义缓存配置
return new RedissonSpringCacheManager(redissonClient);
}
@Bean
@ConditionalOnMissingBean
public RedisCache redisCache() {
return new RedisCache();
}
@Bean
public RedissonAutoConfigurationCustomizer redissonAutoConfigurationCustomizer() {
// 增加支持序列化java8的时间
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
JsonJacksonCodec jsonJacksonCodec = new JsonJacksonCodec(objectMapper);
return config -> config.setCodec(jsonJacksonCodec);
}
}
3.5 RedissonClientUtil.java
package com.pine.common.redis.config;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import java.time.Duration;
/**
* redissonClient 工具类
*
* @author pine manage
* @since 2024-08-22
*/
public class RedissonClientUtil {
private static RedissonClient redissonClient;
public static RedissonClient getRedissonClient() {
init();
return redissonClient;
}
/**
* 设置缓存
*
* @param key key
* @param value value
* @param timeToLiveSeconds 失效时间(单位:秒)
*/
public static void setCacheWithExpiration(String key, Object value, long timeToLiveSeconds) {
init();
RBucket<Object> bucket = redissonClient.getBucket(key);
// 设置缓存值和过期时间
bucket.set(value, Duration.ofSeconds(timeToLiveSeconds));
}
/**
* 设置缓存
*
* @param key key
* @param value value
*/
public static void setCache(String key, Object value) {
init();
RBucket<Object> bucket = redissonClient.getBucket(key);
// 设置缓存值
bucket.set(value);
}
/**
* 获取缓存
*
* @param key key
* @return value
*/
public static Object getCache(String key) {
init();
RBucket<Object> bucket = redissonClient.getBucket(key);
return bucket.get();
}
/**
* 移除缓存
*
* @param key key
*/
public static void removeCache(String key) {
init();
redissonClient.getBucket(key).delete();
}
private static void init() {
if (redissonClient == null) {
redissonClient = SpringBeanUtil.getByClass(RedissonClient.class);
}
}
}
3.6 SpringBeanUtil.java
package com.pine.common.redis.config;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
/**
* springbean工具类
*
* @author pine manage
* @since 2024-08-09
*/
@Component
public class SpringBeanUtil implements BeanFactoryAware {
private static BeanFactory beanFactory;
@Override
public void setBeanFactory(@NonNull BeanFactory beanFactory) throws BeansException {
SpringBeanUtil.beanFactory = beanFactory;
}
public static <T> T getByClass(Class<T> clazz) {
return beanFactory.getBean(clazz);
}
}
3.7 RedisLock.java
package com.pine.common.redis.lock;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import com.pine.common.redis.config.SpringBeanUtil;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.function.Function;
/**
* redis锁
*
* @author pine manage
* @since 2024-08-09
*/
public class RedisLock {
private static LockTemplate lockTemplate;
/**
* 获取锁超时时间
*/
private static final Long LIMIT_STREAM_ACQUIRE_TIMEOUT = 0L;
private static final Long ACQUIRE_TIMEOUT = 3000L;
/**
* 锁过期时间
*/
private static final Long EXPIRE = 5000L;
/**
* redis分布式锁(redisson自动续锁)
*
* @param bizRunnable 业务逻辑
* @param key 锁key
*/
public static void lock(Runnable bizRunnable, String key, Long acquireTimeout) {
initLockTemplate();
// 获取锁超时时间,默认3秒
acquireTimeout = Optional.ofNullable(acquireTimeout).orElse(ACQUIRE_TIMEOUT);
// expire锁过期时间为-1:不过期,redisson 自动续锁
LockInfo lockInfo = lockTemplate.lock(key, -1L, acquireTimeout);
if (lockInfo == null) {
throw new RuntimeException("获取锁失败");
}
try {
bizRunnable.run();
lockTemplate.releaseLock(lockInfo);
} catch (Exception e) {
lockTemplate.releaseLock(lockInfo);
throw e;
}
}
/**
* redis分布式锁(redisson自动续锁)
*
* @param bizCallable 业务逻辑
* @param key 锁key
*/
public static <Result> Result lock(Callable<Result> bizCallable, String key, Long acquireTimeout) throws Exception {
initLockTemplate();
// 获取锁超时时间,默认3秒
acquireTimeout = Optional.ofNullable(acquireTimeout).orElse(ACQUIRE_TIMEOUT);
// expire锁过期时间为-1:不过期,redisson 自动续锁
LockInfo lockInfo = lockTemplate.lock(key, -1L, acquireTimeout);
if (lockInfo == null) {
throw new RuntimeException("获取锁失败");
}
try {
Result result = bizCallable.call();
lockTemplate.releaseLock(lockInfo);
return result;
} catch (Exception e) {
lockTemplate.releaseLock(lockInfo);
throw e;
}
}
/**
* 加锁限流<br>
* <b>此处注意:上锁后不手动解锁,等待过期时间自动解锁</b>
*
* @param param 锁参数
*/
public static void lockAndLimitStream(Object param) {
lockAndLimitStream(param, null, null, new RedisLockJsonKeyFunction());
}
/**
* 加锁限流<br>
* <b>此处注意:上锁后不手动解锁,等待过期时间自动解锁</b>
*
* @param expire 锁过期时间
* @param acquireTimeout 获取锁超时时间
*/
public static void lockAndLimitStream(Object param, Long expire, Long acquireTimeout) {
lockAndLimitStream(param, expire, acquireTimeout, new RedisLockJsonKeyFunction());
}
/**
* 加锁限流<br>
* <b>此处注意:上锁后不手动解锁,等待过期时间自动解锁</b>
*
* @param expire 锁过期时间
* @param acquireTimeout 获取锁超时时间
* @param keyFunction 计算锁key的函数
*/
public static void lockAndLimitStream(Object param, Long expire, Long acquireTimeout, Function<Object, String> keyFunction) {
initLockTemplate();
// 锁过期时间默认5秒
expire = Optional.ofNullable(expire).orElse(EXPIRE);
// 获取锁超时时间默认0秒
acquireTimeout = Optional.ofNullable(acquireTimeout).orElse(LIMIT_STREAM_ACQUIRE_TIMEOUT);
// 计算锁key
String key = keyFunction.apply(param);
// 尝试获取锁
LockInfo lockInfo = lockTemplate.lock(key, expire, acquireTimeout);
// 获取锁失败
if (lockInfo == null) {
throw new RuntimeException("调用过于频繁,业务处理中,请稍后再试");
}
}
/**
* 初始化lockTemplate
*/
private static void initLockTemplate() {
if (lockTemplate == null) {
lockTemplate = SpringBeanUtil.getByClass(LockTemplate.class);
}
Objects.requireNonNull(lockTemplate, "lockTemplate is null");
}
}
3.8 RedisLockJsonKeyFunction.java
package com.pine.common.redis.lock;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
/**
* redis锁key生成函数
*
* @author pine manage
* @since 2024-08-09
*/
public class RedisLockJsonKeyFunction implements Function<Object, String> {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final Lock LOCK = new ReentrantLock();
/**
* Applies this function to the given argument.
*
* @param object the function argument
* @return the function result
*/
@Override
public String apply(Object object) {
return serializeToJson(object);
}
/**
* 序列化对象为json
*
* @param object 对象
* @return json
*/
private static String serializeToJson(Object object) {
try {
LOCK.lock();
return OBJECT_MAPPER.writeValueAsString(object);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
LOCK.unlock();
}
}
}
4、pine-manage-common-redis的使用
在要使用的模块中,引入如下依赖:
<dependency>
<groupId>com.pine</groupId>
<artifactId>pine-manage-common-redis</artifactId>
<version>${pine-manage-common-redis.version}</version>
</dependency>
随后直接使用RedisLock的静态方法即可,比如:
// 限流
RedisLock.lockAndLimitStream(triggerContext, expire, acquireTimeout);
而如果要使用缓存功能,则直接注入 RedisCache
,然后使用即可,比如:
@Resource
protected RedisCache redisCache;
// 设置缓存
redisCache.set(CacheNameConstant.SYS_CONFIG_CACHE_NAME, sysConfig.getConfigKey(), newSysConfigBo);