Lettuce客户端 Redis异常 Can't assign requested address

Redis异常 Can’t assign requested address

记一次很久之前的Redis连接异常。当时使用的是Redis Cluster集群,微服务的商品服务需要保存商品到Redis中,短时间内会连续执行HSET和HGET操作,程序启动跑了几分钟,就抛了一些错误异常,让我措手不及。异常信息如下:

Caused by: io.lettuce.core.RedisConnectionException: Unable to connect to 127.0.0.1<7994
at io.lettuce.core.RedisConnectionException.create RedisConnectionException.jav a:56 
at io.lettuce.core.cluster.PooledClusterConnectionProvider.lambda$getConnectio nAsync$6 PooledClusterConnectionProvider.java:322 
at java.util.concurrent.CompletableFuture.uniHandle CompletableFuture.java:822 
at java.util.concurrent.CompletableFuture$UniHandle.tryFire CompletableFuture.ja va:797 
at java.util.concurrent.CompletableFuture.postComplete CompletableFuture.java: 474 
at java.util.concurrent.CompletableFuture.completeExceptionally CompletableFutu re.java:1977 
at io.lettuce.core.AbstractRedisClient.lambda$initializeChannelAsync$1 AbstractR edisClient.java:275 
at io.netty.util.concurrent.DefaultPromise.notifyListener0 DefaultPromise.java:511 
at io.netty.util.concurrent.DefaultPromise.notifyListenersNow DefaultPromise.java: 485 
at io.netty.util.concurrent.DefaultPromise.notifyListeners DefaultPromise.java: 424 
at io.netty.util.concurrent.DefaultPromise.tryFailure DefaultPromise.java: 121 
at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.connect Abstract NioChannel.java:290 
at io.netty.channel.DefaultChannelPipeline$HeadContext.connect DefaultChannel Pipeline.java:1366 
at io.netty.channel.AbstractChannelHandlerContext.invokeConnect AbstractChan
nelHandlerContext.java:545  at
io.netty.channel.AbstractChannelHandlerContext.connect AbstractChannelHan dlerContext.java:530 
at io.netty.channel.ChannelDuplexHandler.connect ChannelDuplexHandler.java: 50 
at io.netty.channel.AbstractChannelHandlerContext.invokeConnect AbstractChan nelHandlerContext.java:545 
at io.netty.channel.AbstractChannelHandlerContext.connect AbstractChannelHan dlerContext.java:530 
at io.netty.channel.ChannelOutboundHandlerAdapter.connect ChannelOutboundH andlerAdapter.java:47 
at io.netty.channel.AbstractChannelHandlerContext.invokeConnect AbstractChan nelHandlerContext.java:545 
at io.netty.channel.AbstractChannelHandlerContext.connect AbstractChannelHan dlerContext.java:530 
at io.netty.channel.ChannelDuplexHandler.connect ChannelDuplexHandler.java: 50 
at io.netty.channel.AbstractChannelHandlerContext.invokeConnect AbstractChan nelHandlerContext.java:545 
at io.netty.channel.AbstractChannelHandlerContext.connect AbstractChannelHan dlerContext.java:530 
at io.netty.channel.AbstractChannelHandlerContext.connect AbstractChannelHan dlerContext.java:512 
at io.netty.channel.DefaultChannelPipeline.connect DefaultChannelPipeline.java: 1024 
at io.netty.channel.AbstractChannel.connect AbstractChannel.java:259  at io.netty.bootstrap.Bootstrap$3.run Bootstrap.java:252 
at
io.netty.util.concurrent.AbstractEventExecutor.safeExecute AbstractEventExecu tor.java:163 
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks SingleThreadEv entExecutor.java:404 
at io.netty.channel.nio.NioEventLoop.run NioEventLoop.java:463 
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run SingleThreadEventEx ecutor.java:886 
at io.netty.util.concurrent.FastThreadLocalRunnable.run FastThreadLocalRunnable .java:30 
at java.lang.Thread.run Thread.java:748 
Caused by: java.util.concurrent.CompletionException: io.netty.channel.AbstractChannel$AnnotatedSocketException: Can't assign requested address: /127.0.0.1<7994
at java.util.concurrent.CompletableFuture.encodeThrowable CompletableFuture.ja va:292 
at java.util.concurrent.CompletableFuture.completeThrowable CompletableFuture. java:308 
at java.util.concurrent.CompletableFuture.uniApply CompletableFuture.java:593 
at java.util.concurrent.CompletableFuture$UniApply.tryFire CompletableFuture.jav a:577 
... 30 common frames omitted
Caused by: io.netty.channel.AbstractChannel$AnnotatedSocketException: Can't assign requested address: /127.0.0.1<7994
at sun.nio.ch.Net.connect0 Native Method 
at sun.nio.ch.Net.connect Net.java:454 
at sun.nio.ch.Net.connect Net.java:446 
at sun.nio.ch.SocketChannelImpl.connect SocketChannelImpl.java:648  at io.netty.util.internal.SocketUtils$3.run SocketUtils.java:83 
at io.netty.util.internal.SocketUtils$3.run SocketUtils.java:80  at java.security.AccessController.doPrivileged Native Method  at io.netty.util.internal.SocketUtils.connect SocketUtils.java:80  at
io.netty.channel.socket.nio.NioSocketChannel.doConnect NioSocketChannel.ja va:310 
at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.connect Abstract NioChannel.java:254 
... 22 common frames omitted
Caused by: java.net.BindException: Can't assign requested address
... 32 common frames omitted

原因是 按单条商品记录保存到Redis,hset(key, field, value),而且是两个线程同时操作不同商品插入Redis与从Redis获取商品,这个操作是单条命令连续执行,而不是批量保存和批量获取,另外在构建Lettuce客户端是没有使用线程连接池,最终导致Lettuce Redis客户端频繁连接Redis服务器,由于每次连接短时间内结束,导致很多TCP TIME_WAIT,这样旧连接还没结束,连接数有限,新连接没办法绑定端口,即Cannot assign requested address

netstat -nat | grep 127.0.0.1:7994 会看到连接127.0.0.1:7994的状态,你会发现很多TIME_WAIT

为什么单个客户端多次操作,会出现这么多连接了呢?

这是lettuce redis没有完善的地方:传送门

要到Spring Data Redis 2.1 发布才行。

其中的spring boot redis maven依赖如下:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- redis lettuce 客户端需要的依赖 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.4.2</version>
        </dependency>

解决方案:

  1. 把单条执行改批量执行,如HSET改为HMSET,HGET改HMGET

  2. 需要加redis 线程连接池。幸好spring boot redis 已经帮我们构建好了一个LettuceConnectionFactory连接工厂Bean,该工厂类Bean已经实现了连接池。

    直接依赖注入就行。

    附上实现代码:

    package xxxx.xxxx.xxxx.xxxx;
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisClusterConfiguration;
    import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    import java.util.List;
    import java.util.Objects;
    
    import lombok.Data;
    
    @Configuration
    @Data
    public class RedisClusterConfig {
    
      private LettuceConnectionFactory lettuceConnectionFactory;
    
      @Autowired
      public RedisClusterConfig(LettuceConnectionFactory lettuceConnectionFactory) {
        // 依赖注入LettuceConnectionFactory工厂类
        // LettuceConnectionConfiguration自动注入了含lettuce Pool连接池和集群Cluster的LettuceConnectionFactory连接工厂类
        this.lettuceConnectionFactory = lettuceConnectionFactory;
      }
    
    
      @Bean
      RedisTemplate<String, Object> lettuceRedisTemplate() {
        // 设置序列化
        Jackson2JsonRedisSerializer<String> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<String>(String.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.enableDefaultTyping(DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
    
        // 配置redisTemplate
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
        redisTemplate.setConnectionFactory(lettuceConnectionFactory);
        RedisSerializer<?> stringSerializer = new StringRedisSerializer();
        // key 序列化
        redisTemplate.setKeySerializer(stringSerializer);
        // value 序列化
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
    
        // Hash Key序列化
        redisTemplate.setHashKeySerializer(stringSerializer);
        // Hash Value 序列化
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        // 初始化赋值
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
      }
    }
    

    ​参考链接
    https://blog.csdn.net/hguisu/article/details/10241519

    原文作者:martin6699
    原文地址: https://www.jianshu.com/p/ac90f7bacd31
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞