分析「spring-data-redisのredistemplateの大きな穴を使用することを忘れた」

6417 ワード

先日spring-data-redisのソースコード(1.0.1-RELESE)をざっと見たばかりです
 
今日の朝、「spring-data-redisのredistemplateを使うのを忘れた大きな穴」を見ました.http://www.iteye.com/topic/1125295
 
またこの部分についてソースコードを分析し、以下のようにまとめた.
 
 
Spring-data-redisの各種Operations実装クラス,例えばRedisTemplate,DefaultSetOperations,DefaultListOperationsなど,Redisコマンドのクローズドは以下のような構造で呼び出される.
 
RedisTemplateのhasKey
 
 
	public Boolean hasKey(K key) {
		final byte[] rawKey = rawKey(key);

		return execute(new RedisCallback() {
			
			public Boolean doInRedis(RedisConnection connection) {
				return connection.exists(rawKey);
			}
		}, true);
	}

 
戻りタイプを指定するRedisCallbackインタフェースを実装するには、関数doInRedisが1つしかありません.
doInRedisのパラメータはRedisConnectionインタフェースであり、spring-data-redisは非Redis Java Clientに対して対応するRedisConnectionを実現している.
JedisConnection
JredisConnection
RjcConnection
SrpConnection
目的は異なるClientのAPIを統一してアダプターと見なすことでしょう.
 
executeこれはテンプレート関数で、主に接続の問題を処理します.
Spring-data-redisは、非Redis Java Clientに対しても対応するRedisConnectionFactoryを実現し、接続を取得します. 
 
例えばJedisConnectionFactory内部ではJedisPoolによる接続工場が実現されている.
 
executeテンプレート関数ソース:
	public  T execute(RedisCallback action, boolean exposeConnection, boolean pipeline) {
		Assert.notNull(action, "Callback object must not be null");

		RedisConnectionFactory factory = getConnectionFactory();
		RedisConnection conn = RedisConnectionUtils.getConnection(factory);

		boolean existingConnection = TransactionSynchronizationManager.hasResource(factory);
		preProcessConnection(conn, existingConnection);

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

		try {
			RedisConnection connToExpose = (exposeConnection ? conn : createRedisConnectionProxy(conn));
			T result = action.doInRedis(connToExpose);

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

			// TODO: any other connection processing?
			return postProcessResult(result, conn, existingConnection);
		} finally {
			RedisConnectionUtils.releaseConnection(conn, factory);
		}
	}

 
通常、RedisCallbackでdoInRedisが取得したRedisConnectionは毎回新しい
 
「このtemplateが毎回新しい接続を生成する以上、このmultiとexecコマンドには卵があるのではないでしょうか. ”
 
RedisTemplateを使うと確かにこの質問がありますが、著者の公式回答に対する理解には違いがあります.
 
The methods are exposed in case the connection is shared across methods. Currently we don't provide any out of the box support for connection binding but the RedisTemplate supports it - just like with the rest of the templates, one connection could be bound to the running thread and the RT will use it without creating new ones. 
 
回答ではRedisTemplateが接続バインドをサポートしていないことは確かに認められたが、後半では「他のテンプレートクラスでは、接続は現在のスレッドにバインドでき、RedisTemplateはこの接続を使用し、再作成しない」と述べた.
 
OK! 次にソースコードを分析し、このバインドがどのように実現されているかを見てみましょう.
 
org.springframework.data.redis.support.collections.AbstractRedisCollectionはspring-data-redis対redisのいくつかの集合型のデータ構造パッケージクラスの抽象クラスである
分析“备忘使用spring-data-redis中的redistemplate的一个大坑”_第1张图片
 
中にはrenameという関数があります
 
 
	public void rename(final String newKey) {
		CollectionUtils.rename(key, newKey, operations);
		key = newKey;
	}

しかし、その実現は内部保有のRedisOperationsではなくCollectionUtilsである.では、このCollectionUtilsを見てみましょう.rename
 
このrenameはトランザクションによってrename操作が保証される場合、元のkeyは必ず存在する
 
	static  void rename(final K key, final K newKey, RedisOperations operations) {
		operations.execute(new SessionCallback() {
			@SuppressWarnings("unchecked")
			
			public Object execute(RedisOperations operations) throws DataAccessException {
				do {
					operations.watch(key);

					if (operations.hasKey(key)) {
						operations.multi();
						operations.rename(key, newKey);
					}
					else {
						operations.multi();
					}
				} while (operations.exec() == null);
				return null;
			}
		});
	}

これは静的関数であり,Redisに対する操作は伝達されたRedisOperationsによって達成される.
renameはRedisOperationsのexecuteを呼び出し、匿名のコールバックインタフェースに転送され、具体的な操作を実現します.これは前述したexecuteテンプレートと同じように見えます.しかし、ここのRedisOperationsはなぜwatchとmultiを使うことができますか?前にRedisTemplateは直接watchとmultiができないと言ったのではないでしょうか.(RedisOperationsはRedisTemplateのインタフェースであり、異なるデータ構造に対する操作クラスや集合はRedisTemplateから生成される、つまりRedisTemplateによってRedisが操作される)
 
実はこのexecuteテンプレートは前のものとは違いますが、ここのパラメータはSessionCallbackで、前のものはRedisCallbackです.名前から分かるように、この中のexecute呼び出しはセッション、つまり接続を維持しているのです.
 
ここのexecuteソースを見てください
 
 
	public  T execute(SessionCallback session) {
		RedisConnectionFactory factory = getConnectionFactory();
		// bind connection
		RedisConnectionUtils.bindConnection(factory);
		try {
			return session.execute(this);
		} finally {
			RedisConnectionUtils.unbindConnection(factory);
		}
	}
 
 
あまり説明する必要はないでしょうが、SessionCallbackの実装を呼び出して具体的な操作を行う前後に、接続をバインドして解縛しました.
 
そしてセッションでexecuteでoperationsが呼び出されます.watch(key); 操作を待つと、これらの操作は前の分析のRedisTemplateのhasKeyの流れと同じで、前の分析のexecuteを呼び出して操作します.
 
前に戻ってソースコードを見て、そこのRedisConnectionはRedisConnectionUtilsで取りました.
 
RedisConnection conn = RedisConnectionUtils.getConnection(factory);

 
接続バインド解除もRedisConnectionUtilsで行われています.ここではTransactionSynchronizationManagerで現在のスレッドに接続をバインドしています.springのDBトランザクション管理と同じです.ここでは詳しく分析しません(主にThreadLocalを使用します).
 
このような分析ではspring-data-redisはwatchとmultiを使用することができ,肝心なのはどのように使用するかの問題である. 
 
まとめ:
 
RedisTemplateとその他の特定のタイプの操作クラスは、主に基本的な操作機能を実現しており、一部の高度な機能(上のrenameやRedisAtomicIntegerなどの高度な操作)もある.
 
SessionCallbackによって接続をバインドする操作クラスを取得することができ、その上の操作はすべて1つの接続にあり、高度な機能を実現することができます.
 
spring-data-redisでwatchとmultiを用いる、ソースコードのCollectionUtilsを参照することができる.rename
 
Spring-data-redisにも確かに問題があります.このブログを参照してください.http://ldd600.iteye.com/blog/1115196
 
私はプロジェクトの中で今でもJedisを直接使っています.そのソースコードはよく書けています.http://jimgreat.iteye.com/blog/1586671