搜索​​​​

清除过滤器
文章
Michael Lei · 十月 10, 2022

互操作性--创建和连接业务主机Business Host的步骤一二三

Hi 大家好, 我最近开始学习InterSystems IRIS 的互操作性,我发现官方文档对理解它的工作原理很有帮助,尽管我自己在实现它时仍有一些困难。在我的同事的帮助下,我成功地创建了一个系统的Demo,并从实践中学习。因此,我决定写一下文章,分享我得到的帮助,来帮助更多的其他人。 介绍 首先,让我们掌握一些基本概念: 互操作性 - 这个词的含义并不像它的发音那样复杂--它基本上是把各种信息从一个系统带到另一个系统的“魔术”。 业务主机 - 如果把互操作性比作是魔术,那么业务主机Business Host就是魔术师的魔法帽--业务主机里有能够识别和接收信息的业务服务Business Service/BS,并将其作为消息发送给业务流程BP或业务操作BO。业务操作执行所需的操作(顾名思义)并传递信息。业务流程控制着消息的流动:它们定义了消息的去向(基于你所选择的任何东西)以及它是如何被传递的。 适配器 - 适配器是一些我们可以用来识别和操作我们可能要处理的各种信息的类。在实践中,我们把它们作为参数和(可选)属性来访问其方法和属性 准备搭建Production 从简单的开始比较容易--让我们先想想服务和操作--比如说你有一个接收一种信息的服务,它很容易被我们唯一的操作所识别。 当生产的目的和它的部分非常清楚时,开发就比较容易。如果你愿意,画一张图或写下你希望它完成的步骤可能会有帮助。 例如: 首先要问自己, "需要做什么?" - 在我的演示中,我需要操作一个SQL表--我要把一本书的标题和作者等信息,并将其插入到一个表中。 "那么,我需要Production做什么呢?- 它必须接收一个包含书名和作者的信息,并执行一个SQL INSERT。 "好的,如何让这件事发生?- 业务服务(BS)将接收标题和作者,将其传递给业务操作(BO)。BO执行SQL代码。 "现在我有了信息将遵循的路径,我需要理解信息。它是什么?" - 我有很多方法可以发送数据。我可以在一个文件中发送,或一个REST应用程序,甚至一个电子邮件。让我们选择文件来开始简单。我的BS将接收文件,读取它并将其信息发送给BO。BO执行查询。 开始干活 你可以从你对最有信心的代码部分开始。 业务服务 Business Service/BS 我是从业务服务BS开始的. 现在我很清楚,我需要实现Request(请求)类,以便它存储数据和将被发送到的操作。 业务操作Business Operation/BO 请求Request Request类很简单。我只用它来存储一些数据。%XML.Adaptor是为了在管理门户上显示Request以进行错误管理。 业务操作Business Operation BO有一个消息地图,为每一种到达的消息提供足够的方向。例如,如果在BS中,在SendRequestSync方法和Request参数中,我使用了不同的类型,如 "Demo.Books.BO.SearchTable.Request",我可以用这种消息类型创建另一个MapItem,引用一个Search方法。 方法Method 在这里,你实现了操作应该做的任何事情. 管理门户设置 最后,为了让事情顺利进行,请遵循以下步骤: 管理门户 > 互操作性 > 列表 > 生产 > 新建(Management Portal > Interoperability > List > Productions > New) 门户网站将用Production生产信息创建一个类。 然后,你用你创建的类添加一个服务,并设置文件路径(你将把输入文件放在哪里)和工作路径。 另外,用你创建的类添加一个操作,必要时指定其设置。 几点观察 业务操作可以接收一个同步请求,这意味着服务只有在BO检索到它的信息后才会响应,因为服务的响应取决于BO的响应,例如,如果它必须在表操作中执行一个搜索。因为Production生产只执行INSERT操作,BS只需要发送信息,BO就会INSERT;不需要响应,所以我们可以有一个异步请求。 讨论例如文件和SQL适配器等适配器的规格超出了本文的范围,本文的目的是对编码和步骤进行概述,以便更好地理解实际工作。 欢迎与我联系--我很乐意提供任何可能的帮助!
文章
姚 鑫 · 六月 4, 2023

第二十四章 开发Productions - ObjectScript Productions - 定义业务服务

# 第二十四章 开发Productions - ObjectScript Productions - 定义业务服务 本页介绍如何定义业务服务类。 提示: `IRIS® `提供使用特定入站适配器的专用业务服务类,其中之一可能适合需要。如果是这样,则不需要编程。有关部分列表,请参阅 `Introducing Interoperability Productions` 中的连接选项。 # 介绍 业务服务负责接受来自外部应用程序的请求到 `IRIS`。下图显示了它是如何工作的: 请注意,此图仅显示数据的输入流,而不是可选响应。 业务服务负责以下活动: - 等待特定的外部事件(例如来自应用程序的通知、收到 `TCP` 消息等)。 - 读取、解析和验证伴随此类事件的数据, - 如果需要,返回对外部应用程序的确认,表明已收到事件。 - 创建请求消息的实例并将其转发到适当的业务流程或业务操作以进行处理。 业务服务的目的通常是接收数据输入。在大多数情况下,业务服务有一个与之关联的入站适配器。但是,在某些情况下不需要适配器,因为应用程序能够将请求消息发送到服务中,或者因为业务服务已被编写为处理特定类型的外部调用,例如来自复合应用程序的调用。这种类型的业务服务称为无适配器业务服务。 当业务服务具有入站适配器时,它处于数据拉取(而不是推送)模式。在这种模式下,业务服务会定期轮询适配器,看它是否有数据。同时,如果适配器随时遇到输入数据,它会调用业务服务来处理输入。 当业务服务没有适配器时,它不会拉取数据。相反,客户端应用程序调用业务服务并告诉它处理输入(这是一种数据推送模式)。 # 关键原则 首先,务必阅读 `Programming in InterSystems IRIS`。 在业务服务中,可以访问关联适配器的属性和方法,这些适配器作为业务服务的 Adapter 属性提供。这意味着可以更改适配器的默认行为;这样做可能合适也可能不合适。记住封装原则很有用。封装的思想是适配器类应该负责技术特定的逻辑,而业务服务类应该负责生产特定的逻辑。 如果发现有必要在业务服务类中大量或频繁地改变适配器类的行为,那么创建适配器类的自定义子类可能更合适。请参阅不太常见的任务。 这个原则也适用于商业运作。 # 定义业务服务类 要创建一个业务服务类,定义一个类如下: - 类必须在(或子类)中扩展 `Ens.BusinessService`。 - 在类中,`ADAPTER` 参数必须等于此业务服务要使用的适配器类的名称。 提示:如果只是希望业务服务定期唤醒和运行而不关心 `IRIS` 外部的事件,请使用适配器类 `Ens.InboundAdapter`。 - 类必须实现 `OnProcessInput()` 方法,如实现 `OnProcessInput()` 方法中所述。 - 类可以添加或删除设置。请参阅添加和删除设置。 - 类可以实现任何或所有启动和拆卸方法。请参阅覆盖启动和停止行为。 - 类可以包含完成自身内部工作的方法。 有关业务服务类的示例,请参阅适配器指南。 # 实施 `OnProcessInput()` 方法 在业务服务类中, `OnProcessInput()` 方法可以具有以下通用签名: ```java Method OnProcessInput(pInput As %RegisteredObject, pOutput As %RegisteredObject) As %Status ``` 这里的`pInput`是适配器要发送给这个业务服务的输入对象,`pOutput`是输出对象。 首先查看选择的适配器类。 建议编辑 `OnProcessInput()` 方法签名以使用适配器所需的特定输入参数。 `OnProcessInput()` 方法应该执行以下部分或全部操作: 1. 可选地设置业务服务类的属性(在任何适当的时间)。最受关注的业务服务属性是 `%WaitForNextCallInterval`。它的值控制 `IRIS` 调用适配器的 `OnTask()` 方法的频率。 有关其他属性,请参阅 `Ens.BusinessService`的类参考。 2. 如有必要,验证输入对象。 3. 检查输入对象并决定如何使用它。 4. 创建请求消息类的实例,这将是业务服务发送的消息。 5. 对于请求消息,使用输入对象中的值适当地设置其属性。 6. 确定要将请求消息发送到哪里。当发送消息时,将需要在生产中使用业务主机的配置名称。 7. 将请求消息发送到生产(业务流程或业务操作)中的目的地。请参阅下一节。 8. 确保设置输出参数 (`pOutput`)。通常,将其设置为等于您收到的响应消息。此步骤是必需的。 9. 返回适当的状态。此步骤是必需的。
文章
Claire Zheng · 十月 18, 2022

技能帖:更好地利用开发者社区的发帖功能!

各位开发者社区的同学们,大家好! 您想更好地获得帮助、讨论有趣的功能、发布公告或分享您的知识吗?在这篇文章中,我们将告诉你如何做到这一切。 我们将通过以下几部分来分享“如何做”: 一般发帖步骤 问题 文章或公告 讨论 一般发帖步骤 首先,你需要点击开发者社区网站顶部菜单中的“发布新帖”按钮: 之后,您将看到编辑器中显示创建一个问题、一则公告、一篇文章或一个讨论。不同类型的帖子有自己的一组必填字段和可选字段。 首先,让我们讨论所有类型的帖子的公共字段,然后继续讨论细节。 基本上,每篇文章都有一个标题*、正文*、组*、标签和一些额外的选项,你可以在其中添加调查或附加PDF文件。所有用星号(*)标记的文本字段都是必填项。因此,首先,你需要选择帖子的类型,可以像上面提到的问题,公告,文章或讨论。 接下来,请用最精确和简洁的方式表述你的问题的主要思想,并将其作为标题。 之后,在文章主体中,你可以写任何你想与他人分享的东西。在写文章的时候有两个选择。你可以使用编辑器“所见即所得”(WYSIWYG)模式或者Markdown。当然,这两种方法得到的结果是一样的。 vs. 在你写完文本后,你必须选择组,通常是InterSystems提供的技术、产品或服务。 在组字段之后,有一个标签字段,您可以在其中添加与文章内容相关的标签。有相当多的标签,所以请认真选择,因为其他成员会通过这些标签寻找或排序所需要的信息。 在标签下面,有一个链接可以查看更多选项。在那里,您可以附加一个pdf文档(例如,pdf格式的事件时间表)并提供您想要显示的名称。 你可以通过“更多选项”做的另一件事是添加投票。在字段中填写一个问题、可能的答案、选择持续时间等。 完成后,你可以预览你的帖子,看看它对其他人来说是什么样子,你可以保存它以便以后继续编辑,或立即发布它。 此外,您可以预约发布您的文章。只需点击向下箭头,选择安排帖子,并设置日期和时间。 一切都设置好后,只需点击安排帖子,就完成了。 基本上,这是创建帖子的常见功能。 问题 从它的名字来看,很明显,如果你需要别人的帮助,你应该选择这种类型的帖子。在这里,在开发者社区中,有很多专家,有些人可能已经遇到了相同的情况。所以不要犹豫,提出问题或回答问题吧:) 要寻求帮助,请阐明你的问题的主要思想,并将其作为一个标题写下来。接下来,请选择您正在使用的产品版本,因为不同版本具有不同的功能和类,一些建议可能对某些版本有用,而对其他版本无用。 更准确地说,您可以提供当前在$ZV文本框中使用的完整构建。要获得完整版本,可以打开Terminal并执行以下命令: write $ZV 在你正在使用的IDE中也可以执行相同的操作,或者你可以在管理门户(Management Portal)中看到: 其余字段与前面描述的相同。 文章或公告 要分享你的知识或发布公告,你应该分别选择一种类型的帖子——文章或公告。这些类型的帖子除了公共字段之外还有一些额外的字段。这些是上一篇文章、下一篇文章和打开Exchange应用程序链接。 因此,基本上,如果当前的文章/声明(或讨论的分支)链接到另一篇文章,您可以在“上一个公告”字段中添加链接,这样其他社区成员将在文章末尾看到以下相关文章块。 你不需要再打开上一篇文章去添加到下一篇文章的链接,它将自动链接。 添加完这些链接后,用户也可以通过使用链接文章右上角的导航按钮轻松地从一个帖子导航到另一个帖子。 如果你的帖子在Open Exchange上有一个项目链接到它,你可以在相应的字段中添加这个项目的链接。 讨论 要开始关于某个功能的对话,或者分享您使用该技术的经验并寻求反馈,您可以开始一个讨论。这种类型的文章有所有公共字段,也有到上一篇和下一篇的链接。 就这些! 这就是您在社区上开始发布一个新帖子时所需要知道的。 期待着看到您的精彩发帖:)
文章
Kelly Huang · 七月 12, 2023

当 GPT 与 FHIR 碰撞出火花:利用Open API 的规范力量

FHIR 通过提供标准化数据模型来构建医疗保健应用程序并促进不同医疗保健系统之间的数据交换,彻底改变了医疗保健行业。由于 FHIR 标准基于现代 API 驱动的方法,因此移动和 Web 开发人员更容易使用它。然而,与 FHIR API 交互仍然具有挑战性,尤其是在使用自然语言查询数据时。 隆重推出FHIR - AI 和 OpenAPI 链应用程序,该解决方案允许用户使用自然语言查询与 FHIR API 进行交互。该应用程序使用OpenAI 、 LangChain和Streamlit构建,简化了查询 FHIR API 的过程并使其更加用户友好。 FHIR OpenAPI 规范是什么? OpenAPI 规范(以前称为 Swagger,目前是OpenAPI Initiative的一部分)已成为软件开发领域的重要工具,使开发人员能够更有效地设计、记录 API 并与 API 交互。 OpenAPI 规范定义了一种标准的机器可读格式来描述 RESTful API,提供了一种清晰一致的方式来理解其功能并有效地使用它们。 在医疗保健领域,FHIR 成为数据交换和互操作性的领先标准。为了增强FHIR的互操作能力, HL7正式记录了FHIR OpenAPI规范,使开发人员能够将FHIR资源和操作无缝集成到他们的软件解决方案中。 FHIR OpenAPI 规范的优点: 标准化 API 描述:OpenAPI 规范提供 FHIR 资源、操作和交互的全面且标准化的描述。开发人员可以轻松了解基于 FHIR 的 API 的结构和功能,从而更轻松地构建集成并与医疗保健系统交互。 促进互操作性:促进开发人员之间的协作,推动 FHIR 标准和最佳实践的采用。该规范提供了一种通用语言和框架,用于讨论基于 FHIR 的集成和实现,促进开发人员之间的协作。 增强的文档和测试:交互式文档和测试套件,以便更好地理解和验证。开发人员可以创建详细的API文档,使其他开发人员更容易理解和使用基于FHIR的API。基于规范的测试套件可以对API集成进行全面的测试和验证,确保医疗数据交换的可靠性和准确性。 改进的开发人员体验:自动生成客户端库和 SDK 以实现无缝集成。这简化了集成过程,并减少了将 FHIR 功能合并到应用程序中所需的时间和精力 FHIR、OpenAI 和 OpenAPI Chain 如何协同工作? FHIR - AI 和 OpenAPI Chain应用程序利用 LangChain 来加载和解析 OpenAPI 规范( OpenAPI Chain )。之后,根据这些规范,通过 OpenAI 给出的提示链旨在理解自然语言查询并将其转换为适当的 FHIR API 请求。用户可以用简单的语言提出问题,应用程序将与所选的 FHIR API 交互以检索相关信息。 例如,用户可能会问:“患者 John Doe (ID 111) 的最新血压读数是多少?”然后,应用程序会将此查询转换为 FHIR API 请求,获取所需的数据,并以易于理解的格式将其呈现给用户。 FHIR - AI 和 OpenAPI 链的优势 用户友好的交互:通过允许用户使用自然语言查询与 FHIR API 交互,该应用程序使非技术用户可以更轻松地访问和分析医疗保健数据。 提高效率:该应用程序简化了查询 FHIR API 的过程,减少了获取相关信息所需的时间和精力。此外,它还有可能减少从应用程序中查找任何特定信息的点击次数(花费的时间)。 可定制:FHIR 标准简化了从任何 FHIR 服务器检索一致数据的过程,从而可以轻松定制。它可以轻松配置为与任何 FHIR API 无缝集成,为不同的医疗保健数据需求提供灵活且适应性强的解决方案。 FHIR 入门 - AI 和 OpenAPI 链 要开始使用 FHIR - AI 和 OpenAPI Chain 应用程序,请按照以下步骤操作: 从OpenAI Platform获取 OpenAI API 密钥。 获取 FHIR 服务器 API 端点。您可以使用自己的示例 FHIR 服务器(需要未经身份验证的访问),也可以按照InterSystems IRIS FHIR 学习平台中给出的说明创建临时示例服务器。 在线试用该应用程序或使用提供的说明在本地进行设置。 通过集成人工智能和自然语言处理功能,FHIR - AI 和 OpenAPI Chain 应用程序提供了一种与 FHIR API 交互的更直观的方式,使所有技术背景的用户都更容易访问和分析医疗数据。 如果您发现我们的应用程序很有前途,请在大奖赛中投票! 如果您能想到使用此实现的任何潜在应用程序,请随时在讨论线程中分享它们。 @Ikram Shah 致敬原创作者~
文章
Jingwei Wang · 九月 16, 2022

Python应用程序连接到InterSystemsIRIS数据库 - 使用 DB-API

连接前准备: Python 开发环境 DB-API驱动:irispython wheel 文件 Connection String 步骤: 安装irispython wheel 文件 pip install intersystems_irispython-3.2.0-py3-none-any.whl Connection String:其中import iris 用来导入iris, connection = iris.connect是connection string。connection.close()用来断开连接。 import iris def main(): connection_string = "localhost:1972/USER" username = "SQLAdmin" password = "deployment-password" connection = iris.connect(connection_string, username, password) # when finished, use the line below to close the connection # connection.close() if __name__ == "__main__": main()
文章
姚 鑫 · 三月 29, 2021

第十三章 使用动态SQL(七)

# 第十三章 使用动态SQL(七) # SQL元数据 动态SQL提供以下类型的元数据: - 在“准备”之后,描述查询类型的元数据。 - 在“准备”之后,描述查询中选择项的元数据(“列”和“扩展列信息”)。 - 在准备之后,描述查询参数的元数据:参数,`:var`参数和常量。 (语句参数,形式参数和对象) - 执行之后,描述查询结果集的元数据。在执行Prepare操作(`%Prepare()`,`%PrepareClassQuery()`或`%ExecDirect()`)之后,可以使用`%SQL.StatementMetadata`属性值。 - 可以直接为最新的`%Prepare()`返回`%SQL.Statement`元数据属性。 - 可以返回包含`%SQL.StatementMetadata`属性的oref的`%SQL.Statement%Metadata`属性。这使可以返回多个准备操作的元数据。 `SELECT`或`CALL`语句返回所有这些元数据。 `INSERT`,`UPDATE`或`DELETE`返回语句类型元数据和形式参数。 ## 语句类型元数据 使用`%SQL.Statement`类进行`Prepare`之后,可以使用`%SQL.StatementMetadata statementType`属性来确定准备哪种类型的SQL语句,如以下示例所示。本示例使用`%SQL.Statement%Metadata`属性来保存和比较两`个Prepare`操作的元数据: ```java /// d ##class(PHA.TEST.SQL).MetaData() ClassMethod MetaData() { SET tStatement = ##class(%SQL.Statement).%New() SET myquery1 = "SELECT TOP ? Name,Age,AVG(Age),CURRENT_DATE FROM Sample.Person" SET myquery2 = "CALL Sample.SP_Sample_By_Name(?)" SET qStatus = tStatement.%Prepare(myquery1) IF qStatus'=1 { WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT } SET meta1 = tStatement.%Metadata SET qStatus = tStatement.%Prepare(myquery2) IF qStatus'=1 { WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT } SET meta2 = tStatement.%Metadata WRITE "语句类型query 1: ",meta1.statementType,! WRITE "语句类型query 2: ",meta2.statementType,! WRITE "End of metadata" } ``` ```java DHC-APP>d ##class(PHA.TEST.SQL).MetaData() 语句类型query 1: 1 语句类型query 2: 45 End of metadata ``` `statementType`属性的“类引用”条目列出了语句类型整数代码。最常见的代码是1(`SELECT`查询)和45(`CALL`到存储的查询)。 可以使用`%GetImplementationDetails()`实例方法返回相同的信息,如成功准备的结果中所述。 执行查询后,可以从结果集中返回语句类型名称(例如`SELECT`)。 ## 选择项目Select-item元数据 使用`%SQL.Statement`类准备`SELECT`或`CALL`语句之后,可以通过显示所有元数据或指定各个元数据项来返回有关查询中指定的每个选择项列的元数据。此列元数据包括ODBC数据类型信息,以及客户端类型和InterSystems Objects属性的起源以及类类型信息。 以下示例返回最近准备的查询中指定的列数: ```java /// d ##class(PHA.TEST.SQL).MetaData1() ClassMethod MetaData1() { SET myquery = "SELECT %ID AS id,Name,DOB,Age,AVG(Age),CURRENT_DATE,Home_State FROM Sample.Person" SET tStatement = ##class(%SQL.Statement).%New() SET qStatus = tStatement.%Prepare(myquery) IF qStatus'=1 {WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT} WRITE "Number of columns=",tStatement.%Metadata.columnCount,! WRITE "End of metadata" } ``` ```java DHC-APP>d ##class(PHA.TEST.SQL).MetaData1() Number of columns=7 End of metadata ``` 以下示例返回列名称(或列别名),ODBC数据类型,最大数据长度(精度),以及每个`SELECT`项目字段的比例: ```java /// d ##class(PHA.TEST.SQL).MetaData2() ClassMethod MetaData2() { SET $NAMESPACE="SAMPLES" SET myquery=2 SET myquery(1)="SELECT Name AS VendorName,LastPayDate,MinPayment,NetDays," SET myquery(2)="AVG(MinPayment),$HOROLOG,%TABLENAME FROM Sample.Vendor" SET rset = ##class(%SQL.Statement).%New() SET qStatus = rset.%Prepare(.myquery) IF qStatus'=1 { WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT } SET x=rset.%Metadata.columns.Count() SET x=1 WHILE rset.%Metadata.columns.GetAt(x) { SET column=rset.%Metadata.columns.GetAt(x) WRITE !,x," ",column.colName," 是数据类型 ",column.ODBCType WRITE " 大小为 ",column.precision," 规模 = ",column.scale SET x=x+1 } WRITE !,"End of metadata" } ``` ```java DHC-APP>d ##class(PHA.TEST.SQL).MetaData2() 1 VendorName 是数据类型 12 大小为 50 规模 = 0 2 LastPayDate 是数据类型 9 大小为 10 规模 = 0 3 MinPayment 是数据类型 8 大小为 6 规模 = 0 4 NetDays 是数据类型 4 大小为 3 规模 = 0 5 Aggregate_5 是数据类型 8 大小为 20 规模 = 0 6 Expression_6 是数据类型 12 大小为 255 规模 = 0 7 Literal_7 是数据类型 12 大小为 13 规模 = 0 End of metadata ``` 下面的示例使用`%SQL.StatementMetadata%Display()`实例方法显示所有列元数据: ```java /// d ##class(PHA.TEST.SQL).MetaData3() ClassMethod MetaData3() { SET tStatement = ##class(%SQL.Statement).%New() SET qStatus = tStatement.%Prepare("SELECT %ID AS id,Name,DOB,Age,AVG(Age),CURRENT_DATE,Home_State FROM Sample.Person") IF qStatus'=1 {WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT} DO tStatement.%Metadata.%Display() WRITE !,"End of metadata" } ``` ```java DHC-APP>d ##class(PHA.TEST.SQL).MetaData3() Columns (SQLRESULTCOL, property 'columns'): Column Name Type Prec Scale Null Label Table Schema CType ----------- ---- ---- ----- ---- ------------ ------------ ------------ ----- id 4 10 0 0 id Person Sample 5 Name 12 50 0 0 Name Person Sample 10 DOB 9 10 0 1 DOB Person Sample 2 Age 4 10 0 1 Age Person Sample 5 Aggregate_5 2 20 8 1 Aggregate_5 14 Expression_6 9 11 0 2 Expression_6 2 Home_State 12 2 0 1 Home_State Person Sample 10 Extended Column Info (SQLRESULTCOL) Flags: 1:AutoIncrement,2:CaseSensitive,3:Currency,4:ReadOnly,5:RowVersion, 6:Unique,7:Aliased,8:Expression,9:Hidden,10:Identity,11:KeyColumn, 12:RowId Column Name Linked Prop Type Class Flags ------------ --------------------- --------------------- ----------------------- id Sample.Person Y,N,N,Y,N,Y,Y,N,N,Y,Y,Y Name Sample.Person.Name %Library.String N,N,N,N,N,N,N,N,N,N,N,N DOB Sample.Person.DOB %Library.Date N,N,N,N,N,N,N,N,N,N,N,N Age Sample.Person.Age %Library.Integer N,N,N,N,N,N,N,N,N,N,N,N Aggregate_5 %Library.Numeric N,N,N,Y,N,N,Y,N,N,N,N,N Expression_6 %Library.Date N,N,N,Y,N,N,Y,Y,N,N,N,N Home_State Sample.Address.State %Library.String N,N,N,N,N,N,N,N,N,N,N,N Statement Parameters (property 'parameters'): Nbr. Type precision scale nullable colName columntype ---- ---- --------- ----- -------- ------------ ---------- Formal Parameters (property 'formalParameters'): Nbr. Type precision scale nullable colName columntype ---- ---- --------- ----- -------- ------------ ---------- Objects: Col Column Name Extent ExportCall --- ----------- ----------------- ----------------------------- 1 id Sample.Person ##class(Sample.Person).%SQLQuickLoad ``` 这将返回所选字段的两个表列表。第一列元数据表列出了列定义信息: 显示标题 | `%SQL.StatementColumn`属性 | 描述 ---|---|--- Column Name | colName |列的SQL名称。如果为该列提供了别名,则会在此处列出该列的别名,而不是字段名称。名称和别名将被截断为12个字符。对于表达式,聚合,文字,主机变量或子查询,列出了分配的`“ Expression_n”`,`“ Aggregate_n”`,`“ Literal_n”`,`“ HostVar_n”`或`“ Subquery_n”`标签(`n`为`SELECT`项序列号)。如果为表达式,聚合,文字,主机变量或子查询分配了别名,则在此处列出该别名。 Type| ODBCType |ODBC数据类型的整数代码。请注意,这些ODBC数据类型代码与CType数据类型代码不同。 Prec| precision|精度或最大长度(以字符为单位)。日期,时间,PosixTime和TimeStamp数据类型中描述了TIME数据类型的精度和小数位元数据。 Scale| scale| 小数位数的最大数目。对于整数或非数值返回0。日期,时间,PosixTime和TimeStamp数据类型中描述了`TIME`数据类型的精度和小数位元数据。 Null| isNullable| 一个整数值,指示是否将列定义为`Non-NULL(0)`,或者是否允许`NULL(1)`。 RowID返回0。如果`SELECT`项是可能导致`NULL`的聚合或子查询,或者如果它指定`NULL`文字,则该项设置为1。如果`SELECT`项是表达式或主机变量,则设置此项到2(无法确定)。 Label| label| 列名或列别名(与列名相同)。 Table| tableName| SQL表名称。即使为表指定了别名,也始终在此处列出实际的表名。如果`SELECT`项是表达式或聚合,则不会列出任何表名。如果`SELECT`项是子查询,则列出子查询表名称。 Schema| schemaName|表的架构名称。如果未指定架构名称,则返回系统范围的默认架构。如果`SELECT`项是表达式或聚合,则不会列出任何模式名称。如果SELECT项是子查询,则不会列出任何架构名称。 CType| clientType| 客户端数据类型的整数代码。 第二列元数据表列出了扩展列信息。扩展列信息表列出了具有十二个布尔标志(SQLRESULTCOL)的每一列,这些标志被指定为Y(是)或N(否): 显示标题 | `%SQL.StatementColumn`属性 | 描述 ---|---|--- 1: AutoIncrement| isAutoIncrement| TRowID和IDENTITY字段返回Y。 2: CaseSensitive| isCaseSensitive |具有`%EXACT`归类的字符串数据类型字段返回Y。引用`%SerialObject`嵌入式对象的属性返回Y。 3: Currency| isCurrency| 使用%Library.Currency数据类型定义的字段,例如`MONEY`数据类型。 4: ReadOnly |isReadOnly|表达式,聚合,文字,`HostVar`或子查询返回Y。RowID,IDENTITY和RowVersion字段返回Y。 5: RowVersion| isRowVersion|RowVersion字段返回Y。 6: Unique| isUnique| 定义为具有唯一值约束的字段。 RowID和IDENTITY字段返回Y。 7: Aliased| isAliased| 系统为非字段选择项提供别名。因此,无论用户是否通过指定列别名替换了系统别名,表达式,聚合,文字,HostVar或子查询都将返回Y。此标志不受用户指定的列别名的影响。 8: Expression| isExpression| 表达式返回Y。 9: Hidden| isHidden| 如果使用`%PUBLICROWID`或`SqlRowIdPrivate = 0`(默认值)定义表,则RowID字段返回N。否则,RowID字段返回Y。引用`%SerialObject`嵌入式对象的属性返回Y。 10: Identity| isIdentity| 定义为IDENTITY字段的字段返回Y。如果未隐藏RowID,则RowID字段返回Y。 11: KeyColumn| isKeyColumn| 定义为主键字段或外键约束目标的字段。 RowID字段返回Y。 12: RowID| isRowId |ROWID和Identity字段返回Y. 扩展列信息元数据表列出了每个选定字段的列名称(SQL名称或列别名),链接属性(链接的持久性类属性)和类型类(数据类型类)。请注意,链接属性列出了持久性类名(不是SQL表名)和属性名(不是列别名)。 - 对于普通表字段(`SELECT Name FROM Sample.Person`): `Linked Prop=Sample.Person.Name, Type Class=%Library.String`. - 对于表格的RowID (`SELECT %ID FROM Sample.Person`): `Linked Prop= [none], Type Class=Sample.Person`. - 对于表达式,聚合,文字,`HostVar`或子查询 (`SELECT COUNT(Name) FROM Sample.Person`): `Linked Prop= [none], Type Class=%Library.BigInt`. - 供参考`%Serial Object`嵌入式对象属性 (`SELECT Home_State FROM Sample.Person`). `Linked Prop=Sample.Address.State, Type Class=%Library.String.` - 对于引用`%SerialObject`嵌入式对象的字段(`SELECT Home FROM Sample.Person`). `Linked Prop=Sample.Person.Home, Type Class=Sample.Address`. 在此示例中,`Sample.Person`中的`Home_State`字段引用`%SerialObject`类`Sample.Address`的`State`属性。 下面的示例返回带有一个形式参数(也就是语句参数)的被调用存储过程的元数据: ```java /// d ##class(PHA.TEST.SQL).MetaData4() ClassMethod MetaData4() { SET $NAMESPACE="SAMPLES" SET mysql = "CALL Sample.SP_Sample_By_Name(?)" SET tStatement = ##class(%SQL.Statement).%New() SET qStatus = tStatement.%Prepare(.mysql) IF qStatus'=1 { WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT } DO tStatement.%Metadata.%Display() WRITE !,"End of metadata" } ``` 它不仅返回列(字段)信息,还返回语句参数,形式参数和对象的值。 以下示例返回具有三个形式参数的的元数据。这三个参数之一用问号(`?`)指定,使其成为语句参数: ```java /// d ##class(PHA.TEST.SQL).MetaData5() ClassMethod MetaData5() { SET $NAMESPACE="SAMPLES" SET mycall = "CALL personsets(?,'MA')" SET tStatement = ##class(%SQL.Statement).%New(0,"sample") SET qStatus = tStatement.%Prepare(mycall) IF qStatus'=1 {WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT} DO tStatement.%Metadata.%Display() WRITE !,"End of metadata" } ``` ```java DHC-APP>d ##class(PHA.TEST.SQL).MetaData5() Columns (SQLRESULTCOL, property 'columns'): Column Name Type Prec Scale Null Label Table Schema CType ----------- ---- ---- ----- ---- ------------ ------------ ------------ ----- Extended Column Info (SQLRESULTCOL) Flags: 1:AutoIncrement,2:CaseSensitive,3:Currency,4:ReadOnly,5:RowVersion, 6:Unique,7:Aliased,8:Expression,9:Hidden,10:Identity,11:KeyColumn, 12:RowId Column Name Linked Prop Type Class Flags ------------ --------------------- --------------------- ----------------------- Statement Parameters (property 'parameters'): Nbr. Type precision scale nullable colName columntype ---- ---- --------- ----- -------- ------------ ---------- 1 12 50 0 2 name 1 Formal Parameters (property 'formalParameters'): Nbr. Type precision scale nullable colName columntype ---- ---- --------- ----- -------- ------------ ---------- 1 4 4 0 2 _isc_sp_ret_val 5 2 12 50 0 2 name 1 3 12 50 0 2 state 1 Objects: Col Column Name Extent ExportCall --- ----------- ----------------- ----------------------------- End of metadata ``` 请注意,此元数据不返回任何列信息,但是“语句参数”,“形式参数”列表包含列名称和数据类型。 ## Query参数元数据 使用`%SQL.Statement`类进行`Prepare`之后,您可以返回有关查询参数的元数据:输入参数(指定为问号(`?`)),输入主机变量(指定为`:varname`)和常量(文字值)。可以返回以下元数据: - `?`参数`:parameterCount`属性 - ODBC数据类型为`?`参数`:%SQL.StatementMetadata%Display()`实例方法“语句参数”列表。 - ?,v(:var)和c(常量)参数的列表:`%GetImplementationDetails()`实例方法,如成功准备的结果中所述。 - ?,v(:var)和c(常量)参数的ODBC数据类型:`formalParameters`属性。 `%SQL.StatementMetadata%Display()`实例方法“形式参数”列表。 - 查询文本,其中显示以下参数:`%GetImplementationDetails()`实例方法,如成功准备结果中所述。 语句元数据`%Display()`方法列出了“语句参数”和“形式参数”。对于每个参数,它列出了顺序参数号,ODBC数据类型,精度,小数位数,该参数是否可为空(2表示始终提供一个值)及其对应的属性名称(colName)和列类型。 请注意,某些ODBC数据类型以负整数形式返回。 下面的示例按顺序返回每个查询参数(`?`,`:var`和常量)的ODBC数据类型。请注意,`TOP`参数以数据类型12(`VARCHAR`)而不是数据类型4(`INTEGER`)返回,因为可以指定`TOP ALL`: ```java /// d ##class(PHA.TEST.SQL).MetaData6() ClassMethod MetaData6() { SET myquery = 4 SET myquery(1) = "SELECT TOP ? Name,DOB,Age+10 " SET myquery(2) = "FROM Sample.Person" SET myquery(3) = "WHERE %ID BETWEEN :startid :endid AND DOB=?" SET myquery(4) = "ORDER BY $PIECE(Name,',',?)" SET tStatement = ##class(%SQL.Statement).%New() SET qStatus = tStatement.%Prepare(.myquery) IF qStatus'=1 { WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT } SET prepmeta = tStatement.%Metadata WRITE "Number of ? parameters=",prepmeta.parameterCount,! SET formalobj = prepmeta.formalParameters SET i=1 WHILE formalobj.GetAt(i) { SET prop=formalobj.GetAt(i) WRITE prop.colName," type= ",prop.ODBCType,! SET i=i+1 } WRITE "End of metadata" } ``` 执行`Execute`之后,无法从查询结果集元数据中获取参数元数据。在结果集中,所有参数均已解析。因此`parameterCount = 0`,`formalParameters`不包含任何数据。 ## Query结果集元数据 使用`%SQL.Statemen`t类执行`Execute`之后,可以通过调用返回结果集元数据: - `%SQL.StatementResult`类的属性。 - `%SQL.StatementResult%GetMetadata()`方法,访问`%SQL.StatementMetadata`类属性。 ### %SQL.StatementResult属性 执行查询操作后,`%SQL.StatementResult`返回: - `%StatementType`属性返回与最近执行的SQL语句相对应的整数代码。以下是这些整数代码的部分列表:`1 = SELECT; 2 = INSERT; 3 = UPDATE; 4 = DELETE or TRUNCATE TABLE; 9 = CREATE TABLE; 15 = CREATE INDEX; 45 = CALL`. - `%StatementTypeName`计算的属性基于`%StatementType`返回最近执行的SQL语句的命令名称。此名称以大写字母返回。请注意,`TRUNCATE TABLE`操作将作为`DELETE`返回。即使执行了更新操作,`INSERT OR UPDATE`也将作为`INSERT`返回。 - `%ResultColumnCount`属性返回结果集行中的列数。 下面的示例显示这些属性: ```java /// d ##class(PHA.TEST.SQL).MetaData7() ClassMethod MetaData7() { SET myquery = "SELECT TOP ? Name,DOB,Age FROM Sample.Person WHERE Age > ?" SET tStatement = ##class(%SQL.Statement).%New() SET qStatus = tStatement.%Prepare(myquery) IF qStatus'=1 { WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT } SET rset = tStatement.%Execute(10,55) IF rset.%SQLCODE=0 { WRITE "Statement type=",rset.%StatementType,! WRITE "Statement name=",rset.%StatementTypeName,! WRITE "Column count=",rset.%ResultColumnCount,! WRITE "End of metadata" } ELSE { WRITE !,"SQLCODE=",rset.%SQLCODE," ",rset.%Message } } ``` ### %SQL.StatementResult %GetMetadata() 执行之后,可以使用`%SQL.StatementResult %GetMetadata()`方法访问`%SQL.StatementMetadata`类属性。这些是在Prepare之后由`%SQL.Statement%Metadata`属性访问的相同属性。 以下示例显示了属性: ```java /// d ##class(PHA.TEST.SQL).MetaData8() ClassMethod MetaData8() { SET myquery=2 SET myquery(1)="SELECT Name AS VendorName,LastPayDate,MinPayment,NetDays," SET myquery(2)="AVG(MinPayment),$HOROLOG,%TABLENAME FROM Sample.Vendor" SET tStatement = ##class(%SQL.Statement).%New() SET qStatus = tStatement.%Prepare(.myquery) IF qStatus'=1 { WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT } SET rset = tStatement.%Execute() IF rset.%SQLCODE=0 { SET rsmeta=rset.%GetMetadata() SET x=rsmeta.columns.Count() SET x=1 WHILE rsmeta.columns.GetAt(x) { SET column=rsmeta.columns.GetAt(x) WRITE !,x," ",column.colName," is data type ",column.ODBCType WRITE " with a size of ",column.precision," and scale = ",column.scale SET x=x+1 } } ELSE { WRITE !,"SQLCODE=",rset.%SQLCODE," ",rset.%Message } WRITE !,"End of metadata" } ``` 请注意,结果集元数据不提供参数元数据。这是因为`Execute`操作会解析所有参数。因此,在结果集中,`parameterCount = 0`,而`formalParameters`不包含任何数据。 # 审核动态SQL InterSystems IRIS支持动态SQL语句的可选审核。启用%System /%SQL / DynamicStatement系统审核事件时,将执行动态SQL审核。默认情况下,未启用此系统审核事件。 如果启用%System /%SQL / DynamicStatement,则系统将自动审核在系统范围内执行的每个`%SQL.Statement`动态语句。审核将信息记录在审核数据库中。 要查看审核数据库,请依次转到管理门户,系统管理,选择安全性,审核,然后查看审核数据库。可以将“事件名称”过滤器设置为DynamicStatement,以将View Audit Database限制为Dynamic SQL语句。审核数据库列出了时间(本地时间戳),用户,PID(进程ID)和事件的描述。说明指定动态SQL语句的类型。例如,SQL SELECT语句(`%SQL.Statement`)或SQL CREATE VIEW语句(`%SQL.Statement`)。 通过选择事件的详细信息链接,可以列出其他信息,包括事件数据。事件数据包括执行的SQL语句和该语句的任何参数的值。例如: ```java SELECT TOP ? Name , Age FROM Sample . MyTest WHERE Name %STARTSWITH ? /*#OPTIONS {"DynamicSQLTypeList":",1"} */ Parameter values: %CallArgs(1)=5 %CallArgs(2)="Fred" ``` 事件数据的总长度(包括语句和参数)为3,632,952个字符。如果该语句和参数长于3632952,则事件数据将被截断。 InterSystems IRIS还支持ODBC和JDBC语句的审核(事件名称= XDBCStatement),以及嵌入式SQL语句的审核(事件名称= EmbeddedStatement)。
文章
Michael Lei · 七月 4, 2021

数据平台和性能 - 第 7 部分 用于确保性能、可伸缩性和可用性的企业缓存协议ECP

(ECP) Caché 出色的可用性和扩展特性之一是企业缓存协议 (ECP)。 在应用程序开发过程中,如对使用 ECP 的分布式处理加以考虑,可以横向扩展 Caché 应用程序的架构。 应用程序处理可以调整为非常高的速率,处理能力从单个应用程序服务器扩展到最多 255 个应用程序服务器,并且不需要任何应用程序更改。 在我参与的 TrakCare 部署中,ECP 已广泛使用多年。 十年前,主要供应商之一的一台“大型”x86 服务器可能总共只有八个核心。 对于大型部署来说,ECP 是横向扩展商业服务器处理能力的方式,不适合单台昂贵的大型企业服务器。 即使是高核心数的企业服务器也有限制,因此 ECP 也用于扩展这些服务器上的部署。 如今,大多数的新 TrakCare 部署或升级到当前硬件_不需要 ECP_ 即可扩展。 目前的双插槽 x86 生产服务器可以拥有数十个核心和巨大容量的内存。 我们看到,在最近的 Caché 版本中,TrakCare 以及许多其他 Caché 应用程序具有可预测的线性扩展能力,能够随着单台服务器中 CPU 核心数量和内存的增加而支持逐渐增多的用户和事务。 在现场,我看到大多数的新部署都是虚拟化的,即使如此,虚拟机也可以根据需要扩展到主机服务器的规模。 如果资源需求超过单个物理主机可以提供的资源,则使用 ECP 进行横向扩展。 - ___提示:___ _为了简化管理和部署规模,在部署 ECP 之前,先在单台服务器内扩展。_ 在本帖中,我将展示一个示例架构以及 ECP 工作原理的基础知识,然后评论性能注意事项,重点是存储。 有关配置 ECP 和应用程序开发的具体信息,请参见在线的 [Caché 分布式数据管理指南](http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GDDM),并且[社区上有一个 ECP 学习轨迹](https://community.intersystems.com/learning-track/enterprise-cache-protocol-ecp-videos)。 ECP 的其他关键特性之一是提高了应用程序可用性,有关详细信息,请参见 [Caché 高可用性指南](http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GHA_ecp)中的 ECP 部分。 [本系列其他帖子的列表](https://cn.community.intersystems.com/post/intersystems-数据平台的容量规划和性能系列文章) # ECP 架构基础知识 ECP 的架构和运行在概念上很简单,ECP 提供了在多个服务器系统之间有效共享数据、锁定和可执行代码的方法。 从应用程序服务器角度看,数据和代码远程存储在_数据服务器_上,但缓存在_应用程序服务器_的本地内存中,以提供对活动数据的有效访问,同时尽可能减少网络流量。 数据服务器管理对磁盘上持久性存储的数据库读写,而多个应用程序服务器是解决方案的主力,执行大多数应用程序处理。 ## 多层架构 ECP 采用多层架构。 描述处理层和它们扮演的角色有多种不同的方式,以下是我在描述基于 Web 浏览器的 Caché 应用程序时发现很有用的方式,也是我的帖子的模型和术语。 我知道可能有不同的方法来细分层级,但现在先使用我的方法 :) 基于浏览器的应用程序(例如 Caché Server Pages (CSP))使用多层架构,其中表示、应用程序处理和数据管理功能在逻辑上是分开的。 具有不同角色的__逻辑__“服务器”填充各层。 逻辑服务器不必保留在单独的物理主机或虚拟服务器上,出于成本效益和可管理性的考虑,部分甚至全部逻辑服务器可能位于单个主机或操作系统实例上。 随着部署规模的扩展,服务器可以通过 ECP 划分到多个物理或虚拟主机上,从而可根据需要分散处理工作负载,而无需更改应用程序。 主机系统可以是物理的或虚拟化的,具体取决于容量和可用性要求。 以下层和逻辑服务器构成了一个部署: - _表示层:_包括在基于浏览器的客户端和应用程序层之间充当网关的 Web 服务器。 - _应用程序层:_这是 ECP 应用程序服务器所在的位置。 如上文所述,这是一个逻辑模型,其中应用程序服务器不必与数据服务器分开,而且除了最大型的站点外,所有情况下通常都不需要分开。 该层还可能包括进行专门处理的其他服务器,如报告服务器。 - _数据层:_这是数据服务器所在的位置。 数据服务器执行事务处理,是存储在 Caché 数据库中的应用程序代码和数据存储库。 数据服务器负责读写持久性磁盘存储。 ## 逻辑架构 下图是一个基于浏览器的应用程序在部署为三层架构时的逻辑视图: 尽管初看之下该架构可能很复杂,但构成它的组件仍然与安装在单台服务器上的 Caché 系统的组件相同,只是逻辑组件安装在多个物理或虚拟服务器上。 服务器之间的所有通信都通过 TCP/IP 进行。 ### 逻辑视图中的 ECP 操作 上图从顶部开始,显示用户安全地连接到多个已进行负载平衡的 Web 服务器。 这些 Web 服务器在客户端和应用程序层(应用程序服务器)之间传递 CSP 网页请求,应用程序层进行所有处理,允许动态创建内容,并通过 Web 服务器将完成的页面返回给客户端。 在这个三层模型中,应用程序处理通过 ECP 分散到多个应用程序服务器上。 应用程序只将数据(您的应用程序数据库)视为应用程序服务器的本地数据。 当应用程序服务器发出数据请求时,它将尝试从本地缓存满足请求,如果不能满足,ECP 将向数据服务器请求必要的数据,数据服务器自己的缓存可能会满足请求,否则将从磁盘获取数据。 数据服务器对应用程序服务器的回复包括存储该数据的数据库块。 这些块将被使用,并且此时将缓存到应用程序服务器上。 ECP 自动负责管理整个网络中的缓存一致性,并将变化传播回数据服务器。 客户端会体验到快速响应,因为它们经常使用本地缓存的数据。 默认情况下,Web 服务器与首选的应用程序服务器通信,确保同一应用程序服务器满足相关数据的后续请求,因为这些数据可能已经在本地缓存中。 - ___提示:___ _如 [Caché 文档](http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GDDM_develop#GDDM_develop_considerations)中详述,在循环或负载平衡方案中,应避免用户连接到应用程序服务器,因为这会影响应用程序服务器上缓存的优势。 理想情况下,相同的用户或用户组保持连接到同一应用程序服务器。_ 该解决方案通过在表示层添加 Web 服务器和在应用程序层添加其他应用程序服务器来进行扩展,无需用户停机。 数据层通过增加数据服务器的 CPU 和内存来进行扩展。 ## 物理架构 下图显示了与三层逻辑架构示例相同的三层部署中使用的物理主机示例: 请注意,在每一层部署物理或虚拟主机时均采用 n+1 或 n+2 模式,以确保在主机故障或计划维护时保持 100% 能力。 由于用户分布在多个 Web 和应用程序服务器上,单个服务器故障只会影响少量用户,他们会自动重新连接到其余服务器之一。 数据管理层具有高度可用性,例如,位于连接到一个或多个存储阵列的故障转移集群上(例如,虚拟化 HA、InterSystems 数据库镜像或传统的故障转移集群)。 如果硬件或服务出现故障,集群将在其中一个幸存节点上重启服务。 ECP 的一个附加好处是内置弹性,并且在数据库节点集群发生故障转移时能保持事务完整性,应用程序用户将观察到处理暂停,直到故障转移和自动恢复完成,随后用户将无缝继续,不会断开连接。 同样的架构也可以映射到虚拟化服务器,例如,VMware vSphere 可用于虚拟化应用程序服务器。 # ECP 容量规划 如上文所述,数据服务器管理对持久性磁盘的数据库读写,而多个应用程序服务器是解决方案的主力,执行大多数应用程序处理。 这是考虑系统资源容量规划时的一个关键概念,总结来说: - ___数据服务器___(有时称为数据库服务器)通常执行很少的应用程序处理,因此_对 CPU 要求低_,但该服务器执行大部分存储 IO,因此可能有_非常高的存储 IOPS_,即数据库读写以及日志写入(稍后将详细介绍日志 IO)。 - ___应用程序服务器___执行大多数应用程序处理,因此_对 CPU 要求高_,但存储 IO 非常少。 通常,调整 ECP 服务器 CPU、内存和 IO 要求的规则与调整非常大的单服务器解决方案的规则相同,同时考虑 N+1 或 N+2 台服务器以确保高可用性。 ## 基本 CPU 和存储规模调整: 假设 My_Application 需要最多 72 个 CPU 核心进行应用程序处理(记得还要考虑余量),并且预计在写入守护进程周期期间需要 20,000 次写入,以及 10,000 次随机数据库读取的持续峰值。 一个简单的虚拟或物理服务器规模调整方案为: - 4 台 32 CPU 应用程序服务器(3 台服务器 + 1 台服务器用于确保 HA)。 低 IOPS 要求。 - 2 台 10 CPU 数据服务器(镜像或集群以确保 HA)。 [低延迟 IOPS 要求](https://cn.community.intersystems.com/post/数据平台和性能-第-6-部分-caché-存储-io-配置文件)为 20K 写入、10K 读取,加上 WIJ 和日志。 虽然数据服务器只执行非常少的处理,但考虑到系统和 Caché 进程,将其规模调整为 8-10 个 CPU。 应用程序服务器的规模可以根据每台物理主机的最佳性价比和/或可用性来进行调整。 横向扩展时会有一些效率损失,但通常可以在服务器块中增加处理能力,并预计吞吐量有近乎线性的增长。 限制更有可能首先在存储 IO 中出现。 - ___提示:____与确保 HA 一样,要考虑主机、机箱或机架故障的影响。 在 VMWare 上虚拟化应用程序和数据服务器时,确保应用 vSphere DRS 和相关性规则以分散处理负载并确保可用性。_ ## 日志同步 IO 要求 ECP 部署的另一个容量规划注意事项是,由于日志同步,它们需要较高 IO,并且对存储响应时间的要求非常严格,以保持数据服务器上日志记录的可伸缩性 。 同步请求可以触发对日志中最后一个块的写入,以确保数据耐久性。 不过您的情况可能有所不同;在一个典型的以高事务处理速率运行的客户站点上,我经常看到非 ECP 配置上的日志写入 IOPS 为每秒十几次。 在繁忙的系统上使用 ECP 时,由于 ECP 强制日志同步,可以在日志磁盘上看到 100 到 1000 的写入 IOPS。 - ___提示:____如果在繁忙的系统上显示 mgstat 或查看 [pButtons](https://cn.community.intersystems.com/post/intersystems-数据平台和性能-–-第-1-篇) 中的 mgstat,您将看到 Jrnwrts(日志写入次数),您将在存储 IO 资源规划中对其加以考虑。 在 ECP 数据服务器上,还有未显示在 mgstat 中的对日志磁盘的日志同步写入,要了解这些信息,您需要查看日志磁盘的操作系统指标,例如使用 iostat 查看_。 ### 什么是日志同步? 需要日志同步的原因: - 确保在数据服务器发生故障时数据的耐久性和可恢复性。 - 它们也是确保应用程序服务器之间的缓存一致性的触发器。 在非 ECP 配置中,对 Caché 数据库的修改将写入日志缓冲区(128 x 64K 缓冲区),当日志缓冲区满时或每两秒由日志守护程序写入磁盘上的日志文件。 Caché 为整个缓冲区分配 64k,并且这些缓冲区总是被重复使用,而不是被销毁和重新创建,Caché 只是跟踪末尾偏移量。 在大多数情况下(除非一次进行大量更新),日志写入次数非常小。 ECP 系统中也有日志同步。 日志同步可以定义为将当前日志缓冲区的相关部分重新写入磁盘,以确保磁盘上的日志始终是最新的。 因此,日志同步会请求多次重新写入同一日志块的某个部分(大小在 2k 到 64k 之间)。 ECP 客户端上可以触发日志同步请求的事件为更新(SET 或 KILL)或 LOCK。 例如,对于每个 SET 或 KILL,都会将当前日志缓冲区写入(或重新写入)磁盘。 在非常繁忙的系统中,单次同步操作中的日志同步可能被捆绑或延迟为多个同步请求。 ### 日志同步的容量规划 为确保持续的吞吐量,日志同步的平均写入响应时间必须: - _
文章
姚 鑫 · 六月 29, 2021

第二十二章 计算XPath表达式

# 第二十二章 计算XPath表达式 `XPath`(XML路径语言)是一种基于XML的表达式语言,用于从XML文档获取数据。使用类中的`%XML.XPATH.Document`,可以轻松地计算`XPath`表达式(给定提供的任意XML文档)。 注意:使用的任何XML文档的XML声明都应该指明该文档的字符编码,并且文档应该按照声明的方式进行编码。如果未声明字符编码,InterSystems IRIS将使用本书前面的“输入和输出的字符编码”中描述的默认值。如果这些默认值不正确,请修改XML声明,使其指定实际使用的字符集。 ## IRIS中XPath表达式求值概述 要使用InterSystems IRIS XML支持使用任意XML文档计算`XPath`表达式,请执行以下操作: 1. 创建`%XML.XPATH.Document`的实例。为此,请使用以下类方法之一:`CreateFromFile()`、`CreateFromStream()`或`CreateFromString()``。使用这些方法中的任何一种,都可以将输入XML文档指定为第一个参数,并接收%XML.XPATH.Document`的一个实例作为输出参数。 这一步使用内置的XSLT处理器解析XML文档。 2. 使用`%XML.XPATH.Document`实例的`EvaluateExpression()`方法。对于此方法,需要指定节点上下文和要计算的表达式。 节点上下文指定要在其中计算表达式的上下文。这使用`XPath`语法来表示到所需节点的路径。例如: ```java "/staff/doc" ``` 要计算的表达式还使用`XPath`语法。例如: ```java "name[@last='Marston']" ``` 可以将结果作为输出参数(作为第三个参数)接收。 注意:如果要迭代一大组文档并计算每个文档的`XPath`表达式,建议在处理完文档后,在打开下一个文档之前将该文档的`OREF`设置为`NULL`。这绕过了第三方软件的一个限制。在循环中处理大量文档时,此限制会导致CPU使用率略有增加。 # 创建XPath文档时的参数列表 若要在创建`%XML.XPATH.Document`的实例,请使用该类的`CreateFromFile()`、`CreateFromStream()`或`CreateFromString()`类方法。对于这些类方法,完整的参数列表按顺序如下: 1. PSource、pStream或pString-源文档。 - 对于`CreateFromFile()`,此参数是文件名。 - 对于`CreateFromStream()`,此参数是二进制流。 - 对于`CreateFromString()`,此参数是一个字符串。 2. PDocument-作为输出参数返回的结果。这是`%XML.XPATH.Document`的实例。 3. PResolver-解析源时使用的可选实体解析器。 4. PErrorHandler-一个可选的自定义错误处理程序。 5. PFlags-控制SAX解析器执行的验证和处理的可选标志。 6. PSchemaSpec-可选的架构规范,用于验证文档源。此参数是一个字符串,其中包含以逗号分隔的命名空间/URL对列表: ```java "namespace URL,namespace URL" ``` 这里,`Namespace`是用于模式的XML名称空间,URL是提供模式文档位置的URL。名称空间和URL值之间有一个空格字符。 7. PPrefix Mappings-可选的前缀映射字符串。 `CreateFromFile()`、`CreateFromStream()`和`CreateFromString()`方法返回应检查的状态。例如: ```java Set tSC=##class(%XML.XPATH.Document).CreateFromFile("c:\sample.xml",.tDocument) If $$$ISERR(tSC) Do $System.OBJ.DisplayError(tSC) ``` # 为默认命名空间添加前缀映射 当XML文档使用默认名称空间时,这会给`XPath`带来问题。请考虑以下示例: ```xml Mr. Marston Mr. Bertoni Mr. Leslie Ms. Farmer ``` 在本例中, `` 元素属于名称空间,但没有名称空间前缀。`XPath`不提供访问 `` 元素的简单方法。 - 可以设置`%XML.XPATH.Document`实例的`Prefix Mappings`属性。该属性旨在为源文档中的每个默认名称空间提供唯一的前缀,以便`XPath`表达式可以使用这些前缀,而不是使用完整的名称空间URI。 `PrefixMappings` 属性是一个由逗号分隔的列表组成的字符串;每个列表项都是一个前缀,后跟一个空格,后跟一个命名空间URI。 - 调用`CreateFromFile()`、`CreateFromStream()`或`CreateFromString()`时,可以指定`PrefixMappings`参数。此字符串的格式必须与前面描述的相同。 然后以与使用任何名称空间前缀相同的方式使用这些前缀。 例如,假设将前面的XML读入`%XML.XPATH.Document`的实例时,按如下方式指定了前缀映射: ```java "s http://www.staff.org" ``` 在本例中,可以使用`"/s:staff/s:doc"`访问`` 元素。 请注意,可以使用实例方法`GetPrefix()`来获取先前为文档中的给定路径指定的前缀。 # 计算XPath表达式 要计算`XPath`表达式,请使用`%XML.XPATH.Document`实例的`EvaluateExpression()`方法。对于此方法,请按顺序指定以下参数: 1. PContext-节点上下文,指定在其中计算表达式的上下文。指定一个字符串,该字符串包含指向所需节点的路径的`XPath`语法。例如: ```java "/staff/doc" ``` 2. PExpression-选择特定结果的谓词。指定包含所需XPath语法的字符串。例如: ```java "name[@last='Marston']" ``` 注意:对于其他技术,通常的做法是将谓词连接到节点路径的末尾。类中的`%XML.XPATH.Document`不支持此语法,因为基础XSLT处理器需要节点上下文和谓词作为单独的参数。 3. PResults-作为输出参数返回的结果。 `EvaluateExpression()`方法返回应该检查的状态。例如: ```java Set tSC=tDoc.EvaluateExpression("/staff/doc","name[@last='Smith']",.tRes) If $$$ISERR(tSC) {Do $System.OBJ.DisplayError(tSC)} ``` # 使用XPath结果 XPath表达式可以返回XML文档的一个子树、多个子树或标量结果。在`%XML.XPATH.Document`的`EvaluateExpression()`方法旨在处理所有这些情况。具体地说,它返回一个结果列表。该列表中的每个项目都有一个Type属性,该属性具有下列值之一: - `$$$XPATHDOM`-指示该项包含XML文档的子树。此项目是`%XML.XPATH.DOMResult`实例,提供导航和检查子树的方法。 - `$$$XPATHVALUE`-指示该项是单个标量结果。此项目是`%XML.XPATH.ValueResult`实例。 这些宏在`%occXSLT.inc`包含文件中定义。 ## 检查XML子树 本节介绍如何导航由`%XML.XPATH.DOMResult`表示的XML子树,以及如何获取有关您在该子树中当前位置的信息。 ### 导航子树 要导航`%XML.XPATH.DOMResult`的实例,可以使用该实例的以下方法:`Read()`、`MoveToAttributeIndex()`、`MoveToAttributeName()`、`MoveToElement()`和`Rewind()`。 要移动到文档中的下一个节点,请使用`read()`方法。`Read()`方法返回TRUE值,直到没有更多节点可读为止(即,直到到达文档末尾)。 导航到某个元素时,如果该元素具有属性,则可以使用以下方法导航到这些属性: - 使用`MoveToAttributeIndex()`方法按索引(属性在元素中的序号位置)移动到特定属性。此方法只有一个参数:属性的索引号。请注意,可以使用`AttributeCount`属性来了解给定元素有多少个属性。 - 使用`MoveToAttributeName()`方法按名称移动到特定属性。此方法有两个参数:属性名称和命名空间URI(可选)。 完成当前元素的属性后,可以通过调用其中一个导航方法(如`read()`)移动到文档中的下一个元素。或者,可以调用`MoveToElement()`方法返回到包含当前属性的元素。 这里描述的所有方法都在文档中前进,但`Rewind()`方法除外,它导航到文档的开头并重置所有属性。 ### 节点的属性 除`Type`属性外,`%XML.XPATH.DOMResult`的以下属性还提供有关当前位置的信息。 #### AttributeCount 如果当前节点是元素,则此属性指示元素的属性数。 #### EOF 如果读取器已到达源文档的末尾,则为true;否则为false。 #### HasAttributes 如果当前节点是一个元素,则如果该元素具有属性,则此属性为true(如果没有属性,则为false)。如果当前节点是属性,则此属性为true。 对于任何其他类型的节点,此属性为False。 #### HasValue 如果当前节点是具有值的节点类型(即使该值为空),则为True。否则,此属性为false。 #### LocalName 对于属性或元素类型的节点,这是当前元素或属性的名称,不带命名空间前缀。对于所有其他类型的节点,此属性为`NULL`。 #### Name 当前节点的完全限定名称,视节点类型而定。 #### NodeType 当前节点的类型,如下之一:`attribute`, `chars`, `cdata`, `comment`, `document`, `documentfragment`, `documenttype`, `element`, `entity`, `entityreference`, `notation`,或处理指令。 #### Path 对于元素类型的节点,这是到元素的路径。 对于所有其他类型的节点,此属性为空。 #### ReadState 表示总体读状态,有以下几种: - `“initial”`表示`Read()`方法还没有被调用。 - `“cursoractive”`意味着`Read()`方法至少被调用过一次。 - `“eof”`表示已经到达文件的末尾。 #### Uri 当前节点的URI。 返回的值取决于节点的类型。 #### Value 值(如果有的话),适合于节点类型。 如果该值小于`32kb`,则为字符串。 否则,它是一个字符流。 ## 检查标量结果 本节介绍在类中使用由`%XML.XPATH.ValueResult`表示的`XPath`结果。除`Type`属性外,该类还提供`Value`属性。 请注意,如果该值的长度大于32KB,则会自动将其放入流对象中。除非确定将收到的结果类型,否则应该检查`Value`是否为流对象。为此,可以使用`$IsObject`函数。(也就是说,如果此值是对象,则它是流对象,因为它是唯一可以是对象的类型。) ```java // 如果结果长度大于32KB,则值可以是流 Set tValue=tResult.Value If $IsObject(tValue){ Write ! Do tValue.OutputToDevice() } else { Write tValue } ``` ## 一般方法 除非可以确定在计算`XPath`表达式时会收到什么样的结果,否则应该编写代码来处理最常见的情况。代码的可能组织如下: 1. 查找返回结果列表中的元素数量。遍历此列表。 2. 对于每个列表项,检查`Type`属性。 - 如果`Type`为`$$$XPATHDOM`,, 在类中使用`%XML.XPATH.DOMResult`的方法导航并检查此XML子树。 - 如果`Type`为`$$$XPATHVALUE`,请检查`Value`属性是否为流对象。如果是流对象,则使用常用的流接口访问数据。否则,`Value`属性为字符串。 # 示例 本节中的示例针对以下`XML`文档计算`XPath`表达式: ```xml Yao Xin Mr. Bertoni Mr. Leslie Ms. Farmer Ms. Midy Mr. Dick Mr. Boag Mr. Curcuru Mr. Kesselman Mr. Auriemma ``` ## 计算具有子树结果的XPath表达式 ```java /// 计算返回DOM Result的XPath表达式 ClassMethod Example1() { Set tSC=$$$OK do { Set tSC=##class(%XML.XPATH.Document).CreateFromFile(filename,.tDoc) If $$$ISERR(tSC) {Do $System.OBJ.DisplayError(tSC) Quit} Set context="/staff/doc" Set expr="name[@last='Marston']" Set tSC=tDoc.EvaluateExpression(context,expr,.tRes) If $$$ISERR(tSC) Quit Do ##class(%XML.XPATH.Document).ExampleDisplayResults(tRes) } while (0) If $$$ISERR(tSC) {Do $System.OBJ.DisplayError(tSC)} Quit } ``` 本例选择了``元素的`last`属性等于`Yao`的任何节点。 该表达式在``元素的``节点中计算。 请注意,此示例使用`%XML.XPATH.Document`的`ExampleDisplayResults()`类方法。 执行`example1()`方法时,将先前的XML文件作为输入提供,您会看到以下输出: ```java DHC-APP>d ##class(PHA.TEST.Xml).Example1("E:\temp\xmlXpath.txt") XPATH DOM element: name attribute: first Value: Xin attribute: last Value: Yao chars : #text Value: Yao Xin ``` ## 计算具有标量结果的`XPath`表达式 下面的类方法读取XML文件并计算返回标量结果的XPath表达式: ```java /// 计算返回值结果的XPath表达式 /// d ##class(PHA.TEST.Xml).Example2("E:\temp\xmlXpath.txt") ClassMethod Example2(filename) { Set tSC=$$$OK do { Set tSC=##class(%XML.XPATH.Document).CreateFromFile(filename,.tDoc) If $$$ISERR(tSC) {Do $System.OBJ.DisplayError(tSC) Quit} Set tSC=tDoc.EvaluateExpression("/staff","count(doc)",.tRes) If $$$ISERR(tSC) Quit Do ##class(%XML.XPATH.Document).ExampleDisplayResults(tRes) } while (0) If $$$ISERR(tSC) {Do $System.OBJ.DisplayError(tSC)} Quit } ``` 这个例子统计``子节点。 该表达式在``元素中求值。 当执行`Example2()`方法,提供前面的XML文件作为输入时,会看到以下输出: ```java DHC-APP> d ##class(PHA.TEST.Xml).Example2("E:\temp\xmlXpath.txt") XPATH VALUE 2 ```
文章
Lele Yang · 六月 8, 2023

没有虚拟 IP 地址的数据库镜像

++ 更新:2018 年 8 月 1 日 使用内置于 Caché 数据库镜像的 InterSystems 虚拟 IP (VIP) 地址有一定的局限性。特别是,它只能在镜像成员驻留在同一网络子网时使用。当使用多个数据中心时,由于增加了网络复杂性( 此处有更详细的讨论),网络子网通常不会“延伸”到物理数据中心之外。出于类似的原因,当数据库托管在云端时,虚拟 IP 通常无法使用。 负载均衡器(物理或虚拟)等网络流量管理设备可用于实现相同级别的透明度,为客户端应用程序或设备提供单一地址。网络流量管理器自动将客户端重定向到当前镜像主服务器的真实 IP 地址。自动化旨在满足灾难后 HA 故障转移和 DR 升级的需求。 网络流量管理器的集成 当今市场上有许多支持网络流量重定向的选项。这些中的每一个都支持类似甚至多种方法来根据应用程序要求控制网络流量。为了简化这些方法,我们考虑了三个类别:数据库服务器调用 API、网络设备轮询或两者的组合。 下一节将概述这些方法中的每一个,并就如何将这些方法与 InterSystems 产品集成提供指导。在所有情况下,仲裁器都用于在镜像成员无法直接通信时提供安全的故障转移决策。可以在此处找到有关仲裁器的详细信息。 出于本文的目的,示例图将描述 3 个镜像成员:主机、备份和 DR 异步。但是,我们知道您的配置可能比这更多或更少。 选项 1:网络设备轮询(推荐) 在这种方法中,网络负载均衡设备使用其内置的轮询机制与两个镜像成员通信以确定主镜像成员。 使用 2017.1 中可用的 CSP 网关的mirror_status.cxw页面的轮询方法可以用作 ELB 健康监视器中对添加到 ELB 服务器池的每个镜像成员的轮询方法。只有主镜像会响应“SUCCESS”,从而将网络流量仅定向到活动的主镜像成员。 此方法不需要向 ^ZMIRROR 添加任何逻辑。请注意,大多数负载均衡网络设备对运行状态检查的频率都有限制。通常,最高频率不少于 5 秒,这通常可以接受以支持大多数正常运行时间服务级别协议。 对以下资源的 HTTP 请求将测试本地缓存配置的镜像成员状态。 /csp/bin/mirror_status.cxw 对于所有其他情况,这些镜像状态请求的路径应该使用与请求真实 CSP 页面所用的相同的层次机制解析到适当的缓存服务器和名称空间。 示例:测试 /csp/user/ 路径中应用程序配置服务的镜像状态: /csp/user/mirror_status.cxw 注意:调用镜像状态检查不会消耗 CSP 许可证。 根据目标实例是否是活动主机,网关将返回以下 CSP 响应之一: ** 成功(是主镜像成员) =============================== HTTP/1.1 200 OK Content-Type: text/plain Connection: close Content-Length: 7 SUCCESS ** 失败(不是主镜像成员) =============================== HTTP/1.1 503 Service Unavailable Content-Type: text/plain Connection: close Content-Length: 6 FAILED ** 失败(Caché服务器不支持Mirror_Status.cxw请求) =============================== HTTP/1.1 500 Internal Server Error Content-Type: text/plain Connection: close Content-Length: 6 FAILED 考虑下图作为轮询的示例。 同步故障转移镜像成员之间自动发生故障转移: 下图演示了将 DR 异步镜像成员提升到负载均衡池中,这通常假设同一个负载均衡网络设备正在为所有镜像成员提供服务(地理分割方案将在本文后面介绍)。根据标准 DR 程序,灾难恢复成员的提升涉及人为决策,然后是数据库级别的简单管理操作。但是,一旦采取该操作,就不需要对网络设备执行任何管理操作:它会自动发现新的主要设备。 选项 2:数据库服务器调用 API 在这种方法中,使用了网络流量管理设备,它有一个用故障转移镜像成员和潜在的 DR 异步镜像成员定义的服务器池。 当镜像成员成为主镜像成员时,向网络设备发出 API 调用以调整优先级或权重,以立即指示网络设备将网络流量定向到新的主镜像成员。 相同的模型适用于在主镜像成员和备份镜像成员都不可用的情况下提升 DR 异步镜像成员。 此 API 在 ^ZMIRROR 代码中定义为过程调用的一部分: $$CheckBecomePrimaryOK^ZMIRROR() 在此过程调用中,插入可用于相应网络设备的任何 API 逻辑和方法,例如 REST API、命令行界面等。与虚拟 IP 一样,这是网络配置的突然更改,不涉及任何应用程序逻辑以通知连接到故障主镜像成员的现有客户端正在发生故障转移。根据故障的性质,这些连接可能由于应用程序超时或错误、新主实例强制旧主实例关闭或客户端使用的TCP 保持活动计时器过期造成的故障本身而关闭。 因此,用户可能必须重新连接并登录。您的应用程序的行为将决定此行为。 选项 3:地理分散部署 在具有多个数据中心和可能地理分散的部署(例如具有多个可用性区域和地理区域的云部署)的配置中,需要使用基于 DNS 的负载均衡和本地负载均衡在一个简单且易于支持的模型中考虑地理重定向实践。 通过这种组合模型,引入了与 DNS 服务配合使用的附加网络设备,如 Amazon Route 53、F5 Global Traffic Manager、Citrix NetScaler Global Server Load Balancing 或 Cisco Global Site Selector,在每个数据中心、可用性区域或云地理区域与网络负载均衡器相结合。 在此模型中,前面提到的轮询(推荐)或 API 方法在本地用于操作任何镜像成员(故障转移或 DR 异步)的位置。这用于向地理/全球网络设备报告它是否可以将流量定向到任一数据中心。同样在此配置中,本地网络流量管理设备将其自己的 VIP 提供给地理/全球网络设备。 在正常稳定状态下,活动主镜像成员向本地网络设备报告它是主镜像成员并提供“启动”状态。此“启动”状态被转发到地理/全球设备以调整和维护 DNS 记录,以将所有请求转发到此活动的主镜像成员。 在同一数据中心内的故障转移场景中(备份同步镜像成员成为主镜像成员),API 或轮询方法与本地负载均衡器一起使用,现在重定向到同一数据中心内的新主镜像成员。由于新的主镜像成员处于活动状态,因此本地负载均衡器仍以“启动”状态响应,因此未对地理/全局设备进行任何更改。 出于本示例的目的,API 方法在下图中用于本地集成到网络设备。 在使用 API 或轮询方法到不同数据中心(备用数据中心中的同步镜像或 DR 异步镜像成员)的故障转移场景中,新提升的主镜像成员开始向本地网络设备报告为主要成员。 在故障转移期间,曾经包含主镜像成员的数据中心现在不再从本地负载均衡器向地理/全球报告“Up”。地理/全球设备不会将流量定向到该本地设备。备用数据中心的本地设备将向地理/全球设备报告“Up”,并将调用 DNS 记录更新以现在定向到备用数据中心的本地负载均衡器提供的虚拟 IP。 选项 4:多层和地理分散的部署 为了使解决方案更进一步,引入了一个单独的 Web 服务器层,既可以作为私有 WAN 的内部,也可以通过 Internet 访问。此选项可能是大型企业应用程序的典型部署模型。 以下示例显示了使用多个网络设备安全隔离和支持 Web 和数据库层的示例配置。在此模型中,使用了两个地理位置分散的位置,其中一个位置被视为“主要”位置,另一个位置纯粹是数据库层的“灾难恢复”位置。数据库层灾难恢复位置将在主要位置因任何原因停止服务的情况下使用。此外,此示例中的 Web 层将显示为双活,这意味着用户将根据各种规则(例如最低延迟、最低连接数、IP 地址范围或您认为合适的其他路由规则)定向到任一位置。 如上例所示,如果在同一位置发生故障转移,则会发生自动故障转移,并且本地网络设备现在指向新的主机。用户仍然连接到任一位置的 Web 服务器, Web 服务器及其关联的 CSP 网关继续指向位置 A。 在下一个示例中,考虑在位置 A 发生的整个故障转移或中断,其中主要和备份故障转移镜像成员都无法使用。然后,DR 异步镜像成员将被手动提升为主要和备份故障转移镜像成员。在升级后,新指定的主镜像成员将允许位置 B 的负载均衡设备使用前面讨论的 API 方法(轮询方法也是一个选项)报告“Up”。由于本地负载均衡器现在报告“启动”,基于 DNS 的设备将识别这一点并将流量从位置 A 重定向到现在的位置 B 以用于数据库服务器服务。 结论 在没有虚拟 IP 的情况下设计镜像故障转移有许多可能的排列。这些选项可应用于最简单的高可用性场景或具有多层的多地理区域部署,包括故障转移和 DR 异步镜像成员,以获得高可用性和容灾解决方案,旨在为您的应用程序维持最高水平的运营弹性. 希望本文提供了一些关于成功部署具有故障转移的数据库镜像的可能的不同组合和用例的见解,这些组合和用例适合您的应用程序和可用性要求。
文章
Nicky Zhu · 一月 11, 2021

ObjectScript包管理器中的单元测试和测试覆盖率

本文将描述通过ObjectScript包管理器(见https://openexchange.intersystems.com/package/ObjectScript-Package-Manager-2)运行单元测试的过程,包括测试覆盖率测量(见https://openexchange.intersystems.com/package/Test-Coverage-Tool)。 ## ObjectScript中的单元测试 关于在ObjectScript中编写单元测试,已经有很好的文档,因此我就不再赘述了。您可以在这里找到单元测试教程:https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=TUNT_preface 最好的做法是将单元测试代码单独放在源代码树中,无论它只是“/tests”还是其他名字。在InterSystems中,我们最终使用/internal/testing/unit_tests/作为我们事实上的标准,这是有意义的,因为测试是内部/非发布的,而且除了单元测试还有其他类型的测试,但这对于简单的开源项目来说可能有点复杂。您可以在我们的一些GitHub仓库中看到这种结构。 从工作流的角度来看,这在VSCode中非常简单,您只需创建目录并将类放在里面。对于较老的以服务器为中心的源代码控制方法(Studio中使用的方法),您需要正确地地映射这个包,使用的方法会因源代码控制程序而异。 从单元测试类命名的角度来看,我个人的偏好(以及我的团队的最佳实践)是: UnitTest.[.] 例如,如果在类MyApplication.SomeClass 中对方法Foo进行单元测试,单元测试类将被命名为UnitTest.MyApplication.SomeClass.Foo;如果测试是针对整个类的,那么名字就是UnitTest.MyApplication.SomeClass。 ## ObjectScript 包管理器中的单元测试 让ObjectScript包管理器知道您的单元测试,很简单!只需按如下所示向module.xml中添加一行代码(来自https://github.com/timleavitt/ObjectScript-Math/blob/master/module.xml - 这是Open Exchange上的@Peter Steiwer的出色数学扩展包,我以它作为简单的正面例子): ```objectscript   ...   ``` 这些代码的意思是: - 单元测试位于模块根目录下的“tests”目录中。 - 单元测试在“UnitTest.Math”包中。这样很直观,因为被测试的类就在“Math”包中。 - 单元测试在包生命周期的“测试”阶段运行。(当然还有一个可以运行它们的“验证”阶段,这里不赘述。) ## 运行单元测试 对于上述定义的单元测试,包管理器提供了一些实用工具来运行它们。您仍然可以像平常使用%UnitTest.Manager那样设置^UnitTestRoot等,但下面的方法可能更简单,尤其在同一个环境下做几个项目的时候。 您可以克隆上述objectscript-math仓库,然后用 ```zpm "load /path/to/cloned/repo/" ```加载,或者在您自己的包上使用包名(和测试名)替换“objectscript-math”来尝试所有这些方法。 重新加载模块,然后运行所有单元测试: ```zpm "objectscript-math test" ``` 只运行单元测试(不重新加载): ```zpm "objectscript-math test -only" ``` 只运行单元测试(不重新加载)并提供详细输出: ```zpm "objectscript-math test -only -verbose" ``` 运行一个特定的测试套件(指一个测试目录 - 在本例中,是UnitTest/Math/Utils中的所有测试)而不重新加载,并提供详细输出: ```zpm "objectscript-math test -only -verbose -DUnitTest.Suite=UnitTest.Math.Utils" ``` 运行一个特定的测试用例(在本例中,是UnitTest.Math.Utils.TestValidateRange)而不重新加载,并提供详细输出: ```zpm "objectscript-math test -only -verbose -DUnitTest.Case=UnitTest.Math.Utils.TestValidateRange" ``` 如果您只是想解决单个测试方法中的小问题: ```zpm "objectscript-math test -only -verbose -DUnitTest.Case=UnitTest.Math.Utils.TestValidateRange -DUnitTest.Method=TestpValueNull" ``` ## 通过ObjectScript包管理器进行测试覆盖率测量 怎样评估单元测试的质量?测量测试覆盖率虽然并不全面,但至少有参考意义。早在2018年的全球峰会上,我就展示过。 见 - https://youtu.be/nUSeGHwN5pc。 首先需要安装“测试覆盖率”包: ```zpm "install testcoverage" ``` 注意,并不需要ObjectScript包管理器才能安装/运行;可以在Open Exchange上了解更多信息:https://openexchange.intersystems.com/package/Test-Coverage-Tool 不过如果您已经在用ObjectScript包管理器,那么您可以更好地利用这个“测试覆盖率”工具。 运行测试前,需要指定测试所覆盖的类/routine宏。这一点很重要,因为在非常大的代码库中(例如,HealthShare),测试和收集项目中所有文件的测试覆盖率所需要的内存可能超出您的系统内存。(提一句,如果您感兴趣,可以使用逐行监视器的gmheap。) 文件列表在您的单元测试根目录下的coverage.list文件中;单元测试的不同子目录(套件)可以拥有它们自己的副本,以覆盖在测试套件运行时将跟踪的类/例程。 有关objectscript-math的简单示例,见:https://github.com/timleavitt/ObjectScript-Math/blob/master/tests/UnitTest/coverage.list;测试覆盖率工具用户指南有更详细的介绍。 要在启用测试覆盖率测量的情况下运行单元测试,只需再向命令添加一个参数,指定应使用TestCoverage.Manager而非%UnitTest.Manager 来运行测试: ```zpm "objectscript-math test -only -DUnitTest.ManagerClass=TestCoverage.Manager" ``` 输出(即使是非详细模式)将包括一个URL,供您查看您的类/routine(宏)的哪些行被单元测试覆盖了,以及一些汇总统计信息。 ## 接下来的步骤 这些能不能在CI中自动化?能不能报告单元测试的结果和覆盖率分数/差异?答案是:能!您可以使用Docker,Travis CI和codecov.io来试一下这个简单示例,见https://github.com/timleavitt/ObjectScript-Math;我打算以后写篇文章来详细讲讲,介绍几种不同的方法。
文章
姚 鑫 · 五月 13, 2021

第五章 管理全局变量(二)

# 第五章 管理全局变量(二) # 在全局变量中查找值 “查找全局变量字符串”页使可以在下标或选定全局变量的值中查找给定的字符串。 要访问和使用此页,请执行以下操作: 1. 显示“全局变量”页。 2. 选择要使用的全局变量。为此,请参阅“全局页简介”一节中的步骤2和3。 3. 单击查找按钮。 4. 对于查找内容,输入要搜索的字符串。 5. (可选)清除大小写匹配。默认情况下,搜索区分大小写。 6. 单击Find First或Find All。 然后,页面显示选定全局变量中下标或值包含给定字符串的第一个节点或所有节点。该表左侧显示了节点下标,右侧显示了相应的值。 7. 如果使用的是Find First,请根据需要单击Find Next以查看下一个节点。 8. 完成后,单击关闭窗口。 ## 执行批量更换 注意:在进行任何编辑之前,请确保知道IRIS使用哪个全局系统,以及应用程序使用哪个全局系统;参见“一般建议”此选项会永久更改数据。不建议在生产系统中使用。 出于开发目的,“查找全局字符串”页面还提供了对全局节点中的值进行整体更改的选项。要使用此选项: 1. 显示“全局”页面。 2. 选择要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3 3. 单击替换按钮。 4. 使用此页面查找上一节中描述的值。 5. 为“替换为”指定一个值。 6. 单击全部替换。 7. 单击确定确认此操作。然后,页面会显示变更的预览。 8. 如果结果可以接受,请单击保存。 9. 单击确定确认此操作。 # 导出全局变量 注意:因为导入全局是非常容易的(这是一个不可逆转的变化),所以最好只导出需要导入的全局。请注意,如果导出所有全局变量,导出将包括所有包含代码的全局变量。请确保知道IRIS使用哪些全局系统,以及应用程序使用哪些全局系统; “导出全局”页面允许导出全局。 要访问和使用此页面: 1. 显示“全局”页面。 2. 指定要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3 3. 单击导出按钮。 4. 指定要将全局文件导出到的文件。为此,请在输入服务器上的导出路径和名称字段中输入文件名(包括其绝对或相对路径名),或者单击浏览并导航到该文件。 5. 使用字符集列表选择导出文件的字符集。 6. 在页面的中央框中:选择输出格式,选择记录格式 7. 选择或清除“在此检查”以在后台运行导出... 8. 单击导出。 9. 如果文件已经存在,请单击“确定”用新版本覆盖它。 导出会创建一个. gof文件。 # 导入全局变量 注意:在导入任何全局变量之前,请确保知道IRIS使用哪些全局变量,以及应用程序使用哪些全局变量;参见“一般建议”没有撤消选项。将全局导入现有全局(从而合并数据)后,无法将全局恢复到其以前的状态。 “导入全局”页面允许导入全局。要访问和使用此页面: 1. 显示“全局”页面。 2. 单击导入按钮。 3. 指定导入文件。为此,请在输入导入文件的路径和名称字段中输入文件(包括其绝对或相对路径名),或者单击浏览并导航到该文件。 4. 使用字符集列表选择导入文件的字符集。 5. 选择下一步。 6. 使用表中的复选框选择要导入的全局。 7. 也可以选择在后台运行导入。如果选择此项,任务将在后台运行。 8. 单击导入。 # 删除全局变量 注意:在删除任何全局变量之前,请确保知道IRIS使用哪些全局变量,以及应用程序使用哪些全局变量;参见“一般建议”没有撤消选项。无法恢复已删除的全局。 “删除全局”页面允许删除全局。要访问和使用此页面: 1. 显示“全局”页面。 2. 选择要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3 3. 单击删除按钮。 4. 单击确定确认此操作。 # 管理任务的应用程序接口 InterSystems IRIS还提供了以下应用编程接口来执行本章中描述的一些任务: - 类`%SYSTEM.OBJ`提供了以下方法: - `Export()`使能够将全局导出到一个XML文件。 - `Load()`和`LoadDir()`使能够导入包含在`XML`文件中的全局。 这两者都可以通过$SYSTEM变量获得,例如:`$SYSTEM.OBJ.Export` - 类别`%Library.Global`提供了以下方法: - `Export()`使能够将全局导出到`.gof`和其他文件格式(不包括XML)。 - `Import()`使能够将全局导入到`.gof`和其他文件格式(不包括XML)。 `%Library.Global` 还提供了`Get()`类查询,根据给定的搜索条件,可以使用该查询来查找全局。 ![image](/sites/default/files/inline/images/1_44.png)
文章
姚 鑫 · 五月 13, 2021

第五章 管理全局变量(二)

# 第五章 管理全局变量(二) # 在全局变量中查找值 “查找全局变量字符串”页使可以在下标或选定全局变量的值中查找给定的字符串。 要访问和使用此页,请执行以下操作: 1. 显示“全局变量”页。 2. 选择要使用的全局变量。为此,请参阅“全局页简介”一节中的步骤2和3。 3. 单击查找按钮。 4. 对于查找内容,输入要搜索的字符串。 5. (可选)清除大小写匹配。默认情况下,搜索区分大小写。 6. 单击Find First或Find All。 然后,页面显示选定全局变量中下标或值包含给定字符串的第一个节点或所有节点。该表左侧显示了节点下标,右侧显示了相应的值。 7. 如果使用的是Find First,请根据需要单击Find Next以查看下一个节点。 8. 完成后,单击关闭窗口。 ## 执行批量更换 注意:在进行任何编辑之前,请确保知道IRIS使用哪个全局系统,以及应用程序使用哪个全局系统;参见“一般建议”此选项会永久更改数据。不建议在生产系统中使用。 出于开发目的,“查找全局字符串”页面还提供了对全局节点中的值进行整体更改的选项。要使用此选项: 1. 显示“全局”页面。 2. 选择要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3 3. 单击替换按钮。 4. 使用此页面查找上一节中描述的值。 5. 为“替换为”指定一个值。 6. 单击全部替换。 7. 单击确定确认此操作。然后,页面会显示变更的预览。 8. 如果结果可以接受,请单击保存。 9. 单击确定确认此操作。 # 导出全局变量 注意:因为导入全局是非常容易的(这是一个不可逆转的变化),所以最好只导出需要导入的全局。请注意,如果导出所有全局变量,导出将包括所有包含代码的全局变量。请确保知道IRIS使用哪些全局系统,以及应用程序使用哪些全局系统; “导出全局”页面允许导出全局。 要访问和使用此页面: 1. 显示“全局”页面。 2. 指定要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3 3. 单击导出按钮。 4. 指定要将全局文件导出到的文件。为此,请在输入服务器上的导出路径和名称字段中输入文件名(包括其绝对或相对路径名),或者单击浏览并导航到该文件。 5. 使用字符集列表选择导出文件的字符集。 6. 在页面的中央框中:选择输出格式,选择记录格式 7. 选择或清除“在此检查”以在后台运行导出... 8. 单击导出。 9. 如果文件已经存在,请单击“确定”用新版本覆盖它。 导出会创建一个. gof文件。 # 导入全局变量 注意:在导入任何全局变量之前,请确保知道IRIS使用哪些全局变量,以及应用程序使用哪些全局变量;参见“一般建议”没有撤消选项。将全局导入现有全局(从而合并数据)后,无法将全局恢复到其以前的状态。 “导入全局”页面允许导入全局。要访问和使用此页面: 1. 显示“全局”页面。 2. 单击导入按钮。 3. 指定导入文件。为此,请在输入导入文件的路径和名称字段中输入文件(包括其绝对或相对路径名),或者单击浏览并导航到该文件。 4. 使用字符集列表选择导入文件的字符集。 5. 选择下一步。 6. 使用表中的复选框选择要导入的全局。 7. 也可以选择在后台运行导入。如果选择此项,任务将在后台运行。 8. 单击导入。 # 删除全局变量 注意:在删除任何全局变量之前,请确保知道IRIS使用哪些全局变量,以及应用程序使用哪些全局变量;参见“一般建议”没有撤消选项。无法恢复已删除的全局。 “删除全局”页面允许删除全局。要访问和使用此页面: 1. 显示“全局”页面。 2. 选择要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3 3. 单击删除按钮。 4. 单击确定确认此操作。 # 管理任务的应用程序接口 InterSystems IRIS还提供了以下应用编程接口来执行本章中描述的一些任务: - 类`%SYSTEM.OBJ`提供了以下方法: - `Export()`使能够将全局导出到一个XML文件。 - `Load()`和`LoadDir()`使能够导入包含在`XML`文件中的全局。 这两者都可以通过$SYSTEM变量获得,例如:`$SYSTEM.OBJ.Export` - 类别`%Library.Global`提供了以下方法: - `Export()`使能够将全局导出到`.gof`和其他文件格式(不包括XML)。 - `Import()`使能够将全局导入到`.gof`和其他文件格式(不包括XML)。 `%Library.Global` 还提供了`Get()`类查询,根据给定的搜索条件,可以使用该查询来查找全局。 ![image](/sites/default/files/inline/images/1_44.png)
文章
Michael Lei · 四月 17, 2022

翻译博客文章--浏览医疗保健的未来

在最近一次探索马里兰小镇的 "度假 "期间,我偶然发现了一家非常令人愉快的书店,在那里我愉快地消磨了一下午。我和我的家人都是读者,喜欢各种类型的书--新的、二手的、印刷的、电子的。我们尽量在当地购物,以帮助零售店保持运营。 这次访问促使我思考图书行业所发生的事情与我们的医疗保健系统所发生的事情之间的一些相似之处。 医疗保健行业与图书行业的趋势 数字化 我们阅读内容的格式已经发生了根本性的变化。在2020年,电子书几乎占美国市场的四分之一。音频书占美国图书收入的10亿美元。许多印刷书籍是按需出版的,而不是保存在库存中。同样,医疗保健早已不再是一个“伸出舌头说啊 ”的行业,基因组测试、由人工智能算法读取的X射线、可植入设备和远程医疗访问已经改变了医疗的面貌。 虚拟服务 书店现在有多种形式,医疗机构也是如此。订阅图书服务,从当地独立的小公司、大的连锁店、电子零售的网上订单。而与你的本地门诊竞争的是你手机上的一个应用程序。同样,你的治疗师可能是一个机器人,你的基层医疗服务可能由你社区附近药店的驻店医师提供,你可能在一个办公园区做手术。在所有这些竞争中,我们如何确保在我们需要时仍有健康的、提供全面服务的医院? 更智能的算法 分析和预测模型现在几乎和个人推荐一样重要。过去,当我想要一本书的建议,或者一个医生,我就会问朋友。虽然我仍然这样做,但我也同样有可能去看Goodreads,或查看在线医生评论。当我进行搜索时,亚马逊、苹果或谷歌也同样可能提供他们的建议,不管我是否要求它们。他们知道我是谁,我的购买模式是什么,我检查过哪些疾病和症状,以及在当地急诊科订购书籍或看医生的等待时间是什么。 合并和收购 无论你是卖书还是卖医疗服务,改变或死亡都是关键词。我们附近的一家大的巴诺书店(Barnes & Nobles,美国最大的实体书店)最近搬到了一个不到以前一半的地方。大多数独立书店出售的礼品和书籍一样多,而且许多书店同时出售新货和二手货。像Alibris这样的网站将当地的小企业与世界各地的买家联系起来。同样,根据普华永道的数据,2021年医疗保健业的合并和收购增长了56%,预计这一趋势在2022年还会继续。IQVIA艾昆纬研究所的一份报告发现,在主要的应用程序商店中,有大约35万个数字健康应用程序。而美国最大的零售商都在医疗保健领域进行了大量投资。例如,亚马逊是颠覆性的电子书业务的主要参与者,现在也有实体书店,它正积极地进入医疗保健服务领域,包括线上和线下服务。 对未来医疗的影响 对未来医疗的影响是什么?有趣的是,这两种业务都唤起了人们对也许是神话般的过去的相当大的怀念。虽然现在的实体书店比以前少了,但书籍实际上比以前更容易获得。对很大一部分人来说,亲切的家庭医生上门服务从来就不是那么容易的,而且不同地区采用的差异巨大的护理标准也不一定能带来最好的结果。 新世界秩序的便利性和选择令人难以置信地吸引人,无论我是在手机上购买一本书,还是在街上走到我附近的CVS公司购买Covid 疫苗加强针。但是,一方面浏览完所有这些选择也会让人感到困惑,另一方面我不想失去浏览当地商店的货架或者我年迈的母亲随时获得住院床位的选择。 没有任何整齐划一的策略可以向前推进。因此,作为一个消费者,我将继续光顾本地小店来帮助他们经营下去。作为一名医疗IT专业人士,我将继续关注如何利用信息来指导未来,带着一点点害怕,但更多的还是兴奋来展望未来。 关于作者: Kathleen Aller负责InterSystems公司的医疗市场战略。她在医疗和技术领域有多年的经验,在分析、患者管理、电子健康记录、医疗信息共享以及质量和绩效评估方面有专长。 博客原文:https://www.intersystems.com/pulse-blog/browsing-the-future-of-healthcare
文章
姚 鑫 · 七月 27, 2021

类关键字PropertyClass,ServerOnly,Sharded,SoapBindingStyle

# 第二十九章 类关键字 - PropertyClass 向该类添加属性参数。 # 用法 要向该类添加属性参数,请使用以下语法: ```java Class PropClass.MyClass Extends %RegisteredObject [ PropertyClass = PropClass.MyPropertyClass ] { //class members } ``` 其中·propertyclasslist·是下列之一: ```java [ PropertyClass = PropClass.MyPropertyClass ] ``` - 用逗号分隔的类名列表,用括号括起来。 # 详情 如果需要添加自定义属性参数,请执行以下操作: 1. 定义并编译一个定义一个或多个类参数的类。例如: ```java Class PropClass.MyPropertyClass { Parameter MYPARM As %String = "XYZ"; } ``` 这些类参数在下一步中成为属性参数。 2. 在定义属性的类中,指定`PropertyClass`关键字。 # 对子类的影响 子类继承这个关键字添加的自定义行为。如果子类为关键字指定了一个值,则该值会指定一个或多个为该类的属性指定参数的附加类。 # 第三十章 类关键字 - ServerOnly 指定此类是否被投影到Java客户端。 # 用法 要覆盖将类投影到`Java`客户端的默认方式,请使用以下语法: ```java Class Sample.NewClass1 [ ServerOnly = serveronlyvalue ] { //class members } ``` 其中`serveronlyvalue`是以下值之一: - 0表示可以投影此类。 - 1表示这个类不会被投影。 # 详解 如果该关键字为`1`,则该类不会被投影到`Java`客户端。如果该关键字为`0`,则将投影该类。 # 对子类的影响 此关键字不是继承的。 # 默认 如果省略这个关键字,这个类如果不是存根就会被投影(但是如果是存根就不会被投影)。 # 第三十一章 类关键字 - Sharded 指定此类是否被分片。仅适用于包含分片集群的环境中的持久类。 # 用法 要将类定义为分片类,请使用以下语法: ```java Class MyApp.MyClass Extends %Persistent [ Sharded = 1 ] { //class members } ``` 否则,省略此关键字。 # 详解 分片是一种水平扩展数据存储的机制。如果一个类被分片,该类的实例将分布在分片集群中任何已定义的数据节点上。 如果有一个分片环境,并且将一个类定义为未分片,那么该类的实例只存储在第一个数据节点上,尽管所有节点都可以看到该数据。 # 对子类的影响 这个关键字是继承的。 # 默认 如果省略这个关键字,类就不会被分割。 # 第三十二章 类关键字 - SoapBindingStyle 指定此类中定义的任何web方法使用的绑定样式或SOAP调用机制。仅适用于定义为web服务或web客户端的类。 # 用法 要指定此类中定义`web method`使用的绑定样式,请使用以下语法: ```java Class MyApp.MyClass [ SoapBindingStyle = soapbindingstyle ] { //class members } ``` 其中`soapbindingstyle`是下列之一: - `document` 文档(默认)—默认情况下,此类中的`web method`使用文档样式的绑定。 使用这种绑定风格,`SOAP`消息被格式化为文档,并且通常只有一个部分。 在`SOAP`消息中,``元素通常包含一个子元素。``元素的每个子元素对应于一个消息部分。 - `rpc` —默认情况下,此类中的`web method`使用`rpc`(远程过程调用)样式的绑定。 使用这种绑定风格,`SOAP`消息被格式化为具有多个部分的消息。 在`SOAP`消息中,``元素包含一个子元素,其名称取自相应的操作名称。这个元素是一个生成的包装元素,它为方法的参数列表中的每个参数包含一个子元素。 如果`SoapBindingStyle`是文档,如果`ARGUMENTSTYLE`是消息,那么消息样式与`RPC`非常相似; 重要提示:对于手动创建的`web service`,该关键字的默认值通常是合适的。当使用`SOAP`向导从`WSDL`生成`web客户端或服务`时,InterSystems IRIS会将此关键字设置为适合该`WSDL`;如果修改该值,`web客户端或服务`可能不再工作。 # 详解 此关键字允许指定此类中定义的任何`web method`使用的默认绑定样式。它影响`SOAP`主体的格式(但不影响任何SOAP头)。 通过使用`SoapBindingStyle`方法关键字或`SoapBindingStyle`查询关键字,可以重写单个方法的绑定样式。 # 对子类的影响 此关键字不是继承的。 # 默认 默认值为文档。 # 与WSDL的关系 `SoapBindingStyle`类关键字指定了`WSDL`的``部分中``元素的样式属性的值。例如,如果`SoapBindingStyle`是文档,则`WSDL`可能如下所示: ```xml ... ... ``` 如这里所示,在`WSDL`的``部分中,`SoapBindingStyle`类关键字还指定了``元素的样式属性的默认值;该属性由`SoapBindingStyle`方法关键字进一步控制。 相比之下,如果`SoapBindingStyle`是`rpc`,则`WSDL`可以改为如下所示: ```xml ... ... ``` 绑定样式也会影响``元素,如下所示: - 如果绑定样式是文档,默认情况下,消息只有一个部分。例如: ```xml ``` 如果`ARGUMENTSTYLE`参数是`message`,那么一条消息可以有多个部分。例如: ```xml ``` - 如果绑定样式是`rpc`,消息可以有多个部分。例如: ```xml ``` # 对SOAP消息的影响 对`SOAP`消息的主要影响是控制`SOAP`主体是否可以包含多个子元素。 对于使用`RPC`样式绑定和编码样式消息的`web method`,下面显示了请求消息正文的示例: ```xml 10 5 17 2 ``` 相比之下,下面显示了使用文字绑定和编码样式消息的`web method`的请求消息正文的示例: ```xml 10 5 17 2 ``` 在这种情况下,`SOAP`主体只有一个子元素。 # 与 `%XML.DataSet` 一起使用 对于 `%XML.DataSet`, 类型的对象,并非所有 `SoapBindingStyle` 和 `SoapBodyUse` 关键字的排列都是允许的,,如下表总结: type | supported?| supported? ---|---|--- 空 |SoapBodyUse=literal(默认) | SoapBodyUse=encoded SoapBindingStyle=document(default) |supported| not supported SoapBindingStyle=rpc| supported |supported
文章
姚 鑫 · 八月 19, 2021

查询关键字Private,SoapBindingStyle,SoapBodyUse,SoapNameSpace

# 第121章 查询关键字 - Private 指定查询是否为私有查询。 # 用法 要指定此查询为私有查询,请使用以下语法: ```java Query name(formal_spec) As classname [ Private ] { //implementation } ``` 否则,请省略此关键字或将该词放在该关键字之前。 # 详解 私有类成员只能由同一类(或其子类)的其他成员使用。请注意,其他语言通常使用单词`Protected`来描述这种可见性,使用单词`Private`来表示从子类不可见。 # 默认 如果省略此关键字,则此查询不是私有的。 # 第122章 查询关键字 - SoapBindingStyle 指定此查询用作`Web方法`时使用的绑定样式或`SOAP`调用机制。仅适用于定义为`Web服务`或`Web客户端`的类。 # 用法 要覆盖查询使用的默认绑定样式(当它用作`Web方法`时),请使用以下语法: ```java Query name(formal_spec) As classname [ WebMethod, SoapBindingStyle = soapbindingstyle ] { //implementation } ``` 其中`soapbindingstyle`为下列值之一: - `document` - 此`Web方法`使用文档式调用。 使用这种绑定样式,`SOAP`消息被格式化为文档,并且通常只有一个部分。 在`SOAP`消息中,``元素通常包含单个子元素。``元素的每个子元素对应一个消息部分。 - `rpc` - 此`Web方法`使用`RP`C(远程过程调用)风格的调用。 使用这种绑定样式,`SOAP`消息被格式化为具有多个部分的消息。 在`SOAP`消息中,``元素包含一个子元素,其名称取自相应的操作名称。此元素是生成的包装元素,它为方法的参数列表中的每个参数包含一个子元素。 重要提示:对于手动创建的`Web服务`,此关键字的默认值通常比较合适。当使用`SOAP`向导从`WSDL`生成`Web客户端`或`服务`时,InterSystems IRIS会将此关键字设置为适用于该`WSDL`;如果修改此值,`Web客户端`或服务可能不再工作。 # 详情 此关键字允许指定此查询在作为`Web方法`调用时使用的绑定样式。 对于给定查询,此关键字覆盖`SoapBindingStyle`类关键字。 # 默认 如果忽略此关键字,``元素的`style`属性将由`SoapBindingStyle`类关键字的值决定。 # WSDL的关系 (请注意,与方法关键字和查询关键字相比,同名的`class关键字`对`WSDL`的影响更大。) # 对SOAP消息的影响 有关信息,请参阅`SoapBindingStyle`类关键字的条目。 # 第123章 查询关键字 - SoapBodyUse 指定该查询用作`web方法`时,输入和输出使用的编码。 仅应用于定义为`web服务`或`web客户端`的类。 # 用法 要覆盖查询的输入和输出使用的默认编码(当它被用作`web方法`时),请使用以下语法: ```java Query name(formal_spec) As classname [ WebMethod, SoapBodyUse = encoded ] { //implementation } ``` 其中,`soapbodyuse`是下列值之一: - `literal` - 这个`web方法`使用文字数据。 也就是说,`SOAP`消息的``中的`XML`与`WSDL`中给出的模式完全匹配。 - `encoded` = 这个web方法使用soap编码的数据。 也就是说,`SOAP`消息的``中的`XML`根据所使用的`SOAP`版本使用适当的SOAP编码,满足以下规范的要求: - `SOAP 1.1` (https://www.w3.org/TR/2000/NOTE-SOAP-20000508/) - `SOAP 1.2` (https://www.w3.org/TR/soap12-part2/) 重要提示:对于手工创建的`web服务`,这个关键字的默认值通常是合适的。 当使用`SOAP`向导从`WSDL`生成`web客户端`或服务时, IRIS将此关键字设置为适合该`WSDL`的; 如果修改了该值,`web客户端`或服务可能不再工作。 # 详解 该关键字允许您指定该查询作为`web方法`调用时的输入和输出的编码。 对于给定的查询,此关键字覆盖`SoapBodyUse`类关键字。 # 默认 如果忽略此关键字,则使用`SoapBodyUse`类关键字的值。 # 与WSDL的关系以及对SOAP消息的影响 有关信息,请参阅`SoapBodyUse`类关键字的条目。 # 第124章 查询关键字 - SoapNameSpace 在`WSDL`中的绑定操作级别指定名称空间。 仅应用于定义为`web服务`或`web客户端`的类。 # 用法 要在绑定操作级别覆盖默认命名空间(当查询被用作`web方法`时),请使用以下语法: ```java Query name(formal_spec) As classname [ SoapNameSpace = "soapnamespace", WebMethod ] { //implementation } ``` 其中,`soapnamespace`是一个名称空间`URI`。 注意,如果`URI`包含冒号(`:`),则字符串必须用引号括起来。 也就是说,你可以使用以下方法: ```java Query MyQuery() [ SoapNameSpace = "http://www.mynamespace.org", WebMethod ] ``` 或以下: ```java Query MyQuery() [ SoapNameSpace = othervalue, WebMethod ] ``` 但以下情况并非如此: ```java Query MyQuery() [ SoapNameSpace = http://www.mynamespace.org, WebMethod ] ``` 重要提示:对于手工创建的`web服务`,这个关键字的默认值通常是合适的。 当使用`SOAP`向导从`WSDL`生成`web客户`端或服务时, IRIS将此关键字设置为适合该WSDL的; 如果修改了该值,`web客户端`或服务可能不再工作。 # 详解 该关键字允许指定查询作为`web方法`调用时使用的`XML`名称空间。 注意:此关键字仅在查询使用`rpc`样式绑定时有效。 也就是说,查询(或包含它的类)必须用等于`rpc`的`SoapBindingStyle`标记。 (如果为使用文档样式绑定的查询指定此关键字,则`WSDL`将不是自一致的。) # 默认 如果忽略此关键字,则`web方法`位于由`web服务`或客户端类的`namespace`参数指定的命名空间中。 # 与WSDL的关系以及对SOAP消息的影响 有关信息,请参阅`SoapNameSpace`方法关键字的条目。