异步请求入门,使用DeferredResult,结合Redis发
2018-03-27 本文已影响724人
在路上phper
上两节课我们做了两个非常简单的方式 实现异步请求 并且做个简单的页面(test1.html) 测试异步提交和同步提交的区别
这节课我们学习异步请求的第三种方式
上两节课学到了
这节课学下
image.png
这节课我们首先看下DeferredResult如何使用
来到官网https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-async-deferredresult
image.png
官方给我们一段示例如上
上节课我们做的异步代码 返回的是Callable
通过官网可知使用DeferredResult处理异步过程 要返回DeferredResult 并且实例化一个DeferredResult
然后可以在其他线程里对实例化的deferredResult 结果进行赋值
下面我们看下其他线程如何配合 如果我们写些模拟代码 上节课我们通过线程的等待来做些模拟代码(这看起来没什么意义)
所以这节课我们结合实际场景 看下如何使用
image.png
网站和我们的消息队列(这节课使用redis演示下,使用它来进行交互和我们数据处理的基本流程)
首先按照上图编号来看
有个做的网站A(当用户提交数据过来时假设并发很高,那么一般来说会把数据放入消息队列),在外部一定有个消费者程序(死循环程序)部署在其他服务器上 专门来对消息队列进行读取 读取到消息后,就进行处理(更新数据库做些统计更新等等)做完之后把处理好的结果放入消息队列 这个时候网站本身也是个消费者(专门有线程处理)那么这个网站 比如用户提交一个信息后,它需要到消息队列看下是否完成 如果没有完成 会等待(可以设置一定的超时时间)如果完成了 就把数据返回给我们的请求线程
下面我们做过案例(结合redis理解上面的过程)spring和redis或其他类库进行交互调用
在NewsController中添加官方的DeferredResult使用代码如下
image.png
修改下test1.html
image.png
添加如上(存在localhost:8999/test1.html)
编译发布代码
浏览器访问http://localhost:8999/test1.html
image.png
点击async_submit(Deffered)按钮 (异步提交)
结果如下
image.png
从上面结果好像看不出任何异步的过程
下面我们进入今天重点内容
首先看下Spring如何和redis交互
image.png
我们来到https://projects.spring.io/spring-data-redis/官网(Spring封装好的操作redis的库 Spring data redis)
其内部包含个Jedis库 来到Jedis的github地址https://github.com/xetorthio/jedis 它可以赤裸裸的连接redis 但是一些方法包括一些函数分类并没有Spring data redis分的比较业务化
我们这节课用下Spring data redis
下面看下使用步骤
根据官方提示
image.png
首先在pom.xml中加入依赖
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.0.5.RELEASE</version>
</dependency>
接下来来到Spring data redis 详细文档 查看下 使用所需依赖https://docs.spring.io/spring-data/redis/docs/2.0.5.RELEASE/reference/html/
点击目录的Requirements
image.png
image.png
可以看见对JDK和Spring framework等有一定的要求
因为redis里面使用到了连接池 并且依赖jedis库
下面加入这两个依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
配置好了之后
我们对redis里面订阅发布功能(就是使用redis来完成一个简单的消息队列)
redis官方里有个Pub/Sub(发布/订阅功能)
官方说明如下
image.png
下面我们来模拟如下需求
image.png
比如我们通过发布 在客户端可以订阅这个消息 订阅好之后 如果我们一旦有新消息 那么我们多客户端都可以同时得到这个消息来对它进行处理
image.png
一旦有消息 只要订阅过该频道 都可以获取这个消息
首先打开一个cmd客户端
进入到redis安装目录
启动一个redis服务 命令结果如下
image.png
然后我们在启动一个cmd客户端
image.png
然后我们再来一个
image.png
我们共启动了3个redis服务(连接到服务器里共有3个客户端)
假设有个频道叫做users(自定义的频道)
首先订阅subscribe users(可以看做消费者)
image.png
这时会等待我们频道发送消息
接下来在另外一个cmd窗口同样订阅如下
image.png
接下来假设还有最后一个cmd客户端窗口认为是个发布者(可以发布一个消息)
执行命令如下 publish users abc(abc为自己起的名字)
如下
image.png
其中2表示有2个客户端进行接收消息
然后观看另外两个cmd窗口变化
image.png
同时会出现3个消息 第一个为消息类型。第二个为频道名,第三个为真实值。
同样我们在发布端在发布一条消息
publish users hello
如下
image.png
观看订阅端窗口接收消息如下(立即出现 只要发布了消息)
image.png
这个应用到我们的java里面 肯定也是通过java程序来执行publish 专门有个死循环程序进行订阅 我们网站本身也可以订阅
在订阅的过程中一定是单开个线程来执行这个订阅过程的 如果有外部发布消息 那么我们订阅的这个线程一定可以得到这个消息
然后做处理
接下来首先关掉两个cmd客户端窗口(执行subscribe users命令的窗口 不是发布窗口)
而把publish users hello这个cmd窗口认为死循环程序(做完处理后 执行publish)
image.png
接下来把网站同时作为订阅端
我们在UserController中的update方法中执行发布后(也就是下面这个方法),发布消息后。然后等待后端处理
image.png
下面看下用java程序如何做
image.png
如何创建内容以及在Spring中如何配置 (如何操作redis)
首先看官网https://docs.spring.io/spring-data/redis/docs/2.0.5.RELEASE/reference/html/
image.png
首先做个连接工厂(连接器)
可以手工写代码或者spring配置文件的方式(通过IOC容器进行控制 推荐这种方式)
所以我们首先在context-spring配置文件中加入如下
image.png
可以发现代码报红色了 是因为缺少命名空间
在context-spring中beans节点上加入如下命名空间(根据官网看到的)
image.png
再看下我们添加的依赖报错消失了
image.png
所加命名空间在官网如下可看见
image.png
接下来看这页内容如下
image.png
接下来加入RedisTemplate(这节课我们没用到,但以后肯定会用到) 所以这里先加入
context-spring中添加如下
这是一个spring data redis帮我们封装好的操作工具类
image.png
接下来就可以在代码里引用配置的这个bean了
在UsersController中引用配置好的bean
首先在UsersController类上打入@AutoWired注解(自动装配)
image.png
引入redisTemplate对象
接下来看下如何进行监听(其实网站在启动的时候就需要它实现监听 因为一旦我们有消息发送过来,它必定马上得到消息后进行进一步的处理)
来到官网(5.9章节)可以看见如下 一个是发送 一个是接收
image.png
这节课我们要演示的是异步请求 做个简单的配置
配置内容如下
image.png
首先定义一个redis-listener监听器(官方如下)
image.png
下面我们把配置加入到context-spring中 添加如下(这个是我们默认的ConnectionFactory 我们这里需要指定成我们配置的jedisConnectionFactory)
image.png
修改后添加如下
image.png
其中container内容包含的就是监听的内容(是用来监听users频道的 也就是我们cmd中发布的信息)
定义好监听器之后 他就可以帮我们实现相关的一些监听 但是我们需要去实现一旦监听到消息后 应该如何做(这个是需要我们执行的)
里面的属性意思为topic就是我们的频道 因此我们这里需要改为users
因为我们发布的频道为users
image.png
所以在此修改context-spring如下
image.png
剩下两个属性 看我们具体操作
首先在com.jtthink包下面创建个包叫redis
目录如下
里面写个IMsgListener接口
image.png
代码如下
image.png
接下来在redis包下面创建个实现类MsgListener
继承IMsgListener接口实现handleMessage方法
代码如下
image.png
接下来需要在Spring配置文件中加入一个bean
打开context-spring.xml文件
image.png
修改如上 加入了一个bean是我们定义的实现类 然后把id绑定给redis-listener中的ref(将二者进行关联)
这时网站启动 自动会进行监听 然后一旦得到消息 会交给MsgListener处理
接下来写好代码 重新发布下
发布完成后来到cmd客户端窗口发布一条消息
publish users javaabc如下
image.png
其中的1代表有一个客户端得到了消息
同时这时我们的控制台输出了javaabc如下(我们已经实现了监听 并且一旦得到消息后 可以来处理 是一个单独的线程处理的 并不是我们的请求线程)
image.png
我们在发布一条消息
publish users hello
image.png
控制台
image.png
这样的结果有什么意义呢 和我们之前的请求有什么关系呢
下面进行关联
接下来改造下异步的代码(update函数 UsersController中)
首先注释掉一句
image.png
接下来来到MsgListener定义一个属性
并生成get和set方法
代码如下
image.png
这时我们在UsersController中就可以得到deferredResult的值
由于我们的监听器是在context-spring配置文件中配置的 (使用IOC容器自动加载)
所以我们要在UsersController中打入自动装配注解 引入MsgListener类
代码如下
image.png
接下来需要对update方法进行修改(UsersController中)
代码如下
image.png
由于我们把deferredResult传给了msgListener
也就是说只有其他线程里设置setResult的时候才会触发(把我们当前的数据传给我们请求主线程)才会执行update方法
deferredResult创建后需要在当前线程或者其他线程进行setResult设置,否则里面没值
接下来重新发布代码
首先我先点击async_submit(deffred)按钮(会请求我们UsersController中的update异步方法)
过了5s中弹出如下
image.png
因为我们在没有在任何线程里执行setResult方法并把其返回出来
接下来打开cmd 开启一个redis客户端窗口如下
image.png
我们在次点击async_submit(deffred)按钮 然后立马在redis客户端中发布一条消息(5s内发布)
操作步骤先点击按钮 在5s内在cmd客户端窗口执行publish users hello (发布消息)
image.png
cmd窗口出现如下 说明有一个客户端连接
看test1.html页面内容
image.png
监听到了我们的users频道 并且立马弹出内容hello不会超时
其中这里面完整过程如下
首先我们UsersController中的(update)方法完成对数据进行更新,一旦得到数据后,在这个方法里面可以直接调用消息队列
比如我们publish(发布)的频道(users)这时我们在users这个消息队列里面就会有另外一个死循环程序在监听(users)频道,得到消息后,就会对数据进行处理,然后把数据放入另一个渠道(b)里面 我们所监听的内容就是监听这个b 一旦b里面有数据了 我们立马就返回 这就是基本的过程