摘要:定時(shí)器例子之前通過調(diào)用定時(shí)器,需要傳一個(gè)回調(diào),然后所有的代碼邏輯都包在里面。這里定時(shí)器會阻塞在這一行,直到一秒后才會執(zhí)行下面的一行。
之前介紹過quasar,如果你希望在vert.x項(xiàng)目里使用coroutine的話,建議使用vertx-sync。本篇將介紹vertx-sync。
本來打算另起一篇,寫其他方面的東西,但是最近比較忙,就先寫一篇實(shí)踐方面的文章。
vertx-sync是什么上一篇我們已經(jīng)講了 Fiber 相關(guān)的知識,想必大家對Java實(shí)現(xiàn)類似Golang的coroutine已經(jīng)有印象了,既然Java世界里有第三方提供了這么好的庫,那我們就看看怎么跟 vert.x 結(jié)合起來使用。
vert.x官方為了解決異步代碼編寫的困難,使之更加同步化對開發(fā)人員更友好,便基于quasar包裝了一個(gè)的同步庫,vertx-sync,該庫的作者同樣也是vert.x的原作者,所以完成度還是很高的。
vertx-sync 對外只是暴露了幾個(gè)簡單的靜態(tài)API,來完成對vert.x體系內(nèi)一系列的操作包裝,其實(shí)主要也就是三靜態(tài)API而已。
Sync.fiberHandler 如果你希望你的handler里有一些邏輯需要在Fiber里運(yùn)行,則你的handler必須用這個(gè)方法包一下。
Sync.awaitEvent 從Handler里返回一個(gè)事件結(jié)果(同步的),且 不會阻塞EventLoop
Sync.awaitResult 從Handler里返回一個(gè)異步事件結(jié)果(同步的),且 不會阻塞EventLoop
直接看介紹可能有點(diǎn)不直觀,下面跑幾個(gè)例子。
使用vertx-sync之前介紹過quasar,如果你希望在項(xiàng)目里使用coroutine的話,需要在JVM里設(shè)置一個(gè)參數(shù),用于應(yīng)用啟動(dòng)前修改字節(jié)碼(注入一些中斷方法),從而可以達(dá)到協(xié)程的目的。
具體方法也很簡單。
-javaagent:/path/to/the/quasar-core-0.7.5-jdk8.jar
如果是基于Maven跑單元測試,那只需要引用quasar instrument的插件就可以里
com.vlkan quasar-maven-plugin 0.7.3 true true true instrument co.paralleluniverse quasar-core 0.7.5
上面是一些非常必要的準(zhǔn)備工作,否則你無法使用quasar以及vertx-sync。
之前通過vert.x調(diào)用定時(shí)器,需要傳一個(gè)回調(diào)handler,然后所有的代碼邏輯都包在里面。
vertx.setTimer(1000L, h -> { System.out.println("time"s up"); });
現(xiàn)在我們來重新塑造一下三觀。
awaitEvent(h -> vertx.setTimer(1000L, h)); System.out.println("time"s up");
這里定時(shí)器會阻塞在awaitEvent這一行,直到一秒后才會執(zhí)行下面的一行。有點(diǎn)類似執(zhí)行 Thread.sleep(1000L),但是并不會阻塞 EventLoop 因?yàn)閝uasar會在EventLoop基礎(chǔ)之上再開啟一個(gè)fiber。
下面我看個(gè)稍微復(fù)雜點(diǎn)的例子。
我們先用傳統(tǒng)的回調(diào)方式使用vert.x的HttpClient API。
HttpClientRequest httpClientRequest = vertx.createHttpClient().get("leapcloud.cn"); httpClientRequest.handler(response -> { response.handler(responseBody -> { System.out.println(responseBody.toString()); }); }).end();
這里有兩層回調(diào)嵌套,一層是得到Http的Response,另一層是從Response里得到詳細(xì)的body。因?yàn)橛衛(wèi)ambda表達(dá)式才使得Java現(xiàn)在看起來并不是那么惡心。但是如果我們需要根據(jù)body的內(nèi)容進(jìn)一步做判斷去繼續(xù)請求其他頁面,則嵌套會變的非常的深。下面嘗試改造成sync方式看看。
HttpClientRequest httpClientRequest = vertx.createHttpClient().get("leapcloud.cn"); HttpClientResponse response = awaitEvent(Sync::fiberHandler); Buffer body = awaitEvent(response::handler); System.out.println(body.toString());
額,是不是感覺看著很舒服,無嵌套,直接順序下來,非常的直觀,加上Java8特有的方法引用,會讓代碼更精簡。
寫過vert.x同學(xué)肯定知道其vertx-jdbc-client為了使其兼容異步開發(fā)模型,將JDBC的底層線程池用異步方式包裝了一下,也就是說JDBC層還是通過線程池去訪問數(shù)據(jù)庫的,但是是通過vert.x的context做了層封裝,使其可以將結(jié)果放到對應(yīng)的 EventLoop 里,這樣比較符合vert.x的開發(fā)風(fēng)格。但是帶來的弊端就是嵌套太深。
final JDBCClient client = JDBCClient.createShared(vertx, new JsonObject() .put("url", "jdbc:hsqldb:mem:test?shutdown=true") .put("driver_class", "org.hsqldb.jdbcDriver") .put("max_pool_size", 30)); client.getConnection(conn -> { if (conn.failed()) { System.err.println(conn.cause().getMessage()); return; } final SQLConnection connection = conn.result(); connection.execute("create table test(id int primary key, name varchar(255))", res -> { if (res.failed()) { throw new RuntimeException(res.cause()); } // insert some test data connection.execute("insert into test values(1, "Hello")", insert -> { // query some data connection.query("select * from test", rs -> { for (JsonArray line : rs.result().getResults()) { System.out.println(line.encode()); } // and close the connection connection.close(done -> { if (done.failed()) { throw new RuntimeException(done.cause()); } }); }); }); }); });
上面代碼可以是不是有點(diǎn)惡心呢?嘗試改造一下吧。
final JDBCClient client = JDBCClient.createShared(vertx, new JsonObject() .put("url", "jdbc:hsqldb:mem:test?shutdown=true") .put("driver_class", "org.hsqldb.jdbcDriver") .put("max_pool_size", 30)); try (SQLConnection conn = awaitResult(jdbc::getConnection)) { awaitResult(h -> conn.execute("create table test(id int primary key, name varchar(255))", h)); awaitResult(h -> conn.execute("insert into test values(1, "Hello")", h)); ResultSet query = awaitResult(h -> conn.query("select * from test", h)); for (JsonArray line : query.result.getResults()) { System.out.println(line.encode()); } AsyncResult done = awaitResult(h -> conn.close(h)); if (done.failed()) { throw new RuntimeException(done.cause()) } } catch (Exception e) { e.printStackTrace(); }
除了一個(gè)try catch,其他都沒有嵌套,整體邏輯的可讀性非常高,完全是線性的。
你也許會發(fā)現(xiàn)我們似乎一直都沒有用到 fiberHandler 這個(gè)靜態(tài)方法,上面雖然寫了定義,可能大家還是不能夠理解,這里結(jié)合場景也許能更好理解。
我們嘗試實(shí)現(xiàn)一個(gè)操作很耗時(shí)的邏輯然后包到fiber里,避免 EventLoop 被阻塞。這里你也許會很好奇,既然 Fiber 這么廉價(jià)開啟10萬8萬的無所謂啊,恩,這里再提一下quasar的重點(diǎn)部分: fiber可以很廉價(jià)的被創(chuàng)造出來,但是他本質(zhì)上還是跑在一個(gè)線程上面,如果其中一個(gè)fiber執(zhí)行了非常耗時(shí)的操作,則后面的fiber會一直等待,從而造成整個(gè)線程阻塞。 也就是說一個(gè)fiber不能執(zhí)行非常耗時(shí)的操作,比如計(jì)算100萬以內(nèi)的素?cái)?shù)之和,對于這種操作,我們可以通過直接將邏輯放到vert.x的worker線程里多帶帶去跑,然后通過fiber包裝一下就可以了。
AsyncResultresult = awaitResult(fiberHandler(h -> vertx.executeBlocking((Handler >) event -> { //求百萬以內(nèi)素?cái)?shù)之和,這里的邏輯會在vert.x的worker線程里跑。隨便耗時(shí)多久,都不會阻塞EventLoop long sum = sumOfPrime(1, 000, 000); event.complete(sum); }, h))); //打印結(jié)果 System.out.println(result.result());
這里你會注意到 awaitReslt 里用了 fiberHandler ,因?yàn)閑xecuteBlocking里的 handler 邏輯本身并沒有跑在fiber體系下,所以會導(dǎo)致無效,而fiberHandler的作用就是將一段vert.x的handler包到 fiber 里。使之后續(xù)的await可以將其結(jié)果返回,這里使用awaitResult返回結(jié)果。
我們再深入一點(diǎn)看看 fiberHandler 方法里到底干了什么。
@Suspendable public staticHandler fiberHandler(Handler handler) { FiberScheduler scheduler = getContextScheduler(); return p -> new Fiber (scheduler, () -> handler.handle(p)).start(); }
這里獲取Fiber的調(diào)度器,然后直接new了一個(gè) Fiber ,避免了我們自己對邏輯做Fiber包裝。是不是很簡單呢。
總結(jié)相比較傳統(tǒng)的回調(diào)Handler,vertx-sync的包裝十分優(yōu)雅,基本可以恢復(fù)到同步方法調(diào)用級別,很好的減輕了異步回調(diào)帶來的心智負(fù)擔(dān)。
但是這個(gè)畢竟不是JVM級別的實(shí)現(xiàn),所以或多或少還是有點(diǎn)門檻的,比如部署的時(shí)候,需要通過設(shè)置JVM參數(shù)來修改部分字節(jié)碼,同時(shí)還要注意一些需要掛起的方法上面加注釋或者強(qiáng)行讓其拋出可中斷異常。個(gè)人建議在一些不重要的工具級項(xiàng)目里使用,非常重要的項(xiàng)目不推薦使用,當(dāng)然了如果你覺得你的業(yè)務(wù)只需要依賴vert.x那么強(qiáng)烈你推薦你使用,只要記得打開 BlockingChecker 就好,可以即時(shí)的發(fā)現(xiàn)潛在的阻塞邏輯。
責(zé)編:另外7.24號,我們力譜宿云攜手Vert.x中國用戶組在太庫·上海的贊助下舉辦了一場關(guān)于Vert.x的技術(shù),之后還會有系列活動(dòng),有興趣的同學(xué)們可以關(guān)注我們的微信公眾號:MaxLeap_yidongyanfa,了解更多資訊。
作者往期佳作
次時(shí)代Java編程(一) Java里的協(xié)程
作者信息
本文系力譜宿云 LeapCloud旗下MaxLeap團(tuán)隊(duì)_UX成員:劉小溪 【原創(chuàng)】
力譜宿云首發(fā)地址:https://blog.maxleap.cn/archi...
劉小溪,Maxleap的高級開發(fā)工程師,喜歡倒騰一些有意思的技術(shù)框架,對新的技術(shù)以及語言非常有興趣,以前在shopex擔(dān)任架構(gòu)師,目前在Maxleap負(fù)責(zé)基礎(chǔ)架構(gòu)以及服務(wù)框架這塊技術(shù),同時(shí)也會對Vert.x的社區(qū)提供一些開源上的支持。
關(guān)于MaxLeap
MaxLeap 是力譜宿云推出,為移動(dòng)應(yīng)用開發(fā)、運(yùn)營提供一站式后端云服務(wù), 包括應(yīng)用開發(fā)所需的后端云數(shù)據(jù)庫、云數(shù)據(jù)源、云代碼、云容器、 IM、移動(dòng)支付、應(yīng)用內(nèi)社交、第三方登錄、社交分享等基礎(chǔ)服務(wù),以及針對應(yīng)用運(yùn)營的數(shù)據(jù)分析、推送營銷,用戶支持等服務(wù), 覆蓋移動(dòng)應(yīng)用的研發(fā)、運(yùn)營完整生命周期。
MaxLeap 致力于讓移動(dòng)應(yīng)用開發(fā)運(yùn)營更快速簡單。
官網(wǎng):https://maxleap.cn/
歡迎掃二維碼,關(guān)注我們
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/64975.html
摘要:用基于快速實(shí)現(xiàn)一個(gè)最簡單的代理服務(wù)器,只需要分鐘時(shí)間。監(jiān)控統(tǒng)計(jì)客戶端的請求情況,請求分布統(tǒng)計(jì)請求類型等,以此來優(yōu)化數(shù)據(jù)庫的使用。 用java8基于vert.x3 快速實(shí)現(xiàn)一個(gè)最簡單的mysql代理服務(wù)器,只需要5分鐘時(shí)間。 showImg(/img/bVz0vh); 什么是mysql 代理? mysql代理是介于client端和mysql服務(wù)端中間層服務(wù),如下圖所示: showImg(...
摘要:為此我們可以使用來發(fā)送一個(gè)比如流,會自動(dòng)為我們實(shí)現(xiàn)管道傳輸數(shù)據(jù)。因?yàn)榱鞯拈L度不確定,請求將使用即分塊傳輸。方法可以被多次安全的調(diào)用,這對于無數(shù)據(jù)的請求很容易復(fù)用配置和。如此在接受響應(yīng)的回調(diào)函數(shù)里可以直接得到設(shè)置的響應(yīng)解碼體。 本文主要介紹Vert.x 3.4.x 版本新組件Web Client的使用 showImg(https://segmentfault.com/img/bVL8iX...
摘要:為什么我會說它們是一樣的簡單思考一下我的后端書架后端掘金我的后端書架月前本書架主要針對后端開發(fā)與架構(gòu)。一方案調(diào)研版本選擇當(dāng)前主流版本是和應(yīng)用的后臺運(yùn)行配置后端掘金醬油一篇,整理一下關(guān)于后臺運(yùn)行的一些配置方式。 分享 50 個(gè)完整的 React Native 項(xiàng)目 - 掘金本文為 Marno 原創(chuàng),轉(zhuǎn)載必須保留出處! 公眾號 aMarno,關(guān)注后回復(fù) RN 加入交流群 簡書專題《 Rea...
閱讀 2997·2021-10-19 11:46
閱讀 989·2021-08-03 14:03
閱讀 2949·2021-06-11 18:08
閱讀 2921·2019-08-29 13:52
閱讀 2774·2019-08-29 12:49
閱讀 493·2019-08-26 13:56
閱讀 934·2019-08-26 13:41
閱讀 856·2019-08-26 13:35