Reactive-MongoDB 异步 Java Driver 解读
一、关于 异步驱动
从3.0 版本开始,MongoDB 开始提供异步方式的驱动(Java Async Driver),这为应用提供了一种更高性能的选择。
但实质上,使用同步驱动(Java Sync Driver)的项目也不在少数,或许是因为先入为主的原因(同步Driver的文档说明更加的完善),又或者是为了兼容旧的 MongoDB 版本。
无论如何,由于 Reactive 的发展,未来使用异步驱动应该是一个趋势。
在使用 Async Driver 之前,需要对 Reactive 的概念有一些熟悉。
二、理解 Reactive (响应式)
响应式(Reactive)是一种异步的、面向数据流的开发方式,最早是来自于.NET 平台上的 Reactive Extensions 库,随后被扩展为各种编程语言的实现。
在著名的 Reactive Manifesto(响应式宣言) 中,对 Reactive 定义了四个特征:

- 及时响应(Responsive):系统能及时的响应请求。
- 有韧性(Resilient):系统在出现异常时仍然可以响应,即支持容错。
- 有弹性(Elastic):在不同的负载下,系统可弹性伸缩来保证运行。
- 消息驱动(Message Driven):不同组件之间使用异步消息传递来进行交互,并确保松耦合及相互隔离。
在响应式宣言的所定义的这些系统特征中,无一不与响应式的流有若干的关系,于是乎就有了 2013年发起的 响应式流规范(Reactive Stream Specification)。
https://www.reactive-streams.org/
其中,对于响应式流的处理环节又做了如下定义:
- 具有处理无限数量的元素的能力,即允许流永不结束
- 按序处理
- 异步地传递元素
- 实现非阻塞的负压(back-pressure)
Java 平台则是在 JDK 9 版本上发布了对 Reactive Streams 的支持。
下面介绍响应式流的几个关键接口:
- Publisher
Publisher 是数据的发布者。Publisher 接口只有一个方法 subscribe,用于添加数据的订阅者,也就是 Subscriber。 - Subscriber
Subscriber 是数据的订阅者。Subscriber 接口有4个方法,都是作为不同事件的处理器。在订阅者成功订阅到发布者之后,其 onSubscribe(Subscription s) 方法会被调用。
Subscription 表示的是当前的订阅关系。
当订阅成功后,可以使用 Subscription 的 request(long n) 方法来请求发布者发布 n 条数据。发布者可能产生3种不同的消息通知,分别对应 Subscriber 的另外3个回调方法。
数据通知:对应 onNext 方法,表示发布者产生的数据。
错误通知:对应 onError 方法,表示发布者产生了错误。
结束通知:对应 onComplete 方法,表示发布者已经完成了所有数据的发布。
在上述3种通知中,错误通知和结束通知都是终结通知,也就是在终结通知之后,不会再有其他通知产生。
- Subscription
Subscription 表示的是一个订阅关系。除了之前提到的 request 方法之外,还有 cancel 方法用来取消订阅。需要注意的是,在 cancel 方法调用之后,发布者仍然有可能继续发布通知。但订阅最终会被取消。
这几个接口的关系如下图所示:

图片出处:http://wiki.jikexueyuan.com/index.php/project/reactor-2.0/05.html
MongoDB 的异步驱动为 mongo-java-driver-reactivestreams 组件,其实现了 Reactive Stream 的上述接口。
> 除了 reactivestream 之外,MongoDB 的异步驱动还包含 RxJava 等风格的版本,有兴趣的读者可以进一步了解
http://mongodb.github.io/mongo-java-driver-reactivestreams/1.11/getting-started/quick-tour-primer/
三、使用示例
接下来,通过一个简单的例子来演示一下 Reactive 方式的代码风格:
A. 引入依赖
org.mongodb
mongodb-driver-reactivestreams
1.11.0
> 引入mongodb-driver-reactivestreams 将会自动添加 reactive-streams, bson, mongodb-driver-async组件
B. 连接数据库
//服务器实例表
List servers = new ArrayList();
servers.add(new ServerAddress("localhost", 27018));
//配置构建器
MongoClientSettings.Builder settingsBuilder = MongoClientSettings.builder();
//传入服务器实例
settingsBuilder.applyToClusterSettings(
builder -> builder.hosts(servers));
//构建 Client 实例
MongoClient mongoClient = MongoClients.create(settingsBuilder.build());
C. 实现文档查询
//获得数据库对象
MongoDatabase database = client.getDatabase(databaseName);
//获得集合
MongoCollection collection = database.getCollection(collectionName);
//异步返回Publisher
FindPublisher publisher = collection.find();
//订阅实现
publisher.subscribe(new Subscriber() {
@Override
public void onSubscribe(Subscription s) {
System.out.println("start...");
//执行请求
s.request(Integer.MAX_VALUE);
}
@Override
public void onNext(Document document) {
//获得文档
System.out.println("Document:" + document.toJson());
}
@Override
public void onError(Throwable t) {
System.out.println("error occurs.");
}
@Override
public void onComplete() {
System.out.println("finished.");
}
});
注意到,与使用同步驱动不同的是,collection.find()方法返回的不是 Cursor,而是一个 FindPublisher对象,这是Publisher接口的一层扩展。
而且,在返回 Publisher 对象时,此时并没有产生真正的数据库IO请求。 真正发起请求需要通过调用 Subscription.request()方法。
在上面的代码中,为了读取由 Publisher 产生的结果,通过自定义一个Subscriber,在onSubscribe 事件触发时就执行 数据库的请求,之后分别对 onNext、onError、onComplete进行处理。
尽管这种实现方式是纯异步的,但在使用上比较繁琐。试想如果对于每个数据库操作都要完成一个Subscriber 逻辑,那么开发的工作量是巨大的。
为了尽可能复用重复的逻辑,可以对Subscriber的逻辑做一层封装,包含如下功能:
- 使用 List 容器对请求结果进行缓存
- 实现阻塞等待结果的方法,可指定超时时间
- 捕获异常,在等待结果时抛出
代码如下:
public class ObservableSubscriber implements Subscriber {
//响应数据
private final List received;
//错误信息
private final List errors;
//等待对象
private final CountDownLatch latch;
//订阅器
private volatile Subscription subscription;
//是否完成
private volatile boolean completed;
public ObservableSubscriber() {
this.received = new ArrayList();
this.errors = new ArrayList();
this.latch = new CountDownLatch(1);
}
@Override
public void onSubscribe(final Subscription s) {
subscription = s;
}
@Override
public void onNext(final T t) {
received.add(t);
}
@Override
public void onError(final Throwable t) {
errors.add(t);
onComplete();
}
@Override
public void onComplete() {
completed = true;
latch.countDown();
}
public Subscription getSubscription() {
return subscription;
}
public List getReceived() {
return received;
}
public Throwable getError() {
if (errors.size() > 0) {
return errors.get(0);
}
return null;
}
public boolean isCompleted() {
return completed;
}
/**
* 阻塞一定时间等待结果
*
* @param timeout
* @param unit
* @return
* @throws Throwable
*/
public List get(final long timeout, final TimeUnit unit) throws Throwable {
return await(timeout, unit).getReceived();
}
/**
* 一直阻塞等待请求完成
*
* @return
* @throws Throwable
*/
public ObservableSubscriber await() throws Throwable {
return await(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
}
/**
* 阻塞一定时间等待完成
*
* @param timeout
* @param unit
* @return
* @throws Throwable
*/
public ObservableSubscriber await(final long timeout, final TimeUnit unit) throws Throwable {
subscription.request(Integer.MAX_VALUE);
if (!latch.await(timeout, unit)) {
throw new MongoTimeoutException("Publisher onComplete timed out");
}
if (!errors.isEmpty()) {
throw errors.get(0);
}
return this;
}
}
借助这个基础的工具类,我们对于文档的异步操作就变得简单多了。
比如对于文档查询的操作可以改造如下:
ObservableSubscriber subscriber = new ObservableSubscriber();
collection.find().subscribe(subscriber);
//结果处理
subscriber.get(15, TimeUnit.SECONDS).forEach( d -> {
System.out.println("Document:" + d.toJson());
});
当然,这个例子还有可以继续完善,比如使用 List 作为缓存,则要考虑数据量的问题,避免将全部(或超量) 的文档一次性转入内存。
原文地址:https://www.mongochina.com/article/655.html
Reactive-MongoDB 异步 Java Driver 解读的更多相关文章
- MongoDb C/java driver
1,在linux下安装客户端连接windows下 的MongoDBServer.
- MongoDB Java Driver操作指南
MongoDB为Java提供了非常丰富的API操作,相比关系型数据库,这种NoSQL本身的数据也有点面向对象的意思,所以对于Java来说,Mongo的数据结构更加友好. MongoDB在今年做了一次重 ...
- Mongodb Java Driver 参数配置解析
要正确使用Mongodb Java Driver,MongoClientOptions参数配置对数据库访问的并发性能影响极大. connectionsPerHost:与目标数据库能够建立的最大conn ...
- MongoDB的Java驱动使用整理 (转)
MongoDB Java Driver 简单操作 一.Java驱动一致性 MongoDB的Java驱动是线程安全的,对于一般的应用,只要一个Mongo实例即可,Mongo有个内置的连接池(池大小默认为 ...
- update document in mongodb using java -摘自网络
update document in mongodb using java: Mongodb driver provides functionality to update document in m ...
- Ubuntu14.04下Mongodb的Java API编程实例(手动项目或者maven项目)
不多说,直接上干货! 若大家,不会安装的话,则请移步,随便挑选一种. Ubuntu14.04下Mongodb(在线安装方式|apt-get)安装部署步骤(图文详解)(博主推荐) Ubuntu14.04 ...
- [MongoDB]MongoDB与JAVA结合使用CRUD
汇总: 1. [MongoDB]安装MongoDB2. [MongoDB]Mongo基本使用:3. [MongoDB]MongoDB的优缺点及与关系型数据库的比较4. [MongoDB]MongoDB ...
- 【MongoDB for Java】Java操作MongoDB
上一篇文章: http://www.cnblogs.com/hoojo/archive/2011/06/01/2066426.html介绍到了在MongoDB的控制台完成MongoDB的数据操作,通过 ...
- 基于mongodb的java之增删改查(CRUD)
1,下载驱动https://github.com/mongodb/mongo-java-driver/downloads,导入工程java中 2,建立测试代码 import java.net.Unkn ...
随机推荐
- python 爬虫之-- 正则表达式
正则表达式是一个特殊的字符序列,它能帮助你方便的检查一个字符串是否与某种模式匹配. 正则表达式非python独有,python 提供了正则表达式的接口,re模块 一.正则匹配字符简介 模式 描述 \d ...
- eclipse集成maven(四)
一.配置maven 打开Window-Preference-Maven,我们可以看到,默认是使用Eclipse的,不是我们要的maven,可以在Installations中,点击"Add&q ...
- 浙大&川大提出脉冲版ResNet:继承ResNet优势,实现当前最佳
浙大&川大提出脉冲版ResNet:继承ResNet优势,实现当前最佳 选自arXiv,作者:Yangfan Hu等,机器之心编译. 脉冲神经网络(SNN)具有生物学上的合理性,并且其计算潜能和 ...
- SLAM:
十四讲: 传感器约束了外部环境 测到的通常都是一些间接的物理量而不是直接的位置数据 只能通过一些间接的手段,从这些数据推算自己的位置 好处是没有对环境提出任何要求 camera:单目.双目.深度 Mo ...
- Day01 确定选题
一起来选题 一.谁想个选题? 今天是第一节大软课,大家需要进行分组和确定选题.分组固然是快乐的,但是确定选题是让人费脑筋的.要新颖!要有需求!要我们能实现(笑)......大家面面相觑.面对这种情况, ...
- 【转】HTTPS 如何保证数据传输的安全性?
大家都知道,在客户端与服务器数据传输的过程中,HTTP协议的传输是不安全的,也就是一般情况下HTTP是明文传输的.但HTTPS协议的数据传输是安全的,也就是说HTTPS数据的传输是经过加密的. 在客户 ...
- 牛客CSP-S提高组赛前集训营1———2019.10.29 18:30 至 22:00
期望得分:100+0+10 实际得分:40+0+0 考炸了... T1:题目链接 究竟为什么会这样,,, 仔细研读我的丑代码 发现... 枯辽.... #include<cstdio> # ...
- Attention篇(二)
主要是对<Attention is all you need>的分析 结合:http://www.cnblogs.com/robert-dlut/p/8638283.html 以及自己的 ...
- 安装WIN7系统备忘录
安装WIN7系统备忘录因为安装WIN7设置项太多,制作RAMOS如果忘了某项设置,终归是不方便,记录如下:1.用WINNTSETUP安装到VHD中,安装时优化选项中建议勾选关闭休眠和虚拟内存功能(假设 ...
- MySQL实战45讲学习笔记:第二十一讲
一.引子 在上一篇文章中,我和你介绍了间隙锁和 next-key lock 的概念,但是并没有说明加锁规则.间隙锁的概念理解起来确实有点儿难,尤其在配合上行锁以后,很容易在判断是否会出现锁等待的问题上 ...