内容简介:Sirix是一个存储系统,它的核心是日志结构,读取可以是随机的,并且在事务提交期间将写入批处理并同步到磁盘。数据永远不会写回到同一个地方,因此不会就地修改,相反,Sirix在记录级别使用写时复制(COW)(因此,它创建页面片段并且通常不复制整个页面),每次必须修改页面时,已更改的记录都会写入新位置,确切复制哪些记录取决于所使用的版本控制算法。对数据库/资源的更改发生在资源绑定事务中。因此,必须打开ResourceManager才能创建写入事务。在任何时候,只允许与N读取事务同时进行一次写入事务。每个事务
Sirix是一个存储系统,它的核心是日志结构,读取可以是随机的,并且在事务提交期间将写入批处理并同步到磁盘。数据永远不会写回到同一个地方,因此不会就地修改,相反,Sirix在记录级别使用写时复制(COW)(因此,它创建页面片段并且通常不复制整个页面),每次必须修改页面时,已更改的记录都会写入新位置,确切复制哪些记录取决于所使用的版本控制算法。
对数据库/资源的更改发生在资源绑定事务中。因此,必须打开ResourceManager才能创建写入事务。在任何时候,只允许与N读取事务同时进行一次写入事务。每个事务都绑定到一个修订版,而它们可以在任何修订版上打开,无论哪个修订版都无关紧要。
Vert.x在Node.js和JVM之后进行了严格的建模。Vert.x中的所有内容都应该是非阻塞的。因此,称为事件循环的单个线程可以处理大量请求。阻止调用必须在特殊的线程池上处理。默认值是每个CPU两个事件循环(多反应器模式)。
我们正在使用Kotlin,因为它简单而简洁。其中一个非常有趣的功能是协同程序。从概念上讲,它们就像非常轻量级的线程 另一方面,创建线程非常昂贵。关于协同程序的一个很酷的事情是,它们允许编写几乎像顺序的异步代码。每当一个协程将被挂起时,底层线程不会被阻塞并且可以被重用。在引擎盖下,每个挂起函数通过Kotlin编译器获得另一个参数,这是一个延续,它存储恢复函数的位置(正常恢复,恢复异常)。
Keycloak用作OAuth2授权服务器(密码凭据流量),因为我们决定不 自己 实现授权。
为了获得访问令牌,首先必须针对POST / login进行请求- 使用身份中作为JSON对象发送的用户名/密码凭证进行路由。实现代码:
post(<font>"/login"</font><font>).produces(</font><font>"application/json"</font><font>).coroutineHandler { rc ->
val userJson = rc.bodyAsJson
val user = keycloak.authenticateAwait(userJson)
rc.response().end(user.principal().toString())
}
</font>
coroutine-handler是一个简单的扩展函数:
<font><i>/* An extension method for simplifying coroutines usage with Vert.x Web routers. */</i></font><font>
<b>private</b> fun Route.coroutineHandler(fn: suspend (RoutingContext) -> Unit) {
handler { ctx ->
launch(ctx.vertx().dispatcher()) {
<b>try</b> {
fn(ctx)
} <b>catch</b> (e: Exception) {
ctx.fail(e)
}
}
}
}
</font>
协程序在Vert.x事件循环(调度程序)上启动。
这是为了执行更长的运行处理程序:
vertxContext.executeBlockingAwait(Handler < Future < Nothing >> {
<font><i>//更长时间运行任务</i></font><font>
})
</font>
Vert.x为这类任务使用不同的线程池。因此,该任务在另一个线程中执行。请注意当协程被暂停,事件循环不会被阻止。
现在我们再次将焦点转移到我们的API,并展示它是如何设计的。我们首先需要设置我们的服务器和Keycloak。
一旦两个服务器都启动并运行,我们就能够编写一个简单的HTTP客户端。我们首先必须让/login使用指定的“用户名/密码”JSON-Object 从端点获取令牌。在Kotlin中使用异步HTTP客户端(来自Vert.x),它看起来像这样:
val server = <font>"https://localhost:9443"</font><font>
val credentials = json {
obj(</font><font>"username"</font><font> to </font><font>"testUser"</font><font>,
</font><font>"password"</font><font> to </font><font>"testPass"</font><font>)
}
val response = client.postAbs(</font><font>"$server/login"</font><font>).sendJsonAwait(credentials)
<b>if</b> (200 == response.statusCode()) {
val user = response.bodyAsJsonObject()
val accessToken = user.getString(</font><font>"access_token"</font><font>)
}
</font>
然后,必须在Authorization HTTP-Header中为每个后续请求发送此访问令牌。存储第一个资源看起来像这样(简单的HTTP PUT-Request):
val xml = <font>""</font><font></font><font>"
<xml>
foo
<bar/>
</xml>
</font><font>""</font><font></font><font>".trimIndent()
<b>var</b> httpResponse = client.putAbs(</font><font>"$server/database/resource1"</font><font>).putHeader(HttpHeaders.AUTHORIZATION.toString(), </font><font>"Bearer $accessToken"</font><font>).sendBufferAwait(Buffer.buffer(xml))
<b>if</b> (200 == response.statusCode()) {
println(</font><font>"Stored document."</font><font>)
} <b>else</b> {
println(</font><font>"Something went wrong ${response.message}"</font><font>)
}
</font>
首先,创建一个名称database带有一些元数据的空数据库,然后使用名称存储XML片段resource1。PUT HTTP-Request是幂等的。具有相同URL端点的另一个PUT-Request将删除以前的数据库和资源并重新创建数据库/资源
HTTP-Response应为200,产生HTTP-body:
<<b>rest</b>:sequence xmlns:<b>rest</b>=<font>"https://sirix.io/rest"</font><font>>
<<b>rest</b>:item>
<xml <b>rest</b>:id=</font><font>"1"</font><font>>
foo
<bar <b>rest</b>:id=</font><font>"3"</font><font>/>
</xml>
</<b>rest</b>:item>
</<b>rest</b>:sequence>
</font>
以上是从存储系统为元素节点序列化生成ID。
然后通过GET HTTP-Request,https://localhost:9443/database/resource1我们还可以再次检索存储的资源。
然而,到目前为止,这并不是很有趣。我们可以更新资源POST-Request。假设我们像以前一样检索了访问令牌,我们可以简单地执行POST-Request并使用我们之前收集的有关node-ID的信息:
val xml = <font>""</font><font></font><font>"
<test>
yikes
<bar/>
</test>
</font><font>""</font><font></font><font>".trimIndent()
val url = </font><font>"$server/database/resource1?nodeId=3&insert=asFirstChild"</font><font>
val httpResponse = client.postAbs(url).putHeader(HttpHeaders.AUTHORIZATION
.toString(), </font><font>"Bearer $accessToken"</font><font>).sendBufferAwait(Buffer.buffer(xml))
</font>
有趣的部分是URL,我们用作端点。我们简单地说,选择ID为3的节点,然后将给定的XML片段作为第一个子片段插入。这将生成以下序列化XML文档:
<<b>rest</b>:sequence xmlns:<b>rest</b>=<font>"https://sirix.io/rest"</font><font>>
<<b>rest</b>:item>
<xml <b>rest</b>:id=</font><font>"1"</font><font>>
foo
<bar <b>rest</b>:id=</font><font>"3"</font><font>>
<test <b>rest</b>:id=</font><font>"4"</font><font>>
yikes
<bar <b>rest</b>:id=</font><font>"6"</font><font>/>
</test>
</bar>
</xml>
</<b>rest</b>:item>
</<b>rest</b>:sequence>
</font>
每个PUT-以及POST请求都隐含commits了底层事务。因此,我们现在能够再次发送第一个GET请求来检索整个资源的内容,例如通过指定一个简单的XPath查询,在所有版本中选择根节点GET https://localhost:9443/database/resource1?query=/xml/all-time::*并获得以下XPath结果:
<<b>rest</b>:sequence xmlns:<b>rest</b>=<font>"https://sirix.io/rest"</font><font>>
<<b>rest</b>:item <b>rest</b>:revision=</font><font>"1"</font><font> <b>rest</b>:revisionTimestamp=</font><font>"2018-12-20T18:44:39.464Z"</font><font>>
<xml <b>rest</b>:id=</font><font>"1"</font><font>>
foo
<bar <b>rest</b>:id=</font><font>"3"</font><font>/>
</xml>
</<b>rest</b>:item>
<<b>rest</b>:item <b>rest</b>:revision=</font><font>"2"</font><font> <b>rest</b>:revisionTimestamp=</font><font>"2018-12-20T18:44:39.518Z"</font><font>>
<xml <b>rest</b>:id=</font><font>"1"</font><font>>
foo
<bar <b>rest</b>:id=</font><font>"3"</font><font>>
<xml <b>rest</b>:id=</font><font>"4"</font><font>>
foo
<bar <b>rest</b>:id=</font><font>"6"</font><font>/>
</xml>
</bar>
</xml>
</<b>rest</b>:item>
</<b>rest</b>:sequence>
</font>
一般来说,我们支持几个额外的时间XPath轴:future ::,future-or-self ::,past ::,past-or-self ::,previous ::,previous-or-self ::,next ::, next-or-self ::,first ::,last ::,all-time ::
通过在GET请求中指定序列化(开始和结束修订参数)的一系列修订,可以实现相同的目的:
GET https://localhost:9443/database/resource1?start-revision=1&end-revision=2
或通过时间戳:
我们肯定也能够通过更新XQuery表达式(不是非常RESTful)或使用简单的DELETEHTTP请求来删除资源或其任何子树:
val url = <font>"$server/database/resource1?nodeId=3"</font><font>
val httpResponse = client.deleteAbs(url).putHeader(HttpHeaders.AUTHORIZATION
.toString(), </font><font>"Bearer $accessToken"</font><font>).sendAwait()
<b>if</b> (200 == httpResponse.statusCode()) {
...
}
</font>
这将删除ID为3的节点,在我们的例子中,因为它是整个子树的元素节点。肯定它已作为修订版3提交,因此所有旧版本仍然可以查询整个子树(或者在第一个修订版中,它只是名称为“bar”而没有任何子树的元素)。
如果我们想得到一个差异,目前以XQuery Update语句的形式(但我们可以以任何格式序列化它们),只需调用XQuery函数sdb:diff,该函数定义为:
sdb:diff($coll as xs:string, $res as xs:string, $rev1 as xs:int, $rev2 as xs:int) as xs:string
例如,通过我们上面创建的数据库/ resource1这样的GET请求,我们可以发出以下请求:
GET https://localhost:9443/?query= sdb%3Adiff%28%27database%27%2C%27resource1%27%2C1%2C2%29
请注意,query-String必须进行URL编码,然后对其进行解码
sdb:diff('database','resource1',1,2)
在我们的示例中,diff的输出是包含在封闭sequence-element中的XQuery-Update语句:
<<b>rest</b>:sequence xmlns:<b>rest</b>=<font>"https://sirix.io/rest"</font><font>>
let $doc := sdb:doc('database','resource1', 1)
<b>return</b> (
insert nodes <xml>foo<bar/></xml> as first into sdb:select-node($doc, 3)
)
</<b>rest</b>:sequence>
</font>
这意味着resource1从database第一次修订中打开,然后将子树<xml>foo<bar/></xml>附加到具有稳定节点ID 3作为第一子节点的节点。
以上所述就是小编给大家介绍的《Sirix.io是如何基于Vert.x和Kotlin协程构建异步RESTful API》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 深度剖析 redux applyMiddleware 中 compose 构建异步数据流的思路
- 基于 Swoole 构建高性能 Laravel 应用系列 —— 基于 Swoole 在 Laravel 中实现异步任务队列
- 基于 Swoole 构建高性能 Laravel 应用系列 —— 基于 Swoole 在 Laravel 中实现异步事件监听及处理
- SpringBoot | :异步开发之异步调用
- 改进异步封装:处理带返回值的异步调用
- 异步发展流程 —— Generators + co 让异步更优雅
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Nature of Code
Daniel Shiffman / The Nature of Code / 2012-12-13 / GBP 19.95
How can we capture the unpredictable evolutionary and emergent properties of nature in software? How can understanding the mathematical principles behind our physical world help us to create digital w......一起来看看 《The Nature of Code》 这本书的介绍吧!