美文网首页hello world
Lettuce客户端 Redis异常 Can't as

Lettuce客户端 Redis异常 Can't as

作者: martin6699 | 来源:发表于2018-09-13 22:46 被阅读590次

    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

    相关文章

      网友评论

        本文标题:Lettuce客户端 Redis异常 Can't as

        本文链接:https://www.haomeiwen.com/subject/jonggftx.html