在基于Springboot框架下,常用的分布式缓存解决方案,当属Redis莫属了。 因此,日常开发中,需要缓存,通常采用Redis来解决。
在本文中,主要是介绍基于Redis的各种实现方式,让大家较为全面地了解各个实现方案的特点或优势。
一、Spring Cache方案
- 添加依赖关系
Spring Cache,通常可以在新建Spring boot工程的时候,引入即可(web、cache、redis),具体依赖如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
- 增加配置参数
依赖引入后,需要在application.properites中配置如下信息(注意:根据Redis开发环境自行调整端口、ip地址):
spring.redis.port=6379
spring.redis.host=127.0.0.1
spring.cache.cache-names=common-cache
- 开启缓存(@EnableCaching)
@EnableCaching
@SpringBootApplication
public class DemoApplication
{
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
- Redis在Spring boot配置类注入
在完成开启缓存后,需要将redis相关配置类注入到容器,进而完成使用前的初始化,具体代码如下:
@Configuration
@ConditionalOnClass(RedisConnectionFactory.class)
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory,
ResourceLoader resLoader) {
RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(determineConfiguration(resourceLoader.getClassLoader()));
List<String> cacheNames = this.cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
}
return this.customizerInvoker.customize(builder.build());
}
}
Springboot 默认会初始化RedisCacheManager 这个Bean,Spring通过这个Bean实现Cache接口,因此有了这个Bean,就可以使用spring的缓存注解和接口了。
- 缓存使用
// i1、类注解,标注该类下的所有方法使用的缓存名称
@Service
@CacheConfig(cacheNames="common-cache")
public class TaskService {
}
// i2、方法注解 (对查询方法返回值进行缓存, 默认为key对应方法入参)
@Cacheable(key = "#id")
public Task getTaskById(Integer id) {
return queryTaskById(id);
}
// i3、更新方法上的注解, 伴随着数据的更新,缓存也跟着更新
@CachePut(key = "#task.id")
public Task updateTaskById(Task task) {
return update(task);
}
// i4、 删除方法上的注解,当数据库删除后,随之删除缓存
@CacheEvict()
public void deleteTaskById (Integer id) {
deleteTaskById(id);
}
二、Spring Data Redis方案
使用该方案,需要按照如下步骤:
- 引入依赖(引入Spring data redis + 连接池)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
</dependencies>
- 参数配置
# 基础配置
spring.redis.database=0
spring.redis.password=abc@123
spring.redis.port=6379
spring.redis.host=127.0.0.1
# 连接池信息配置
spring.redis.lettuce.pool.min-idle=1
spring.redis.lettuce.pool.max-idle=10
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=1ms
spring.redis.lettuce.shutdown-timeout=100ms
- 相关配置类
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> tp = new RedisTemplate<>();
tp.setConnectionFactory(redisConnectionFactory);
tp..setKeySerializer(new StringRedisSerializer());
return tp;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate st = new StringRedisTemplate();
st.setConnectionFactory(redisConnectionFactory);
st.setKeySerializer(new StringRedisSerializer());
return st;
}
}
上面中,主要实例化了两个不同的bean对象:RedisTemplate 和 StringRedisTemplate,主要区别为:RedisTemplate 意味着 key、value都可以为对象; StringRedisTemplate 的key、value只能是String字符串!
- 具体使用
// RedisTemplate 使用示例
@Service
public class TaskService {
@Autowired
RedisTemplate redisTemplate;
public void test() {
ValueOperations ops = redisTemplate.opsForValue();
ops.set("key", "test");
Object key = ops.get("key");
System.out.println(key);
}
}
// StringRedisTemplate 使用示例
@Service
public class TaskService {
@Autowired
StringRedisTemplate stringRedisTemplate;
public void test() {
ValueOperations ops = stringRedisTemplate.opsForValue();
ops.set("key", "test");
String key = ops.get("key");
System.out.println(key);
}
}
注意,上面的示例主要是针对单机Redis,不适用集群redis
三、JRedis方案
- 添加依赖关系
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>
- 添加配置
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=abc@123
spring.redis.timeout=10
spring.redis.jedis.pool.max-active=200
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.min-idle=10
spring.redis.jedis.pool.max-wait=10000
- 相关配置类
@Configuration
@Data
public class JedisPoolFactory {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.jedis.pool.max-active}")
private int maxActive;
@Value("${spring.redis.jedis.pool.max-idle}")
private int maxIdle;
@Value("${spring.redis.jedis.pool.min-idle}")
private int minIdle;
@Value("${spring.redis.jedis.pool.max-wait}")
private long maxWaitMillis;
@Bean
public JedisPool jedisPoolFactory() {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(maxActive);
poolConfig.setMaxIdle(maxIdle);
poolConfig.setMinIdle(minIdle);
poolConfig.setMaxWaitMillis(maxWaitMillis);
JedisPool jedisPool = new JedisPool(poolConfig, host, port, timeout, password);
return jedisPool;
}
}
- 编写Util类
@Component
public class RedisUtil {
@Autowired
private JedisPool jedisPool;
/**
* 存储字符串键值对
*/
public String set(String key, String value) {
try (Jedis jedis = jedisPool.getResource()) {
return jedis.set(key, value);
} catch (Exception e) {
throw new RedisException(e.getMessage());
}
}
/**
* 得到对应键的字符串值
*/
public String get(String key) {
try (Jedis jedis = jedisPool.getResource()) {
return jedis.get(key);
} catch (Exception e) {
throw new RedisException(e.getMessage());
}
}
/**
* 删除字符串键值对
*/
public Long del(String key) {
try (Jedis jedis = jedisPool.getResource()) {
return jedis.del(key);
} catch (Exception e) {
throw new RedisException(e.getMessage());
}
}
/**
* 存储对象
*/
public String setObject(String key, Object value) {
try (Jedis jedis = jedisPool.getResource()) {
return jedis.set(key.getBytes(), ObjectUtil.serialize(value));
} catch (Exception e) {
throw new RedisException(e.getMessage());
}
}
/**
* 得到对应键的对象
*/
public Object getObject(String key) {
try (Jedis jedis = jedisPool.getResource()) {
byte[] byteArr = jedis.get(key.getBytes());
return ObjectUtil.unSerialize(byteArr);
} catch (Exception e) {
throw new RedisException(e.getMessage());
}
}
}
ObjectUtil类示例:
public class ObjectUtil {
/**
* 判断对象是否为空
*/
public static boolean isEmpty(Object o) {
if (o == null) {
return true;
}
if (o instanceof String) {
String s = (String) o;
return "".equals(s.trim()) ? true : false;
} else if (o instanceof Collection) {
return ((Collection) o).isEmpty();
} else if (o instanceof Map) {
return ((Map) o).isEmpty();
} else if (o.getClass().isArray()) {
return Array.getLength(o) == 0;
} else {
return false;
}
}
/**
* 序列化
*/
public static byte[] serialize(Object o) {
try (ObjectOutputStream oos = new ObjectOutputStream(baos);
ByteArrayOutputStream baos = new ByteArrayOutputStream();) {
oos = new ObjectOutputStream(baos);
oos.writeObject(o);
return baos.toByteArray();
} catch (IOException e) {
throw new RedisException(e.getMessage());
}
}
/**
* 反序列化
*/
public static Object unSerialize(byte[] byteArr) {
try (ObjectInputStream ois = new ObjectInputStream(bais);
ByteArrayInputStream bais = new ByteArrayInputStream(byteArr);){
ois = new ObjectInputStream(bais);
o = ois.readObject();
return o;
} catch (Exception e) {
throw new RedisException(e.getMessage());
}
}
}
四、Redission方案
redission是当前是否火的redis解决方案,github上,start 有18K!下面引用一段官方简介:
Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。
Redisson底层采用的是Netty 框架。支持Redis 2.8以上版本,支持Java1.6+以上版本。
基于上述介绍,估计大家也会跃跃欲试了!下面开始介绍如何使用!
- 引入依赖关系
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.16.4</version>
</dependency>
- 添加配置
# 基础配置
spring.redis.database=0
spring.redis.password=abc@123
spring.redis.port=6379
spring.redis.host=127.0.0.1
- 配置类编写
@Slf4j
@Configuration
public class RedissonConfig {
@Autowired
private RedisProperties redisProperties;
@Bean
public RedissonClient redissonClient() throws Exception {
RedisProperties.Cluster cluster = redisProperties.getCluster();
if (null == cluster) {
Config cfg = new Config();
String redisUrl = String.format("redis://%s:%s", redisProperties.getHost(), redisProperties.getPort());
cfg.useSingleServer().setAddress(redisUrl).setPassword(redisProperties.getPassword());
cfg.useSingleServer().setDatabase(redisProperties.getDatabase());
return Redisson.create(cfg);
}
List<String> nodes = cluster.getNodes();
if (CollectionUtils.isEmpty(nodes)) {
throw new RedisException();
}
String[] nodeArr = new String[nodes.size()];
for (int i = 0; i < nodes.size(); i++) {
nodeArr[i] = "redis://" + nodes.get(i);
}
Config cfg = new Config();
cfg.useClusterServers()
.addNodeAddress(nodeArr).setPassword(redisProperties.getPassword()).setRetryAttempts(5).setScanInterval(2000);
cfg.useClusterServers().setPassword(redisProperties.getPassword());
return Redisson.create(cfg);
}
}
- 具体使用示例
@RestController
public class TestController {
@Autowired
private RedissonClient redissonClient;
@GetMapping(value = "/lock/{key}")
public String lockTest(@PathVariable("key") String key) {
RLock lock = redissonClient.getLock(key);
try {
lock.lock();
Thread.sleep(10000);
} catch (Exception e) {
} finally {
lock.unlock();
}
return "ok";
}
}
其他使用示例十分丰富,这里就不一一列举了!
五、总结
本文中,总结了常用的四种方案,相信这些使用方案,或多或少,大家都在项目中在使用,这里进行归总,对于没有使用过的同学,也可以参看学习!
相关问题,欢迎留言提问;欢迎大家点赞、关注、收藏~
本文暂时没有评论,来添加一个吧(●'◡'●)