Análisis del principio de implementación del grupo de conexiones jedis jedisPool

 prefacio

jedisPool se basa en la tecnología de grupo de objetos abstractos (objectPool) de Apache-commons-pool 2. En jedisPool, una conexión es una instancia de objeto jedisObject. El núcleo de jedisPool es: de acuerdo con el conjunto poolConfig, alrededor del contenedor del grupo de conexiones LinkedBlockingDeque, en las acciones relacionadas, realice la lógica de adición y eliminación. Por lo tanto, lo siguiente describe principalmente el comportamiento de las configuraciones clave y los métodos clave, que básicamente pueden espiar la realización de jedisPool.

Ejemplo de uso

/**
 * @author : kl (http://kailing.pub)
 * @since : 2022-04-26 12:37
 */
public class KLMain1 {
    public static void main(String[] args) {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMinIdle(10);

        JedisPool pool = new JedisPool(config,Protocol.DEFAULT_HOST, Protocol.DEFAULT_PORT);
        try (Jedis jedis = pool.getResource()) {
            jedis.set("foo", "kl");
            System.out.println(jedis.get("foo"));
        }
    }
}

Descripción general de las configuraciones clave

La clase de configuración del grupo de conexiones jedis es GenericObjectPoolConfig y la subclase JedisPoolConfig. Generalmente, la subclase JedisPoolConfig se usa para construir los parámetros del grupo de conexiones. Inicializará algunas configuraciones que administran el ciclo de vida de la conexión redis de forma predeterminada.

capacidad de la piscina

Controle el tamaño del grupo y configure la estrategia para adquirir conexiones

nombre
valores predeterminados 
Visión de conjunto
maxTotal 8 Número máximo de conexiones
maxIdle 8 Número máximo de conexiones inactivas
mininactivo 0 Número mínimo de conexiones inactivas

lifo

verdadero La política al devolver la conexión, que se vuelve a poner al principio de la cola por defecto
bloquear cuando se agota verdadero Al obtener una conexión, bloquear la espera de una conexión cuando el grupo de conexiones está vacío
maxWaitDuration -1 Solo tiene efecto cuando blockWhenExhausted = true, lo que indica cuánto tiempo esperar para el bloqueo. -1 significa bloquear hasta que se adquiera una conexión
testOnCreate falso Si verificar la validez de la conexión al crear la conexión, si la conexión no es válida, devolver nulo
testOnBorrow falso Ya sea para verificar la validez de la conexión al alquilar la conexión, la conexión no válida estará directamente extasiada e intentará continuar obteniendo
testOnReturn falso Si verificar la validez de la conexión al devolver la conexión, el enlace no válido se destruirá directamente y se creará y devolverá una nueva conexión

ciclo vital

Controla si habilitar el subproceso denominado commons-pool-evictor para conexión inactiva, detección de conexión no válida. y una política para configurar la detección de conexiones

nombre
Valor predeterminado de GenericObjectPoolConfig
valor predeterminado de jedisPoolConfig
Visión de conjunto

prueba mientras está inactivo

falso verdadero Ya sea para detectar enlaces no válidos cuando la línea de desalojo está habilitada

duración entre ejecuciones de desalojo

-1 30s El intervalo de tiempo entre cada detección del hilo de desalojo, en milisegundos. -1 significa no iniciar el hilo de desalojo

numTestsPerEvictionRun

3 -1 Cuántos enlaces verificar cada vez, -1 significa verificar todos

minEvictableIdleDuration

30 minutos años 60 Cuando la conexión de desalojo, el tiempo mínimo de inactividad de la conexión para vivir

softMinEvictableIdleDuration

-1 -1 Tiempo de inactividad mínimo para vivir durante el desalojo suave, -1 significa intMax

lógica clave

Los siguientes códigos están todos en la clase GenericObjectPool. Aquí hay ejemplos de definiciones de métodos clave y elementos de configuración utilizados directamente por cada método. Después de leer esta parte, puede saber cómo funcionan los parámetros configurados normalmente. El código fuente publicado a continuación se eliminó debido a razones de espacio, y solo se conservan y resaltan las partes lógicas clave.

definición de método
Elementos de configuración utilizados
Visión de conjunto
Objeto agrupado<T> create() maxTotal, testOnCreate Crear una conexión
T orderObject (préstamo largo final MaxWaitMillis) blockWhenExhausted、maxWaitDuration、testOnBorrow conexión arrendada
void returnObject(final T obj) maxIdle, lifo, testOnReturn conexión de retorno
anular el desalojo () testWhileIdle、durationBetweenEvictionRuns、numTestsPerEvictionRun、minEvictableIdleDuration、softMinEvictableIdleDuration、minIdle desalojar conexión
void asegurarMinIdle() lifo, minIdle mantener un mínimo de conexiones inactivas
anular agregarObjeto() lifo Agregar una conexión al grupo de conexiones

crear

    private PooledObject<T> create() throws Exception {
        int localMaxTotal = getMaxTotal();
        if (localMaxTotal < 0) {
            localMaxTotal = Integer.MAX_VALUE;
        }
        Boolean create = null;
        while (create == null) {
            synchronized (makeObjectCountLock) {
                final long newCreateCount = createCount.incrementAndGet();
                if (newCreateCount > localMaxTotal) {
                    // 池当前处于满负荷状态或正在制造足够的新对象以使其达到满负荷状态。
                    createCount.decrementAndGet();
                    if (makeObjectCount == 0) {
                        // 没有正在进行的 makeObject() 调用,因此池已满。不要尝试创建新对象。
                        create = Boolean.FALSE;
                    } else {
                        // 正在进行的 makeObject() 调用可能会使池达到容量。这些调用也可能会失败,因此请等待它们完成,然后重新测试池是否已满负荷
                        makeObjectCountLock.wait(localMaxWaitTimeMillis);
                    }
                } else {
                    // The pool is not at capacity. Create a new object.
                    makeObjectCount++;
                    create = Boolean.TRUE;
                }
            }

            // Do not block more if maxWaitTimeMillis is set.
            if (create == null &&
                    (localMaxWaitTimeMillis > 0 &&
                            System.currentTimeMillis() - localStartTimeMillis >= localMaxWaitTimeMillis)) {
                create = Boolean.FALSE;
            }
        }

        if (!create.booleanValue()) {
            return null;
        }

        final PooledObject<T> p;
        try {
            p = factory.makeObject();
            if (getTestOnCreate() && !factory.validateObject(p)) {
                createCount.decrementAndGet();
                return null;
            }
        }
        //...省略
        return p;
    }

La creación de una conexión es un método de bajo nivel que se utiliza para alquilar una conexión, devolver una conexión y mantener una conexión inactiva. Antes de crear una conexión, juzgará si la conexión actual alcanza maxTotal.Si alcanza maxTotal y no se está creando ninguna conexión en este momento, significa que el grupo está lleno y devuelve nulo. Si la conexión se crea correctamente, de acuerdo con la configuración de testOnCreate, si testOnCreate= true, la desconexión está disponible.Si la conexión no es válida, todavía se devuelve nulo. 

pedir prestado

   public T borrowObject(final Duration borrowMaxWaitDuration) throws Exception {
        assertOpen();

        final AbandonedConfig ac = this.abandonedConfig;
        if (ac != null && ac.getRemoveAbandonedOnBorrow() && (getNumIdle() < 2) &&
                (getNumActive() > getMaxTotal() - 3)) {
            removeAbandoned(ac);
        }

        PooledObject<T> p = null;

        // Get local copy of current config so it is consistent for entire
        // method execution
        final boolean blockWhenExhausted = getBlockWhenExhausted();

        boolean create;
        final long waitTimeMillis = System.currentTimeMillis();

        while (p == null) {
            create = false;
            p = idleObjects.pollFirst();
            if (p == null) {
                p = create();
                if (p != null) {
                    create = true;
                }
            }
            if (blockWhenExhausted) {
                if (p == null) {
                    if (borrowMaxWaitDuration.isNegative()) {
                        p = idleObjects.takeFirst();
                    } else {
                        p = idleObjects.pollFirst(borrowMaxWaitDuration);
                    }
                }
                if (p == null) {
                    throw new NoSuchElementException(appendStats(
                            "Timeout waiting for idle object, borrowMaxWaitDuration=" + borrowMaxWaitDuration));
                }
            } else if (p == null) {
                throw new NoSuchElementException(appendStats("Pool exhausted"));
            }
            if (!p.allocate()) {
                p = null;
            }

            if (p != null) {
                try {
                    factory.activateObject(p);
                } catch (final Exception e) {
                    try {
                        destroy(p, DestroyMode.NORMAL);
                    } catch (final Exception e1) {
                        // Ignore - activation failure is more important
                    }
                    p = null;
                    if (create) {
                        final NoSuchElementException nsee = new NoSuchElementException(
                                appendStats("Unable to activate object"));
                        nsee.initCause(e);
                        throw nsee;
                    }
                }
                if (p != null && getTestOnBorrow()) {
                    boolean validate = false;
                    Throwable validationThrowable = null;
                    try {
                        validate = factory.validateObject(p);
                    } catch (final Throwable t) {
                        PoolUtils.checkRethrow(t);
                        validationThrowable = t;
                    }
                    if (!validate) {
                        try {
                            destroy(p, DestroyMode.NORMAL);
                            destroyedByBorrowValidationCount.incrementAndGet();
                        } catch (final Exception e) {
                            // Ignore - validation failure is more important
                        }
                        p = null;
                        if (create) {
                            final NoSuchElementException nsee = new NoSuchElementException(
                                    appendStats("Unable to validate object"));
                            nsee.initCause(validationThrowable);
                            throw nsee;
                        }
                    }
                }
            }
        }

        updateStatsBorrow(p, Duration.ofMillis(System.currentTimeMillis() - waitTimeMillis));

        return p.getObject();
    }

La conexión alquilada se obtiene del encabezado de la lista de contenedores del grupo de conexiones. Si es nulo, se llama a create() para crear la conexión. Si aún es nulo, se determina si esperar a la conexión de acuerdo con la configuración de blockWhenExhausted=true, y maxWaitDuration=-1 para esperar todo el tiempo. De lo contrario, espere el tiempo de maxWaitDuration. Si no se obtiene la conexión después de esperar, se lanzará la excepción [ Tiempo de espera para el objeto inactivo ]. Si blockWhenExhausted=false , cuando no hay una conexión disponible en el grupo de conexiones, el grupo de conexiones está lleno [ Grupo agotado ] Se generará una excepción inmediatamente. Después de obtener la conexión con éxito, si testOnBorrow=true, continúe evaluando si la conexión es válida, el enlace inválido se destruye directamente y repita las operaciones anteriores hasta que se obtenga una conexión disponible.

devolverObjeto

   public void returnObject(final T obj) {
        final PooledObject<T> p = getPooledObject(obj);

        if (p == null) {
            if (!isAbandonedConfig()) {
                throw new IllegalStateException(
                        "Returned object not currently part of this pool");
            }
            return; // Object was abandoned and removed
        }

        markReturningState(p);

        final Duration activeTime = p.getActiveDuration();

        if (getTestOnReturn() && !factory.validateObject(p)) {
            try {
                destroy(p, DestroyMode.NORMAL);
            } catch (final Exception e) {
                swallowException(e);
            }
            try {
                ensureIdle(1, false);
            } catch (final Exception e) {
                swallowException(e);
            }
            updateStatsReturn(activeTime);
            return;
        }

        try {
            factory.passivateObject(p);
        } catch (final Exception e1) {
            swallowException(e1);
            try {
                destroy(p, DestroyMode.NORMAL);
            } catch (final Exception e) {
                swallowException(e);
            }
            try {
                ensureIdle(1, false);
            } catch (final Exception e) {
                swallowException(e);
            }
            updateStatsReturn(activeTime);
            return;
        }

        if (!p.deallocate()) {
            throw new IllegalStateException(
                    "Object has already been returned to this pool or is invalid");
        }

        final int maxIdleSave = getMaxIdle();
        if (isClosed() || maxIdleSave > -1 && maxIdleSave <= idleObjects.size()) {
            try {
                destroy(p, DestroyMode.NORMAL);
            } catch (final Exception e) {
                swallowException(e);
            }
            try {
                ensureIdle(1, false);
            } catch (final Exception e) {
                swallowException(e);
            }
        } else {
            if (getLifo()) {
                idleObjects.addFirst(p);
            } else {
                idleObjects.addLast(p);
            }
            if (isClosed()) {
                // Pool closed while object was being added to idle objects.
                // Make sure the returned object is destroyed rather than left
                // in the idle object pool (which would effectively be a leak)
                clear();
            }
        }
        updateStatsReturn(activeTime);
    }

Devolver una conexión es volver a colocar el objeto de conexión en el contenedor del conjunto de conexiones. Al devolver la conexión, si testOnReturn=true, se verifica la validez de la conexión, se destruye directamente el enlace inválido y se asegura al menos una conexión inactiva. Luego juzgue si maxIdle está lleno, destruya la conexión actual si está llena y asegúrese de que haya al menos una conexión inactiva, de lo contrario, la conexión volverá al encabezado de la lista vinculada de acuerdo con lifo=true, de lo contrario será volvió a la cola de la lista enlazada. Aquí, lifo se usa para controlar si la conexión alquilada es la conexión más reciente o la conexión más antigua. Generalmente, la conexión más reciente se usa de forma predeterminada.

desalojar

    public void evict() throws Exception {
        assertOpen();

        if (!idleObjects.isEmpty()) {

            PooledObject<T> underTest = null;
            final EvictionPolicy<T> evictionPolicy = getEvictionPolicy();

            synchronized (evictionLock) {
                final EvictionConfig evictionConfig = new EvictionConfig(
                        getMinEvictableIdleDuration(),
                        getSoftMinEvictableIdleDuration(),
                        getMinIdle());

                final boolean testWhileIdle = getTestWhileIdle();

                for (int i = 0, m = getNumTests(); i < m; i++) {
                    if (evictionIterator == null || !evictionIterator.hasNext()) {
                        evictionIterator = new EvictionIterator(idleObjects);
                    }
                    if (!evictionIterator.hasNext()) {
                        // Pool exhausted, nothing to do here
                        return;
                    }

                    try {
                        underTest = evictionIterator.next();
                    } catch (final NoSuchElementException nsee) {
                        // Object was borrowed in another thread
                        // Don't count this as an eviction test so reduce i;
                        i--;
                        evictionIterator = null;
                        continue;
                    }

                    if (!underTest.startEvictionTest()) {
                        // Object was borrowed in another thread
                        // Don't count this as an eviction test so reduce i;
                        i--;
                        continue;
                    }

                    // User provided eviction policy could throw all sorts of
                    // crazy exceptions. Protect against such an exception
                    // killing the eviction thread.
                    boolean evict;
                    try {
                        evict = evictionPolicy.evict(evictionConfig, underTest,
                                idleObjects.size());
                    } catch (final Throwable t) {
                        // Slightly convoluted as SwallowedExceptionListener
                        // uses Exception rather than Throwable
                        PoolUtils.checkRethrow(t);
                        swallowException(new Exception(t));
                        // Don't evict on error conditions
                        evict = false;
                    }

                    if (evict) {
                        destroy(underTest, DestroyMode.NORMAL);
                        destroyedByEvictorCount.incrementAndGet();
                    } else {
                        if (testWhileIdle) {
                            boolean active = false;
                            try {
                                factory.activateObject(underTest);
                                active = true;
                            } catch (final Exception e) {
                                destroy(underTest, DestroyMode.NORMAL);
                                destroyedByEvictorCount.incrementAndGet();
                            }
                            if (active) {
                                boolean validate = false;
                                Throwable validationThrowable = null;
                                try {
                                    validate = factory.validateObject(underTest);
                                } catch (final Throwable t) {
                                    PoolUtils.checkRethrow(t);
                                    validationThrowable = t;
                                }
                                if (!validate) {
                                    destroy(underTest, DestroyMode.NORMAL);
                                    destroyedByEvictorCount.incrementAndGet();
                                    if (validationThrowable != null) {
                                        if (validationThrowable instanceof RuntimeException) {
                                            throw (RuntimeException) validationThrowable;
                                        }
                                        throw (Error) validationThrowable;
                                    }
                                } else {
                                    try {
                                        factory.passivateObject(underTest);
                                    } catch (final Exception e) {
                                        destroy(underTest, DestroyMode.NORMAL);
                                        destroyedByEvictorCount.incrementAndGet();
                                    }
                                }
                            }
                        }
                        if (!underTest.endEvictionTest(idleObjects)) {
                            // TODO - May need to add code here once additional
                            // states are used
                        }
                    }
                }
            }
        }
        final AbandonedConfig ac = this.abandonedConfig;
        if (ac != null && ac.getRemoveAbandonedOnMaintenance()) {
            removeAbandoned(ac);
        }
    }

El código publicado aquí no es la imagen completa, solo se intercepta la lógica clave. Cuando se inicializa jedisPool, comenzará una programación de subprocesos denominada commons-pool-evictor de acuerdo con la configuración de durationBetweenEvictionRuns.Si durationBetweenEvictionRuns=-1, no se habilitará y toda la lógica de desalojo no se ejecutará, de lo contrario, la lógica de desalojo se ejecutará cada duración entre el tiempo de ejecución de desalojos. numTestsPerEvictionRun controla cuántas conexiones se verifican cada vez. Cada vez que verifique si la conexión actual debe ser expulsada, hay dos estrategias de la siguiente manera, una de las cuales puede ser satisfecha:

  • Política A: el softMinEvictableIdleDuration configurado es menor que el tiempo de inactividad de la conexión actual [y] el minIdle configurado es menor que el número total de conexiones inactivas
  • Estrategia B: el minEvictableIdleDuration configurado es menor que el tiempo de inactividad de la conexión actual

Generalmente, bajo los parámetros predeterminados, la estrategia A no se activará. Es fácil activar la estrategia B. Si la conexión actual es desalojada, continúe con el siguiente juicio de conexión. De lo contrario, si testWhileIdle=true, continúe evaluando la validez de la conexión actual, se destruirá el enlace no válido y se eliminará el conjunto de conexiones. Después de una ronda de detección, se llamará a sureMinIdle() para garantizar la conexión inactiva mínima en el grupo de conexiones.

asegurarMinIdle

    private void ensureIdle(final int idleCount, final boolean always) throws Exception {
        if (idleCount < 1 || isClosed() || (!always && !idleObjects.hasTakeWaiters())) {
            return;
        }

        while (idleObjects.size() < idleCount) {
            final PooledObject<T> p = create();
            if (p == null) {
                // Can't create objects, no reason to think another call to
                // create will work. Give up.
                break;
            }
            if (getLifo()) {
                idleObjects.addFirst(p);
            } else {
                idleObjects.addLast(p);
            }
        }
        if (isClosed()) {
            // Pool closed while object was being added to idle objects.
            // Make sure the returned object is destroyed rather than left
            // in the idle object pool (which would effectively be a leak)
            clear();
        }
    }

asegurarMinIdle eventualmente llamará al método sureIdle(), y el parámetro de entrada idleCount es el minIdle configurado. Aquí, la conexión inactiva actual se comparará con el número de inactividad entrante hasta que la conexión inactiva del conjunto de conexiones alcance el valor de inactividad.

añadirObjeto

    public void addObject() throws Exception {
        assertOpen();
        if (factory == null) {
            throw new IllegalStateException("Cannot add objects without a factory.");
        }
        addIdleObject(create());
    }

Cree una conexión y agréguela al grupo de conexiones. Este método se usa generalmente para la inicialización del grupo de conexiones. Por ejemplo, el grupo de conexiones se precalienta por adelantado.

Epílogo

  • La mayoría de las personas generalmente solo se enfocan en la configuración de la capacidad del grupo de conexiones, ignorando la configuración del ciclo de vida de la conexión.

Incluyéndome a mí, siempre ha habido una percepción errónea de que el grupo de conexiones garantizará el valor minIdle y, siempre que la conexión aplicada actualmente sea menor que el grupo de conexiones minIdle, no se escalará para crear una nueva conexión. No fue hasta que miré el código fuente que descubrí que, siempre que el subproceso de conexión de desalojo esté habilitado, siempre que el tiempo de supervivencia inactivo sea mayor que el valor configurado minEvictableIdleDuration, se expulsará y luego se establecerá una nueva conexión. ser reconstruido de acuerdo con minIdle. Y debido a que minEvictableIdleDuration de jedisPoolConfig tiene un valor predeterminado de 60 s, cuando la configuración minIdle es mucho más grande que la necesidad real, debido a que la estrategia de adquisición de conexión predeterminada es lifo (siempre tomar la conexión más reciente), siempre obtendrá la conexión recién creada, la destruirá y seguir creándolo en ciclo. Cuando el tráfico aumenta repentinamente, es posible que la cantidad de conexiones en minIdle no se mantenga en el conjunto de conexiones, y se debe crear una gran cantidad de conexiones repentinamente, lo que inevitablemente tendrá un cierto impacto en la aplicación. Aquí se recomienda que cuando el subproceso de desalojo esté habilitado, el valor de minEvictableIdleDuration se establezca un poco más grande, o -1 directamente, para que la conexión creada se mantenga activa.

 
{{o.nombre}}
{{m.nombre}}

Supongo que te gusta

Origin my.oschina.net/klblog/blog/5520235
Recomendado
Clasificación