java响应式编程有几种方式_什么是响应式编程,Java 如何实现

什么是响应式编程,Java 如何实现

我们这里用通过唯一 id 获取知乎的某个回答作为例子,首先我们先明确下,一次HTTP请求到服务器上处理完之后,将响应写回这次请求的连接,就是完成这次请求了,如下:

public void request(Connection connection, HttpRequest request) {

//处理request,省略代码

connection.write(response);//完成响应

}

假设获取回答需要调用两个接口,获取评论数量还有获取回答信息,传统的代码可能会这么去写:

//获取评论数量

public void getCommentCount(Connection connection, HttpRequest request) {

Integer commentCount = null;

try {

//从缓存获取评论数量,阻塞IO

commentCount = getCommnetCountFromCache(id);

} catch(Exception e) {

try {

//缓存获取失败就从数据库中获取,阻塞IO

commentCount = getVoteCountFromDB(id);

} catch(Exception ex) {

}

}

connection.write(commentCount);

}

//获取回答

public void getAnswer(Connection connection, HttpRequest request) {

//获取点赞数量

Integer voteCount = null;

try {

//从缓存获取点赞数量,阻塞IO

voteCount = getVoteCountFromCache(id);

} catch(Exception e) {

try {

//缓存获取失败就从数据库中获取,阻塞IO

voteCount = getVoteCountFromDB(id);

} catch(Exception ex) {

}

}

//从数据库获取回答信息,阻塞IO

Answer answer = getAnswerFromDB(id);

//拼装Response

ResultVO response = new ResultVO();

if (voteCount != null) {

response.setVoteCount(voteCount);

}

if (answer != null) {

response.setAnswer(answer);

}

connection.write(response);//完成响应

}

在这种实现下,你的进程只需要一个线程池,承载了所有请求。这种实现下,有两个弊端:

线程池 IO 阻塞,导致某个存储变慢或者缓存击穿的话,所有服务都堵住了。假设现在评论缓存突然挂了,全都访问数据库,导致请求变慢。由于线程需要等待 IO 响应,导致唯一一个线程池被堆满,无法处理获取回答的请求。

对于获取回答信息,获取点赞数量其实和获取回答信息是可以并发进行的。不用非得先获取点赞数量之后再获取回答信息。

现在,NIO 非阻塞 IO 很普及了,有了非阻塞 IO,我们可以通过响应式编程,来让我们的线程不会阻塞,而是一直在处理请求。这是如何实现的呢

传统的 BIO,是线程将数据写入 Connection 之后,当前线程进入 Block 状态,直到响应返回,之后接着做响应返回后的动作。NIO 则是线程将数据写入 Connection 之后,将响应返回后需要做的事情以及参数缓存到一个地方之后,直接返回。在有响应返回后,NIO 的 Selector 的 Read 事件会是 Ready 状态,扫描 Selector 事件的线程,会告诉你的线程池数据好了,然后线程池中的某个线程,拿出刚刚缓存的要做的事情还有参数,继续处理。

那么,怎样实现缓存响应返回后需要做的事情以及参数的呢Java 本身提供了两种接口,一个是基于回调的 Callback 接口(Java 8 引入的各种Functional Interface),一种是 Future 框架。

基于 Callback 的实现:

//获取回答

public void getAnswer(Connection connection, HttpRequest request) {

ResultVO resultVO = new ResultVO();

getVoteCountFromCache(id, (count, throwable) - {

//异常不为null则为获取失败

if (throwable != null) {

//读取缓存失败就从数据库获取

getVoteCountFromDB(id, (count2, throwable2) - {

if (throwable2 == null) {

resultVO.setVoteCount(voteCount);

}

//从数据库读取回答信息

getAnswerFromDB(id, (answer, throwable3) - {

if (throwable3 == null) {

resultVO.setAnswer(answer);

connection.write(resultVO);

} else {

connection.write(throwable3);

}

});

});

} else {

//获取成功,设置voteCount

resultVO.setVoteCount(voteCount);

//从数据库读取回答信息

getAnswerFromDB(id, (answer, throwable2) - {

if (throwable2 == null) {

resultVO.setAnswer(answer);

//返回响应

connection.write(resultVO);

} else {

//返回错误响应

connection.write(throwable2);

}

});

}

});

}

可以看出,随着调用层级的加深,callback 层级越来越深,越来越难写,而且啰嗦的代码很多。并且,基于 CallBack 想实现获取点赞数量其实和获取回答信息并发是很难写的,这里还是先获取点赞数量之后再获取回答信息。

那么基于 Future 呢我们用 Java 8 之后引入的 CompletableFuture 来试着实现下。

//获取回答

public void getAnswer(Connection connection, HttpRequest request) {

ResultVO resultVO = new ResultVO();

//所有的异步任务都执行完之后要做的事情

CompletableFuture.allOf(

getVoteCountFromCache(id)

//发生异常,从数据库读取

.exceptionallyComposeAsync(throwable - getVoteCountFromDB(id))

//读取完之后,设置VoteCount

.thenAccept(voteCount - {

resultVO.setVoteCount(voteCount);

}),

getAnswerFromDB(id).thenAccept(answer - {

resultVO.setAnswer(answer);

})

).exceptionallyAsync(throwable - {

connection.write(throwable);

}).thenRun(() - {

connection.write(resultVO);

});

}

这种实现就看上去简单多了,并且读取点赞数量还有读取回答内容是同时进行的。

Project Reactor 在 Completableuture 这种实现的基础上,增加了更多的组合方式以及更完善的异常处理机制,以及面对背压时候的处理机制,还有重试机制。

每日一刷,轻松提升技术,斩获各种offer:

什么是响应式编程,Java 如何实现 相关文章

什么原因才导致 select * 效率低下的

面试官:“小陈,说一下你常用的SQL优化方式吧。”陈小哈:“那很多啊,比如不要用SELECT *,查询效率低。巴拉巴拉...”面试官:“为什么不要用SELECT * 它在哪些情况下效率低呢”陈小哈:“SELECT * 它好像比写指定列名多一次全表查询吧,还多查了一些无用

linux配置DNS主从配置

DNS主从搭建 什么是DNS DNS( Domain Name System)是“域名系统”的英文缩写,是一种组织成域层次结构的计算机和网络服务命名系统,使用的是UDP协议的53号端口,它用于TCP/IP网络,它所提供的服务是用来将主机名和域名转换为IP地址的工作。DNS就是这样的一位

Java(4) | 匿名对象

# 什么是匿名对象 匿名对象没有名字和赋值运算 new 类名称(); 创建对象的标准格式: 类名称 对象名 = new 类名称(); # 注意事项 匿名对象只能使用一次 所以如果确定某个对象只是用一次,则可以使用匿名对象 # 代码演示 package com.leerep.javabase.anonymou

MQ学习1

MQ(bilibili) MQ和RabbitMQ基础 1 什么是MQ ?举例:用户注册 ?原来: 用户--短信服务--邮件服务--数据库 ?多线程:用户--短信(1秒) ?用户--邮件(1秒) ?用户--数据库(1秒) ?消息队列:用户--数据库(1秒) ?用户--消息队列(MQ)(0.5秒) ?消息队列(MQ)--短信 ?消

Day8

1什么是构造函数 构造函数是bai一种特殊的成员函数du,它主要用于为对象分配存zhi储空间,对数据成dao员进zhuan行初始化.构造函数shu具有一些特殊的性质: (1)构造函数的名字必须与类同名; (2)构造函数没有返回类型,它可以带参数,也可以不带参数; (3)声明类对象

关于Java注解(annotation)的简单理解

一、什么是注解 从 JDK5 开始,Java增加对元数据的支持,也就是注解。简单理解就是代码里的特殊标志,这些标志可以在编译,类加载,运行时被读取,并执行相应的处理,以便于其他工具补充信息或者进行部署。 二、为什么要使用注解 注解可以被其他程序(比如:

RestHighLevelClient的使用

是什么 es官网推荐的es客户端组件RestHighLevelClient, 其封装了操作es的crud方法,底层原理就是模拟各种es需要的请求,如put,post,delete,get等方式 使用步骤 1.引入pom依赖 2.application.yml配置ES 3.查询 // 构建查询参数SearchSourceBuilder search

makefile终极奥义

什么是makefile? 或许很多 Winodws 的程序员都不知道这个东西,因为那些 Windows 的 IDE 都为你做了这个工作,但是一个好的和 professional 的程序员, makefile 还是要懂。这就好像现在有这么多的 HTML 的编辑器,但如果你想成为一个专业人士,你还是要了

HTTP请求响应过程

HTTP请求 HTTP是超文本传输协议,其定义了客户端与服务器端之间文本传输的规范。HTTP默认使用80端口,这个端口指的是服务端的端口,而客户端使用的端口是动态分配的。当我们没有指定端口访问时,浏览器会默认帮我们添加80端口。我们也可以自己指定访问端口如

你不知道的 Proxy

在从观察者模式到响应式的设计原理这篇文章中,阿宝哥介绍了 observer-util 这个库如何使用 Proxy 来实现响应式。而对于 vue-next 项目中的 @vue/reactivity 模块,也是利用 Proxy 来实现响应式。因此,如果你要学习 @vue/reactivity 模块的话,就需要先掌握

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值