JAVA语言异步非阻塞设计模式(应用篇)

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/55/55c6af122f7b2ec148b24c32b3cc3360.gif","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"1.概述","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本系列文章共2篇。在上一篇《原理篇》中,我们看到了异步非阻塞模型,它能够有效降低线程IO状态的耗时,提升资源利用率和系统吞吐量。异步API可以表现为listener或Promise形式;其中Promise API提供了更强的灵活性,支持同步返回和异步回调,也允许注册任意数目的回调。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在本文《应用篇》中,我们将进一步探索异步模式和Promise的应用:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"第2章:Promise与线程池。","attrs":{}},{"type":"text","text":" 在异步执行耗时请求时,ExecutorService+Future是一个备选方案;但是相比于Future,Promise支持纯异步获取响应数据,能够消除更多阻塞。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"第3章:异常处理。","attrs":{}},{"type":"text","text":" Java程序并不总能成功执行请求,有时会遇到网络问题等不可抗力。对于无法避免的异常情况,异步API必须提供异常处理机制,以提升程序的容错性。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"第4章:请求调度。","attrs":{}},{"type":"text","text":" Java程序有时需要提交多条请求,这些请求之间可能存在一定的关联关系,包括顺序执行、并行执行、批量执行。异步API需要对这些约束提供支持。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文不限定Promise的具体实现,读者在生产环境可以选择一个Promise工具类(如netty DefaultPromise[A]、jdk CompletableFuture[B]等);此外,由于Promise的原理并不复杂,读者也可以自行实现所需功能。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2.Promise与线程池","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Java程序有时需要执行耗时的IO操作,如数据库访问;在此期间,相比于纯内存计算,IO操作的持续时间明显更长。为了减少IO阻塞、提高资源利用率,我们应该使用异步模型,将请求提交到其他线程中执行,从而连续提交多条请求,而不必等待之前的请求返回。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本章对几种IO模型进行对比(见2.1节),考察调用者线程的阻塞情况。其中,Promise支持纯异步的请求提交及响应数据处理,能够最大程度地消除不必要的阻塞。在实际项目中,如果底层API不支持纯异步,那么我们也可以进行适当重构,使其和Promise兼容(见2.2节)。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.1 对比:同步、Future、Promise","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本节对几种 IO 模型进行对比,包括同步 IO、基于线程池(ExecutorService)的异步 IO、基于 Promise 的异步IO,考察调用者线程的阻塞情况。假设我们要执行数据库访问请求。由于需要跨越网络,单条请求需要进行耗时的 IO操作,才能最终收到响应数据;但是请求之间没有约束,允许随时提交新的请求,而不需要收到之前的响应数据。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先我们来看看","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"几种模型的样例代码","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"1.同步IO","attrs":{}},{"type":"text","text":"。db.writeSync()方法是同步阻塞的。函数阻塞,直至收到响应数据。因此,调用者一次只能提交一个请求,必须等待该请求返回,才能再提交下一个请求。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"/* 提交请求并阻塞,直至收到响应数据*/\nString result = db.writeSync(\"data\");\nprocess(result);","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"2.基于线程池(ExecutorService)的异步IO","attrs":{}},{"type":"text","text":"。db.writeSync()方法不变;但是将其提交到线程池中来执行,使得调用者线程不会阻塞,从而可以连续提交多条请求data1-3。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"提交请求后,线程池返回 Future 对象,调用者调用 Future.get() 以获取响应数据。Future.get() 方法却是阻塞的,因此调用者在获得响应数据之前无法再提交后续请求。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"/* 提交请求*/\n// executor: ExecutorService\nFuture resultFuture1 = executor.submit(() -> db.writeSync(\"data1\"));\nFuture resultFuture2 = executor.submit(() -> db.writeSync(\"data2\"));\nFuture resultFuture3 = executor.submit(() -> db.writeSync(\"data3\"));\n\n/* 获取响应:同步*/\nString result1 = resultFuture1.get();\nString result2 = resultFuture2.get();\nString result3 = resultFuture3.get();\nprocess(result1);\nprocess(result2);\nprocess(result3);","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"3.基于Promise的异步IO","attrs":{}},{"type":"text","text":"。db.writeAsync()方法是纯异步的,提交请求后返回 Promise 对象;调用者调用Promise.await()注册回调,当收到响应数据后触发回调。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在《原理篇》中,我们看到了 Promise API 可以基于线程池或响应式模型实现;不论哪种方式,回调函数可以在接收响应的线程中执行,而不需要调用者线程阻塞地等待响应数据。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"/* 提交请求*/\nPromise resultPromise1 = db.writeAsync(\"data1\");\nPromise resultPromise2 = db.writeAsync(\"data2\");\nPromise resultPromise3 = db.writeAsync(\"data3\");\n\n/* 获取响应:异步*/\nresultPromise1.await(result1 -> process(result1));\nresultPromise2.await(result2 -> process(result2));\nresultPromise3.await(result3 -> process(result3));","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下来我们看看以上几种模型中,调用者线程状态随时间变化的过程,如图2-1所示。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"a.","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"同步 IO","attrs":{}},{"type":"text","text":"。调用者一次只能提交一个请求,在收到响应之前不能提交下一个请求。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"b.","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"基于线程池的异步 IO","attrs":{}},{"type":"text","text":"。同一组请求(请求1-3,以及请求4-6)可以连续提交,而不需要等待前一条请求返回。然而,一旦调用者使用 Future.get() 获取响应数据(result1-3),就会阻塞而无法再提交下一组请求(请求4-6),直至实际收到响应数据。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"c.","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"基于 Promise 的异步 IO。","attrs":{}},{"type":"text","text":" 调用者随时可以提交请求,并向Promise注册对响应数据的回调函数;稍后接收线程向Promise通知响应数据,以触发回调函数。上述过程中,调用者线程不需要等待响应数据,始终不会阻塞。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a4/a49ddbfa2a9bff6dfd83aa0ef0a2cc63.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"图2-1a 线程时间线:同步IO","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/30/306334c7f3a16ff1a2d29dc7647d4664.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"图2-1b 线程时间线:基于线程池的异步IO","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/2d/2df938bc26820ca9480e364ac055ac01.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"图2-1c 线程时间线:基于 Promise 的异步IO","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.2 Promise结合线程池","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"和 ExecutorService+Future相比,Promise 具有纯异步的优点;然而在某些场景下也需要把 Promise 和线程池结合使用。例如:1.底层 API 只支持同步阻塞模型,不支持纯异步;此时只能在线程池中调用 API,才能做到非阻塞。2.需要重构一段遗留代码,将其线程模型从线程池模型改为响应式模型;可以先将对外接口改为 Promise API,而底层实现暂时使用线程池。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面的代码片段展示了 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Promise 和线程池结合的用法:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"创建 Promise 对象作为返回值。注意这里使用了 PromiseOrException,以防期间遇到异常;其可以通知响应数据,也可以在失败时通知抛出的 Exception。详见3.1小节。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"在线程池中执行请求(2a),并在收到响应数据后向 Promise 通知(2b)","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"处理线程池满异常。线程池底层关联一个 BlockingQueue 来存储待执行的任务,一般设置为有界队列以防无限占用内存,当队列满时会丢弃某个任务。为了向调用者通知该异常,线程池的拒绝策略须设置为 AbortPolicy,当队列满时丢弃所提交的任务,并抛出 RejectedExecutionException;一旦捕获该异常,就要向 Promise 通知请求失败。","attrs":{}}]}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"public PromiseOrException writeAsync() {\n// 1.创建Promise对象\n PromiseOrException resultPromise = new PromiseOrException<>();\n try {\n executor.execute(() -> {\n String result = db.writeSync(\"data\"); // 2a.执行请求。只支持同步阻塞\n resultPromise.signalAllWithResult(result); // 2b.通知Promise\n });\n\n }catch (RejectedExecutionException e){ // 3.异常:线程池满\n resultPromise.signalAllWithException(e); \n }\n\n return resultPromise;\n}","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"3.异常处理:PromiseOrException","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Java 程序有时会遇到不可避免的异常情况,如网络连接断开;因此,程序员需要设计适当的异常处理机制,以提升程序的容错性。本章介绍异步 API 的异常处理,首先介绍 Java 语言异常处理规范;然后介绍 Promise 的变体 PromiseOrException,使得 Promise API 支持规范的异常处理。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.1异常处理规范","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"个人认为,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Java 代码的异常处理","attrs":{}},{"type":"text","text":"应当符合下列规范:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"显式区分正常出口和异常出口。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"支持编译时刻检查,强制调用者处理不可避免的异常。","attrs":{}}]}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}},{"type":"strong","attrs":{}}],"text":"区分正常出口和异常出口","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"异常是 Java 语言的重要特性,是一种基本的控制流。Java 语言中,一个函数允许有一个返回值,以及抛出多个不同类型的异常。函数的返回值是正常出口,函数返回说明函数能够正常工作,并计算出正确的结果;相反,一旦函数遇到异常情况无法继续工作,如网络连接断开、请求非法等,就要抛出相应的异常。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"虽然 if-else 和异常都是控制流,但是程序员必","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"须辨析二者的使用场景","attrs":{}},{"type":"text","text":"。if-else 的各个分支一般是对等的,都用于处理正常情况;而函数的返回值和异常是不对等的,抛出异常表示函数遇到无法处理的故障,已经无法正常计算结果,其与函数正常工作所产生的返回值有本质区别。在 API 设计中,混淆正常出口(返回值)与异常出口(抛出异常),或者在无法继续工作时不抛异常,都是严重的设计缺陷。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以数据库访问为例,下面的代码对比了API进行异常处理的两种形式。数据库访问过程中,如果网络连接顺畅,并且服务端能够正确处理请求,那么db.write()应该返回服务端的响应数据,如服务端为所写数据生成的自增id、条件更新实际影响的数据条数等;如果网络连接断开,或者客户端和服务端版本不匹配导致请求无法解析,从而无法正常工作,那么db.write()应该抛出异常以说明具体原因。从“是否正常工作”的角度看,上述两种情况的性质是截然不同的,显然应该选用异常作为控制流,而不是 if-else。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"/* 正确*/\ntry {\n String result = db.write(\"data\");\n process(result); // 正常出口\n} catch (Exception e) {\n log.error(\"write fails\", e); // 异常出口\n}\n\n/* 错误*/\nString resultOrError = db.write(\"data\");\nif (resultOrError.equals(\"OK\")) {\n process(resultOrError); // 正常出口\n} else {\n log.error(\"write fails, error: \" + resultOrError); // 异常出口\n} ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}},{"type":"strong","attrs":{}}],"text":"强制处理不可避免的异常","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Java 语言的异常处理体系中,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"异常主要分为以下几类","attrs":{}},{"type":"text","text":":Exception、RuntimeException、Error;三者都是Throwable 的子类,即可以被函数抛出。注意,由于 RuntimeException 是 Exception 的子类,本文为避免混淆,“Exception”特指“是 Exception 但不是 RuntimeException ”的那些异常。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"个人认为,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"几种异常类型分别用于下列场景","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"1. Exception","attrs":{}},{"type":"text","text":":程序外部的不可抗力造成的异常情况,如网络连接断开。即使 Java 代码完美无瑕,也绝对不可能避免这类异常(拔掉网线试试!)。既然无法避免,这种异常就应当强制处理,以提升系统的容错能力。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"2. RuntimeException","attrs":{}},{"type":"text","text":":编程错误造成的异常情况,如数组下标越界ArrayOutOfBoundException、参数不符合取值范围 IllegalArgumentException 等。如果程序员对 API 的输入约束了如指掌,并在调用 API 之前对函数参数进行适当校验,那么 RuntimeException 是可以绝对避免的(除非被调 API 在应当抛 Exception 处,实际抛出了RuntimeException)。既然可以避免,这种异常就没有必要强制处理。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当然,人无完人。假设程序员真的违背了某些约束,函数抛出 RuntimeException 且未被处理,那么作为惩罚,线程或进程会退出,从而提醒程序员改正错误代码。如果线程或进程必须常驻,就要对 RuntimeException 进行兜底,如下面的代码所示。这里将代码缺陷视为无法避免的异常情况,捕获异常后可以记录日志、触发告警,提醒稍后来修正缺陷。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"new Thread(()->{\n while (true){\n try{\n doSomething();\n }catch (RuntimeException e){ // 对RuntimeException进行兜底,以防线程中断\n log.error(\"error occurs\", e);\n }\n }\n});","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"3. Error:jvm内部定义的异常","attrs":{}},{"type":"text","text":",如 OutOfMemoryError。业务逻辑一般不抛出 Error,而是抛出某种Exception或 RuntimeException。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述几类异常中,只有 Exception 是强制处理的,称为 checked exception[C]。如下所示是一个 checked exception的例子。数据库访问DB.write()抛出Exception,表示遇到网络断开、消息解析失败等不可抗情况。异常类型为Exception而不是RuntimeException,以强制调用者添加catch子句处理上述情况;如调用者遗漏了 catch子句,则编译器会报错,从而提示调用者“这里一定会遇到异常情况,必须进行处理”,以完善程序容错能力。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"/**\n * 抛出异常,如果:\n * 1.网络连接断开\n * 2.消息无法解析\n * 3.业务逻辑相关,如服务端扣款时发现余额不足\n * 4.…… // 任何无法避免的情况,都应该抛出Exception!\n */\npublic String write(Object data) throws Exception {\n return \"\";\n}\n\n/**\n * 处理异常\n */\ntry {\n String result = db.write(\"data\");\n process(result); \n} catch (Exception e) { // 如遗漏catch子句,则编译不通过\n log.error(\"write fails, db: ..., data: ...\", e);\n}","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.2 Promise API的异常处理","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上一小节讨论了异常处理的规范:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"显式区分正常出口和异常出口;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不可抗的异常,要在编译时刻强制处理。下面的代码展示了 Promise API 要如何设计异常处理机制,以符合上述规范。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"使用PromiseOrException来通知响应数据和异常","attrs":{}},{"type":"text","text":"。PromiseOrException是Promise的子类,泛型模版X为数据对象ResultOrException,其含有2个字段result和e:e==null表示正常,此时字段result有效;e!=null表示异常,此时不要使用字段result。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"在“重载1”中,调用者从回调函数中获得ResultOrException对象","attrs":{}},{"type":"text","text":"。调用ResultOrException.get()获取响应数据result,或者get()方法抛出异常e。这种方式的代码结构和传统的异常处理一致,可以使用多个catch子句分别处理不同类型的异常。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"在“重载2”中,调用者从回调函数中直接获得result和e","attrs":{}},{"type":"text","text":"。含义同上。这种方式省去了ResultOrException.get();但是如果需要处不同类型的异常,则需要用e instanceof MyException来判断异常类型。","attrs":{}}]}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"// extends Promise>\n PromiseOrException resultPromsie = db.writeAsync(\"data\");\n\n /* 重载1*/ \nresultPromsie.await(resultOrException -> { \n try {\n String result = resultOrException.get();\n process(result); // 正常出口\n } catch (Exception e) {\n log.error(\"write fails\", e); // 异常出口\n }\n});\n \n /* 重载2*/\n resultPromsie.await((result, e) -> {\n if (e == null) {\n process(result); // 正常出口\n } else {\n log.error(\"write fails\", e); // 异常出口\n }\n});","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"PromiseOrException符合上一小节提出的异常处理规范,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"具有如下优点","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"区分正常出口和异常出口。响应数据和异常分别使用 result 和e两个变量来传递,可以靠e==null来判断是否正常。注意result==null不能作为判断条件,因为null有可能是响应数据的合法值。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"强制处理异常。不论使用哪一种回调,不存在一种代码结构能够只获得 result 而不获得e,因此语法上不会遗漏e的异常处理。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"允许定义异常类型。PromiseOrException 的泛型模版E填为 Excetion 不是必需的,也可以填为任意其他类型。注意,受限于 Java 语法,泛型模版处只允许填写一种异常类型,而不像函数抛异常那样允许抛出多种异常。为应对这种限制,我们只能为 API 定义一个异常父类,调用者用 catch 子句或 instanceof 进行向下转型。当然,这种“定义异常父类”的做法也是可以接受的,并在现有工具中广泛应用,因为可以将工具所抛异常区别于Java语言内置的异常类型。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后,在异常处理结构方面个人提出","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"一个建议","attrs":{}},{"type":"text","text":":全部异常通过 PromiseOrException 来通知,而 API 本身不要抛出异常。以数据库访问 API writeAsync()为例,面的代码对比了两种抛异常的方式。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"正确的做法","attrs":{}},{"type":"text","text":"是PromiseOrException 作为唯一出口,如果 API 底层实现抛出异常(submit() throws Exception),则应该将异常封装于 PromiseOrException 对象,而不应该直接从API函数抛出(writeAsync() throws Exception)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"/* 正确:唯一出口PromiseOrException*/\npublic PromiseOrException writeAsync(Object data) {\n try {\n submit(data); // throws exception\n } catch (Exception e) {\n return PromiseOrException.immediatelyException(e);\n }\n PromiseOrException resultPromise = new PromiseOrException<>();\n return resultPromise;\n}\n\n/* 错误:两个出口throws Exception和PromiseOrException*/\npublic PromiseOrException writeAsync(Object data) throws Exception {\n submit(data); // throws exception\n\n PromiseOrException resultPromise = new PromiseOrException<>();\n return resultPromise;\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果错误地设计了含有两个异常出口的 API,调用者就不得不重复书写异常处理逻辑,如下面的代码所示。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"try {\n PromiseOrException resultPromise = db.writeAsync(\"data\");\n resultPromise.await((result, e) -> {\n if (e == null) {\n process(result); // 正常出口\n } else {\n log.error(\"write fails\", e); // 异常出口2\n }\n });\n} catch (Exception e) {\n log.error(\"write fails\", e); // 异常出口1\n}","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"4.请求调度","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Java 程序中有时需要提交多条异步请求,且这些请求之间存在一定的关联关系。在异步非阻塞场景下,这些关联关系都可以借助 Promise 来实现。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"1.顺序请求","attrs":{}},{"type":"text","text":",如图4-1所示。后一条请求依赖前一条请求的响应数据;因此,必须等待前一条请求返回,才能构造并提交下一条请求。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/2c/2c2c5cb4f4bdfc3dcc9b9e4a285a8e25.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"图4-1 顺序请求","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"2.并行请求","attrs":{}},{"type":"text","text":",如图4-2所示。一次提交多条请求,然后等待全部请求返回。所提交的请求之间没有依赖关系,因此可以同时执行;但是必须收到每条请求的响应数据(发生channelRead()事件,事件参数为响应数据),才能执行实际的处理process(result1,2,3)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/01/0155cd28684a39bc1ad81b5fbdf9bda0.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"图4-2 并行请求","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"3.批量请求","attrs":{}},{"type":"text","text":",如图4-3所示。调用者连续提交多条请求,但是暂存在队列中(offer()),而不是立刻执行。一段时间后,从队列中取出若干条请求,组装为批量请求来提交(writeBulk());当收到批量请求的响应消息时,可以从中取出每条请求的响应数据。由于每次网络IO都带来额外开销,故实际应用中经常使用批量请求来减少网络IO频率,以提升总体吞吐量。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d3/d3bd46c78eae3a616bd1165d06b7640f.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"图4-3 批量请求","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"4.1.顺序请求:Promise.then()","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假设一系列操作需要依次完成,即前一操作完成后,才能开始执行下一操作;如果这些操作均表现为 Promise API,我们可以对 Promise.await(listener)进行封装,使代码结构更加简洁。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如下所示是一个异步 Promise API。submit 方法提交请求 request 并返回 Promise 对象;当收到响应数据时, 该 Promise 对象被通知。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"/**\n * 异步Promise API\n */\n public static Promise submit(Object request) {\n Promise resultPromise = new Promise<>();\n // ……\n return resultPromise;\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"现假设有5个请求称为“A”-“E”,这些请求必须依次提交。例如,由于请求B的参数依赖请求 A 的响应数据,故提交请求A后必须先处理其响应数据 resultA,然后才能再提交请求B。这种场景可以用如下所示的代码来实现。某次调用submit(\"X\")函数后,我们在其返回的 Promise 对象上注册回调;回调函数内处理响应数据 resultX,并调用submit(\"X+1\")来提交下一请求。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这种方式虽然能实现功能需求,但是嵌套式的代码结构可读性非常差——每增加一个请求,代码就要多嵌套、缩进一个层级。当调用逻辑复杂、请求数较多时,代码会非常难以维护。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这种情况也称为 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"“回调地狱”","attrs":{}},{"type":"text","text":"[D],在 JavaScript 语言中相关讨论颇多,可以作为参考。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"submit(\"A\").await(resultA -> {\n submit(\"B\").await(resultB -> {\n submit(\"C\").await(resultC -> {\n submit(\"D\").await(resultD -> {\n submit(\"E\").await(resultE -> {\n process(resultE);\n });\n });\n });\n });\n});","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为改进代码结构,我们对 Promise.await(Consumer) 方法进行封装,提供Promise.then(Function>)方法,如下所示。类似于await(),then()也可以注册一个回调函数resultX->submit(\"X+1\"),回调函数处理响应数据resultX,并提交下一请求submit(\"X+1\");then()的返回值即submit(\"X+1\")的返回值,用于通知下一请求的响应数据resultX+1。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"Promise resultPromiseA = submit(\"A\");\nPromise resultPromiseB = resultPromiseA.then(resultA -> submit(\"B\"));\nPromise resultPromiseC = resultPromiseB.then(resultB -> submit(\"C\"));\nPromise resultPromiseD = resultPromiseC.then(resultC -> submit(\"D\"));\nPromise resultPromiseE = resultPromiseD.then(resultD -> submit(\"E\"));\nresultPromiseE.await(resultE -> process(resultE));","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下来,我们将中间变量 resultPromiseA-E 内联,即得到基于then()的链式调用结构。相比于await(),then()消除了套娃般的嵌套回调。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"submit(\"A\")\n .then(resultA -> submit(\"B\")) // 返回resultPromiseB\n .then(resultB -> submit(\"C\")) // 返回resultPromiseC\n .then(resultC -> submit(\"D\")) // 返回resultPromiseD\n .then(resultD -> submit(\"E\")) // 返回resultPromiseE\n .await(resultE -> process(resultE));","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后,我们来看一下 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Promise.then() 的一种简单实现","attrs":{}},{"type":"text","text":",如下所示:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"then()方法提供一个泛型模版Next,以说明下一请求的响应数据类型。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"根据泛型模版Next,then()内部创建Promise作为返回值,用于通知下一请求的响应数据。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"对于当前请求,调用await()注册响应数据的回调result;当收到响应数据后,执行函数func,以提交下一请求:func.apply(result)。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"当收到下一请求的响应数据后,Promise被通知:nextPromise::signalAll。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"public Promise then(Function> func) {\n Promise nextPromise = new Promise<>();\n await(result -> {\n Promise resultPromiseNext = func.apply(result);\n resultPromiseNext.await(nextPromise::signalAll);\n });\n return nextPromise;\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"注意,这里只展示了纯异步重载Promise.then(Function>)。根据回调函数是否有返回值、同步执行还是异步执行,Promise可以提供then()的更多种重载;受限于Java语法,如编译器无法辨析各个重载,则可以使用函数名称进行显式区别,如:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"thenRun(Runnable)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"thenAccept(Consumer)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"thenApply(Function)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"thenApplyAsync(Function>)","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"4.2.并行请求:LatchPromise","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上一小节介绍了“顺序请求”的场景,即多条请求需要依次执行;而“并行请求”场景下,多条请求之间没有顺序约束,但是我们仍然需要等待全部请求返回,才能执行后续操作。例如,我们需要查询多张数据库表,这些查询语句可以同时执行;但是必须等待每条查询都返回,我们才能获得完整信息。jdk 提供CountDownLatch 来实现这一场景,但是其只支持同步等待;作为改进,我们采用 LatchPromise 实现相同的功能,并且支持纯异步API。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以数据库访问为例,如下所示的代码","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"展示了 LatchPromise 的使用","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"提交3条请求,并获取每个请求所对应的 Promise 对象 resultPromise1-3,以获取响应数据。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"创建LatchPromise对象,并向其注册需要等待的 Promise 对象resultPromise1-3。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"LatchPromise.untilAllSignaled()返回一个 Promise 对象 allSignaled。当所注册的 resultPromise1-3均被通知后,allSignaled 会被通知。allSignaled 的类型为 VoidPromise,表示 allSignaled 被通知时没有需要处理的响应数据。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"在 allSignaled 上注册回调,在回调函数中调用 resultPromiseX.await() 获取实际的响应数据;此时由于请求已执行完毕,故 await() 立刻返回而不阻塞。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"/* 创建Promise对象*/\nPromise resultPromise1 = db.writeAsync(\"a\");\nPromise resultPromise2 = db.writeAsync(\"b\");\nPromise resultPromise3 = db.writeAsync(\"c\");\n\n/* 向LatchPromise注册要等待的Promise*/\nLatchPromise latch = new LatchPromise();\nlatch.add(resultPromise1);\nlatch.add(resultPromise2);\nlatch.add(resultPromise3);\n\n/* 等待全部Promise被通知*/\nVoidPromise allSignaled = latch.untilAllSignaled();\nallSignaled.await(() -> {\n String result1 = resultPromise1.await();\n String result2 = resultPromise2.await();\n String result3 = resultPromise3.await();\n process(result1, result2, result3);\n});","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作为对比,下面的代码使用 CountDownLatch 实现相同功能,但是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"存在以下缺陷","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"CountDownLatch.await() 只支持同步等待。在纯异步场景下是无法接受的。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"CountDownLatch 对业务逻辑有侵入性。程序员需要在业务逻辑中添加对 CountDownLatch.countDown()的调用,以控制CountDownLatch 的时序;相反,LatchPromise 依赖本来就已经存在的 resultPromise 对象,而不需要编写额外的时序控制代码。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"CountDownLatch 引入了冗余逻辑。创建 CountDownLatch 时,必须在构造参数中填写要等待的请求数;因此,一旦所提交的请求的数目改变,就必须相应地更新创建 CountDownLatch 的代码,修改构造参数。","attrs":{}}]}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"CountDownLatch latch = new CountDownLatch(3);\nresultPromise1.await(result1 -> latch.countDown());\nresultPromise2.await(result2 -> latch.countDown());\nresultPromise3.await(result3 -> latch.countDown());\n\nlatch.await();\nString result1 = resultPromise1.await();\nString result2 = resultPromise2.await();\nString result3 = resultPromise3.await();\nprocess(result1, result2, result3); ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后,我们来看一下 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"LatchPromise 的参考实现","attrs":{}},{"type":"text","text":"。代码原理如下所示:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"设立countUnfinished变量,记录还没有被通知的Promise对象的数目","attrs":{}},{"type":"text","text":"。每当注册一个Promise对象,countUnfinished递增;每当一个Promise被通知,countUnfinished递减。当countUnfinished减到0时,说明所注册全部Promise对象都被通知了,故通知allSignaled。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"设立noMore变量","attrs":{}},{"type":"text","text":",记录是否还需要继续注册新的Promise对象,仅当调用了untilAllSignaled()才认为完成注册;在此之前,即使countUnfinished减至0,也不应该通知allSignaled。考虑这样一种情况:需要注册并等待resultPromise1-3,其中resultPromise1、2在注册期间即已被通知,而resultPromise3未被通知。如果不判断noMore,那么注册完resultPromise1、2后,countUnfinished即已减至0,导致提前通知allSignaled;这是一个时序错误,因为实际上resultPromise3还没有完成。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"为保证线程安全,访问变量时须上锁","attrs":{}},{"type":"text","text":",此处使用synchronized来实现。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"注意","attrs":{}},{"type":"text","text":",调用untilAllSignaled()时,如果countUnfinished的初值已经为0,则应立刻通知allSignaled;因为countUnfinished已经不可能再递减,之后没有机会再通知allSignaled了。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"// private static class Lock。无成员,仅用于synchronized(lock)\nprivate final Lock lock = new Lock();\nprivate int countUnfinished = 0;\nprivate final VoidPromise allSignaled = new VoidPromise();\n\npublic void add(Promise> promise) {\n if (promise.isSignaled()) {\n return;\n }\n\n synchronized (lock) {\n countUnfinished++;\n }\n\n promise.await(unused -> {\n synchronized (lock) {\n countUnfinished--;\n if (countUnfinished == 0 && noMore) {\n allSignaled.signalAll();\n }\n }\n });\n}\n\npublic VoidPromise untilAllSignaled() {\n synchronized (lock) {\n if (countUnfinished == 0) {\n allSignaled.signalAll();\n } else {\n noMore = true;\n }\n }\n\n return allSignaled;\n}","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"4.3.批量请求:ExecutorAsync","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}},{"type":"strong","attrs":{}}],"text":"批量请求的特性","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"“批量请求”(也称“bulk”、“batch”)是指发送一条消息即可携带多条请求,主要用于数据库访问和远程调用等场景。由于减少了网络 IO 次数、节约了构造和传输消息的开销,批量请求能有效提升吞吐量。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"很多数据库 API 都支持批量读写,如JDBC PreparedStatement[E]、elasticsearch bulk API[F]、mongo DB insertMany()[G]、influx DB BatchPoints[H],读者可以查阅参考文献进一步了解。为了提升性能,部分API会牺牲易用性。其中,elasticsearch bulk API 对调用者的限制最少,允许混杂增删改等不同类型的请求,允许写入不同的数据库表(index);mongo DB、influx DB 次之,一个批量请求只能写入同一个数据库表,但是可以自定义每条数据的字段;PreparedStatement 的灵活性最低,其定义了 SQL 语句的模版,调用者只能填写模版参数,而不能修改语句结构。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"虽然数据库 API 已经支持批量访问,但是很多原生 API 仍然需要调用者自己构造批量请求,需要调用者处理请求组装、批量大小、并发请求数等复杂的细节。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在此,我们设计出通用组件 ExecutorAsync,封装请求调度策略以提供更简洁的API。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"ExecutorAsync 的使用流程","attrs":{}},{"type":"text","text":"如下面的代码片段所示:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"类似于线程池 ExecutorService.submit(),调用者可以调用ExecutorAsync.submit()来提交一个请求。其中,请求以数据对象 Request 表示,用于存储请求类型和请求参数。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"提交请求后,调用者获得 Promise 对象,以获取响应数据。由于使用了 Promise,ExecutorAsync 支持纯异步操作,提交请求和获取响应数据都不需要阻塞。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"ExecutorAsync 内部对请求进行调度,并非提交一条请求就立刻执行,而是每隔固定时间收集一批请求,将其组装为一个批量请求,再调用实际的数据库访问 API。如果数据库访问 API 允许,那么一批请求可以混杂不同的请求类型,或者操作不同的数据库表。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"ExecutorAsync executor = new ExecutorAsync();\nPromise<...> resultPromise1 = executor.submit(new Request(\"data1\"));\nPromise<...> resultPromise2 = executor.submit(new Request(\"data2\"));\nPromise<...> resultPromise3 = executor.submit(new Request(\"data3\"));","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"具体而言,ExecutorAsync 支持如下调度策略:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"1.排队","attrs":{}},{"type":"text","text":",如图4-4a所示。调用者提交请求Request后不要立刻执行,而是将其缓存在队列queue中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c2/c275260ea662c626ef1d75971533626a.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"图4-4a ExecutorAsync特性:排队","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"2.批量","attrs":{}},{"type":"text","text":",如图4-4b所示。每隔固定时间间隔,ExecutorAsync从队列中取出若干条请求,将其组装为批量请求bulk,并调用底层数据库API提交给服务端。如果队列长度增长得很快,我们也可以定义一个批量大小bulk size,当队列长度到达该值时立刻组装一个批量请求并提交。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/aa/aaf8563140b3b3be19e41f70c877bb4f.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"图4-4b ExecutorAsync 特性:批量","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"3.并发","attrs":{}},{"type":"text","text":",如图4-4c所示。如果底层数据库 API 支持异步提交请求,那么 ExecutorAsync 就可以充分利用这种特性,连续提交多个批量请求,而不需要等待之前的批量请求返回。为避免数据库服务器超载,我们可以定义并发度 parallelism,限制正在执行(in flight)的批量请求的数目;当达到限制时,如果调用者再提交新的请求,就暂存在队列 queue 中等待执行,而不会组装新的批量请求。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/68/6821fb25a533eb75e1fc6c004768c0a1.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"图4-4c ExecutorAsync 特性:并发","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"4.丢弃","attrs":{}},{"type":"text","text":"。如图4-4d所示。在上文提到的 bulk size 和 parallelism 的限制下,如果提交请求的速率远高于服务端响应的速率,那么大量请求就会堆积在队列中等待处理,最终导致超时失败。在这种情况下,将请求发送给服务端已经没有意义,因为调用者已经认定请求失败,而不再关心响应数据。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e0/e04761d4dd1d2ad408cb98228b85b9ba.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"图4-4d 请求超时","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此,ExecutorAsync 应该及时从队列中移除无效请求,而剩余请求仍然“新鲜”。这种策略能够强制缩短队列长度,以降低后续请求在队列中的堆积时长、预防请求超时;同时,由于避免存储和发送无效请求,这种策略也能节约内存和 IO 开销。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/17/17c008ce182273eae10ec0b90ebcf66e.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"图4-4e ExecutorAsync特性:丢弃","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}},{"type":"strong","attrs":{}}],"text":"批量请求的实现","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上一小节我们看到了 ExecutorAsync 的调度策略,包括排队、批量、并发、丢弃。如下面的代码所示,ExecutorAsync 只需对外提供 submit(Request) 方法,用于提交单条请求。请求以数据对象 Request 表示,其字段 Request.resultPromise 是 Promise 对象,用于通知响应数据;在需要进行异常处理的场景下,我们使用 PromiseOrException作为 Promise 的实现,其中泛型模版T改为响应数据的实际类型。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"public class ExecutorAsync {\n\n public PromiseOrException submit(Request request) {\n return request.resultPromise;\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下来我们来看看 ExecutorAsync 的实现原理。由于源码细节较多、篇幅较长,故本节用流程图的形式,来讲解更高层的设计,如图4-5所示。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/cd/cd51a4373a87489cbbd5c2264d782528.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"图4-5 ExecutorAsync原理","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"1.提交请求","attrs":{}},{"type":"text","text":"。调用者调用 ExecutorAsync.submit(Request),每次调用提交一条请求。该条请求存入队列 queue,等待后续调度执行。参数 Request 的结构如下面的代码所示,包括下列字段:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"predicate:函数,判断请求是否有效,无效请求(如超时的请求)将被丢弃。详见步骤2。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"resultPromise:通知响应数据。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"public class Request {\n public final PredicateE predicate; \n public final PromiseOrException resultPromise;\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"2.每隔固定间隔,或者queue.size()达到bulk size,尝试组装批量请求","attrs":{}},{"type":"text","text":"。从队列queue中依次取出请求,每条请求执行函数 Request.predicate,以判断是否仍然要提交该请求;取出的有效请求的条数,不超过bulk size。predicate 是一个函数,类似于 jdk Predicate 接口,形式如下面的代码所示。接口函数test()可以正常返回,表示请求仍然有效;也可以抛出异常,说明请求无效的原因,如等待超时。如果抛出异常,则该条请求直接丢弃,并将发生的异常将通知给 Request.resultPromise,使得调用者执行异常处理逻辑。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"public interface PredicateE {\n void test() throws Exception;\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"3.提交批量请求","attrs":{}},{"type":"text","text":"。第2步从队列 queue 中取出了至多 bulk size 条请求,将其作为参数调用RequestFunc.execute(requests),以提交批量请求。接口 RequestFunc 的形式如下面的代码所示。接口方法execute(requests)以若干条请求为参数,将其组装为批量请求,调用底层的数据库 API 来提交。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"public interface RequestFunc{\n void execute(List> requests);\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"4.当收到响应后,对于每条请求,依次向 Request.resultPromise 通知响应数据","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"5.为防止服务端超载,ExecutorAsync 可限制并发请求数不超过 parallelism","attrs":{}},{"type":"text","text":"。我们设置计数变量 inFlight=0,以统计正在执行的批量请求数:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"a.当尝试组装批量请求(步骤2)时,首先判断inFlight
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章