Spring Boot集成Redis详解!
1 介绍
Redis(官网地址:https://redis.io/)是一种非关系型数据库,使用ANSI C语言开发,是一种Key-Value模式的数据库,支持多种value类型,如string(字符串)、list(链表)、set(集合)、zset(sorted set,有序集合)和hash(哈希类型)。
2 使用Redis
Spring Boot为Redis提供了基本的自动配置,依赖于spring-boot-starter-data-redis包。该包提供了自动配置的RedisConnectionFactory、StringRedisTemplate和Redis-Template实例。如果不配置,则默认连接localhost:6379服务器。
StringRedisTemplate继承自RedisTemplate,默认采用String的序列化策略。如果使用RedisTemplate,则可以实现自己的序列化方式。
连接Redis可以使用Lettuce或Jedis客户端。Spring Boot默认使用Lettuce客户端。因为Lettuce的连接是基于Netty的,所以多线程是安全的。RedisTemplate提供了以下5种数据结构的操作方法:
- opsForValue:操作字符串类型
- opsForHash:操作哈希类型
- opsForList:操作列表类型
- opsForSet:操作集合类型
- opsForZSet:操作有序集合类型
(1)在pom文件中加入Redis依赖,如下所示:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
(2)在配置文件中加入Redis配置,如下所示:
redis:
database: 0
host: 127.0.0.1
port: 6379
password: fs9158
jedis:
pool:
max-active: 20
max-wait: 100
max-idle: 20
min-idle: 5
timeout: 10000
(3)接下来,我们创建一个RedisService用于对Redis缓存进行操作。代码如下:
@Component
public class RedisService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 默认过期时长,单位:秒
*/
public static final long DEFAULT_EXPIRE = 60 * 60 * 24;
/**
* 不设置过期时长
*/
public static final long NOT_EXPIRE = -1;
public boolean existsKey(String key) {
return redisTemplate.hasKey(key);
}
/**
* 重名名key,如果newKey已经存在,则newKey的原值被覆盖
*
* @param oldKey
* @param newKey
*/
public void renameKey(String oldKey, String newKey) {
redisTemplate.rename(oldKey, newKey);
}
/**
* newKey不存在时才重命名
*
* @param oldKey
* @param newKey
* @return 修改成功返回true
*/
public boolean renameKeyNotExist(String oldKey, String newKey) {
return redisTemplate.renameIfAbsent(oldKey, newKey);
}
/**
* 删除key
*
* @param key
*/
public void deleteKey(String key) {
redisTemplate.delete(key);
}
/**
* 删除多个key
*
* @param keys
*/
public void deleteKey(String... keys) {
Set<String> kSet = Stream.of(keys).map(k -> k).collect(Collectors.toSet());
redisTemplate.delete(kSet);
}
/**
* 删除Key的集合
*
* @param keys
*/
public void deleteKey(Collection<String> keys) {
Set<String> kSet = keys.stream().map(k -> k).collect(Collectors.toSet());
redisTemplate.delete(kSet);
}
/**
* 设置key的生命周期
*
* @param key
* @param time
* @param timeUnit
*/
public void expireKey(String key, long time, TimeUnit timeUnit) {
redisTemplate.expire(key, time, timeUnit);
}
/**
* 指定key在指定的日期过期
*
* @param key
* @param date
*/
public void expireKeyAt(String key, Date date) {
redisTemplate.expireAt(key, date);
}
/**
* 查询key的生命周期
*
* @param key
* @param timeUnit
* @return
*/
public long getKeyExpire(String key, TimeUnit timeUnit) {
return redisTemplate.getExpire(key, timeUnit);
}
/**
* 将key设置为永久有效
*
* @param key
*/
public void persistKey(String key) {
redisTemplate.persist(key);
}
}
(4)最后进行RedisService方法的调用。
3 使用Redis缓存
在Spring Boot中提供了强大的基于注解的缓存支持,可以通过注解配置的方式低侵入地给原有Spring应用增加缓存功能,提高数据访问的性能。Spring Boot为我们配置了多个CacheManager的实现,比如EhCache、JCache、Redis等。在不添加任何额外配置的情况下,Spring Boot默认使用SimpleCacheConfiguration。
3.1 添加依赖和配置
在Spring Boot中集成Redis,首先需要在pom.xml文件中引入所需的依赖,具体代码如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Redis缓存配置保持和使用redis的配置一样。
3.2 缓存注解
第一步,为了在Spring Boot应用程序中使用缓存,可以先将@EnableCaching注解写入主类。代码如下:
@Configuration
@EnableCaching
@Aspect
public class RedisConfig extends CachingConfigurerSupport{
@Autowired
private RedisProperties redisProperties;
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new RedisObjectSerializer());
template.afterPropertiesSet();
return template;
}
}
RedisCacheManager是一个由Spring Boot提供的Redis所支持的CacheManager。如果在我们的应用程序类路径中提供了Redis并且所需的配置可用,则Spring Boot会自动配置RedisCacheManager实例。
下一步,将@Cacheable注解添加到要缓存结果的方法中。代码如下:
@Service
public class RedisCacheService {
private static final Logger logger = LoggerFactory.getLogger(RedisCacheService.class);
/**
* '@Cacheable在每次执行前都会检查Cache中是否存在相同key的缓存元素,
* 如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回,
* 否则才会执行并将返回结果存入指定的缓存中。
* 固定key
* redis缓存
* @return
*/
@Cacheable(value = RedisKeys._CACHE_MPC, key = "'" + RedisKeys._CACHE_FS + "'")
public String testCache() {
logger.info("从Redis缓存中读取数据:");
return "";
}
/**
* 存储在Redis中的key自动生成,生成规则详见CachingConfig.keyGenerator()方法
* @param str1
* @param str2
* @return
*/
@Cacheable(value = RedisKeys._CACHE_FS)
public String testCache2(String str1, String str2) {
return RandomStringUtils.randomNumeric(4);
}
/**
* @CachePut在执行前不会去检查缓存中是否存在之前执行过的结果,
* 而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中
* @param user
*/
@CachePut(value="common",key="#user.getUserName()")
public void insertSelective(User user) {
//update();
}
/**
* '@CacheEvict是用来标注在需要清除缓存元素的方法或类上的,
* 当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作
* @param id
*/
@CacheEvict(value="common",key="'id_'+#id")
public void deleteByPrimaryKey(Integer id) {
//Delete();
}
}
- @CachePut表示将方法结果返回存放到缓存中。
- @Cacheable表示先从缓存中通过定义的键查询,如果可以查询到数据,则返回,否则执行该方法,返回数据,并且将返回结果保存到缓存中。
- @CacheEvict 通过定义的键移除缓存,它有一个Boolean类型的配置项beforeInvocation,表示在方法之前或者之后移除缓存。因为其默认值为false,所以默认为方法之后将缓存移除。
3.3 缓存脏数据
使用缓存可以使得系统性能大幅度地提高,但是也引发了很多问题,其中最为严重的问题就是脏数据问题。
对于数据的读操作,一般而言是允许不是实时数据,如一些电商网站还存在一些排名榜单,而这个排名往往都不是实时的,它会存在延迟,其实对于查询是可以存在延迟的,也就是存在脏数据是允许的。但是如果一个脏数据始终存在就说不通了,这样会造成数据失真比较严重。一般对于查询而言,我们可以规定一个时间,让缓存失效,在Redis中也可以设置超时时间,当缓存超过超时时间后,则应用不再能够从缓存中获取数据,而只能从数据库中重新获取最新数据,以保证数据失真不至于太离谱。对于那些要求实时性比较高的数据,我们可以把缓存时间设置得更少一些,这样就会更加频繁地刷新缓存,而不利的是会增加数据库的压力;对于那些要求不是那么高的,则可以使超时时间长一些,这样就可以降低数据库的压力。
对于数据的写操作,往往采取的策略就完全不一样,需要我们谨慎一些,一般会认为缓存不可信,所以会考虑从数据库中先读取最新数据,然后再更新数据,以避免将缓存的脏数据写入数据库中,导致出现业务问题。
3.4 自定义缓存管理器
在Spring Boot中,RedisCacheManager会采用永不超时的机制,这样便不利于数据的及时更新。这个时候我们可以采用自定义缓存管理器的方法。核心代码,如下图所示:
@Configuration
@EnableCaching
@Aspect
public class RedisConfig extends CachingConfigurerSupport{
@Autowired
private RedisProperties redisProperties;
//自定义缓存管理器
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofDays(1)); // 设置缓存有效期一天
return RedisCacheManager
.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
.cacheDefaults(redisCacheConfiguration).build();
}
/**
* 定义缓存数据 key 生成策略的bean 包名+类名+方法名+所有参数
* @return
*/
@Override
public KeyGenerator keyGenerator() {
return new SimpleKeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(".").append(method.getName());
StringBuilder paramsSb = new StringBuilder();
for (Object param : params) {
// 如果不指定,默认生成包含到键值中
if (param != null) {
paramsSb.append(param.toString());
}
}
if (paramsSb.length() > 0) {
sb.append("_").append(paramsSb);
}
return sb.toString();
}
};
}
}
4 最后
在现实中,查询数据要远远多于更新数据,一般一个正常的网站查询和更新的比例大约是1∶9到3∶7,在查询比例较大的网站使用Redis可以数倍地提升网站的性能。缓存有助于通过减少数据库或任何昂贵资源之间的调用次数来提高应用程序的性能。除此之外,Redis还提供了简单的事务机制,通过事务机制可以有效保证在高并发的场景下数据的一致性。所以大家有时候在使用传统关系型数据库时,会与具有高效存取功能的缓存系统结合使用,以提高系统的访问性能。
5 最后的最后
为初学者提供学习指南,为从业者提供参考价值。我坚信码农也具有产生洞见的能力。关注【码农洞见】,一起学习和交流吧!