[转] Azure Redis Best Practices - Java

Jedis Java Client

Jedis instances are single threaded

  • Don't use the same Jedis connection instance from multiple threads at the same time.
  • Using the same Jedis instance from multiple threads at the same time will result in socket connection errors/resets or strange error messages like "expected '$' but got ' '".

Use JedisPool

  • This allows you to talk to redis from multiple threads while still getting the benefits of reused connections.
  • The JedisPool object is thread-safe and can be used from multiple threads at the same time.
  • This pool should be configured once and reused.
  • Make sure to return the Jedis instance back to the pool when done, otherwise you will leak the connection.
  • We have seen a few cases where connections in the pool get into a bad state. As a failsafe, you may want to re-create the JedisPool if you see connection errors that continue for longer than 30 seconds.

Some important settings to consider:

SettingDescription
connectTimeout How long to allow for new connections to be established (in milliseconds). In general, this should be at least 5000ms. If your client application tends to have high spikes CPU usage, setting this to 15000ms or 20000ms would be a good idea.
soTimeout This configures the socket timeout (in milliseconds) for the underlying connection. You can basically think of this as the operation timeout (how long you are willing to wait for a response from Redis). Think about this one in terms of worst case, not best case. Setting this too low can cause you to get timeout errors due to unexpected bursts in load. I typically recommend 1000ms as a good value for most customers.
port In Azure, 6379 is non-ssl and 6380 is SSL/TLS. Important Note: 6379 is disabled by default - you have to explicitly enable this insecure port if you wish to use it.

Choose JedisPoolConfig settings with care

SettingDescription
maxTotal This setting controls the max number of connections that can be created at a given time. Given that Jedis connections cannot be shared across threads, this setting affects the amount of concurrency your application can have when talking to Redis. Note that each connection does have some memory and CPU overhead, so setting this to a very high value may have negative side effects. If not set, the default value is 8, which is probably too low for most applications. When chosing a value, consider how many concurrent calls into Redis you think you will have under load.
maxIdle This is the max number of connections that can be idle in the pool without being immediately evicted (closed). If not set, the default value is 8. I would recommend that this setting be configured the same as maxTotal to help avoid connection ramp-up costs when your application has many bursts of load in a short period of time. If a connection is idle for a long time, it will still be evicted until the idle connection count hits minIdle (described below).
minIdle This is the number of "warm" connections (e.g. ready for immediate use) that remain in the pool even when load has reduced. If not set, the default is 0. When choosing a value, consider your steady-state concurrent requests to Redis. For instance, if your application is calling into Redis from 10 threads simultaneously, then you should set this to at least 10 (probably a bit higher to give you some room.
blockWhenExhausted This controls behavior when a thread asks for a connection, but there aren't any that are free and the pool can't create more (due to maxTotal). If set to true, the calling thread will block for maxWaitMillis before throwing an exception. The default is true and I recommend true for production environments. You could set it to false in testing environments to help you more easily discover what value to use for maxTotal.
maxWaitMillis How long to wait in milliseconds if calling JedisPool.getResource() will block. The default is -1, which means block indefinitely. I would set this to the same as the socketTimeout configured. Related to blockWhenExhausted.
TestOnBorrow Controls whether or not the connection is tested before it is returned from the pool. The default is false. Setting to true may increase resilience to connection blips but may also have a performance cost when taking connections from the pool. In my quick testing, I saw a noticable increase in the 50th percentile latencies, but no significant increase in 98th percentile latencies.
minEvictableIdleTimeMillis This specifies the minimum amount of time an connection may sit idle in the pool before it is eligible for eviction due to idle time. The default value is 60 seconds for JedisPoolConfig, and 30 minutes if you construct a configuration with GenericObjectPoolConfig. Azure Redis currently has 10 minute idle timeout for connections, so this should be set to less than 10 minutes

Use Pipelining

  • This will improve the throughput of the application. Read more about redis pipelining here https://redis.io/topics/pipelining.
  • Jedis does not do pipelining automatically for you. You have to call diffeent APIs in order to get the significant performance benefits that can come from using pipelining.
  • Examples can be found here

Log Pool Usage Periodically

  • Debugging performance problems due to JedisPool contention issues will be easier if you log the pool usage regularly.
  • If you ever get an error when trying to get a connection from the pool, you should definitely log usage stats. There is sample code here that shows which values to log...

Sample Code

  1 import javax.net.ssl.HostnameVerifier;
  2 import javax.net.ssl.SSLPeerUnverifiedException;
  3 import javax.net.ssl.SSLSession;
  4 import redis.clients.jedis.*;
  5 import javax.net.ssl.*;
  6 
  7 public class Redis {
  8     private static Object staticLock = new Object();
  9     private static JedisPool pool;
 10     private static String host;
 11     private static int port; // 6379 for NonSSL, 6380 for SSL
 12     private static int connectTimeout; //milliseconds
 13     private static int operationTimeout; //milliseconds
 14     private static String password;
 15     private static JedisPoolConfig config;
 16 
 17     // Should be called exactly once during App Startup logic.
 18     public static void initializeSettings(String host, int port, String password, int connectTimeout, int operationTimeout) {
 19         Redis.host = host;
 20         Redis.port = port;
 21         Redis.password = password;
 22         Redis.connectTimeout = connectTimeout;
 23         Redis.operationTimeout = operationTimeout;
 24     }
 25 
 26     // MAKE SURE to call the initializeSettings method first
 27     public static JedisPool getPoolInstance() {
 28         if (pool == null) { // avoid synchronization lock if initialization has already happened
 29             synchronized(staticLock) {
 30                 if (pool == null) { // don't re-initialize if another thread beat us to it.
 31                     JedisPoolConfig poolConfig = getPoolConfig();
 32                     boolean useSsl = port == 6380 ? true : false;
 33                     int db = 0;
 34                     String clientName = "MyClientName"; // null means use default
 35                     SSLSocketFactory sslSocketFactory = null; // null means use default
 36                     SSLParameters sslParameters = null; // null means use default
 37                     HostnameVerifier hostnameVerifier = new SimpleHostNameVerifier(host);
 38                     pool = new JedisPool(poolConfig, host, port, connectTimeout,operationTimeout,password, db,
 39                             clientName, useSsl, sslSocketFactory, sslParameters, hostnameVerifier);
 40                 }
 41             }
 42         }
 43         return pool;
 44     }
 45 
 46     public static JedisPoolConfig getPoolConfig() {
 47         if (config == null) {
 48             JedisPoolConfig poolConfig = new JedisPoolConfig();
 49 
 50             // Each thread trying to access Redis needs its own Jedis instance from the pool.
 51             // Using too small a value here can lead to performance problems, too big and you have wasted resources.
 52             int maxConnections = 200;
 53             poolConfig.setMaxTotal(maxConnections);
 54             poolConfig.setMaxIdle(maxConnections);
 55 
 56             // Using "false" here will make it easier to debug when your maxTotal/minIdle/etc settings need adjusting.
 57             // Setting it to "true" will result better behavior when unexpected load hits in production
 58             poolConfig.setBlockWhenExhausted(true);
 59 
 60             // How long to wait before throwing when pool is exhausted
 61             poolConfig.setMaxWaitMillis(operationTimeout);
 62 
 63             // This controls the number of connections that should be maintained for bursts of load.
 64             // Increase this value when you see pool.getResource() taking a long time to complete under burst scenarios
 65             poolConfig.setMinIdle(50);
 66 
 67             Redis.config = poolConfig;
 68         }
 69 
 70         return config;
 71     }
 72 
 73     public static String getPoolCurrentUsage()
 74     {
 75         JedisPool jedisPool = getPoolInstance();
 76         JedisPoolConfig poolConfig = getPoolConfig();
 77 
 78         int active = jedisPool.getNumActive();
 79         int idle = jedisPool.getNumIdle();
 80         int total = active + idle;
 81         String log = String.format(
 82                 "JedisPool: Active=%d, Idle=%d, Waiters=%d, total=%d, maxTotal=%d, minIdle=%d, maxIdle=%d",
 83                 active,
 84                 idle,
 85                 jedisPool.getNumWaiters(),
 86                 total,
 87                 poolConfig.getMaxTotal(),
 88                 poolConfig.getMinIdle(),
 89                 poolConfig.getMaxIdle()
 90         );
 91 
 92         return log;
 93     }
 94 
 95     private static class SimpleHostNameVerifier implements HostnameVerifier {
 96 
 97         private String exactCN;
 98         private String wildCardCN;
 99         public SimpleHostNameVerifier(String cacheHostname)
100         {
101             exactCN = "CN=" + cacheHostname;
102             wildCardCN = "CN=*" + cacheHostname.substring(cacheHostname.indexOf('.'));
103         }
104 
105         public boolean verify(String s, SSLSession sslSession) {
106             try {
107                 String cn = sslSession.getPeerPrincipal().getName();
108                 return cn.equalsIgnoreCase(wildCardCN) || cn.equalsIgnoreCase(exactCN);
109             } catch (SSLPeerUnverifiedException ex) {
110                 return false;
111             }
112         }
113     }
114 }
View Code

Here are some links to some sample code demonstrating these best practices

 

posted @ 2020-09-29 11:46  路边两盏灯  阅读(257)  评论(1编辑  收藏  举报