In a 10,000-word review of FunTester’s past and present, I shared one of the advantages of the FunTester testing framework: the ability to encapsulatefunctionality and performance testing for all Java implementable interfaces.

I have shared the test cases of HTTP protocol, Socket protocol and MySQL, and I am preparing to test the interface of Redis recently. Therefore, I have rewritten the functional interface encapsulation class of Redis in advance. I have to say that my previous knowledge of Redis is really superficial.

Without further ado, let me share with you my thoughts:

  • Redis connection pool management class
  • Redis resource reclamation
  • Redis features encapsulate classes

Pooling technology

Before we begin the body of work, let’s share a technical term called pooling. Pooling techniques are widely used, such as thread pooling, mysql database connection pooling, Http connection pooling and Redis connection pooling.

Pooling is a common programming technique. It can significantly optimize application performance and reduce the resource cost of frequent system connections when there are a large number of requests. Common in our daily work are database connection pool, thread pool, object pool, etc., which are characterized by maintaining “expensive” and “time-consuming” resources in a specific “pool”, specifying the minimum number of connections, maximum number of connections, blocking queue and other configurations, convenient for unified management and reuse. There are usually some supporting functions such as detection mechanism, forced collection, monitoring, etc.

In the process of performance testing, I also used a lot of honest technology. One of my basic ideas is to bind the connection pool and thread pool of the protocol, which can not only avoid the problems caused by resource sharing, but also improve the performance and concurrency ability.

There is a very important knowledge point, is the recycling of resources. I’ll share that later.

Redis connection pool management class

The main function of this connection pool management class is to create a connection pool and set the connection configuration. I didn’t make a configuration file this time, but if you want, you can make a configuration file to save the configuration.

package com.funtester.db.redis;

import com.alibaba.fastjson.JSONObject;
import com.funtester.frame.SourceCode;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/** * redis connection pool */
public class RedisPool extends SourceCode {

    static Logger logger = LogManager.getLogger(RedisPool.class);

    /** * Maximum number of connections */
    private static int MAX_TOTAL = 1000;

    /** * The maximum number of jedis instances in idle state */
    private static int MAX_IDLE = 300;

    /** * The smallest number of jedis instances in idle state */
    private static int MIN_IDLE = 10;

    /** * The maximum waiting time to get the instance */
    private static long MAX_WAIT = 5000;

    /** * Redis connection timeout */
    private static int TIMEOUT = 5000;

    /** * Whether to perform validation when borrow a Jedis instance, if true. The resulting jedis instance must be usable */
    private static boolean testOnBorrow = true;

    /** * whether to perform validation when returning a jedis instance if true is assigned. The jedis instance put back into Jedispool is definitely usable. * /
    private static boolean testOnReturn = true;

    /** * whether to block when the connection is exhausted, false will throw an exception, true will block until timeout. The default is true */
    private static boolean blockWhenExhausted = true;

    private static JedisPoolConfig config = getConfig();

    /** * Initializes the connection pool */
    public static JedisPool getPool(String host, int port) {
        logger.info(Redis connection pool IP: {}, port: {}, timeout setting: {}, host, port, TIMEOUT);
        return new JedisPool(config, host, port, TIMEOUT);
    }

    /** * Default connection pool configuration **@return* /
    private static JedisPoolConfig getConfig(a) {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(MAX_TOTAL);
        config.setMaxIdle(MAX_IDLE);
        config.setMinIdle(MIN_IDLE);
        config.setTestOnBorrow(testOnBorrow);
        config.setTestOnReturn(testOnReturn);
        config.setBlockWhenExhausted(blockWhenExhausted);
        config.setMaxWaitMillis(MAX_WAIT);
        logger.debug("Connect redis configuration: {}", JSONObject.toJSONString(config));
        returnconfig; }}Copy the code

Resource recovery knowledge

The resource here refers to the connection of the Redis connection pool. After we obtain a connection and perform some operations, we need to put the connection back into the connection pool, which has been acquired by other threads for reuse purposes. Here we mainly refer to the official implementation case. In fact, the method is very simple, just need to get a connection Jedis object to operate, call close() can be.

Here I use the Java try-catch syntax:

 /** * Sets the key validity period, in seconds **@param key
     * @param exTime
     * @return* /
    public boolean expire(String key, int exTime) {
        try (Jedis jedis = getJedis()) {
            return jedis.expire(key, exTime) == 1;
        } catch (Exception e) {
            logger.error("expire key:{} error", key, e);
            return false; }}Copy the code

This is equivalent to:

    public boolean expire(String key, int exTime) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.expire(key, exTime) == 1;
        } catch (Exception e) {
            logger.error("expire key:{} error", key, e);
            return false;
        } finally {
            if(jedis ! =null) jedis.close(); }}Copy the code

Function encapsulation class

Before I started looking at the common APIS for Redis operations, I thought the number of apis would be small, but after I looked at all the apis in detail. I found myself really a **! This functionality encapsulates a class where I’ve written some of the most common operations, and some of the less common ones I haven’t written here. If you have needs, through the com. Inheritance funtester. Db. Redis. RedisBase class to add methods of encapsulation.

package com.funtester.db.redis;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.util.*;

public class RedisBase {

    private static Logger logger = LogManager.getLogger(RedisBase.class);

    String host;

    int port;

    JedisPool pool;

    public int index;

    public RedisBase(a) {}public RedisBase(String host, int port) {
        this.host = host;
        this.port = port;
        pool = RedisPool.getPool(host, port);
    }

    /** * get jedis operation object, reclaim resource method close, after 3.0 deprecate other methods, default connect to first database * default use 0 index **@return* /
    public Jedis getJedis(a) {
        Jedis resource = pool.getResource();
        resource.select(index);
        return resource;
    }

    /** * Sets the key validity period, in seconds **@param key
     * @param exTime
     * @return* /
    public boolean expire(String key, int exTime) {
        try (Jedis jedis = getJedis()) {
            return jedis.expire(key, exTime) == 1;
        } catch (Exception e) {
            logger.error("expire key:{} error", key, e);
            return false; }}/** * Set key-value, expiration time **@param key
     * @param value
     * @paramExpiredTime Unit: s *@return* /
    public boolean set(String key, String value, int expiredTime) {
        try (Jedis jedis = getJedis()) {
            return jedis.setex(key, expiredTime, value).equalsIgnoreCase("OK");
        } catch (Exception e) {
            logger.error("setex key:{} value:{} error", key, value, e);
            return false; }}/** * Set the redis content **@param key
     * @param value
     * @return* /
    public boolean set(String key, String value) {
        try (Jedis jedis = getJedis()) {
            return jedis.set(key, value).equalsIgnoreCase("OK");
        } catch (Exception e) {
            logger.error("set key:{} value:{} error", key, value, e);
            return false; }}/** * get value **@param key
     * @return* /
    public String get(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.get(key);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null; }}The redis Lpush command inserts one or more values into the list header. If the key does not exist, an empty list is created and an LPUSH operation is performed. Returns an error * * when key exists but is not a list type@param key
     * @param list
     * @returnThe length of the list * /
    public Long lpush(String key, String... list) {
        try (Jedis jedis = getJedis()) {
            return jedis.lpush(key, list);
        } catch (Exception e) {
            return 0l; }}/** * Inserts one or more values into the head of an existing list, invalid if the list does not exist **@param key
     * @param list
     * @return* /
    public Long lpushx(String key, String... list) {
        try (Jedis jedis = getJedis()) {
            return jedis.lpushx(key, list);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return 0l; }}/** * inserts one or more values at the end of the list (rightmost). * <p> * If the list does not exist, an empty list is created and an RPUSH operation is performed. An error is returned when a list exists but is not of a list type. * *@param key
     * @param list
     * @return* /
    public Long rpush(String key, String... list) {
        try (Jedis jedis = getJedis()) {
            return jedis.rpush(key, list);
        } catch (Exception e) {
            return 0l; }}/** * is used to insert one or more values to the end (rightmost) of an existing list. If the list does not exist, the operation is invalid * *@param key
     * @param list
     * @return* /
    public Long rpushx(String key, String... list) {
        try (Jedis jedis = getJedis()) {
            return jedis.rpushx(key, list);
        } catch (Exception e) {
            return 0l; }}/** * gets the first value from the array and removes it **@param key
     * @return* /
    public String lpop(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.lpop(key);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null; }}/** * removes and returns the last element ** in the list@param key
     * @return* /
    public String rpop(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.rpop(key);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null; }}/** * get the array length **@param key
     * @return* /
    public Long llen(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.llen(key);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return 0l; }}/** * gets the value of the fixed index **@param key
     * @paramIndex starts at 0 *@return* /
    public String lindex(String key, long index) {
        try (Jedis jedis = getJedis()) {
            return jedis.lindex(key, index);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null; }}/** * get a list of **@param key
     * @param start
     * @param end
     * @return* /
    public List<String> lrange(String key, long start, long end) {
        try (Jedis jedis = getJedis()) {
            return jedis.lrange(key, start, end);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return newArrayList<>(); }}/** * Sets the index of the list **@param key
     * @param index
     * @param value
     * @return* /
    public String lset(String key, long index, String value) {
        try (Jedis jedis = getJedis()) {
            return jedis.lset(key, index, value);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null; }}/** * set field in hash table key to value **@param key
     * @param hkey
     * @param value
     * @returnThe number of rows * /
    public Long hset(String key, String hkey, String value) {
        try (Jedis jedis = getJedis()) {
            return jedis.hset(key, hkey, value);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null; }}/** * returns the value of the given field in the hash table key **@param key
     * @param hkey
     * @return* /
    public String hget(String key, String hkey) {
        try (Jedis jedis = getJedis()) {
            return jedis.hget(key, hkey);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null; }}/** * Removes one or more specified fields in the hash table key. Non-existing fields are ignored **@param key
     * @param hkey
     * @return* /
    public Long hdel(String key, String hkey) {
        try (Jedis jedis = getJedis()) {
            return jedis.hdel(key, hkey);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null; }}/** * check whether ** exists in the given field in the hash table key@param key
     * @param hkey
     * @return* /
    public Boolean hexists(String key, String hkey) {
        try (Jedis jedis = getJedis()) {
            return jedis.hexists(key, hkey);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null; }}/** * returns all fields in the hash table key **@param key
     * @return* /
    public Set<String> hkeys(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.hkeys(key);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null; }}/** * returns the values of all fields in the hash table key **@param key
     * @return* /
    public List<String> hvals(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.hvals(key);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null; }}/** * returns the number of fields in the hash table key **@param key
     * @return* /
    public Long hlen(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.hlen(key);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null; }}/** * returns all fields and values ** in the hash table key@param key
     * @return* /
    public Map<String, String> hgetAll(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.hgetAll(key);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null; }}Increment increment increment increment increment increment increment increment increment increment increment increment increment increment increment increment increment If the field does not exist, the value of the field is initialized to 0 * *@param key
     * @param hkey
     * @param num
     * @return* /
    public Long hincrBy(String key, String hkey, long num) {
        try (Jedis jedis = getJedis()) {
            return jedis.hincrBy(key, hkey, num);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null; }}Increment increment increment increment increment increment increment increment increment increment increment increment increment increment increment increment increment If the field does not exist, the value of the field is initialized to 0 * *@param key
     * @param hkey
     * @param num
     * @return* /
    public Double hincrByFloat(String key, String hkey, double num) {
        try (Jedis jedis = getJedis()) {
            return jedis.hincrByFloat(key, hkey, num);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null; }}/** * Add one or more member elements to the set key. Members already in the set are ignored **@param key
     * @param value
     * @return* /
    public Long sadd(String key, String... value) {
        try (Jedis jedis = getJedis()) {
            return jedis.sadd(key, value);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null; }}/** * Returns the cardinality of the set key (the number of elements in the set) **@param key
     * @param value
     * @return* /
    public Long scard(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.scard(key);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null; }}/** * returns all members of the set key. A nonexistent key is considered an empty set * *@param key
     * @return* /
    public Set<String> smembers(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.smembers(key);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null; }}/** * removes and returns a random element ** from the collection@param key
     * @return* /
    public String spop(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.spop(key);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null; }}/** * if count is positive and less than the cardinality of the set, the command returns an array of different elements of count. Return the entire collection if count is greater than or equal to the cardinality of the collection. * If count is negative, the command returns an array whose elements may be repeated multiple times and whose length is the absolute value of count. * *@param key
     * @param count
     * @return* /
    public List<String> srandmember(String key, int count) {
        try (Jedis jedis = getJedis()) {
            return jedis.srandmember(key, count);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null; }}/** * returns a random element in the collection **@param key
     * @return* /
    public String srandmember(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.srandmember(key);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null; }}/** Remove one or more member elements from the set key. Non-existing member elements are ignored *@param key
     * @param value
     * @return* /
    public Long srem(String key,String... value) {
        try (Jedis jedis = getJedis()) {
            return jedis.srem(key,value);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null; }}/** * Whether key ** exists@param key
     * @return* /
    public boolean exists(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.exists(key);
        } catch (Exception e) {
            logger.error("exists key:{} error", key, e);
            return false; }}/** * delete key * jedis returns the value 1 indicating success, 0 indicating failure, possibly non-existent key **@param key
     * @return* /
    public Long del(String... key) {
        try (Jedis jedis = getJedis()) {
            return jedis.del(key);
        } catch (Exception e) {
            logger.error("del key:{} error", key, e);
            return null; }}/** * Obtain the type of the value corresponding to the key **@param key
     * @return* /
    public String type(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.type(key);
        } catch (Exception e) {
            logger.error("type key:{} error", key, e);
            return null; }}/** * Get the key set that meets the criteria **@param pattern
     * @return* /
    public Set<String> getKeys(String pattern) {
        try (Jedis jedis = getJedis()) {
            return jedis.keys(pattern);
        } catch (Exception e) {
            logger.error("type key:{} error", pattern, e);
            return newHashSet<String>(); }}/** * appends value to the end of key's original value **@param key
     * @param content
     * @return* /
    public Long append(String key, String content) {
        try (Jedis jedis = getJedis()) {
            return jedis.append(key, content);
        } catch (Exception e) {
            logger.error("append key:{} ,content:{},error", key, content, e);
            return null; }}/** * close connection pool */
    public void close(a) { pool.close(); }}Copy the code

Have Fun ~ Tester!

  • FunTester test framework architecture diagram
  • Piecewise random practice – Simulating flow on line
  • FunTester Framework Tutorial
  • HTTP Interface Testing Basics
  • Tease the interface documentation together
  • Small white automated test guide
  • How do I choose the right test automation tool
  • Selenium4 Alpha-7 Upgrade experience
  • Selenium4 IDE features: No code trend and SIDE Runner
  • LT browser – responsive web testing tool
  • Soft start of performance test
  • There is no data to drive automated tests
  • Android APP test knowledge 【 interview reserve 】
  • Selenium 4 post, never meet API