Spring Data Redis 使用连接池被占满

2 minute read

本篇博客仅描述本人使用 Spring Data Redis 过程中遇到的一些坑,关于什么是 Spring Data Redis 以及怎么使用在这里不做赘述。

前段时间公司的项目开始使用 Redis 作为缓存。有一天,同事告诉我连接池连接被占满造成后续请求阻塞,但是具体业务请求数量并不是很大,让我有空帮忙查一下是什么问题造成的。不管怎样,刷一下源代码先。

Spring Data Redis 提供一个操作类 RedisTemplate 给外部操作 Redis。日常的操作最终都是执行其类中的 execute 方法。

@Nullable
public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) {

   	Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it");
   	Assert.notNull(action, "Callback object must not be null");

   	RedisConnectionFactory factory = getRequiredConnectionFactory();
   	RedisConnection conn = null;
	try {

	   	if (enableTransactionSupport) {
	   		// only bind resources in case of potential transaction synchronization
            // 绑定一个连接到当前线程
	   		conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);
	   	} else {
	   		conn = RedisConnectionUtils.getConnection(factory);
	   	}

			boolean existingConnection = TransactionSynchronizationManager.hasResource(factory);

			RedisConnection connToUse = preProcessConnection(conn, existingConnection);

			boolean pipelineStatus = connToUse.isPipelined();
			if (pipeline && !pipelineStatus) {
				connToUse.openPipeline();
			}

			RedisConnection connToExpose = (exposeConnection ? connToUse : createRedisConnectionProxy(connToUse));
			T result = action.doInRedis(connToExpose);

			// close pipeline
			if (pipeline && !pipelineStatus) {
				connToUse.closePipeline();
			}

			// TODO: any other connection processing?
			return postProcessResult(result, connToUse, existingConnection);
		} finally {
            // finally 关键字限定 try catch 最终执行的语句
			RedisConnectionUtils.releaseConnection(conn, factory, enableTransactionSupport);
		}
	}

注意 enableTransactionSupport 在项目中我们开启了事务,所以将会运行 conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport); 绑定一个连接到当前线程,具体看看是如何绑定的。RedisConnectionUtils.bindConnection() 内部是调用 doGetConnection() 方法。

public static RedisConnection doGetConnection(RedisConnectionFactory factory, boolean allowCreate, boolean bind,
			boolean enableTransactionSupport) {

	Assert.notNull(factory, "No RedisConnectionFactory specified");

    // 先查看当前线程是否已经绑定连接
	RedisConnectionHolder connHolder = (RedisConnectionHolder) TransactionSynchronizationManager.getResource(factory);

	if (connHolder != null) {
	   	if (enableTransactionSupport) {
	   		potentiallyRegisterTransactionSynchronisation(connHolder, factory);
	   	}
			return connHolder.getConnection();
		}

		if (!allowCreate) {
			throw new IllegalArgumentException("No connection found and allowCreate = false");
		}

		if (log.isDebugEnabled()) {
			log.debug("Opening RedisConnection");
		}

        // 没有的话就新建一个连接
		RedisConnection conn = factory.getConnection();

		if (bind) {

			RedisConnection connectionToBind = conn;
			if (enableTransactionSupport && isActualNonReadonlyTransactionActive()) {
				connectionToBind = createConnectionProxy(conn, factory);
			}

			connHolder = new RedisConnectionHolder(connectionToBind);

           // 然后再绑定到当前线程
			TransactionSynchronizationManager.bindResource(factory, connHolder);
			if (enableTransactionSupport) {
				potentiallyRegisterTransactionSynchronisation(connHolder, factory);
			}

            // 返回绑定的连接
			return connHolder.getConnection();
		}

		return conn;
	}

以上就是如何获取一个连接的过程。然后会进行我们的读写操作,之后再释放连接,让我们看看是如何释放连接的吧。 释放连接调用的是 RedisConnectionUtils.releaseConnection 方法。

public static void releaseConnection(@Nullable RedisConnection conn, RedisConnectionFactory factory,
			boolean transactionSupport) {

		if (conn == null) {
			return;
		}

    // 获取与当前线程绑定的连接
		RedisConnectionHolder connHolder = (RedisConnectionHolder) TransactionSynchronizationManager.getResource(factory);

    // 没有使用 @Transactional 注解,所以 connHolder.isTransactionSynchronisationActive() 返回 false。
		if (connHolder != null && connHolder.isTransactionSyncronisationActive()) {
			if (log.isDebugEnabled()) {
				log.debug("Redis Connection will be closed when transaction finished.");
			}
			return;
		}

    // 判断是否和当前线程绑定的连接是同一个连接
		if (isConnectionTransactional(conn, factory)) {

			// release transactional/read-only and non-transactional/non-bound connections.
			// transactional connections for read-only transactions get no synchronizer registered
            // 没有使用 @Transactional 注解,这个判断没过
			if (transactionSupport && TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
				if (log.isDebugEnabled()) {
					log.debug("Unbinding Redis Connection.");
				}
				unbindConnection(factory);
			} else {

				// Not participating in transaction management.
				// Connection could have been attached via session callback.
				if (log.isDebugEnabled()) {
					log.debug("Leaving bound Redis Connection attached.");
				}
			}

		} else {
			if (log.isDebugEnabled()) {
				log.debug("Closing Redis Connection.");
			}
			conn.close();
		}
	}

可以看到,当 RedisTemplate 打开事务支持(enableTransactionSupport=true)的时候,执行 Redis 操作的方法如果没有添加 @Transactional 则连接不会被释放,一直绑定到到当前线程

Categories:

Updated:

Comments