Làm cách nào để sử dụng nhóm kết nối trong MySQL?

Tổng hợp kết nối là một mẫu truy cập dữ liệu nổi tiếng. Mục đích chính của nó là giảm chi phí liên quan đến việc thực hiện các kết nối cơ sở dữ liệu và các thao tác đọc/ghi cơ sở dữ liệu

Ở cấp độ cơ bản nhất, nhóm kết nối là một triển khai bộ nhớ đệm kết nối cơ sở dữ liệu có thể được định cấu hình để phù hợp với các yêu cầu cụ thể

Trong hướng dẫn này, chúng ta sẽ thảo luận về một vài khung tổng hợp kết nối phổ biến. Sau đó, chúng ta sẽ tìm hiểu cách triển khai nhóm kết nối của riêng mình từ đầu

2. Tại sao kết nối tổng hợp?

Tất nhiên, câu hỏi này là tu từ

Nếu chúng ta phân tích trình tự các bước liên quan đến vòng đời kết nối cơ sở dữ liệu điển hình, chúng ta sẽ hiểu tại sao

  1. Mở kết nối đến cơ sở dữ liệu bằng trình điều khiển cơ sở dữ liệu
  2. Mở socket TCP để đọc/ghi dữ liệu
  3. Đọc/ghi dữ liệu qua socket
  4. Đóng kết nối
  5. Đóng ổ cắm

Rõ ràng là các kết nối cơ sở dữ liệu là các hoạt động khá tốn kém và do đó, nên được giảm xuống mức tối thiểu trong mọi trường hợp sử dụng có thể (chỉ cần tránh trong các trường hợp cạnh)

Đây là nơi triển khai tổng hợp kết nối phát huy tác dụng

Chỉ cần triển khai một bộ chứa kết nối cơ sở dữ liệu, cho phép chúng tôi sử dụng lại một số kết nối hiện có, chúng tôi có thể tiết kiệm hiệu quả chi phí thực hiện một số lượng lớn các chuyến đi cơ sở dữ liệu đắt tiền. Điều này giúp tăng hiệu suất tổng thể của các ứng dụng dựa trên cơ sở dữ liệu của chúng tôi

3. Khung tổng hợp kết nối JDBC

Từ góc độ thực dụng, việc triển khai nhóm kết nối từ đầu là vô nghĩa khi xem xét số lượng khung tổng hợp kết nối “sẵn sàng cho doanh nghiệp” đã có sẵn

Từ góc độ mô phạm, đó là mục tiêu của bài viết này, nó không

Mặc dù vậy, trước khi chúng tôi tìm hiểu cách triển khai nhóm kết nối cơ bản, trước tiên chúng tôi sẽ giới thiệu một vài khung tổng hợp kết nối phổ biến

3. 1. Apache Commons DBCP

Hãy bắt đầu với Thành phần DBCP của Apache Commons, một khung công tác JDBC tổng hợp kết nối đầy đủ tính năng

public class DBCPDataSource {
    
    private static BasicDataSource ds = new BasicDataSource();
    
    static {
        ds.setUrl("jdbc:h2:mem:test");
        ds.setUsername("user");
        ds.setPassword("password");
        ds.setMinIdle(5);
        ds.setMaxIdle(10);
        ds.setMaxOpenPreparedStatements(100);
    }
    
    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }
    
    private DBCPDataSource(){ }
}

Trong trường hợp này, chúng tôi đã sử dụng lớp trình bao bọc có khối tĩnh để dễ dàng định cấu hình các thuộc tính của DBCP

Và đây là cách để có được kết nối gộp với lớp DBCPDataSource

Connection con = DBCPDataSource.getConnection();

3. 2. Hikari CP

Bây giờ, hãy xem xét HikariCP, khung tổng hợp kết nối JDBC nhanh như chớp được tạo bởi Brett Wooldridge (để biết chi tiết đầy đủ về cách định cấu hình và tận dụng tối đa HikariCP, vui lòng xem bài viết này)

public class HikariCPDataSource {
    
    private static HikariConfig config = new HikariConfig();
    private static HikariDataSource ds;
    
    static {
        config.setJdbcUrl("jdbc:h2:mem:test");
        config.setUsername("user");
        config.setPassword("password");
        config.addDataSourceProperty("cachePrepStmts", "true");
        config.addDataSourceProperty("prepStmtCacheSize", "250");
        config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
        ds = new HikariDataSource(config);
    }
    
    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }
    
    private HikariCPDataSource(){}
}

Tương tự, đây là cách tạo kết nối gộp với lớp HikariCPDataSource

Connection con = HikariCPDataSource.getConnection();

3. 3. C3P0

Cuối cùng trong bài đánh giá này là C3P0, một khung tổng hợp câu lệnh và kết nối JDBC4 mạnh mẽ được phát triển bởi Steve Waldman

public class C3p0DataSource {

    private static ComboPooledDataSource cpds = new ComboPooledDataSource();

    static {
        try {
            cpds.setDriverClass("org.h2.Driver");
            cpds.setJdbcUrl("jdbc:h2:mem:test");
            cpds.setUser("user");
            cpds.setPassword("password");
        } catch (PropertyVetoException e) {
            // handle the exception
        }
    }
    
    public static Connection getConnection() throws SQLException {
        return cpds.getConnection();
    }
    
    private C3p0DataSource(){}
}

Như mong đợi, nhận được kết nối gộp với lớp C3p0DataSource tương tự như các ví dụ trước

Connection con = C3p0DataSource.getConnection();

4. Thực hiện đơn giản

Để hiểu rõ hơn logic cơ bản của việc tổng hợp kết nối, hãy tạo một triển khai đơn giản

Chúng ta sẽ bắt đầu với một thiết kế kết hợp lỏng lẻo chỉ dựa trên một giao diện duy nhất

public interface ConnectionPool {
    Connection getConnection();
    boolean releaseConnection(Connection connection);
    String getUrl();
    String getUser();
    String getPassword();
}

Giao diện ConnectionPool xác định API công khai của nhóm kết nối cơ bản

Bây giờ, hãy tạo một triển khai cung cấp một số chức năng cơ bản, bao gồm nhận và giải phóng kết nối gộp

public class BasicConnectionPool 
  implements ConnectionPool {

    private String url;
    private String user;
    private String password;
    private List connectionPool;
    private List usedConnections = new ArrayList<>();
    private static int INITIAL_POOL_SIZE = 10;
    
    public static BasicConnectionPool create(
      String url, String user, 
      String password) throws SQLException {
 
        List pool = new ArrayList<>(INITIAL_POOL_SIZE);
        for (int i = 0; i < INITIAL_POOL_SIZE; i++) {
            pool.add(createConnection(url, user, password));
        }
        return new BasicConnectionPool(url, user, password, pool);
    }
    
    // standard constructors
    
    @Override
    public Connection getConnection() {
        Connection connection = connectionPool
          .remove(connectionPool.size() - 1);
        usedConnections.add(connection);
        return connection;
    }
    
    @Override
    public boolean releaseConnection(Connection connection) {
        connectionPool.add(connection);
        return usedConnections.remove(connection);
    }
    
    private static Connection createConnection(
      String url, String user, String password) 
      throws SQLException {
        return DriverManager.getConnection(url, user, password);
    }
    
    public int getSize() {
        return connectionPool.size() + usedConnections.size();
    }

    // standard getters
}

Mặc dù khá ngây thơ, lớp BasicConnectionPool cung cấp chức năng tối thiểu mà chúng ta mong đợi từ việc triển khai tổng hợp kết nối điển hình

Tóm lại, lớp khởi tạo nhóm kết nối dựa trên ArrayList lưu trữ 10 kết nối, có thể dễ dàng sử dụng lại

Cũng có thể tạo kết nối JDBC với lớp DriverManager và triển khai Nguồn dữ liệu

Vì sẽ tốt hơn nhiều nếu giữ cho việc tạo cơ sở dữ liệu kết nối là bất khả tri, chúng tôi đã sử dụng cái trước trong phương thức tạo tĩnh của nhà máy ()

Trong trường hợp này, chúng tôi đã đặt phương thức trong BasicConnectionPool vì đây là cách triển khai duy nhất của giao diện

Trong một thiết kế phức tạp hơn, với nhiều triển khai ConnectionPool, sẽ tốt hơn nếu đặt nó trong giao diện, do đó có được một thiết kế linh hoạt hơn và mức độ gắn kết cao hơn

Điểm liên quan nhất cần nhấn mạnh ở đây là khi nhóm được tạo, các kết nối sẽ được tìm nạp từ nhóm, do đó không cần phải tạo nhóm mới

Hơn nữa, khi một kết nối được giải phóng, nó thực sự được trả lại cho nhóm, vì vậy các máy khách khác có thể sử dụng lại nó

Không có tương tác nào nữa với cơ sở dữ liệu bên dưới, chẳng hạn như lệnh gọi rõ ràng tới phương thức close() của Kết nối

5. Sử dụng lớp BasicConnectionPool

Như mong đợi, việc sử dụng lớp BasicConnectionPool của chúng tôi rất đơn giản

Hãy tạo một bài kiểm tra đơn vị đơn giản và nhận kết nối H2 trong bộ nhớ gộp

________số 8

6. Cải tiến hơn nữa và tái cấu trúc

Tất nhiên, có rất nhiều chỗ để điều chỉnh/mở rộng chức năng hiện tại của việc triển khai tổng hợp kết nối của chúng tôi

Chẳng hạn, chúng ta có thể cấu trúc lại phương thức getConnection() và thêm hỗ trợ cho kích thước nhóm tối đa. Nếu tất cả các kết nối khả dụng đã được sử dụng và kích thước nhóm hiện tại nhỏ hơn mức tối đa được định cấu hình, thì phương thức này sẽ tạo một kết nối mới

Chúng tôi cũng có thể xác minh xem kết nối thu được từ nhóm có còn tồn tại hay không trước khi chuyển nó tới máy khách

@Override
public Connection getConnection() throws SQLException {
    if (connectionPool.isEmpty()) {
        if (usedConnections.size() < MAX_POOL_SIZE) {
            connectionPool.add(createConnection(url, user, password));
        } else {
            throw new RuntimeException(
              "Maximum pool size reached, no available connections!");
        }
    }

    Connection connection = connectionPool
      .remove(connectionPool.size() - 1);

    if(!connection.isValid(MAX_TIMEOUT)){
        connection = createConnection(url, user, password);
    }

    usedConnections.add(connection);
    return connection;
}

Lưu ý rằng phương thức hiện ném SQLException, nghĩa là chúng ta cũng sẽ phải cập nhật chữ ký giao diện

Hoặc chúng ta có thể thêm một phương thức để tắt phiên bản nhóm kết nối của mình một cách duyên dáng

Connection con = DBCPDataSource.getConnection();
0

Trong các triển khai sẵn sàng sản xuất, nhóm kết nối sẽ cung cấp một loạt các tính năng bổ sung, chẳng hạn như khả năng theo dõi các kết nối hiện đang được sử dụng, hỗ trợ cho nhóm câu lệnh đã chuẩn bị, v.v.

Để giữ cho bài viết này đơn giản, chúng tôi sẽ bỏ qua cách triển khai các tính năng bổ sung này và giữ cho việc triển khai không an toàn theo luồng vì mục đích rõ ràng

7. Phần kết luận

Trong bài viết này, chúng ta đã tìm hiểu sâu về tổng hợp kết nối là gì và tìm hiểu cách triển khai triển khai tổng hợp kết nối của riêng mình

Tất nhiên, chúng tôi không phải bắt đầu lại từ đầu mỗi khi chúng tôi muốn thêm lớp tổng hợp kết nối đầy đủ tính năng vào ứng dụng của mình

Đó là lý do tại sao chúng tôi bắt đầu bằng cách khám phá một số khung nhóm kết nối phổ biến nhất, để chúng tôi có ý tưởng rõ ràng về cách làm việc với chúng và chọn một khung phù hợp nhất với yêu cầu của chúng tôi

Khi nào nên sử dụng nhóm kết nối MySQL?

Tổng hợp kết nối cho phép một số luồng khác sử dụng kết nối nhàn rỗi để thực hiện công việc hữu ích. Trong thực tế, khi một luồng cần làm việc với MySQL hoặc cơ sở dữ liệu khác bằng JDBC , nó sẽ yêu cầu kết nối từ nhóm.

Nhóm kết nối SQL hoạt động như thế nào?

Một nhóm kết nối được tạo cho mỗi chuỗi kết nối duy nhất. Khi một nhóm được tạo, nhiều đối tượng kết nối sẽ được tạo và thêm vào nhóm để đáp ứng yêu cầu về kích thước nhóm tối thiểu. Các kết nối được thêm vào nhóm khi cần, lên đến kích thước nhóm tối đa được chỉ định (100 là mặc định)

Làm cách nào để đặt kích thước nhóm kết nối trong MySQL?

Số lượng kết nối cho phép được kiểm soát bởi biến hệ thống max_connections . Giá trị mặc định của nó là 100. Nếu cần hỗ trợ nhiều kết nối hơn, bạn nên đặt giá trị lớn hơn cho biến này. MySQL SmartPool v.

MySQL có thể xử lý nhiều kết nối không?

Giới hạn kết nối MySQL đồng thời . Giới hạn này giúp ngăn quá tải máy chủ MySQL gây bất lợi cho các trang web khác được lưu trữ trên máy chủ. Each database user is limited to 38 simultaneous MySQL connections. This limitation helps to prevent overloading the MySQL server to the detriment of other sites hosted on the server.