Cloudflare耐用的物体

2021-04-04 11:31:57

持久对象通过两个功能为工人平台提供低延迟协调和一致存储:全局唯一性和事务存储API。

全球唯一性保证,在整个世界各地都会有一个持久的对象,同时运行给定的ID。持久对象ID的请求由Workers运行时路由到拥有持久对象的CloudFlare的窗口点。

事务存储API为持久对象提供了强烈一致的键值存储。每个对象只能读取和修改与该对象关联的键。执行持久对象是单线程的,但可以从它们到达对象的方式处理多个请求事件。

对于耐用对象的高级介绍,请参阅公告博客文章打开外部链接。

耐用的物体目前在Beta中,可供其他人订阅任何人。您可以通过导航到“Workers”,然后“持久对象”启用它们在CloudFlare仪表板上的帐户中为您的帐户启用。

持久对象被命名为您定义的类的实例。就像面向对象编程的类一样,该类定义了持久对象可以访问的方法和数据。

在创建和访问持久对象之前,必须通过导出普通的JavaScript类来定义其行为。其他语言需要一个垫片,将其类定义转换为JavaScript类。

传递给类构造函数的第一个参数包含特定于持久对象的状态,包括访问存储的方法。第二个参数包含您上传时与工人关联的任何绑定。

工人通过fetch API与持久对象进行通信。像工作者一样,通过注册事件处理程序来侦听传入的获取事件的耐用对象。唯一的区别是,对于持久对象,Fetch处理程序被定义为类上的方法。

导出类{构造函数(状态,env){}异步获取(请求){返回新响应('您好世界'); }}

工作人员可以通过标题,HTTP方法,请求正文或请求URI将信息传递给持久对象。

持久对象收到的HTTP请求不会直接来自Internet。他们来自其他工人代码 - 可能是其他持久的物体,或只是简单的工人。我们' ll看到如何在一下发送这样的请求。持久对象使用HTTP熟悉,但我们计划将来引入其他协议。

持久对象通过传递给耐用对象构造函数的第一个参数来访问持久存储API。虽然访问持久对象是单线程的,但重要的是要记住,请记住,当请求执行时仍然可以在I / O等待时彼此交互,例如在等待持久存储方法返回的承诺或获取时要求。

导出类{构造函数(州,env){此。国家=州; }异步获取(请求){允许IP =请求。标题。得到(' cf-connecting-ip');让数据=等待请求。文本 ( ) ;让StoragePromise =这个。状态 。贮存 。放(IP,数据);等待存储妥协;返回新响应(IP +'存储' +数据); }}

每个单独的存储操作都表现类似于数据库事务。更复杂的用例可以在事务中包装多个存储语句。例如,如果其当前值与提供的&#34匹配,则此持久对象仅符合其当前值; if-match"标题值:

导出类{构造函数(州,env){此。国家=州; }异步获取(请求){让键=新URL(请求。URL)。主机让IfMatch =请求。标题。得到(' if-match');让NewValue =等待请求。文本 ( ) ;让epartValue = false;等待这个。状态 。贮存 。事务(异步txn => {让currentvalue = await txn。get(key);如果(currentvalue!= ifmatch& ifmatch!=' *'){txn。回滚();返回;} epartValue = true;等待TXN。放(钥匙,newValue);});返回新响应("已更改:" + exputhvalue); }}

事务在可序列化隔离级别打开外部链接运行。如果它们与同一持久对象运行的并发事务冲突,则此表示事务可能会失败。

在返回错误之前,通过重新运行提供的功能,事务是透明的,并自动重试一次。为了避免交易冲突,Don' t' t' t' t需要它们,不要按住任何超过必要的交易,并限制每个事务所操作的键值对的数量。

由于每个持久对象都是单线程,因此技术上没有必要使用事务来实现事务语义。通过仔细使用承诺,您可以在您的Live对象中序列化操作,以便其中没有可能并发存储操作。我们为那些不想做自己的同步的人提供交易界面。

持久对象中的变量将保持状态,只要不从内存中逐出耐用对象即可保持状态。常用模式是首次访问它第一次从持久存储和设置类变量初始化对象。由于未来访问被路由到相同的对象,因此可以返回任何初始化值而不会进一步调用持久存储。

导出类{构造函数(州,env){此。国家=州; } Async initialize(){让存储=等待它。状态 。贮存 。得到("价值"); //初始化后,未来读取Don' t需要访问存储!这个 。 value =存储|| 0; } //从客户端处理HTTP请求。异步获取(请求){//确保我们'重新初始化存储。如果(!这个。initializePromise){这个。 initializepromise =它。 initialize();等待这个。 initializepromise; //它.Value将保留其状态,直到从内存中逐出此对象...}}

作为持久对象的一部分,我们' VE使工人成为WebSocket端点 - 包括作为客户端或作为服务器。以前,工人可以将WebSocket连接到后端服务器上,但不能直接讲协议。

虽然从技术上讲,任何工人都可以通过这种方式讲WebSocket,但在与持久对象组合时,WebSocket最有用。当客户端使用WebSocket连接到应用程序时,您需要一种向现有套接字连接发送的服务器生成的事件。没有耐用的物体,没有'&#39是没有向拿着Websocket的特定工作者发送事件。使用持久对象,您可以将WebSocket转发到对象。然后可以通过其唯一ID向该对象进行寻址消息,然后该对象可以将WebSocket向客户端转发这些消息。

WebSockets的完整文档将即将推出,但现在看看此重新评论的示例聊天应用程序打开在持久对象中运行的外部链接,以查看其工作原理。

如上所述,持久对象不会直接从互联网接收请求,而是从工人或其他持久对象接收请求。这是通过在呼叫工作人员中配置绑定的每个持久对象类,为您' d喜欢它能够交谈来实现的。这些绑定与KV绑定类似,并且必须在上传时间内配置。绑定暴露的方法可用于与特定的耐用对象实例进行通信。

当工人与持久对象交谈时,它通过A"存根和#34;目的。类绑定' s get()方法返回特定持久对象实例的存根,存根和#39; s fetch()方法向实例发送HTTP请求。

请注意,在下面的示例中,我们使用基于es模块的新工程语法编写了fetch处理程序。持久对象需要此语法。此示例中的fetch处理程序实现了与持久对象交谈的工作者。我们建议在同一脚本中实现持久对象和相应的Fetch处理程序的方法不仅是因为它方便,而且因为截至今天,无法将脚本上传到不实现Fetch处理程序的运行时。

es模块与常规JavaScript文件不同,因为它们具有导入和导出。如上所述,我们在定义我们的课程时写了出口类DurableObjectExample。要实现Fetch处理程序,必须在导出默认{}块中导出名为fetch的方法。

//在模块 - 语法工作人员中,我们使用“导出默认值”(导出默认值)以导出我们的脚本' s // main事件处理程序,例如`fetch`处理程序,用于接收http //请求。在模块前的工作人员中,使用//` addeventhandler(" fetch" event => {...})`中注册了fetch处理程序。这只是//基本上是相同的新语法。导出默认{//在模块中,语法工作人员,绑定作为调用事件处理程序或//类构造函数时作为第二个参数传递的属性。与模块前工人相比,这是新的,其中绑定显示为全局变量。异步获取(请求,env){//从URL路径中派生对象ID。 “example_class`是持久//对象绑定,我们将在下一节中展示如何配置。 //` example_class.idfromName()`始终返回与//相同的字符串作为输入时返回相同的ID(并在同一类上调用),但是对于两个不同的字符串(或针对不同类别)的相同// ID。因此,在此//案例中,我们正在为每个唯一路径创建一个新对象。让ID = ENV。 example_class。 IDFROMNAME(新URL(请求。URL)。pathname); //使用ID构建耐用对象的存根。 A"存根"是//客户端对象用于向持久对象发送消息。让stub =等待Env。 example_class。得到(ID); //将请求转发给持久对象。请注意,“stup.fetch()`has //与全局`fetch()函数相同的签名,除了//请求始终发送到对象,无论请求' s URL如何。 // //我们第一次向新对象发送请求时,对象将为//为我们创建。如果我们不在对象中存储持久状态,它将自动删除(如果我们再次请求,则重新创建)。 //如果我们存储持久状态,则可以从内存中驱逐对象//,但其持久状态将永久地保持周围。让响应=等待存根。获取(请求); //我们收到了一个HTTP响应。我们可以以通常的//方式处理它,但在这种情况下,我们只会将其返回给客户。回复响应; }}

了解有关与工作人员持久对象API参考的持久对象进行通信的更多信息。

在上面的示例中,我们通过调用绑定上的idfromName()函数来使用字符串派生对象ID。您还可以要求系统生成随机唯一ID。系统生成的唯一ID具有更好的性能特征,但要求您将ID存储在某处以便稍后再次访问对象。有关更多信息,请参阅API参考文档。

在撰写本文时,Wrangler中的持久对象支持尚未在完整的版本构建中提供,因此您需要安装发布候选人。有关安装说明和更多信息,请参阅发行说明打开外部链接。

上传实现或绑定到持久对象的工人的最简单方法是使用Wrangler,工人CLI。我们建议您从我们的模板之一开始,最简单的运行可以使用:

这将为您的项目创建一个具有基本配置的目录,以及已设置的单个JavaScript源文件。如果您希望使用Rollup或WebPack使用代码捆绑外部依赖项,或者使用CompenJS模块而不是es模块,您可能需要尝试其中一个其他入门模板:

以下部分将介绍如何自定义配置,但如果您可以立即使用此命令启动生成的项目:

使用模块语法的工人必须有一个"主要"指定的模块从中导出所有持久对象和事件处理程序。应该使用&#34处理作为主模块的文件。模块"在项目中的Package.json文件中的键。

可以通过提供所希望使用绑定访问其对象的类名和脚本名称,在Wrangler.Toml中配置持久对象绑定。创建在与绑定中定义的类的绑定时,可以省略脚本名称。

[]绑定= [{name =" example_class" ,class_name =" durableObjectExample" #绑定到我们的DurableObjectExample类]

通常,当您想使用Wrangler发布工人时,您只需运行Wrangler发布。但是,当您从脚本导出新的持久对象类时,您必须告诉Workers平台关于Itbefore您可以创建和访问与该类关联的持久对象。此过程称为A"迁移",目前通过为Wrangler发布提供额外的选项来执行。

请注意,在您' ve run-new-class for the给定的类名称后,您无需在后续上传的工作人员中包含迁移。你' d只是运行牧马人没有额外的旗帜。

如果要删除与导出的类关联的持久对象,则可以使用--delete-class:

运行--delete类迁移将删除与删除类关联的所有持久对象,包括所有存储的数据。不要在没有首先确保你依赖于持久的物体并且已经将任何重要数据复制到其他地方。

这些是基本的例子 - 如果您&#39喜欢,您可以在单个争吵者发布呼叫中使用这些选项的多个。 Wrangler的未来版本还将包括重命名类或将类从一个文件传输到另一个文件的迁移指令。

在这一点上,我们'完成了!如果将DurableObjectExample和Fetch处理程序代码从上面复制到生成的Wrangler项目中,请使用 - 新级迁移发布它,并向您提出请求,您' ll看到您的请求存储在持久对象中:

当您编写持久对象时,您可以在持久对象运行时API文档中找到更多有用的详细信息。

耐用对象目前在早期的测试版中,尚未启用一些计划的功能。在耐用对象通常可用之前,许多这些限制将是固定的。

此时,我们还没有准备好保证数据赢得' t丢失。我们不会期望数据丢失并保持定期备份,但始终可能。

目前,如果您将数据存储在持久对象中,则可以' t站立丢失,您必须安排将该数据的备份置于其他存储系统中。不要依赖耐用对象来在测试期间存储生产数据。 (当然,无论如何,这总是最好的练习,但它在测试版中尤为重要。)

目前在启动新事件(例如接收HTTP请求)时强制执行唯一性,并且在访问存储时。收到事件后,如果事件需要一些时间才能执行并且不会访问其持久存储,则可能不再可能不再是当前的持久对象实例,并且存在相同对象ID的一些其他实例在其他地方创造。如果事件在此时访问存储,则会收到异常,但如果事件在不访问存储的情况下完成,则可能无法意识到该对象不再是电流。

特别地,在网络分区或软件更新的情况下可以以这种方式(包括持久对象' s类代码或工人系统本身的更新)来以这种方式取代耐用对象。

目前没有支持生成所有现有对象的列表,也不是批量导出对象的任何方式。

工人仪表板尚不支持查看或编辑使用模块语法的工作人员。它还尚未显示有关耐用对象的任何信息,或允许您创建对工人中持久对象的客户端绑定。

并非所有CloudFlare位置都支持持久对象,因此可能无法在首次请求它们的存在点中创建对象。

目前,持久对象不会在初始创建后迁移位置之间。我们将来将在将来启用自动迁移。

使用持久对象通常会添加响应延迟,因为必须将请求转发到对象所在的位置。

虽然持久对象已经表现出很多任务,但我们有很多性能调整。预计绩效(延迟,吞吐量,开销等)改善了测试期 - 如果您遵守绩效问题,请告诉我们它!

我们' Ve包括下面基本计数器的工作者和持久对象的完整示例代码。

//工作者导出默认{异步获取(请求,env){return await handleRequest(请求,env); }} Async函数handlerequest(请求,env){让id = env。 。 IDFROMNAME(" A");让obj = env。 。得到(ID);让resp = await obj。获取(请求。URL);让Count =等待resp。文本 ( ) ;返回新响应("耐用的对象' a' count:" +计数); } //持久对象导出类{构造函数(州,env){this。国家=州; } Async initialize(){让存储=等待它。状态 。贮存 。得到("价值");这个 。 value =存储|| 0; } //从客户端处理HTTP请求。异步获取(请求){//确保我们'重新初始化存储。如果(!这个。initializePromise){这个。 initializepromise =它。 initialize()。 catch((err)=> {//如果在初始化期间抛出任何东西,那么我们需要确定将来的请求将重试initialize()。//请注意重置此共享//承诺中所涉及的并发出错可能是棘手的 - 我们不会' t //建议自定义它。这个。initializepromise =未定义;扔犯罪});等待这个。 initializepromise; //应用请求的操作。让URL =新URL(请求。URL);让CurrentValue =这。价值 ;切换(URL。pathname){

......