2021-10-14

本文主要介绍了SpringBoot2在整合Redis后遇到的命令超时问题,以及三种有效的解决方案。一是通过自定义RedisTemplate并配置LettuceClientConfiguration开启拓扑刷新选项;二是直接在SpringBoot配置文件中启用Lettuce的拓扑刷新;三是改用Jedis连接Redis。这些方法旨在解决因连接长时间未操作导致的超时错误。

springBoot2 整合redis之后频繁报超时错误

org.springframework.dao.QueryTimeoutException: Redis command timed out; nested exception is io.lettuce.core.RedisCommandTimeoutException: Command timed out after 10 second(s)
        at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:70)
        at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:41)
        at org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:44)
        at org.springframework.data.redis.FallbackExceptionTranslationStrategy.translate(FallbackExceptionTranslationStrategy.java:42)
        at org.springframework.data.redis.connection.lettuce.LettuceConnection.convertLettuceAccessException(LettuceConnection.java:273)
        at org.springframework.data.redis.connection.lettuce.LettuceStringCommands.convertLettuceAccessException(LettuceStringCommands.java:799)
        at org.springframework.data.redis.connection.lettuce.LettuceStringCommands.get(LettuceStringCommands.java:68)
        at org.springframework.data.redis.connection.DefaultedRedisConnection.get(DefaultedRedisConnection.java:266)
        at org.springframework.data.redis.core.DefaultValueOperations$1.inRedis(DefaultValueOperations.java:57)
        at org.springframework.data.redis.core.AbstractOperations$ValueDeserializingRedisCallback.doInRedis(AbstractOperations.java:60)
        at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:228)
        at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:188)
        at org.springframework.data.redis.core.AbstractOperations.execute(AbstractOperations.java:96)
        at org.springframework.data.redis.core.DefaultValueOperations.get(DefaultValueOperations.java:53)
        ....
        at java.lang.Thread.run(Thread.java:748)
Caused by: io.lettuce.core.RedisCommandTimeoutException: Command timed out after 10 second(s)
        at io.lettuce.core.ExceptionFactory.createTimeoutException(ExceptionFactory.java:51)
        at io.lettuce.core.LettuceFutures.awaitOrCancel(LettuceFutures.java:119)
        at io.lettuce.core.FutureSyncInvocationHandler.handleInvocation(FutureSyncInvocationHandler.java:75)
        at io.lettuce.core.internal.AbstractInvocationHandler.invoke(AbstractInvocationHandler.java:79)
        at com.sun.proxy.$Proxy192.get(Unknown Source)
        at org.springframework.data.redis.connection.lettuce.LettuceStringCommands.get(LettuceStringCommands.java:66)

网上咨询很多,转载一下靠谱的解决方案

使用到Lettuce连接redis,一段时间后不操作,再去操作redis,会报连接超时错误,在其重连后又可使用。
原因是:Lettuce 自适应拓扑刷新(Adaptive updates)与定时拓扑刷新(Periodic updates) 是默认关闭的导致问题的出现

方案一、重写连接工厂实例,更改其LettuceClientConfiguration 为开启拓扑更新

@Configuration
public class RedisConfig {


    @Autowired
    private RedisProperties redisProperties;

    //这是固定的模板
    //自己定义了一个RedisTemplate
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(@Qualifier("lettuceConnectionFactoryUvPv") RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        //Json序列化配置
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(om.getPolymorphicTypeValidator());
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        //解决序列化问题
        om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        //String的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        //key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        //hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);

        //value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);

        //hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();

        return template;
    }

    /**
     * 为RedisTemplate配置Redis连接工厂实现
     * LettuceConnectionFactory实现了RedisConnectionFactory接口
     * UVPV用Redis
     *
     * @return 返回LettuceConnectionFactory
     */
    @Bean(destroyMethod = "destroy")
    //这里要注意的是,在构建LettuceConnectionFactory 时,如果不使用内置的destroyMethod,可能会导致Redis连接早于其它Bean被销毁
    public LettuceConnectionFactory lettuceConnectionFactoryUvPv() throws Exception {

        List<String> clusterNodes = redisProperties.getCluster().getNodes();
        Set<RedisNode> nodes = new HashSet<>();
        clusterNodes.forEach(address -> nodes.add(new RedisNode(address.split(":")[0].trim(), Integer.parseInt(address.split(":")[1]))));
        RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration();
        clusterConfiguration.setClusterNodes(nodes);
        clusterConfiguration.setPassword(RedisPassword.of(redisProperties.getPassword()));
        clusterConfiguration.setMaxRedirects(redisProperties.getCluster().getMaxRedirects());


        RedisStandaloneConfiguration  redisStandaloneConfiguration=new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(redisProperties.getHost());
        redisStandaloneConfiguration.setPassword(redisProperties.getPassword());
        redisStandaloneConfiguration.setDatabase(redisProperties.getDatabase());
        redisStandaloneConfiguration.setPort(redisProperties.getPort());

        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        poolConfig.setMaxIdle(redisProperties.getLettuce().getPool().getMaxIdle());
        poolConfig.setMinIdle(redisProperties.getLettuce().getPool().getMinIdle());
        poolConfig.setMaxTotal(redisProperties.getLettuce().getPool().getMaxActive());

        return new LettuceConnectionFactory(redisStandaloneConfiguration, getLettuceClientConfiguration(poolConfig));
    }

    /**
     * 配置LettuceClientConfiguration 包括线程池配置和安全项配置
     *
     * @param genericObjectPoolConfig common-pool2线程池
     * @return lettuceClientConfiguration
     */
    private LettuceClientConfiguration getLettuceClientConfiguration(GenericObjectPoolConfig genericObjectPoolConfig) {
        /*
        ClusterTopologyRefreshOptions配置用于开启自适应刷新和定时刷新。如自适应刷新不开启,Redis集群变更时将会导致连接异常!
         */
        ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
                //开启自适应刷新
                //.enableAdaptiveRefreshTrigger(ClusterTopologyRefreshOptions.RefreshTrigger.MOVED_REDIRECT, ClusterTopologyRefreshOptions.RefreshTrigger.PERSISTENT_RECONNECTS)
                //开启所有自适应刷新,MOVED,ASK,PERSISTENT都会触发
                .enableAllAdaptiveRefreshTriggers()
                // 自适应刷新超时时间(默认30秒)
                .adaptiveRefreshTriggersTimeout(Duration.ofSeconds(25)) //默认关闭开启后时间为30秒
                // 开周期刷新
                .enablePeriodicRefresh(Duration.ofSeconds(20))  // 默认关闭开启后时间为60秒 ClusterTopologyRefreshOptions.DEFAULT_REFRESH_PERIOD 60  .enablePeriodicRefresh(Duration.ofSeconds(2)) = .enablePeriodicRefresh().refreshPeriod(Duration.ofSeconds(2))
                .build();
        return LettucePoolingClientConfiguration.builder()
                .poolConfig(genericObjectPoolConfig)
                .clientOptions(ClusterClientOptions.builder().topologyRefreshOptions(topologyRefreshOptions).build())
                //将appID传入连接,方便Redis监控中查看
                //.clientName(appName + "_lettuce")
                .build();
    }

}

方案二、SpringBoot2.3.x后,可使用配置文件中开启lettuce的拓扑刷新

    lettuce:
      pool:
        max-active: 20
        max-wait: -1ms
        max-idle: 10
        min-idle: 2
      cluster:
        refresh:
          adaptive: true
          #20秒自动刷新一次
          period: 20

方案三、更改连接redis的连接方式,使用jedis连接

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
			<exclusions>
				<exclusion>
					<groupId>io.lettuce</groupId>
					<artifactId>lettuce-core</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		
		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
		</dependency>

spring:
  redis:
    jedis:
      pool:
        max-active: ${redis.config.maxTotal:1024}
        max-idle: ${redis.config.maxIdle:50}
        min-idle: ${redis.config.minIdle:1}
        max-wait: ${redis.config.maxWaitMillis:5000}
    #lettuce:
      #pool:
        #max-active: ${redis.config.maxTotal:1024}
        #max-idle: ${redis.config.maxIdle:50}
        #min-idle: ${redis.config.minIdle:1}
        #max-wait: ${redis.config.maxWaitMillis:5000}

转载自

https://www.cnblogs.com/shouyaya/p/14607270.html
https://blog.youkuaiyun.com/zw521cx/article/details/101212549

<think> 我们需要将日期时间列转换为仅日期部分(去掉时间部分) 假设列名为'成交日期',数据格式为字符串或datetime类型 我们将提供多种方法,并处理不同情况 </think> # 将 DataFrame 中的日期时间列格式化为日期字符串 将日期时间列从 `2021-10-14 00:00:00` 格式转换为 `2021-10-14` 格式是常见的数据处理任务。以下是几种高效且健壮的解决方案: ## 方法 1: 使用 Pandas 的 dt 访问器 ```python import pandas as pd import numpy as np # 示例 DataFrame data = { '成交日期': [ '2021-10-14 00:00:00', '2022-05-21 14:30:00', '2023-01-05 09:15:00', '2020-12-31 23:59:59', None, # 空值 '2023-07-04 00:00:00', 'invalid_date', # 无效日期 20211014, # 数值类型 '2021-10-14' ] } df = pd.DataFrame(data) # 方法 1.1: 转换为日期格式后提取日期部分 def format_date_column(df, col_name, errors='coerce'): """ 格式化日期列为 YYYY-MM-DD 格式 参数: df: 包含日期列的DataFrame col_name: 日期列名 errors: 错误处理方式 ('coerce' 将无效解析设为NaT) 返回: 格式化后的Series """ # 转换为 datetime 类型,处理无效日期 date_series = pd.to_datetime(df[col_name], errors=errors) # 格式化为字符串 formatted = date_series.dt.strftime('%Y-%m-%d') # 处理原始为日期格式但无时间部分的字符串 mask = (df[col_name].str.len() == 10) if df[col_name].dtype == 'object' else False if mask.any(): formatted[mask] = df.loc[mask, col_name] return formatted # 应用格式化 df['格式化日期'] = format_date_column(df, '成交日期') # 显示结果 print("格式化结果:") print(df) ``` ## 方法 2: 使用字符串操作(高效处理纯字符串) ```python # 方法 2: 字符串切片(仅当所有日期格式一致时适用) def format_date_with_slice(series): """ 使用字符串切片快速格式化日期(适用于格式统一的列) """ # 先转换为字符串 str_series = series.astype(str) # 使用字符串切片 formatted = str_series.str[:10] # 处理长度不足的字符串 short_mask = str_series.str.len() < 10 formatted[short_mask] = series[short_mask].apply( lambda x: x if pd.isna(x) else str(x)[:10] # 处理数值类型 ) return formatted # 应用 df['切片格式化'] = format_date_with_slice(df['成交日期']) print("\n字符串切片方法结果:") print(df[['成交日期', '切片格式化']]) ``` ## 方法 3: 高性能矢量化方法(大型数据集) ```python # 方法 3: 使用 NumPy 矢量化操作 def vectorized_date_format(series): """ 高性能日期格式化(适用于大型数据集) """ # 转换为 datetime 数组 date_array = pd.to_datetime(series, errors='coerce').values # 创建结果数组 result = np.empty(series.shape, dtype='U10') # Unicode 10字符 # 提取日期部分 for i in range(len(date_array)): if pd.isnull(date_array[i]): result[i] = '' else: dt = date_array[i].astype('datetime64[D]') # 提取日期部分 result[i] = str(dt)[:10] return pd.Series(result, index=series.index) # 创建大型数据集测试 large_data = { '成交日期': ['2021-10-14 00:00:00'] * 1000000 } large_df = pd.DataFrame(large_data) # 应用高性能格式化 large_df['格式化日期'] = vectorized_date_format(large_df['成交日期']) print("\n大型数据集处理结果(前5行):") print(large_df.head()) ``` ## 方法 4: 处理混合格式的日期列 ```python # 方法 4: 处理混合格式的日期列 def format_mixed_dates(series): """ 处理混合格式的日期列(包含多种格式) """ def format_single_date(value): if pd.isnull(value): return '' # 尝试不同日期格式 try: # 尝试解析完整日期时间 return pd.to_datetime(value).strftime('%Y-%m-%d') except: try: # 尝试解析纯日期格式 if len(str(value)) == 10 and '-' in str(value): return str(value) # 尝试解析数值型日期(如20211014) elif str(value).isdigit() and len(str(value)) == 8: return f"{str(value)[:4]}-{str(value)[4:6]}-{str(value)[6:8]}" except: return '无效日期' return series.apply(format_single_date) # 应用 df['混合格式处理'] = format_mixed_dates(df['成交日期']) print("\n混合格式处理结果:") print(df[['成交日期', '混合格式处理']]) ``` ## 完整解决方案类 ```python class DateFormatter: def __init__(self, input_format=None, output_format='%Y-%m-%d'): """ 初始化日期格式化器 参数: input_format: 输入日期格式(如'%Y-%m-%d %H:%M:%S') output_format: 输出日期格式(默认'%Y-%m-%d') """ self.input_format = input_format self.output_format = output_format def format(self, series): """ 格式化日期Series 返回: 格式化后的Series """ # 转换为datetime if self.input_format: dates = pd.to_datetime(series, format=self.input_format, errors='coerce') else: dates = pd.to_datetime(series, errors='coerce') # 格式化输出 formatted = dates.dt.strftime(self.output_format) # 处理转换失败的值 failed_mask = formatted.isna() formatted[failed_mask] = series[failed_mask].apply(self._handle_failed_conversion) return formatted def _handle_failed_conversion(self, value): """处理转换失败的日期值""" if pd.isnull(value): return '' # 尝试处理数值型日期 if isinstance(value, (int, float)): try: str_value = str(int(value)) if len(str_value) == 8: # 20211014格式 return f"{str_value[:4]}-{str_value[4:6]}-{str_value[6:8]}" except: pass # 尝试处理字符串 if isinstance(value, str) and len(value) >= 10: return value[:10] return '无效日期' # 使用示例 formatter = DateFormatter() df['类格式化'] = formatter.format(df['成交日期']) print("\n使用类处理的结果:") print(df[['成交日期', '类格式化']]) ``` ## 性能比较与优化建议 1. **小型数据集**: - 推荐使用 `pd.to_datetime().dt.strftime()` 方法 - 简洁易读,处理能力强 2. **大型数据集**: - 推荐使用矢量化方法(方法3) - 避免使用 `apply()`,效率较低 3. **数据验证**: - 添加日期有效性检查: ```python def is_valid_date(date_str): try: pd.to_datetime(date_str) return True except: return False valid_mask = df['成交日期'].apply(is_valid_date) print(f"有效日期比例: {valid_mask.mean():.2%}") ``` 4. **时区处理**: ```python # 如果需要处理时区 df['带时区日期'] = pd.to_datetime(df['成交日期'], utc=True).dt.tz_convert('Asia/Shanghai') ``` ## 常见问题解决方案 ### 问题1: 日期格式不一致 ```python # 处理多种日期格式 def parse_multiple_formats(series): formats = [ '%Y-%m-%d %H:%M:%S', '%Y/%m/%d %H:%M:%S', '%Y%m%d', '%Y-%m-%d' ] for fmt in formats: try: return pd.to_datetime(series, format=fmt, errors='coerce') except: continue return pd.to_datetime(series, errors='coerce') df['统一日期'] = parse_multiple_formats(df['成交日期']).dt.strftime('%Y-%m-%d') ``` ### 问题2: 处理空值和无效日期 ```python # 创建包含空值和无效日期的列 invalid_data = { '日期': [ '2021-10-14 00:00:00', None, '2023-13-01 00:00:00', # 无效月份 '2022-02-30 00:00:00', # 无效日期 'not a date' ] } invalid_df = pd.DataFrame(invalid_data) # 安全转换方法 invalid_df['安全转换'] = pd.to_datetime(invalid_df['日期'], errors='coerce') invalid_df['格式化'] = invalid_df['安全转换'].dt.strftime('%Y-%m-%d').fillna('无效日期') print("\n安全转换结果:") print(invalid_df) ``` ### 问题3: 日期本地化 ```python # 处理本地化日期 localized_data = { '本地日期': [ '14/10/2021 00:00:00', # 日/月/年 '05/01/2023 00:00:00' ] } localized_df = pd.DataFrame(localized_data) # 指定格式转换 localized_df['格式化'] = pd.to_datetime( localized_df['本地日期'], format='%d/%m/%Y %H:%M:%S' ).dt.strftime('%Y-%m-%d') print("\n本地化日期转换:") print(localized_df) ``` ## 总结 1. **首选方法**:使用 `pd.to_datetime()` 配合 `dt.strftime()` 2. **性能优化**:对于大型数据集,使用矢量化方法 3. **错误处理**:使用 `errors='coerce'` 处理无效日期 4. **格式化**:使用 `strftime('%Y-%m-%d')` 获取日期部分 5. **混合格式**:实现多格式解析逻辑
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值