此 demo 主要演示了 Spring Boot 如何整合 redis,操作redis中的数据,并使用redis缓存数据。连接池使用 Lettuce。
Lettuce官网
org.springframework.boot spring-boot-starter-data-redis
org.springframework.boot spring-boot-starter-cache
org.apache.commons commons-pool2
org.springframework.boot spring-boot-starter-json
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
/*** * redis配置*
**/
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
@EnableCaching //开启缓存
public class RedisConfig extends CachingConfigurerSupport{/*** 自定义RedisTemplate序列化*/@Beanpublic RedisTemplate
官网自定义配置说明:
You can also register an arbitrary number of beans that implement
LettuceClientConfigurationBuilderCustomizer
for more advanced customizations.ClientResources
can also be customized usingClientResourcesBuilderCustomizer
. If you use Jedis,JedisClientConfigurationBuilderCustomizer
is also available. Alternatively, you can register a bean of typeRedisStandaloneConfiguration
,RedisSentinelConfiguration
, orRedisClusterConfiguration
to take full control over the configuration.
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;}}}
/*** * 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);}
}
主要测试使用
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);}
}
主要测试使用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 提供的 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() 快捷方法来获取序列化器。
是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连接池生效,即使用多个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
上一篇:安装了nodejs怎么安装nvm
下一篇:类和对象(一)