文档章节

redisson的理解和使用-调用流程

果子核
 果子核
发布于 2017/06/05 10:05
字数 2339
阅读 91
收藏 1
点赞 0
评论 0

 

redisson是一个用于连接redis的java客户端工作,相对于jedis,是一个采用异步模型,大量使用netty promise编程的客户端框架。

0     代码示例

//创建配置信息
        Config config = new Config();
        config.useSingleServer().setAddress("localhost:6379").setConnectionPoolSize(5);

        Redisson redisson = Redisson.create(config);

        //测试 list
        List<String> strList = redisson.getList("strList");
        strList.clear(); //清除所有数据
        strList.add("测试中文1");
        strList.add("test2");

        redisson.shutdown();

从代码上来看,其基本的使用非常简单,在最后的使用当中。除与redisson打交道之外(获取各种数据结构),完全感觉不到与redis的信息连接。甚至于返回于上层直接不需要考虑下层的实现,一切均由redisson进行了封装。

1     创建初始连接结构

1.1     配置信息config

config信息由4种不同的连接配置和其它属性进行组合,如下所示:

private SentinelServersConfig sentinelServersConfig;//基本哨兵server的配置
    private MasterSlaveServersConfig masterSlaveServersConfig;//基于主从的配置
    private SingleServerConfig singleServerConfig;//基于单台连接的配置
    private ClusterServersConfig clusterServersConfig;//基于集群的配置

    //这里即在整个命令处理过程中,总共的线程数,可以理解为所有的底层运行都是由这些线程来运行(而不是用户线程)
    private int threads = 0; // 0 = current_processors_amount * 2 

    private RedissonCodec codec;//用于实现对象编码的实现(从object->byte[],或byte[]->object) ,默认为jackson

针对上面的4种config,又是基于同一个继承体系,如下所示:

如上所示,在baseConfig中定义了基本的属性,然后除单机器配置之外,其它的都基于一个主从的配置,类似多节点地址的配置信息。在后面的实现中,我们将看到更多这种设计,即通过继承来完成多种不同场景的实现。

然后,通过 Redisson.create来完成一个类似于client的创建,可以认为创建的对象是一个单例。在后面的调用中,都使用此实例即可。其内部结构如下所示:

public class Redisson implements RedissonClient {

    private final ConnectionManager connectionManager;
    private final Config config;
}

其中config即刚才我们定义的配置对象,而connectionManager即可以理解为通过刚才的不同类型的连接配置实现了不同的连接管理器,与配置信息一致,相对象的连接管理器也是按照继承体系来实现的,如下所示:

上面的实现与配置一致,不过这里采用了主从管理的基本实现,其它实现通过override相应的方法来提供不同的子实现。其中单机实现,可以理解为没有slaver的连接处理。连接管理器,实现了连接信息管理的机制,并且实现了如何选择合适的连接对象来执行不同的操作的语义。即向外封装了connection的调用,入口都通过connectionManager来处理。正因为redisson向外提供的是数据结构语义,因此也不需要暴露connection信息,因此这种实现是值得的。

connectionManager的定义如下所示:

//获取相应值,一个封装调用
	<V> V get(Future<V> future);
/** --------------------- 基本同步的操作实现,不同的调用方法 最终都返回具体的值 start -------------- */
	<V, R> R read(String key, SyncOperation<V, R> operation);
	<V, R> R read(SyncOperation<V, R> operation);
	<V, R> R write(String key, SyncOperation<V, R> operation);
	<V, R> R write(SyncOperation<V, R> operation);
	<V, R> R write(String key, AsyncOperation<V, R> asyncOperation);
	<V, R> R write(AsyncOperation<V, R> asyncOperation);
	<V, T> Future<T> writeAllAsync(AsyncOperation<V, T> asyncOperation);
	<V, T> T read(String key, AsyncOperation<V, T> asyncOperation);
	<V, T> T read(AsyncOperation<V, T> asyncOperation);
/** --------------------- 基本同步的操作实现,不同的调用方法 最终都返回具体的值 end -------------- */
/** --------------------- 基本异步的操作实现,返回future 而业务自行决定如何处理 start -------------- */
	<V, T> Future<T> readAsync(String key, AsyncOperation<V, T> asyncOperation);
	<V, T> Future<T> readAsync(AsyncOperation<V, T> asyncOperation);
	<V, T> Future<T> writeAsync(String key, AsyncOperation<V, T> asyncOperation);
	<V, T> Future<T> writeAsync(AsyncOperation<V, T> asyncOperation);
/** --------------------- 基本异步的操作实现,返回future 而业务自行决定如何处理 end -------------- */
/** 创建一个新的连接对象(用于读操作),即实现连接池语义,写操作由子类具体实现 */
	<K, V> RedisConnection<K, V> connectionReadOp(int slot);

2     创建数据连接

创建连接根据当前操作是读还是写来进行,因为如果是读操作,可以通过主从由从机器进行连接。这里仅考虑创建写操作的连接,其在主机器上进行。因此,我们直接查看相应的实现。

protected <K, V> RedisConnection<K, V> connectionWriteOp(int slot) {
        return getEntry(slot).connectionWriteOp();
    }

转交由entry,即masterSlaveEntry来处理,entry可以理解为一个具体的表示单个机器的连接配置对象。

public <K, V> RedisConnection<K, V> connectionWriteOp() {
//这里通过计数信号量来简单进行判定连接数是否已达上线,如果已达上线,则强制阻塞当前线程
	acquireMasterConnection();
//从已创建好的队列中获取,如未创建,则进行创建
	RedisConnection<K, V> conn = masterEntry.getConnections().poll();
	if (conn != null) {
	    return conn;
	}
//具体的创建过程
	    conn = masterEntry.getClient().connect(codec);
......
	    return conn;
    }

从具体的connect方法来看,即创建一个 RedisAsyncConnection 对象,通过注册入连接远程端口的channel中,通过相应的active事件完成对channel的绑定。后续的操作均通过对channel发送事件来完成。具体的代码如下所示:

connect = bootstrap.handler(new ChannelInitializer<Channel>() {
                    @Override
                    protected void initChannel(Channel ch) throws Exception {
                        ch.pipeline().addLast(watchdog, handler, connection);
                    }
                }).connect();
            }
            connect.sync();

从上可以看出,在连接的时候指定了3个处理器,即IN OUT的处理器

watchlog负责处理连接断开的时候是否进行重连

handler负责处理最终进行命令的底层发送和接收

connection则负责进行命令的转发,包括各项对redis命令的封装调用。可以理解为handler工作在网络层,而connection则工作在应用层。

3     发送调用命令

3.1     发送的准备操作

如在redissonList中的size操作,即通过调用connection的dispatch命令来操作。如下所示:

public synchronized <T> Promise<T> dispatch(CommandType type, CommandOutput<K, V, T> output, CommandArgs<K, V> args)

type表示指定的指令,如LLEN

output表示在数据返回之后,由commandHandler将相应的结果反序列化入output中

args即表示指令具体的参数

因为在output数据返回之后,需要通知到外转的信息,因此需要有一个相对应的promise,其被作为listner加入到execute(connection)中的结果回调当中。

3.2     数据命令发送

相应的代码在connection中的dispatch中,主要由如下代码构成:

Command<K, V, T> cmd = new Command<K, V, T>(type, output, args, multi != null, promise);
        queue.put(cmd);
        channel.writeAndFlush(cmd);

然后,这里需要由commandHander来负责具体的数据发送操作(因为,它是注册了channel的相应处理器), 具体代码如下所示:

public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
	Command<?, ?, ?> cmd = (Command<?, ?, ?>) msg;
	ByteBuf buf = ctx.alloc().heapBuffer();
	cmd.encode(buf);
	ctx.write(buf, promise);
}

3.3     数据结果的解析

接下来的操作,由commandHandler来接收相应的数据,如下所示:

//由channelRead转向decode
protected void decode(ChannelHandlerContext ctx, ByteBuf buffer) throws InterruptedException {
    while (true) {
	Command<K, V, ?> cmd = queue.peek();
	if (cmd == null
		|| !rsm.decode(buffer, cmd.getOutput())) {
	    break;
	}
	cmd = queue.take();
	cmd.complete();
    }
}

这里,因为在发送命令时,将每个命令相应的顺序入queue,因此在结果时,也是按照queue相对应的结果对应起来。在这里,直接就认为最先的结果对应于最先的cmd。通过rsm.decode,负责将相应buffer的结果反序列化output中,最后调用cmd.complete来通知进一步的promise。如下所示:

public void complete() {
	completeAmount--;
	if (completeAmount == 0) {
		Object res = output.get();
		...... 略去错误处理
			promise.setSuccess((T)res);
		}
	}
}

3.4     数据结构的反向处理

在上一步的处理中,当相应cmd的complete的处理中,之前传递的promise被设置了result,因此在其上绑定的promise也会进一步设置数据值。如果在最上面,我们使用如下的代码时:

mainPromise.awaitUninterruptibly().getNow()

就会最终获取到相应的结果,其值最终体现在业务代码中。

4     与netty的整合

在上面,我们可以看到,整个redisson和netty的协议进行了完全的整合,包括handler的介入,eventGroup的使用等,都使用了全套的netty体系。然后,由于netty提供了promise功能,这里也大量使用了相应的异步模型来进行数据处理。

5     总结

在redisson中,各个部分均采用了最新的一些技术栈,包括java 5线程语义,Promise编程语义,在技术的学习上有很高的学习意义。相比jedis,其支持的特性并不是很高,但对于日常的使用还是没有问题的。其对集合的封装,编解码的处理,都达到了一个开箱即用的目的。相比jedis,仅完成了一个基本的redis网络实现,可以理解为redisson是一个完整的框架,而jedis即完成了语言层的适配。其次,redisson在设计模式,以及编码上,都有完整的测试示例,代码可读性也非常好,很值得进行源码级学习。

如果在项目中已经使用了netty,那么如果需要集成redis,那么使用redisson是最好的选择了,都不需要另外增加依赖信息。

本文转载自:  http://www.iflym.com/index.php/code/201503290001.html

共有 人打赏支持
果子核
粉丝 2
博文 39
码字总数 10055
作品 0
海淀
程序员
redisson实现分布式锁原理

Redisson分布式锁 之前的基于注解的锁有一种锁是基本redis的分布式锁,锁的实现我是基于redisson组件提供的RLock,这篇来看看redisson是如何实现锁的。 不同版本实现锁的机制并不相同 引用的...

技术小阿哥
2017/11/27
0
0
Redisson源码赏析 - 简介

转自官方wiki概述 Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。其中包括(, , , ...

3jin
01/05
0
0
Redis客户端redisson实战

redis 学习问题总结 http://aperise.iteye.com/blog/2310639 ehcache memcached redis 缓存技术总结 http://aperise.iteye.com/blog/2296219 redis-stat 离线安装 http://aperise.iteye.com......

哲别0
05/21
0
0
Redisson项目介绍

Redisson项目介绍 Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid)。充分的利用了Redis键值数据库提供的一系列优势,基于Java实用工具包中常用接口,为使用者提...

jackygurui
2016/12/08
698
2
程序对批量数据写入数据库的优化--引入Redis并通过定时器来触发

没错,还是上篇文章提到的那个SpringMVC+Mybatis的项目,在客户调我方接口,疯狂的给我们insert数据的时候,应该想到一些优化方案,于是Redis就被引用了。 关于Redis的客户端,服务端的一些用...

Pig-man
2016/03/21
88
0
Redis分布式锁全局锁(悲观锁,Redisson实现)

前因 以前实现过一个Redis实现的全局锁, 虽然能用, 但是感觉很不完善, 不可重入, 参数太多等等. 最近看到了一个新的Redis客户端Redisson, 看了下源码, 发现了一个比较好的锁实现RLock, 于是记...

yiqifendou
2016/10/08
102
0
tomcat8 redis session共享

备注:项目需要由原本的单机扩容到多机服务,历史原因项目里使用session传参的地方比较多。每一处去修改session,改动量较大,回归测试麻烦,也太耗时间,于是想到用redis存储session的方案。...

阿弥陀佛!
05/29
0
0
Redisson 2.2.11 发布,Redis 客户端

Redisson 2.2.11 发布了。改进日志: Feature - new object added Feature - new object added Feature - travis-ci integration (thanks to jackygurui) Improvement - & methods optimizat......

淡漠悠然
2016/04/05
851
5
Redis实现分布式锁全局锁—Redis客户端Redisson中分布式锁RLock实现

前因 以前实现过一个Redis实现的全局锁, 虽然能用, 但是感觉很不完善, 不可重入, 参数太多等等. 最近看到了一个新的Redis客户端Redisson, 看了下源码, 发现了一个比较好的锁实现RLock, 于是记...

德胜
2015/06/22
0
7
Redisson 2.2.5 发布,Redis 客户端

Redisson 2.2.5 发布,此版本更新内容如下: Feature - new object added Feature - new object added Feature - , , and methods added to Feature - and methods added Feature - method ......

oschina
2016/01/11
1K
2

没有更多内容

加载失败,请刷新页面

加载更多

下一页

TextView设置行间距、字体间距

一、设置行间距 1、设置行间距:android:lineSpacingExtra,取值范围:正数、负数和0,正数表示增加相应的大小,负数表示减少相应的大小,0表示无变化 2、设置行间距的倍数:android:lineSpa...

王先森oO
10分钟前
0
0
适配器模式

适配器模式(Adapter):将一个类的接口转换成客户端希望的另外一个接口,适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 适配器用于连接两种不同种类的对象,使其毫...

阿元
10分钟前
0
0
CoreText进阶(四)-文字行数限制和显示更多

CoreText进阶(四)-文字行数限制和显示更多 用例和效果 Demo:CoreTextDemo 效果图: 默认的截断标识和自定义的截断标识符效果图  点击查看更多之后的效果图  为了可以设置显示的行数以...

aron1992
13分钟前
0
0
nginx的五种负载算法

nginx的五种负载算法 2017年04月26日 15:01:11 阅读数:1297 1.round robin(默认) 轮询方式,依次将请求分配到各个后台服务器中,默认的负载均衡方式。 适用于后台机器性能一致的情况。 挂...

linjin200
15分钟前
0
0
Android RecyclerView快速上手

RecyclerView mainMenu = findViewById(R.id.fragmentMain); mainMenu.setLayoutManager(new GridLayoutManager(getActivity(),4)); mainMenu.setAdapter(new MainAdapter......

燕归南
17分钟前
0
0
RabbitMQ实战:理解消息通信 

应用RabbitMQ的5种队列 一、简单队列 P:消息的生产者 C:消息的消费者 红色:队列 简单队列的生产者和消费者关系一对一 但有时我们的需求,需要一个生产者,对应多个消费者,那就可以采用第...

spinachgit
18分钟前
0
0
Linux的使用技巧:到底要不要会用?[图]

Linux的使用技巧:到底要不要会用?[图] 最近有个项目接近了尾声,要进入到调试测试阶段。这是一个使用Springboot框架为后台程序,mpvue构建的小程序项目。服务器我最终仍旧选择了Linux操作系...

原创小博客
19分钟前
0
0
记elasticdump 备份数据导出导入

版本: elasticsearch 5.5.2 elasticdump 2.2 系统 CentOS7.3 因项目需求 从生产导出一份索引到测试 帮助文档 https://github.com/taskrabbit/elasticsearch-dump?utm_source=dbweekly&utm_m......

雁南飞丶
20分钟前
0
0
saltstack配置目录管理

1.服务端配置 -接着编辑之前的 top.sls 文件 #vim /srv/salt/top.sls //修改为如下 base: 'slaver.test.com': - filedir -新建 filedir.sls 文件 # vim /srv/salt/filedir.sls file-dir: fi......

硅谷课堂
21分钟前
0
0
python日期时间

日期和时间 Python内建的datetime模块提供了datetime、date和time类型。datetime类型结合了date和time,是最常使用的: In [102]: from datetime import datetime, date, timeIn [103]:...

火力全開
27分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部