`
jimgreat
  • 浏览: 132180 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

分析“备忘使用spring-data-redis中的redistemplate的一个大坑”

阅读更多

前几天刚刚粗略看了一下spring-data-redis的源码 (1.0.1-RELEASE)

 

今天一早看到了 “备忘使用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<Boolean>() {
			
			public Boolean doInRedis(RedisConnection connection) {
				return connection.exists(rawKey);
			}
		}, true);
	}

 

要实现一个指定返回类型的RedisCallback接口,这个接口只有一个函数doInRedis

doInRedis的参数是RedisConnection接口,spring-data-redis针对不的Redis Java Client都有对应的 RedisConnection实现:

JedisConnection

JredisConnection

RjcConnection

SrpConnection

目的就是将不同Client的API统一了一下,可以看成是Adapter吧 。

 

execute这个是模板函数,主是处理连接的问题。

spring-data-redis 针对不的 Redis Java Client 也都实现了相应的 RedisConnectionFactory,来获取连接。  

 

比如 JedisConnectionFactory 内部是通过 JedisPool 来实现连接工厂。

 

execute模板函数源码:

	public <T> T execute(RedisCallback<T> 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的几个集合类型的数据结构封装类的抽象类


 

其中有 rename 这个函数

 

 

	public void rename(final String newKey) {
		CollectionUtils.rename(key, newKey, operations);
		key = newKey;
	}

但它的实现并没有使用内部持有的RedisOperations,而是CollectionUtils.rename,那我们就来看一个这个CollectionUtils.rename

 

这个rename是通过事务来保证进行rename操作时,原key一定存在

 

	static <K> void rename(final K key, final K newKey, RedisOperations<K, ?> operations) {
		operations.execute(new SessionCallback<Object>() {
			@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> T execute(SessionCallback<T> session) {
		RedisConnectionFactory factory = getConnectionFactory();
		// bind connection
		RedisConnectionUtils.bindConnection(factory);
		try {
			return session.execute(this);
		} finally {
			RedisConnectionUtils.unbindConnection(factory);
		}
	}
 

 

不用多解释了吧,在调用 SessionCallback 的实现进行具体操作前后,对连接进行了绑定和解绑。

 

然后在session.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 是可以获取到绑定连接的操作类的,它上面的操作都是在一个连接上的,这样可以实现高级功能。

 

在spring-data-redis中使用watch和multi,可以参照源码中的CollectionUtils.rename

 

spring-data-redis也确实有些问题,参看这篇博文 http://ldd600.iteye.com/blog/1115196

 

我在项目中目前还是直接使用Jedis,它的源码写得很好 http://jimgreat.iteye.com/blog/1586671

 

 

 

  • 大小: 11.8 KB
分享到:
评论
9 楼 yanhuang12 2017-09-27  
看了博主这篇文章,特意去读了一下spring-data-redis的源码,发现新版本已经解决了这个问题,新版本的RedisTemplate新增了enableTransactionSupport参数,用于对事务的支持,该参数为true时,会调用RedisConnectionUtils的bindConnection方法,这个方法除了获取连接外,还会给connectionHolder设置一个transactionActive属性,当调用releaseConnection时会判断transactionActive是否为true,如果是的话就不会释放,事务管理器那没太追,但是估计也是提交或者回滚以后会设置为false,也就允许释放了吧
8 楼 jewelknife 2016-05-10  
你这文章已经很久了, 我有必要回复一下官网地址,以免误导一些简单搜索的人

http://docs.spring.io/spring-data/redis/docs/1.7.1.RELEASE/reference/html/#tx
7 楼 elam 2013-12-06  
javasoftlover 写道
jimgreat 写道
javasoftlover 写道
我用spring-data-redis 成功的set了 而且也能成功get出来对应的数据~
但是用redis-cli去服务器直接get数据是提示(nil) 这是什么原因啊?


正常是get一个不存在的key才会这样的


但是我用的是同一个key啊···通过spring-data-redis就可以成功get出来~~
如果我直接用jedis存的话就没有这样的问题·· 大侠解惑啊····


默认的Key序列化不是String
所以不行
6 楼 overman 2012-10-17  
redis 事务的分析 watch,multi,exec

http://stackoverflow.com/questions/10750626/transactions-and-watch-statement-in-redis
5 楼 jimgreat 2012-08-07  
javasoftlover 写道
jimgreat 写道
javasoftlover 写道
我用spring-data-redis 成功的set了 而且也能成功get出来对应的数据~
但是用redis-cli去服务器直接get数据是提示(nil) 这是什么原因啊?


正常是get一个不存在的key才会这样的


但是我用的是同一个key啊···通过spring-data-redis就可以成功get出来~~
如果我直接用jedis存的话就没有这样的问题·· 大侠解惑啊····


我在项目中都是直接用jedis
突然想起spring-data-redis 对 key 和 value 都进行了序列化 变成byte[] 再调用对应的redis java client进行存储的。  那应该就是通过spring-data-redis进入redis的key变了
4 楼 javasoftlover 2012-08-07  
jimgreat 写道
javasoftlover 写道
我用spring-data-redis 成功的set了 而且也能成功get出来对应的数据~
但是用redis-cli去服务器直接get数据是提示(nil) 这是什么原因啊?


正常是get一个不存在的key才会这样的


但是我用的是同一个key啊···通过spring-data-redis就可以成功get出来~~
如果我直接用jedis存的话就没有这样的问题·· 大侠解惑啊····
3 楼 jimgreat 2012-08-07  
javasoftlover 写道
我用spring-data-redis 成功的set了 而且也能成功get出来对应的数据~
但是用redis-cli去服务器直接get数据是提示(nil) 这是什么原因啊?


正常是get一个不存在的key才会这样的
2 楼 javasoftlover 2012-08-07  
我用spring-data-redis 成功的set了 而且也能成功get出来对应的数据~
但是用redis-cli去服务器直接get数据是提示(nil) 这是什么原因啊?
1 楼 Surlymo 2012-07-17  
赞,学习了~~

相关推荐

Global site tag (gtag.js) - Google Analytics