设为首页 收藏本站
查看: 925|回复: 0

[经验分享] 分析“备忘使用spring-data-redis中的redistemplate的一个大坑”

[复制链接]

尚未签到

发表于 2016-12-21 07:59:49 | 显示全部楼层 |阅读模式
  前几天刚刚粗略看了一下spring-data-redis的源码 (1.0.1-RELEASE)
  今天一早看到了 “备忘使用spring-data-redis中的redistemplate的一个大坑” http://www.iyunv.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的几个集合类型的数据结构封装类的抽象类
  
DSC0000.jpg
  其中有 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.iyunv.com/blog/1115196
  我在项目中目前还是直接使用Jedis,它的源码写得很好 http://jimgreat.iyunv.com/blog/1586671

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-317084-1-1.html 上篇帖子: 太感动了,redis,你是我的救星 下篇帖子: Redis高级使用特性之服务器消息订阅模式详解及相关设置
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表