浅谈轻量级socket连接池实现

1. 背景

前段时间有幸参与到开源轻量级分布式文件系统Fastdfs Java-SDK的改造,

支持连接池和短连接两种方式,是否开启连接池可选(默认开启),短连接(用完即关闭)

2. 如何实现

调研了连接池下,网上很多socket连接池都是用Apache Commons Pool来实现的,个人感觉可能有点重,所以就完全原生代码实现连接池开发了。

3. 实现代码

  • socket连接类

    public class Connection {
    
      private Socket sock;
      private InetSocketAddress inetSockAddr;
      private Long lastAccessTime = System.currentTimeMillis();
      private boolean needActiveTest = false;
    
      public Connection(Socket sock, InetSocketAddress inetSockAddr) {
          this.sock = sock;
          this.inetSockAddr = inetSockAddr;
      }
    
      /**
       * get the server info
       *
       * @return the server info
       */
      public InetSocketAddress getInetSocketAddress() {
          return this.inetSockAddr;
      }
    
      public OutputStream getOutputStream() throws IOException {
          return this.sock.getOutputStream();
      }
    
      public InputStream getInputStream() throws IOException {
          return this.sock.getInputStream();
      }
    
      public Long getLastAccessTime() {
          return lastAccessTime;
      }
    
      public void setLastAccessTime(Long lastAccessTime) {
          this.lastAccessTime = lastAccessTime;
      }
    
      /**
       *
       * @throws IOException
       */
      public void close() throws IOException {
          //if connection enabled get from connection pool
          if (ClientGlobal.g_connection_pool_enabled) {
              ConnectionPool.closeConnection(this);
          } else {
              this.closeDirectly();
          }
      }
    
      public void release() throws IOException {
          if (ClientGlobal.g_connection_pool_enabled) {
              ConnectionPool.releaseConnection(this);
          } else {
              this.closeDirectly();
          }
      }
    
      /**
       * force close socket,
       */
      public void closeDirectly() throws IOException {
          if (this.sock != null) {
              try {
                  ProtoCommon.closeSocket(this.sock);
              } finally {
                  this.sock = null;
              }
          }
      }
    
      public boolean activeTest() throws IOException {
          if (this.sock == null) {
              return false;
          }
          return ProtoCommon.activeTest(this.sock);
      }
    
      public boolean isConnected() {
          boolean isConnected = false;
          if (sock != null) {
              if (sock.isConnected()) {
                  isConnected = true;
              }
          }
          return isConnected;
      }
      public boolean isAvaliable() {
          if (isConnected()) {
              if (sock.getPort() == 0) {
                  return false;
              }
              if (sock.getInetAddress() == null) {
                  return false;
              }
              if (sock.getRemoteSocketAddress() == null) {
                  return false;
              }
              if (sock.isInputShutdown()) {
                  return false;
              }
              if (sock.isOutputShutdown()) {
                  return false;
              }
              return true;
          }
          return false;
      }
    
      public boolean isNeedActiveTest() {
          return needActiveTest;
      }
    
      public void setNeedActiveTest(boolean needActiveTest) {
          this.needActiveTest = needActiveTest;
      }
    }
    
  • 连接管理器(主要职责:获取连接,关闭连接,释放连接)

public class ConnectionManager {

    private InetSocketAddress inetSocketAddress;

    /**
     * count of total connections 
     */
    private AtomicInteger totalCount = new AtomicInteger();

    /**
     * count of free connections
     */
    private AtomicInteger freeCount = new AtomicInteger();

    /**
     * lock
     */
    private ReentrantLock lock = new ReentrantLock(true);

    private Condition condition = lock.newCondition();

    /**
     * free container connection  
     */
    private LinkedList<Connection> freeConnections = new LinkedList<Connection>();

    private ConnectionManager() {

    }

    public ConnectionManager(InetSocketAddress socketAddress) {
        this.inetSocketAddress = socketAddress;
    }

   /**
    * get connection
    **/
    public Connection getConnection() throws MyException {
        lock.lock();
        try {
            Connection connection = null;
            while (true) {
                if (freeCount.get() > 0) {
                    freeCount.decrementAndGet();
                    connection = freeConnections.poll();
                    if (!connection.isAvaliable() || (System.currentTimeMillis() - connection.getLastAccessTime()) > ClientGlobal.g_connection_pool_max_idle_time) {
                        closeConnection(connection);
                        continue;
                    }
                    if (connection.isNeedActiveTest()) {
                        boolean isActive = false;
                        try {
                            isActive = connection.activeTest();
                        } catch (IOException e) {
                            System.err.println("send to server[" + inetSocketAddress.getAddress().getHostAddress() + ":" + inetSocketAddress.getPort() + "] active test fail ,emsg:" + e.getMessage());
                            isActive = false;
                        }
                        if (!isActive) {
                            closeConnection(connection);
                            continue;
                        } else {
                            connection.setNeedActiveTest(false);
                        }
                    }
                } else if (ClientGlobal.g_connection_pool_max_count_per_entry == 0 || totalCount.get() < ClientGlobal.g_connection_pool_max_count_per_entry) {
                    connection = ConnectionFactory.create(this.inetSocketAddress);
                    totalCount.incrementAndGet();
                } else {
                    try {
                        if (condition.await(ClientGlobal.g_connection_pool_max_wait_time_in_ms, TimeUnit.MILLISECONDS)) {
                            //wait single success
                            continue;
                        }
                        throw new MyException("connect to server " + inetSocketAddress.getAddress().getHostAddress() + ":" + inetSocketAddress.getPort() + " fail, wait_time > " + ClientGlobal.g_connection_pool_max_wait_time_in_ms + "ms");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        throw new MyException("connect to server " + inetSocketAddress.getAddress().getHostAddress() + ":" + inetSocketAddress.getPort() + " fail, emsg:" + e.getMessage());
                    }
                }
                return connection;
            }
        } finally {
            lock.unlock();
        }
    }

   /**
   *release connection
   */
    public void releaseConnection(Connection connection) {
        if (connection == null) {
            return;
        }
        lock.lock();
        try {
            connection.setLastAccessTime(System.currentTimeMillis());
            freeConnections.add(connection);
            freeCount.incrementAndGet();
            condition.signal();
        } finally {
            lock.unlock();
        }

    }

    /**
     *close connection
     *
     */
    public void closeConnection(Connection connection) {
        try {
            if (connection != null) {
                totalCount.decrementAndGet();
                connection.closeDirectly();
            }
        } catch (IOException e) {
            System.err.println("close socket[" + inetSocketAddress.getAddress().getHostAddress() + ":" + inetSocketAddress.getPort() + "] error ,emsg:" + e.getMessage());
            e.printStackTrace();
        }
    }

    public void setActiveTestFlag() {
        if (freeCount.get() > 0) {
            lock.lock();
            try {
                for (Connection freeConnection : freeConnections) {
                    freeConnection.setNeedActiveTest(true);
                }
            } finally {
                lock.unlock();
            }
        }
    }

}

注:其中setActiveTestFlag()方法解释一下,有可能出现连接断了之后,可能是这台服务器重启了,或者网络抽风导致闪断。希望只牺牲一次请求,主要解决服务器重启问题

大致思路就是一旦client请求时一旦有连接出现IOException,就会将所有当前实例对应的所有连接全部变为需要activeTest,当下一个连接获取时,就会检测所有的连接,从而达到只牺牲一次请求的目的.

  • 管理器连接池(由于存在多个实例,一个实例对应一个ConnectionManager,连接获取,释放,关闭的入口)

public class ConnectionPool {
    /**
     * key is ip:port, value is ConnectionManager
     */
    private final static ConcurrentHashMap<String, ConnectionManager> CP = new ConcurrentHashMap<String, ConnectionManager>();

    public static Connection getConnection(InetSocketAddress socketAddress) throws MyException {
        if (socketAddress == null) {
            return null;
        }
        String key = getKey(socketAddress);
        ConnectionManager connectionManager;
        connectionManager = CP.get(key);
        if (connectionManager == null) {
            synchronized (ConnectionPool.class) {
                connectionManager = CP.get(key);
                if (connectionManager == null) {
                    connectionManager = new ConnectionManager(socketAddress);
                    CP.put(key, connectionManager);
                }
            }
        }
        return connectionManager.getConnection();
    }

    public static void releaseConnection(Connection connection) throws IOException {
        if (connection == null) {
            return;
        }
        String key = getKey(connection.getInetSocketAddress());
        ConnectionManager connectionManager = CP.get(key);
        if (connectionManager != null) {
            connectionManager.releaseConnection(connection);
        } else {
            connection.closeDirectly();
        }

    }

    public static void closeConnection(Connection connection) throws IOException {
        if (connection == null) {
            return;
        }
        String key = getKey(connection.getInetSocketAddress());
        ConnectionManager connectionManager = CP.get(key);
        if (connectionManager != null) {
            connectionManager.closeConnection(connection);
            connectionManager.setActiveTestFlag();
        } else {
            connection.closeDirectly();
        }
    }

    private static String getKey(InetSocketAddress socketAddress) {
        if (socketAddress == null) {
            return null;
        }
        return String.format("%s:%s", socketAddress.getAddress().getHostAddress(), socketAddress.getPort());
 }

以上代码已发布于fastdfs-client-java V1.28版本

更多具体实现及源码请查看:https://github.com/happyfish100/fastdfs-client-java/tree/master/src/main/java/org/csource/fastdfs/pool

近期热文

*  浅谈扩展字段设计

  

谢谢阅读!欢迎转发、点赞、关注!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AI觉醒实战营

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值