【Azure Redis 快取 Azure Cache For Redis】Redis連接池

問題描述

Redis根據定價層說明,不同級別支援的連接數最多可達4萬(同時),但是當短時間又大量連接請求建立的時候,Redis服務的服務壓力非常大,到達100%。嚴重影響了高響應的要求。最嚴重時,經常出現Redis Client Operation timeout錯誤。

問題分析

根據設計,Redis 只使用一個執行緒進行命令處理。 Azure Cache for Redis 還利用其它核心進行 I/O 處理。 擁有更多的內核可能不會產生線性縮放,但可提高吞吐量性能。 而且,較大 VM 的頻寬限制通常比較小 VM 的更高。 這有助於避免網路飽和,從而避免應用程式超時。

  • 基本快取和標準快取
    • C0 (250 MB) 快取 – 最多支援 256 個連接
    • C1 (1 GB) 快取 – 最多支援 1,000 個連接
    • C2 (2.5 GB) 快取 – 最多支援 2,000 個連接
    • C3 (6 GB) 快取 – 最多支援 5,000 個連接
    • C4 (13 GB) 快取 – 最多支援 10,000 個連接
    • C5 (26 GB) 快取 – 最多支援 15,000 個連接
    • C6 (53 GB) 快取 – 最多支援 20,000 個連接
  • 高級快取
    • P1 (6 GB – 60 GB) – 最多支援 7,500 個連接
    • P2 (13 GB – 130 GB) – 最多支援 15,000 個連接
    • P3 (26 GB – 260 GB) – 最多支援 30,000 個連接
    • P4 (53 GB – 530 GB) – 最多支援 40,000 個連接

雖然每個快取大小 最多 允許一定數量的連接,但與 Redis 的每個連接都具有其關聯的開銷。 此類開銷的一個示例是,由於 TLS/SSL 加密而導致的 CPU 和記憶體使用。 給定快取大小的最大連接限制假定輕負載快取。 如果連接開銷的負載  客戶端操作的負載超出了系統容量,那麼即使未超出當前快取大小的連接限制,快取也可能會遇到容量問題。

解決方案

啟用連接池,重複使用連接。創建新連接是高開銷的操作,會增大延遲,因此請盡量重複使用連接。 如果你選擇創建新連接,請確保在釋放舊連接之前先將其關閉(即使是在 .NET 或 Java 等託管記憶體語言中)。

避免高開銷操作 – 某些 Redis 操作(例如 KEYS 命令)的開銷很大,應該避免。 有關詳細資訊,請參閱有關長時間運行的命令的一些注意事項

如果請求連接及性能要求已經超過了單個伺服器的極限,則考慮使用Redis Cluster (集群,增加分片數)。

 

連接池實例

一: Jedis的連接池設置

 .................
     
        // boolean useSsl = true;
        String cacheHostname = "xxxx.redis.cache.chinacloudapi.cn";
        String cachekey = " Key";

        JedisPool jdspool = getPool(cacheHostname, 6379, cachekey);

        Jedis jedis1 = jdspool.getResource();
        System.out.println("Cache Response : " + jedis1.set("Message-1","hello pool"));


        String msg = "Hello! The cache is working from Java!";
        for (int i = 0; i < 10000; i++) {
            try {
                Jedis jedis = jdspool.getResource();
                System.out.println("Cache Response : " + jedis.set("Message-Java-" + i, msg + i));
                jedis.close();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        
        
....................

    /**
     * 獲取連接池.
     * 
     * @return 連接池實例
     */
    public static JedisPool getPool(String ip, int port, String cachekey) {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxIdle(20);
        config.setMaxTotal(20);
        config.setMinIdle(10);
        config.setMaxWaitMillis(2000);
        config.setTestOnBorrow(true);
        config.setTestOnReturn(true);

        JedisPool pool = null;
        try {
            /**
             * 如果你遇到 java.net.SocketTimeoutException: Read timed out exception的異常資訊
             * 請嘗試在構造JedisPool的時候設置自己的超時值. JedisPool默認的超時時間是2秒(單位毫秒)
             */
            pool = new JedisPool(config, ip, port, 2000, cachekey);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return pool;
    }

注意:主要的設置參數為 setMaxTotal 和 setMaxIdle, MaxTotal為連接池中連接的最大數,而MaxIdle則表示允許最多多少個連接空閑時,以便隨時提供Jedis連接。 最好情況下,MaxTotal與MaxIdle一樣。

Parameter Description Default value Recommended settings
maxTotal The maximum number of connections that are supported by the pool. 8 For more information, see Recommended settings.
maxIdle The maximum number of idle connections in the pool. 8 For more information, see Recommended settings.
minIdle The minimum number of idle connections in the pool. 0 For more information, see Recommended settings.
blockWhenExhausted Specifies whether the client must wait when the resource pool is exhausted. Only when this parameter is set to true, the maxWaitMillis parameter takes effect. true We recommend that you use the default value.
maxWaitMillis The maximum number of milliseconds that the client must wait when no connection is available. A value of -1 specifies that the connection never times out. We recommend that you do not use the default value.
testOnBorrow Specifies whether to validate connections by using the PING command before the connections are borrowed from the pool. Invalid connections are removed from the pool. false We recommend that you set this parameter to false when the workload is heavy. This allows you to reduce the overhead of a ping test.
testOnReturn Specifies whether to validate connections by using the PING command before the connections are returned to the pool. Invalid connections are removed from the pool. false We recommend that you set this parameter to false when the workload is heavy. This allows you to reduce the overhead of a ping test.
jmxEnabled Specifies whether to enable Java Management Extensions (JMX) monitoring. true We recommend that you enable JMX monitoring. Take note that you must also enable the fe

Recommended settings (//partners-intl.aliyun.com/help/doc-detail/98726.htm

maxTotal: The maximum number of connections.

To set a proper value of maxTotal, take note of the following factors:

  • The expected concurrent connections based on your business requirements.
  • The amount of time that is consumed by the client to run the command.
  • The limit of Redis resources. For example, if you multiply maxTotal by the number of nodes (ECS instances), the product must be smaller than the supported maximum number of connections in Redis. You can view the maximum connections on the Instance Information page in the ApsaraDB for Redis console.
  • The resource that is consumed to create and release connections. If the number of connections that are created and released for a request is large, the processes that are performed to create and release connections are adversely affected.

For example, the average time that is consumed to run a command, or the average time that is required to borrow or return resources and to run Jedis commands with network overhead, is approximately 1 ms. The queries per second (QPS) of a connection is about 1 second/1 millisecond = 1000. The expected QPS of an individual Redis instance is 50,000 (the total number of QPS divided by the number of Redis shards). The theoretically required size of a resource pool (maxTotal) is 50,000/1,000 = 50.

However, this is only a theoretical value. To reserve some resources, the value of the maxTotal parameter can be larger than the theoretical value. However, if the value of the maxTotal parameter is too large, the connections consume a large amount of client and server resources. For Redis servers that have a high QPS, if a large number of commands are blocked, the issue cannot be solved even by a large resource pool.

maxIdle and minIdle

maxIdle is the actual maximum number of connections required by workloads. maxTotal includes the number of idle connections as a surplus. If the value of maxIdle is too small on heavily loaded systems, new Jedis connections are created to serve the requests. minIdle specifies the minimum number of established connections that must be kept in the pool.

The connection pool achieves its best performance when maxTotal = maxIdle. This way, the performance is not affected by the scaling of the connection pool. We recommend that you set the maxIdle and minIdle parameters to the same value if the user traffic fluctuates. If the number of concurrent connections is small or the value of the maxIdle parameter is too large, the connection resources are wasted.

You can evaluate the size of the connection pool used by each node based on the actual total QPS and the number of clients that Redis serves.

Retrieve proper values based on monitoring data

In actual scenarios, a more reliable method is to try to retrieve optimal values based on monitoring data. You can use JMX monitoring or other monitoring tools to find proper values.

 

You cannot obtain resources from the resource pool in the following cases:

  • Timeout:
    redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
    …
    Caused by: java.util.NoSuchElementException: Timeout waiting for idle object
    at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:449)
  • When you set the blockWhenExhausted parameter to false, the time specified by borrowMaxWaitMillis is not used and the borrowObject call blocks the connection until an idle connection is available.

    redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
    …
    Caused by: java.util.NoSuchElementException: Pool exhausted
    at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:464)

This exception may not be caused by a limited pool size. For more information, see Recommended settings. To fix this issue, we recommend that you check the network, the parameters of the resource pool, the resource pool monitoring (JMX monitoring), the code (for example, the reason is that jedis.close() is not executed), slow queries, and the domain name system (DNS).