Spring Boot整合Redis缓存(Lettuce)
创始人
2024-06-01 01:09:55
0

spring-boot-demo-cache-redis

此 demo 主要演示了 Spring Boot 如何整合 redis,操作redis中的数据,并使用redis缓存数据。连接池使用 Lettuce。

Lettuce官网

pom.xml


org.springframework.bootspring-boot-starter-data-redis

org.springframework.bootspring-boot-starter-cache

org.apache.commonscommons-pool2

org.springframework.bootspring-boot-starter-json

application.yml

spring:redis:host: localhost timeout: 10000ms # 连接超时时间(记得添加单位,Duration)database: 0 # Redis默认情况下有16个分片,这里配置具体使用的分片 (默认0)port: 6379 # Redis服务器连接端口lettuce:pool:# 连接池最大连接数(使用负值表示没有限制) 默认 8max-active: 8# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1max-wait: -1ms# 连接池中的最大空闲连接 默认 8max-idle: 8# 连接池中的最小空闲连接 默认 0min-idle: 0cache:# 一般来说是不用配置的,Spring Cache 会根据依赖的包自行装配type: redis

非高度配置RedisConfig.java

/*** 

* redis配置*

**/ @Configuration @AutoConfigureAfter(RedisAutoConfiguration.class) @EnableCaching //开启缓存 public class RedisConfig extends CachingConfigurerSupport{/*** 自定义RedisTemplate序列化*/@Beanpublic RedisTemplate redisCacheTemplate(LettuceConnectionFactory redisConnectionFactory) {RedisTemplate template = new RedisTemplate<>();StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();// 设置key序列化类,否则key前面会多了一些乱码template.setKeySerializer(stringRedisSerializer);template.setHashKeySerializer(stringRedisSerializer);template.setValueSerializer(jsonRedisSerializer);template.setHashValueSerializer(jsonRedisSerializer);// 如果value没设置都是使用默认jdk序列化// 如果取value出现序列化问题,修改为使用默认jdk new JdkSerializationRedisSerializer()template.setConnectionFactory(redisConnectionFactory);template.afterPropertiesSet();return template;}/*** 配置使用注解的时候缓存配置,默认是序列化反序列化的形式,加上此配置则为 json 形式*/@Beanpublic CacheManager cacheManager(RedisConnectionFactory factory) {// 配置序列化RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();RedisCacheConfiguration redisCacheConfiguration = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));return RedisCacheManager.builder(factory).cacheDefaults(redisCacheConfiguration).build();}/*** 自定义Redis连接池其他属性** @return LettuceClientConfigurationBuilderCustomizer* @author: ZhiHao* @date: 2023/3/9*/@Beanpublic LettuceClientConfigurationBuilderCustomizer lettuceClientConfigurationBuilderCustomizer(){// LettuceConnectionConfiguration.java #getLettuceClientConfiguration()后置设置属性return new LettuceClientConfigurationBuilderCustomizer() {@Overridepublic void customize(LettuceClientConfiguration.LettuceClientConfigurationBuilder clientConfigurationBuilder) {LettucePoolingClientConfiguration build = (LettucePoolingClientConfiguration) clientConfigurationBuilder.build();GenericObjectPoolConfig poolConfig = build.getPoolConfig();poolConfig.setTestOnBorrow(Boolean.TRUE);poolConfig.setTestWhileIdle(Boolean.TRUE);// 无连接不阻塞, 进行报错poolConfig.setBlockWhenExhausted(Boolean.FALSE);}};} }

官网自定义配置说明:

You can also register an arbitrary number of beans that implement LettuceClientConfigurationBuilderCustomizer for more advanced customizations. ClientResources can also be customized using ClientResourcesBuilderCustomizer. If you use Jedis, JedisClientConfigurationBuilderCustomizer is also available. Alternatively, you can register a bean of type RedisStandaloneConfiguration, RedisSentinelConfiguration, or RedisClusterConfiguration to take full control over the configuration.

深度自定义配置RedisConfig (工厂和池配置)

import io.lettuce.core.ClientOptions;
import io.lettuce.core.SocketOptions;
import io.lettuce.core.TimeoutOptions;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
import io.lettuce.core.resource.ClientResources;
import lombok.Data;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.StringUtils;import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;/*** 

* redis配置*

**/ @Configuration @AutoConfigureAfter(RedisAutoConfiguration.class) @EnableConfigurationProperties(RedisProperties.class) public class RedisLettuceConfig {@Beanpublic RedisTemplate redisCacheTemplate(LettuceConnectionFactory redisConnectionFactory) {// 省略, 参考上面}/*** 自定义LettuceConnectionFactory工厂** @param redisProperties* @param clientResources* @return LettuceConnectionFactory* @author: ZhiHao* @date: 2023/3/9*/@Beanpublic LettuceConnectionFactory redisConnectionFactory(RedisProperties redisProperties,ClientResources clientResources) {LettucePoolingClientConfiguration lettucePoolingClientConfiguration = this.getLettucePoolingClientConfiguration(redisProperties, clientResources);RedisStandaloneConfiguration redisStandaloneConfiguration = this.getRedisStandaloneConfiguration(redisProperties);LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration, lettucePoolingClientConfiguration);// 开启使用连接前先检测, 开启性能下降默认false// lettuce开启一个共享的物理连接,是一个长连接,所以默认情况下是不会校验连接是否可用的//lettuceConnectionFactory.setValidateConnection(Boolean.TRUE);// 这个属性默认是true,允许多个连接公用一个物理连接。如果设置false ,// 每一个连接的操作都会开启和关闭socket连接。如果设置为false,会导致性能下降//lettuceConnectionFactory.setShareNativeConnection(Boolean.FALSE);return lettuceConnectionFactory;}/*** 自定义LettucePoolingClientConfiguration连接池配置** @param redisProperties* @param clientResources* @return LettucePoolingClientConfiguration* @author: ZhiHao* @date: 2023/3/9*/private LettucePoolingClientConfiguration getLettucePoolingClientConfiguration(RedisProperties redisProperties,ClientResources clientResources) {RedisProperties.Lettuce lettuce = redisProperties.getLettuce();RedisProperties.Pool pool = lettuce.getPool();LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilderbuilder = LettucePoolingClientConfiguration.builder().poolConfig(this.getPoolConfig(pool));if (redisProperties.isSsl()) {builder.useSsl();}if (redisProperties.getTimeout() != null) {builder.commandTimeout(redisProperties.getTimeout());}if (lettuce.getShutdownTimeout() != null && !lettuce.getShutdownTimeout().isZero()) {builder.shutdownTimeout(redisProperties.getLettuce().getShutdownTimeout());}if (StringUtils.hasText(redisProperties.getClientName())) {builder.clientName(redisProperties.getClientName());}builder.clientOptions(this.createClientOptions(redisProperties));builder.clientResources(clientResources);return builder.build();}/*** 自定义 RedisStandaloneConfiguration** @param redisProperties* @return RedisStandaloneConfiguration* @author: ZhiHao* @date: 2023/3/9*/private RedisStandaloneConfiguration getRedisStandaloneConfiguration(RedisProperties redisProperties){RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();if (StringUtils.hasText(redisProperties.getUrl())) {ConnectionInfo connectionInfo = parseUrl(redisProperties.getUrl());config.setHostName(connectionInfo.getHostName());config.setPort(connectionInfo.getPort());config.setUsername(connectionInfo.getUsername());config.setPassword(RedisPassword.of(connectionInfo.getPassword()));}else {config.setHostName(redisProperties.getHost());config.setPort(redisProperties.getPort());config.setUsername(redisProperties.getUsername());config.setPassword(RedisPassword.of(redisProperties.getPassword()));}config.setDatabase(redisProperties.getDatabase());return config;}private ClientOptions createClientOptions(RedisProperties redisProperties) {ClientOptions.Builder builder = this.initializeClientOptionsBuilder(redisProperties);Duration connectTimeout = redisProperties.getConnectTimeout();if (connectTimeout != null) {builder.socketOptions(SocketOptions.builder().connectTimeout(connectTimeout).build());}return builder.timeoutOptions(TimeoutOptions.enabled()).build();}private ClientOptions.Builder initializeClientOptionsBuilder(RedisProperties redisProperties) {if (redisProperties.getCluster() != null) {ClusterClientOptions.Builder builder = ClusterClientOptions.builder();RedisProperties.Lettuce.Cluster.Refresh refreshProperties = redisProperties.getLettuce().getCluster().getRefresh();ClusterTopologyRefreshOptions.Builder refreshBuilder = ClusterTopologyRefreshOptions.builder().dynamicRefreshSources(refreshProperties.isDynamicRefreshSources());if (refreshProperties.getPeriod() != null) {refreshBuilder.enablePeriodicRefresh(refreshProperties.getPeriod());}if (refreshProperties.isAdaptive()) {refreshBuilder.enableAllAdaptiveRefreshTriggers();}return builder.topologyRefreshOptions(refreshBuilder.build());}return ClientOptions.builder();}// BaseObjectPoolConfig与GenericObjectPoolConfig 还有很多连接池属性可以配置, 可以自行查看官网或者源码private GenericObjectPoolConfig getPoolConfig(RedisProperties.Pool pool) {GenericObjectPoolConfig config = new GenericObjectPoolConfig<>();config.setMaxTotal(pool.getMaxActive());config.setMaxIdle(pool.getMaxIdle());config.setMinIdle(pool.getMinIdle());config.setTestOnBorrow(Boolean.TRUE);if (pool.getTimeBetweenEvictionRuns() != null) {config.setTimeBetweenEvictionRuns(pool.getTimeBetweenEvictionRuns());}if (pool.getMaxWait() != null) {config.setMaxWait(pool.getMaxWait());}return config;}protected ConnectionInfo parseUrl(String url) {try {URI uri = new URI(url);String scheme = uri.getScheme();if (!"redis".equals(scheme) && !"rediss".equals(scheme)) {throw new RuntimeException("url异常"+url);}boolean useSsl = ("rediss".equals(scheme));String username = null;String password = null;if (uri.getUserInfo() != null) {String candidate = uri.getUserInfo();int index = candidate.indexOf(':');if (index >= 0) {username = candidate.substring(0, index);password = candidate.substring(index + 1);}else {password = candidate;}}return new ConnectionInfo(uri, useSsl, username, password);}catch (URISyntaxException ex) {throw new RuntimeException("url异常"+url,ex);}}@Dataprotected static class ConnectionInfo {private final URI uri;private final boolean useSsl;private final String username;private final String password;ConnectionInfo(URI uri, boolean useSsl, String username, String password) {this.uri = uri;this.useSsl = useSsl;this.username = username;this.password = password;}boolean isUseSsl() {return this.useSsl;}String getHostName() {return this.uri.getHost();}int getPort() {return this.uri.getPort();}String getUsername() {return this.username;}String getPassword() {return this.password;}}}

UserServiceImpl.java

/*** 

* UserService*

** @description: UserService 使用的是cache集成了redis是使用redis*/ @Service @Slf4j public class UserServiceImpl implements UserService {/*** 模拟数据库*/private static final Map DATABASES = Maps.newConcurrentMap();/*** 初始化数据*/static {DATABASES.put(1L, new User(1L, "user1"));DATABASES.put(2L, new User(2L, "user2"));DATABASES.put(3L, new User(3L, "user3"));}/*** 保存或修改用户** @param user 用户对象* @return 操作结果*/@CachePut(value = "user", key = "#user.id")@Overridepublic User saveOrUpdate(User user) {DATABASES.put(user.getId(), user);log.info("保存用户【user】= {}", user);return user;}/*** 获取用户** @param id key值* @return 返回结果*/@Cacheable(value = "user", key = "#id")@Overridepublic User get(Long id) {// 我们假设从数据库读取log.info("查询用户【id】= {}", id);return DATABASES.get(id);}/*** 删除** @param id key值*/@CacheEvict(value = "user", key = "#id")@Overridepublic void delete(Long id) {DATABASES.remove(id);log.info("删除用户【id】= {}", id);} }

RedisTest.java

主要测试使用 RedisTemplate 操作 Redis 中的数据:

  • opsForValue:对应 String(字符串)
  • opsForZSet:对应 ZSet(有序集合)
  • opsForHash:对应 Hash(哈希)
  • opsForList:对应 List(列表)
  • opsForSet:对应 Set(集合)
  • opsForGeo:** 对应 GEO(地理位置)
/*** 

* Redis测试*

** @package: com.xkcoding.cache.redis* @description: Redis测试* @author: yangkai.shen* @date: Created in 2018/11/15 17:17* @copyright: Copyright (c) 2018* @version: V1.0* @modified: yangkai.shen*/ @Slf4j public class RedisTest extends SpringBootDemoCacheRedisApplicationTests {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Autowiredprivate RedisTemplate redisCacheTemplate;/*** 测试 Redis 操作*/@Testpublic void get() {// 测试线程安全,程序结束查看redis中count的值是否为1000ExecutorService executorService = Executors.newFixedThreadPool(1000);IntStream.range(0, 1000).forEach(i -> executorService.execute(() -> stringRedisTemplate.opsForValue().increment("count", 1)));stringRedisTemplate.opsForValue().set("k1", "v1");String k1 = stringRedisTemplate.opsForValue().get("k1");log.debug("【k1】= {}", k1);// 以下演示整合,具体Redis命令可以参考官方文档String key = "xkcoding:user:1";redisCacheTemplate.opsForValue().set(key, new User(1L, "user1"));// 对应 String(字符串)User user = (User) redisCacheTemplate.opsForValue().get(key);log.debug("【user】= {}", user);} }

UserServiceTest.java

主要测试使用Redis缓存是否起效

/*** 

* Redis - 缓存测试*

** @package: com.xkcoding.cache.redis.service* @description: Redis - 缓存测试* @author: yangkai.shen* @date: Created in 2018/11/15 16:53* @copyright: Copyright (c) 2018* @version: V1.0* @modified: yangkai.shen*/ @Slf4j public class UserServiceTest extends SpringBootDemoCacheRedisApplicationTests {@Autowiredprivate UserService userService;/*** 获取两次,查看日志验证缓存*/@Testpublic void getTwice() {// 模拟查询id为1的用户User user1 = userService.get(1L);log.debug("【user1】= {}", user1);// 再次查询User user2 = userService.get(1L);log.debug("【user2】= {}", user2);// 查看日志,只打印一次日志,证明缓存生效}/*** 先存,再查询,查看日志验证缓存*/@Testpublic void getAfterSave() {userService.saveOrUpdate(new User(4L, "测试中文"));User user = userService.get(4L);log.debug("【user】= {}", user);// 查看日志,只打印保存用户的日志,查询是未触发查询日志,因此缓存生效}/*** 测试删除,查看redis是否存在缓存数据*/@Testpublic void deleteUser() {// 查询一次,使redis中存在缓存数据userService.get(1L);// 删除,查看redis是否存在缓存数据userService.delete(1L);}}

参考资料

  • spring-data-redis 官方文档:https://docs.spring.io/spring-data/redis/docs/2.0.1.RELEASE/reference/html/
  • redis 文档:https://redis.io/documentation
  • redis 中文文档:http://www.redis.cn/commands.html

扩展

StringRedisTemplate和RedisTemplate的区别及使用方法

这里,总结下 Spring 提供的 4 种 RedisSerializer(Redis 序列化器):

默认情况下,RedisTemplate 使用 JdkSerializationRedisSerializer,也就是 JDK 序列化,容易产生 Redis 中保存了乱码的错觉。

通常考虑到易读性,可以设置 Key 的序列化器为 StringRedisSerializer。但直接使用 RedisSerializer.string(),相当于使用了 UTF_8 编码的 StringRedisSerializer,需要注意字符集问题。

如果希望 Value 也是使用 JSON 序列化的话,可以把 Value 序列化器设置为 Jackson2JsonRedisSerializer。默认情况下,不会把类型信息保存在 Value 中,即使我们定义 RedisTemplate 的 Value 泛型为实际类型,查询出的 Value 也只能是 LinkedHashMap 类型。如果希望直接获取真实的数据类型,你可以启用 Jackson ObjectMapper 的 activateDefaultTyping 方法,把类型信息一起序列化保存在 Value 中。

如果希望 Value 以 JSON 保存并带上类型信息,更简单的方式是,直接使用 RedisSerializer.json() 快捷方法来获取序列化器。

Cache注解:

是spring自带的缓存,本质就是缓存方法返回的结果,下次在访问这个方法就是从缓存取.默认Spring Cache是缓存到jvm虚拟机缓存中,这样的并不好,所有一般使用整合dataRedis一起使用,就是缓存在Redis中!

使用scan 命令模糊查询key

/*** 使用scan模糊查询key** @param key* @return java.util.Set 匹配到的Key集合* @author: ZhiHao* @date: 2021/5/13*/public  Set fuzzyQueryKey(K key) {// 需要模糊搜索的KeyString keys = String.format(key.toString(), "*");Set rawKeys = (Set) redisTemplate.execute((RedisCallback>) connection -> {Set set = new HashSet<>();Cursor cursor = connection.scan(ScanOptions.scanOptions().match(keys).count(1000L).build());while (cursor.hasNext()) {set.add(cursor.next());}return set;}, true);RedisSerializer keySerializer = redisTemplate.getKeySerializer();return keySerializer != null ? SerializationUtils.deserialize(rawKeys, keySerializer) : (Set) rawKeys;}

lettuce连接池生效

要想使lettuce连接池生效,即使用多个redis物理连接。这行设置不能缺少
genericObjectPoolConfig.setTimeBetweenEvictionRunsMillis(100); 这个设置是,每隔多少毫秒,空闲线程驱逐器关闭多余的空闲连接,且保持最少空闲连接可用,这个值最好设置大一点,否者影响性能。同时 genericObjectPoolConfig.setMinIdle(minIdle); 中minldle值要大于0。
lettuce连接池属性timeBetweenEvictionRunsMillis如果不设置 默认是 -1,当该属性值为负值时,lettuce连接池要维护的最小空闲连接数的目标minIdle就不会生效 。源码中的解释如下:

/*** Target for the minimum number of idle connections to maintain in the pool. This* setting only has an effect if both it and time between eviction runs are* positive.*/private int minIdle = 0;

1

相关内容

热门资讯

读什么什么有感的英文 读什么什么有感的英文英语读后感标题 “读XXX有感”用英语说是 “Reading after XX...
秦岚个人资料身高体重 秦岚个人资料身高体重身高:165公分 体重:46公斤秦岚 生日:七月十七日 星座:巨蟹座 出生地:沈...
双鱼和天秤会纠缠一辈子,既相配... 双鱼和天秤会纠缠一辈子,既相配又相克,为什么?双鱼座的人和天秤座的人都是比较细心的,而且特别敏感,有...
独自一人在外怎样和别人相处? 独自一人在外怎样和别人相处?我觉得独自一个人在外面一定要好好的照顾自己,应该找一份工作,找一个住的地...
朱自清散文集有哪些写的好,值得... 朱自清散文集有哪些写的好,值得背诵的?《背影》、《 春》、《 荷塘月色》、《 匆匆》都是不错的佳...
大家最讨厌的电视剧的哪一个主角... 大家最讨厌的电视剧的哪一个主角?我觉得最讨厌的电视剧主角是容嬷嬷。都挺好,里面的苏大强就是越看越别扭...
69DT伤害怎么才能上1300... 69DT伤害怎么才能上1300 !我加点是4L1M!现在60了!伤害才800!我没大号,想买梦幻币买...
孩子上课不认真听讲 孩子上课不认真听讲我的孩子七周半,已经上二年级了,但是上课不认真听讲总是搞小动作,说了很多次也不听,...
《红脸儿》的主要内容 《红脸儿》的主要内容  红脸儿主要内容:   小说以散淡而富有诗意的语言回顾了“我”与3个小伙伴之间...
异地恋的成功例子 异地恋的成功例子 情侣异地恋8年终成正果 两人存下186张火车票见证爱情一对河南的情侣在大学恋爱时便...
小狗吃了死耗子怎么办 小狗吃了死耗子怎么办你好,没事的,放心吧,你的小狗是宠物狗还是土狗,若是宠物狗的话可能会给它造成身体...
请问有没有死亡万花筒广播剧资源... 请问有没有死亡万花筒广播剧资源?死亡万花筒,我有呀!死亡万花筒广播剧,地·址:9525.video(...
徐缺有哪些女人 徐缺有哪些女人徐缺是小说《最强反套路系统》中的角色,他有许多女性关系,其中包括:1. 林小红:徐缺的...
假如我是四大名著中的人物作文9... 假如我是四大名著中的人物作文900假如你是的林黛玉的话那你就会好好读书,不至于连900个字都写不出了...
西游记81难? 西游记81难?西游记的81难是师徒四人取经回来在河中落水经书被淹了的事
魔兽世界风暴王子问题! 魔兽世界风暴王子问题!现在3.05这版本 王子第4阶段的屏障 是不是可以被MS驱散? 屏障驱散后是...
如何评价张杰的少年中国说 如何评价张杰的少年中国说我觉得非常棒,张杰的家庭条件不好。从小就非常努力。刻苦学习音乐,经过拼搏奋斗...
智取生辰纲中杨志是怎样的人?他... 智取生辰纲中杨志是怎样的人?他失败的原因是什么?简短些志有智慧,但是他忽略了一个重要的因素:团队的合...
有一本书,名字忘记了.好象是美... 有一本书,名字忘记了.好象是美国人写的.梭罗《瓦尔登湖》 如果你用的是新教材,应该是这篇吧是 海明威...
四岁孩子看什么书 四岁孩子看什么书可以看一些带有简单数字的书、色彩鲜艳的图画、动物图画等,培养他的数字感和色彩感,尽量...